From 860ba55976b4b9c12b8eeca6a6169a4bc68c1a92 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Wed, 27 Jan 2021 11:32:09 +0000 Subject: [PATCH 1/3] promote unknown to dimensionless units in aux factories --- lib/iris/aux_factory.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/iris/aux_factory.py b/lib/iris/aux_factory.py index 5b63ff53ed..8fca2f087a 100644 --- a/lib/iris/aux_factory.py +++ b/lib/iris/aux_factory.py @@ -619,6 +619,10 @@ def _check_dependencies(delta, sigma, surface_air_pressure): warnings.warn(msg, UserWarning, stacklevel=2) # Check units. + if sigma is not None and sigma.units.is_unknown(): + # Be graceful, and promote unknown to dimensionless units. + sigma.units = "1" + if sigma is not None and not sigma.units.is_dimensionless(): raise ValueError("Invalid units: sigma must be dimensionless.") if ( @@ -863,6 +867,10 @@ def _check_dependencies(sigma, eta, depth, depth_c, nsigma, zlev): ) raise ValueError(msg) + if sigma is not None and sigma.units.is_unknown(): + # Be graceful, and promote unknown to dimensionless units. + sigma.units = "1" + if sigma is not None and not sigma.units.is_dimensionless(): msg = ( "Invalid units: sigma coordinate {!r} " @@ -1127,6 +1135,10 @@ def _check_dependencies(sigma, eta, depth): warnings.warn(msg, UserWarning, stacklevel=2) # Check units. + if sigma is not None and sigma.units.is_unknown(): + # Be graceful, and promote unknown to dimensionless units. + sigma.units = "1" + if sigma is not None and not sigma.units.is_dimensionless(): msg = ( "Invalid units: sigma coordinate {!r} " @@ -1335,6 +1347,10 @@ def _check_dependencies(s, c, eta, depth, depth_c): # Check units. coords = ((s, "s"), (c, "c")) for coord, term in coords: + if coord is not None and coord.units.is_unknown(): + # Be graceful, and promote unknown to dimensionless units. + coord.units = "1" + if coord is not None and not coord.units.is_dimensionless(): msg = ( "Invalid units: {} coordinate {!r} " @@ -1551,6 +1567,10 @@ def _check_dependencies(s, eta, depth, a, b, depth_c): raise ValueError(msg) # Check units. + if s is not None and s.units.is_unknown(): + # Be graceful, and promote unknown to dimensionless units. + s.units = "1" + if s is not None and not s.units.is_dimensionless(): msg = ( "Invalid units: s coordinate {!r} " @@ -1776,6 +1796,10 @@ def _check_dependencies(s, c, eta, depth, depth_c): # Check units. coords = ((s, "s"), (c, "c")) for coord, term in coords: + if coord is not None and coord.units.is_unknown(): + # Be graceful, and promote unknown to dimensionless units. + coord.units = "1" + if coord is not None and not coord.units.is_dimensionless(): msg = ( "Invalid units: {} coordinate {!r} " From 72ddb887007fe68031e970a459e07b22a85049d9 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Wed, 27 Jan 2021 15:39:29 +0000 Subject: [PATCH 2/3] patch aux factories to promote unknown to dimensionless units for formula terms --- docs/iris/src/conf.py | 4 +- docs/iris/src/whatsnew/3.0.1.rst | 520 ++++++++++++++++++ docs/iris/src/whatsnew/index.rst | 1 + lib/iris/__init__.py | 2 +- lib/iris/aux_factory.py | 13 +- lib/iris/fileformats/netcdf.py | 3 + .../aux_factory/test_HybridPressureFactory.py | 9 + .../unit/aux_factory/test_OceanSFactory.py | 6 + .../unit/aux_factory/test_OceanSg1Factory.py | 9 + .../unit/aux_factory/test_OceanSg2Factory.py | 9 + .../aux_factory/test_OceanSigmaFactory.py | 6 + .../aux_factory/test_OceanSigmaZFactory.py | 6 + .../netcdf/test__load_aux_factory.py | 41 +- 13 files changed, 618 insertions(+), 11 deletions(-) create mode 100644 docs/iris/src/whatsnew/3.0.1.rst diff --git a/docs/iris/src/conf.py b/docs/iris/src/conf.py index 7232d7c40e..8be849b7ce 100644 --- a/docs/iris/src/conf.py +++ b/docs/iris/src/conf.py @@ -82,8 +82,8 @@ def autolog(message): if iris.__version__ == "dev": version = "dev" else: - # major.feature(.minor)-dev -> major.minor - version = ".".join(iris.__version__.split("-")[0].split(".")[:2]) + # 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__ diff --git a/docs/iris/src/whatsnew/3.0.1.rst b/docs/iris/src/whatsnew/3.0.1.rst new file mode 100644 index 0000000000..408381f466 --- /dev/null +++ b/docs/iris/src/whatsnew/3.0.1.rst @@ -0,0 +1,520 @@ +.. include:: ../common_links.inc + +v3.0.1 (27 Jan 2021) +******************** + +This document explains the changes made to Iris for this release +(:doc:`View all changes `.) + + +.. dropdown:: :opticon:`alert` v3.0.1 Patches + :container: + shadow + :title: text-primary text-center font-weight-bold + :body: bg-light + :animate: fade-in + :open: + + The patches included in this release include: + + 💼 **Internal** + + * `@bjlittle`_ gracefully promote formula terms within :mod:`~iris.aux_factory` that have ``units`` of ``unknown`` + to ``units`` of ``1`` (dimensionless), where the formula term **must** have dimensionless ``units``. Without this + graceful treatment of ``units`` the resulting :class:`~iris.cube.Cube` will **not** contain the expected auxiliary + factory, and the associated derived coordinate will be missing. + + +.. dropdown:: :opticon:`report` Release Highlights + :container: + shadow + :title: text-primary text-center font-weight-bold + :body: bg-light + :animate: fade-in + :open: + + The highlights for this major release of Iris include: + + * We've finally dropped support for ``Python 2``, so welcome to ``Iris 3`` + and ``Python 3``! + * We've extended our coverage of the `CF Conventions and Metadata`_ by + introducing support for `CF Ancillary Data`_ and `Quality Flags`_, + * Lazy regridding is now available for several regridding schemes, + * Managing and manipulating metadata within Iris is now easier and more + consistent thanks to the introduction of a new common metadata API, + * :ref:`Cube arithmetic ` has been significantly improved with + regards to extended broadcasting, auto-transposition and a more lenient + behaviour towards handling metadata and coordinates, + * Our :ref:`documentation ` has been refreshed, + restructured, revitalised and rehosted on `readthedocs`_, + * It's now easier than ever to :ref:`install Iris ` + as a user or a developer, and the newly revamped developers guide walks + you though how you can :ref:`get involved ` + and contribute to Iris, + * Also, this is a major release of Iris, so please be aware of the + :ref:`incompatible changes ` and + :ref:`deprecations `. + + And finally, get in touch with us on `GitHub`_ if you have any issues or + feature requests for improving Iris. Enjoy! + + +📢 Announcements +================ + +* Congratulations to `@bouweandela`_, `@jvegasbsc`_, and `@zklaus`_ who + recently became Iris core developers. They bring a wealth of expertise to the + team, and are using Iris to underpin `ESMValTool`_ - "*A community diagnostic + and performance metrics tool for routine evaluation of Earth system models + in CMIP*". Welcome aboard! 🎉 + +* Congratulations also goes to `@jonseddon`_ who recently became an Iris core + developer. We look forward to seeing more of your awesome contributions! 🎉 + + +✨ Features +=========== + +* `@MoseleyS`_ greatly enhanced the :mod:`~iris.fileformats.nimrod` + module to provide richer meta-data translation when loading ``Nimrod`` data + into cubes. This covers most known operational use-cases. (:pull:`3647`) + +* `@stephenworsley`_ improved the handling of + :class:`iris.coords.CellMeasure`\ s in the :class:`~iris.cube.Cube` + statistical operations :meth:`~iris.cube.Cube.collapsed`, + :meth:`~iris.cube.Cube.aggregated_by` and + :meth:`~iris.cube.Cube.rolling_window`. These previously removed every + :class:`~iris.coords.CellMeasure` attached to the cube. Now, a + :class:`~iris.coords.CellMeasure` will only be removed if it is associated + with an axis over which the statistic is being run. (:pull:`3549`) + +* `@stephenworsley`_, `@pp-mo`_ and `@abooton`_ added support for + `CF Ancillary Data`_ variables. These are created as + :class:`iris.coords.AncillaryVariable`, and appear as components of cubes + much like :class:`~iris.coords.AuxCoord`\ s, with the new + :class:`~iris.cube.Cube` methods + :meth:`~iris.cube.Cube.add_ancillary_variable`, + :meth:`~iris.cube.Cube.remove_ancillary_variable`, + :meth:`~iris.cube.Cube.ancillary_variable`, + :meth:`~iris.cube.Cube.ancillary_variables` and + :meth:`~iris.cube.Cube.ancillary_variable_dims`. + They are loaded from and saved to NetCDF-CF files. Special support for + `Quality Flags`_ is also provided, to ensure they load and save with + appropriate units. (:pull:`3800`) + +* `@bouweandela`_ implemented lazy regridding for the + :class:`~iris.analysis.Linear`, :class:`~iris.analysis.Nearest`, and + :class:`~iris.analysis.AreaWeighted` regridding schemes. (:pull:`3701`) + +* `@bjlittle`_ added `logging`_ support within :mod:`iris.analysis.maths`, + :mod:`iris.common.metadata`, and :mod:`iris.common.resolve`. Each module + defines a :class:`logging.Logger` instance called ``logger`` with a default + ``level`` of ``INFO``. To enable ``DEBUG`` logging use + ``logger.setLevel("DEBUG")``. (:pull:`3785`) + +* `@bjlittle`_ added the :mod:`iris.common.resolve` module, which provides + infrastructure to support the analysis, identification and combination + of metadata common between two :class:`~iris.cube.Cube` operands into a + single resultant :class:`~iris.cube.Cube` that will be auto-transposed, + and with the appropriate broadcast shape. (:pull:`3785`) + +* `@bjlittle`_ added the :ref:`common metadata API `, which provides + a unified treatment of metadata across Iris, and allows users to easily + manage and manipulate their metadata in a consistent way. (:pull:`3785`) + +* `@bjlittle`_ added :ref:`lenient metadata ` support, to + allow users to control **strict** or **lenient** metadata equivalence, + difference and combination. (:pull:`3785`) + +* `@bjlittle`_ added :ref:`lenient cube maths ` support and + resolved several long standing major issues with cube arithmetic regarding + a more robust treatment of cube broadcasting, cube dimension auto-transposition, + and preservation of common metadata and coordinates during cube math operations. + Resolves :issue:`1887`, :issue:`2765`, and :issue:`3478`. (:pull:`3785`) + +* `@pp-mo`_ and `@TomekTrzeciak`_ enhanced :meth:`~iris.cube.Cube.collapse` to allow a 1-D weights array when + collapsing over a single dimension. + Previously, the weights had to be the same shape as the whole cube, which could cost a lot of memory in some cases. + The 1-D form is supported by most weighted array statistics (such as :meth:`np.average`), so this now works + with the corresponding Iris schemes (in that case, :const:`~iris.analysis.MEAN`). (:pull:`3943`) + + +🐛 Bugs Fixed +============= + +* `@stephenworsley`_ fixed :meth:`~iris.cube.Cube.remove_coord` to now also + remove derived coordinates by removing aux_factories. (:pull:`3641`) + +* `@jonseddon`_ fixed ``isinstance(cube, collections.Iterable)`` to now behave + as expected if a :class:`~iris.cube.Cube` is iterated over, while also + ensuring that ``TypeError`` is still raised. (Fixed by setting the + ``__iter__()`` method in :class:`~iris.cube.Cube` to ``None``). + (:pull:`3656`) + +* `@stephenworsley`_ enabled cube concatenation along an axis shared by cell + measures; these cell measures are now concatenated together in the resulting + cube. Such a scenario would previously cause concatenation to inappropriately + fail. (:pull:`3566`) + +* `@stephenworsley`_ newly included :class:`~iris.coords.CellMeasure`\ s in + :class:`~iris.cube.Cube` copy operations. Previously copying a + :class:`~iris.cube.Cube` would ignore any attached + :class:`~iris.coords.CellMeasure`. (:pull:`3546`) + +* `@bjlittle`_ set a :class:`~iris.coords.CellMeasure`'s + ``measure`` attribute to have a default value of ``area``. + Previously, the ``measure`` was provided as a keyword argument to + :class:`~iris.coords.CellMeasure` with a default value of ``None``, which + caused a ``TypeError`` when no ``measure`` was provided, since ``area`` or + ``volume`` are the only accepted values. (:pull:`3533`) + +* `@trexfeathers`_ set **all** plot types in :mod:`iris.plot` to now use + `matplotlib.dates.date2num`_ to format date/time coordinates for use on a plot + axis (previously :meth:`~iris.plot.pcolor` and :meth:`~iris.plot.pcolormesh` + did not include this behaviour). (:pull:`3762`) + +* `@trexfeathers`_ changed date/time axis labels in :mod:`iris.quickplot` to + now **always** be based on the ``epoch`` used in `matplotlib.dates.date2num`_ + (previously would take the unit from a time coordinate, if present, even + though the coordinate's value had been changed via ``date2num``). + (:pull:`3762`) + +* `@pp-mo`_ newly included attributes of cell measures in NETCDF-CF + file loading; they were previously being discarded. They are now available on + the :class:`~iris.coords.CellMeasure` in the loaded :class:`~iris.cube.Cube`. + (:pull:`3800`) + +* `@pp-mo`_ fixed the netcdf loader to now handle any grid-mapping + variables with missing ``false_easting`` and ``false_northing`` properties, + which was previously failing for some coordinate systems. See :issue:`3629`. + (:pull:`3804`) + +* `@stephenworsley`_ changed the way tick labels are assigned from string coords. + Previously, the first tick label would occasionally be duplicated. This also + removes the use of Matplotlib's deprecated ``IndexFormatter``. (:pull:`3857`) + +* `@znicholls`_ fixed :meth:`~iris.quickplot._title` to only check + ``units.is_time_reference`` if the ``units`` symbol is not used. (:pull:`3902`) + +* `@rcomer`_ fixed a bug whereby numpy array type attributes on a cube's + coordinates could prevent printing it. See :issue:`3921`. (:pull:`3922`) + +.. _whatsnew 3.0.1 changes: + +💣 Incompatible Changes +======================= + +* `@pp-mo`_ rationalised :class:`~iris.cube.CubeList` extraction + methods: + + The former method ``iris.cube.CubeList.extract_strict``, and the ``strict`` + keyword of the :meth:`~iris.cube.CubeList.extract` method have been removed, + and are replaced by the new routines :meth:`~iris.cube.CubeList.extract_cube` + and :meth:`~iris.cube.CubeList.extract_cubes`. + The new routines perform the same operation, but in a style more like other + ``Iris`` functions such as :meth:`~iris.load_cube` and :meth:`~iris.load_cubes`. + Unlike ``strict`` extraction, the type of return value is now completely + consistent : :meth:`~iris.cube.CubeList.extract_cube` always returns a + :class:`~iris.cube.Cube`, and :meth:`~iris.cube.CubeList.extract_cubes` + always returns an :class:`iris.cube.CubeList` of a length equal to the + number of constraints. (:pull:`3715`) + +* `@pp-mo`_ removed the former function + ``iris.analysis.coord_comparison``. (:pull:`3562`) + +* `@bjlittle`_ moved the + :func:`iris.experimental.equalise_cubes.equalise_attributes` function from + the :mod:`iris.experimental` module into the :mod:`iris.util` module. Please + use the :func:`iris.util.equalise_attributes` function instead. + (:pull:`3527`) + +* `@bjlittle`_ removed the module ``iris.experimental.concatenate``. In + ``v1.6.0`` the experimental ``concatenate`` functionality was moved to the + :meth:`iris.cube.CubeList.concatenate` method. Since then, calling the + :func:`iris.experimental.concatenate.concatenate` function raised an + exception. (:pull:`3523`) + +* `@stephenworsley`_ changed the default units of :class:`~iris.coords.DimCoord` + and :class:`~iris.coords.AuxCoord` from `"1"` to `"unknown"`. (:pull:`3795`) + +* `@stephenworsley`_ changed Iris objects loaded from NetCDF-CF files to have + ``units='unknown'`` where the corresponding NetCDF variable has no ``units`` + property. Previously these cases defaulted to ``units='1'``. + This affects loading of coordinates whose file variable has no "units" + attribute (not valid, under `CF units rules`_): These will now have units + of `"unknown"`, rather than `"1"`, which **may prevent the creation of + a hybrid vertical coordinate**. While these cases used to "work", this was + never really correct behaviour. (:pull:`3795`) + +* `@SimonPeatman`_ added attribute ``var_name`` to coordinates created by the + :func:`iris.analysis.trajectory.interpolate` function. This prevents + duplicate coordinate errors in certain circumstances. (:pull:`3718`) + +* `@bjlittle`_ aligned the :func:`iris.analysis.maths.apply_ufunc` with the + rest of the :mod:`iris.analysis.maths` API by changing its keyword argument + from ``other_cube`` to ``other``. (:pull:`3785`) + +* `@bjlittle`_ changed the :meth:`iris.analysis.maths.IFunc.__call__` to ignore + any surplus ``other`` keyword argument for a ``data_func`` that requires + **only one** argument. This aligns the behaviour of + :meth:`iris.analysis.maths.IFunc.__call__` with + :func:`~iris.analysis.maths.apply_ufunc`. Previously a ``ValueError`` + exception was raised. (:pull:`3785`) + + +.. _whatsnew 3.0.1 deprecations: + +🔥 Deprecations +=============== + +* `@stephenworsley`_ removed the deprecated :class:`iris.Future` flags + ``cell_date_time_objects``, ``netcdf_promote``, ``netcdf_no_unlimited`` and + ``clip_latitudes``. (:pull:`3459`) + +* `@stephenworsley`_ changed :attr:`iris.fileformats.pp.PPField.lbproc` to be an + ``int``. The deprecated attributes ``flag1``, ``flag2`` etc. have been + removed from it. (:pull:`3461`) + +* `@bjlittle`_ deprecated :func:`~iris.util.as_compatible_shape` in preference + for :class:`~iris.common.resolve.Resolve` e.g., ``Resolve(src, tgt)(tgt.core_data())``. + The :func:`~iris.util.as_compatible_shape` function will be removed in a future + release of Iris. (:pull:`3892`) + + +🔗 Dependencies +=============== + +* `@stephenworsley`_, `@trexfeathers`_ and `@bjlittle`_ removed ``Python2`` + support, modernising the codebase by switching to exclusive ``Python3`` + support. (:pull:`3513`) + +* `@bjlittle`_ improved the developer set up process. Configuring Iris and + :ref:`installing_from_source` as a developer with all the required package + dependencies is now easier with our curated conda environment YAML files. + (:pull:`3812`) + +* `@stephenworsley`_ pinned Iris to require `Dask`_ ``>=2.0``. (:pull:`3460`) + +* `@stephenworsley`_ and `@trexfeathers`_ pinned Iris to require + `Cartopy`_ ``>=0.18``, in order to remain compatible with the latest version + of `Matplotlib`_. (:pull:`3762`) + +* `@bjlittle`_ unpinned Iris to use the latest version of `Matplotlib`_. + Supporting ``Iris`` for both ``Python2`` and ``Python3`` had resulted in + pinning our dependency on `Matplotlib`_ at ``v2.x``. But this is no longer + necessary now that ``Python2`` support has been dropped. (:pull:`3468`) + +* `@stephenworsley`_ and `@trexfeathers`_ unpinned Iris to use the latest version + of `Proj`_. (:pull:`3762`) + +* `@stephenworsley`_ and `@trexfeathers`_ removed GDAL from the extensions + dependency group. We no longer consider it to be an extension. (:pull:`3762`) + + +.. _whatsnew 3.0.1 docs: + +📚 Documentation +================ + +* `@tkknight`_ moved the + :ref:`sphx_glr_generated_gallery_oceanography_plot_orca_projection.py` + from the general part of the gallery to oceanography. (:pull:`3761`) + +* `@tkknight`_ updated documentation to use a modern sphinx theme and be + served from https://scitools-iris.readthedocs.io/en/latest/. (:pull:`3752`) + +* `@bjlittle`_ added support for the `black`_ code formatter. This is + now automatically checked on GitHub PRs, replacing the older, unittest-based + ``iris.tests.test_coding_standards.TestCodeFormat``. Black provides automatic + code format correction for most IDEs. See the new developer guide section on + :ref:`code_formatting`. (:pull:`3518`) + +* `@tkknight`_ and `@trexfeathers`_ refreshed the :ref:`whats_new_contributions` + for the :ref:`iris_whatsnew`. This includes always creating the ``latest`` + what's new page so it appears on the latest documentation at + https://scitools-iris.readthedocs.io/en/latest/whatsnew. This resolves + :issue:`2104`, :issue:`3451`, :issue:`3818`, :issue:`3837`. Also updated the + :ref:`iris_development_releases_steps` to follow when making a release. + (:pull:`3769`, :pull:`3838`, :pull:`3843`) + +* `@tkknight`_ enabled the PDF creation of the documentation on the + `Read the Docs`_ service. The PDF may be accessed by clicking on the version + at the bottom of the side bar, then selecting ``PDF`` from the ``Downloads`` + section. (:pull:`3765`) + +* `@stephenworsley`_ added a warning to the + :func:`iris.analysis.cartography.project` function regarding its behaviour on + projections with non-rectangular boundaries. (:pull:`3762`) + +* `@stephenworsley`_ added the :ref:`cube_maths_combining_units` section to the + user guide to clarify how ``Units`` are handled during cube arithmetic. + (:pull:`3803`) + +* `@tkknight`_ overhauled the :ref:`developers_guide` including information on + getting involved in becoming a contributor and general structure of the + guide. This resolves :issue:`2170`, :issue:`2331`, :issue:`3453`, + :issue:`314`, :issue:`2902`. (:pull:`3852`) + +* `@rcomer`_ added argument descriptions to the :class:`~iris.coords.DimCoord` + docstring. (:pull:`3681`) + +* `@tkknight`_ added two url's to be ignored for the ``make linkcheck``. This + will ensure the Iris github project is not repeatedly hit during the + linkcheck for issues and pull requests as it can result in connection + refused and thus travis-ci_ job failures. For more information on linkcheck, + see :ref:`contributing.documentation.testing`. (:pull:`3873`) + +* `@tkknight`_ enabled the napolean_ package that is used by sphinx_ to cater + for the existing google style docstrings and to also allow for `numpy`_ + docstrings. This resolves :issue:`3841`. (:pull:`3871`) + +* `@tkknight`_ configured ``sphinx-build`` to promote warnings to errors when + building the documentation via ``make html``. This will minimise technical + debt accruing for the documentation. (:pull:`3877`) + +* `@tkknight`_ updated :ref:`installing_iris` to include a reference to + Windows Subsystem for Linux. (:pull:`3885`) + +* `@tkknight`_ updated the :ref:`iris_docs` homepage to include panels so the + links are more visible to users. This uses the sphinx-panels_ extension. + (:pull:`3884`) + +* `@bjlittle`_ created the :ref:`Further topics ` section and + included documentation for :ref:`metadata`, :ref:`lenient metadata`, and + :ref:`lenient maths`. (:pull:`3890`) + +* `@jonseddon`_ updated the CF version of the netCDF saver in the + :ref:`saving_iris_cubes` section and in the equivalent function docstring. + (:pull:`3925`) + +* `@bjlittle`_ applied `Title Case Capitalization`_ to the documentation. + (:pull:`3940`) + + +💼 Internal +=========== + +* `@pp-mo`_ and `@lbdreyer`_ removed all Iris test dependencies on `iris-grib`_ + by transferring all relevant content to the `iris-grib`_ repository. (:pull:`3662`, + :pull:`3663`, :pull:`3664`, :pull:`3665`, :pull:`3666`, :pull:`3669`, + :pull:`3670`, :pull:`3671`, :pull:`3672`, :pull:`3742`, :pull:`3746`) + +* `@lbdreyer`_ and `@pp-mo`_ overhauled the handling of dimensional + metadata to remove duplication. (:pull:`3422`, :pull:`3551`) + +* `@trexfeathers`_ simplified the standard license header for all files, which + removes the need to repeatedly update year numbers in the header. + (:pull:`3489`) + +* `@stephenworsley`_ changed the numerical values in tests involving the + Robinson projection due to improvements made in + `Proj`_. (:pull:`3762`) (see also `Proj#1292`_ and `Proj#2151`_) + +* `@stephenworsley`_ changed tests to account for more detailed descriptions of + projections in `GDAL`_. (:pull:`3762`) (see also `GDAL#1185`_) + +* `@stephenworsley`_ changed tests to account for `GDAL`_ now saving fill values + for data without masked points. (:pull:`3762`) + +* `@trexfeathers`_ changed every graphics test that includes `Cartopy's coastlines`_ + to account for new adaptive coastline scaling. (:pull:`3762`) + (see also `Cartopy#1105`_) + +* `@trexfeathers`_ changed graphics tests to account for some new default + grid-line spacing in `Cartopy`_. (:pull:`3762`) (see also `Cartopy#1117`_) + +* `@trexfeathers`_ added additional acceptable graphics test targets to account + for very minor changes in `Matplotlib`_ version ``3.3`` (colormaps, fonts and + axes borders). (:pull:`3762`) + +* `@rcomer`_ corrected the Matplotlib backend in Iris tests to ignore + `matplotlib.rcdefaults`_, instead the tests will **always** use ``agg``. + (:pull:`3846`) + +* `@bjlittle`_ migrated the `black`_ support from ``19.10b0`` to ``20.8b1``. + (:pull:`3866`) + +* `@lbdreyer`_ updated the CF standard name table to the latest version: `v75`_. + (:pull:`3867`) + +* `@bjlittle`_ added :pep:`517` and :pep:`518` support for building and + installing Iris, in particular to handle the `PyKE`_ package dependency. + (:pull:`3812`) + +* `@bjlittle`_ added metadata support for comparing :attr:`~iris.cube.Cube.attributes` + dictionaries that contain `numpy`_ arrays using `xxHash`_, an extremely fast + non-cryptographic hash algorithm, running at RAM speed limits. + +* `@bjlittle`_ added the ``iris.tests.assertDictEqual`` method to override + :meth:`unittest.TestCase.assertDictEqual` in order to cope with testing + metadata :attr:`~iris.cube.Cube.attributes` dictionary comparison where + the value of a key may be a `numpy`_ array. (:pull:`3785`) + +* `@bjlittle`_ added the :func:`~iris.config.get_logger` function for creating + a generic :class:`logging.Logger` with a :class:`logging.StreamHandler` and + custom :class:`logging.Formatter`. (:pull:`3785`) + +* `@owena11`_ identified and optimised a bottleneck in ``FieldsFile`` header + loading due to the use of :func:`numpy.fromfile`. (:pull:`3791`) + +* `@znicholls`_ added a test for plotting with the label being taken from the unit's symbol, see :meth:`~iris.tests.test_quickplot.TestLabels.test_pcolormesh_str_symbol` (:pull:`3902`). + +* `@znicholls`_ made :func:`~iris.tests.idiff.step_over_diffs` robust to hyphens (``-``) in the input path (i.e. the ``result_dir`` argument) (:pull:`3902`). + +* `@bjlittle`_ migrated the CIaaS from `travis-ci`_ to `cirrus-ci`_, and removed `stickler-ci`_ support. (:pull:`3928`) + +* `@bjlittle`_ introduced `nox`_ as a common and easy entry-point for test automation. + It can be used both from `cirrus-ci`_ in the cloud, and locally by the developer to + run the Iris tests, the doc-tests, the gallery doc-tests, and lint Iris + with `flake8`_ and `black`_. (:pull:`3928`) + + +.. _Read the Docs: https://scitools-iris.readthedocs.io/en/latest/ +.. _Matplotlib: https://matplotlib.org/ +.. _CF units rules: https://cfconventions.org/Data/cf-conventions/cf-conventions-1.8/cf-conventions.html#units +.. _CF Ancillary Data: https://cfconventions.org/Data/cf-conventions/cf-conventions-1.8/cf-conventions.html#ancillary-data +.. _Quality Flags: https://cfconventions.org/Data/cf-conventions/cf-conventions-1.8/cf-conventions.html#flags +.. _iris-grib: https://github.com/SciTools/iris-grib +.. _Cartopy: https://github.com/SciTools/cartopy +.. _Cartopy's coastlines: https://scitools.org.uk/cartopy/docs/latest/matplotlib/geoaxes.html?highlight=coastlines#cartopy.mpl.geoaxes.GeoAxes.coastlines +.. _Cartopy#1105: https://github.com/SciTools/cartopy/pull/1105 +.. _Cartopy#1117: https://github.com/SciTools/cartopy/pull/1117 +.. _Dask: https://github.com/dask/dask +.. _matplotlib.dates.date2num: https://matplotlib.org/api/dates_api.html#matplotlib.dates.date2num +.. _Proj: https://github.com/OSGeo/PROJ +.. _black: https://black.readthedocs.io/en/stable/ +.. _Proj#1292: https://github.com/OSGeo/PROJ/pull/1292 +.. _Proj#2151: https://github.com/OSGeo/PROJ/pull/2151 +.. _GDAL: https://github.com/OSGeo/gdal +.. _GDAL#1185: https://github.com/OSGeo/gdal/pull/1185 +.. _@MoseleyS: https://github.com/MoseleyS +.. _@stephenworsley: https://github.com/stephenworsley +.. _@pp-mo: https://github.com/pp-mo +.. _@abooton: https://github.com/abooton +.. _@bouweandela: https://github.com/bouweandela +.. _@bjlittle: https://github.com/bjlittle +.. _@trexfeathers: https://github.com/trexfeathers +.. _@jonseddon: https://github.com/jonseddon +.. _@tkknight: https://github.com/tkknight +.. _@lbdreyer: https://github.com/lbdreyer +.. _@SimonPeatman: https://github.com/SimonPeatman +.. _@TomekTrzeciak: https://github.com/TomekTrzeciak +.. _@rcomer: https://github.com/rcomer +.. _@jvegasbsc: https://github.com/jvegasbsc +.. _@zklaus: https://github.com/zklaus +.. _@znicholls: https://github.com/znicholls +.. _ESMValTool: https://github.com/ESMValGroup/ESMValTool +.. _v75: https://cfconventions.org/Data/cf-standard-names/75/build/cf-standard-name-table.html +.. _sphinx-panels: https://sphinx-panels.readthedocs.io/en/latest/ +.. _logging: https://docs.python.org/3/library/logging.html +.. _numpy: https://github.com/numpy/numpy +.. _xxHash: https://github.com/Cyan4973/xxHash +.. _PyKE: https://pypi.org/project/scitools-pyke/ +.. _matplotlib.rcdefaults: https://matplotlib.org/3.1.1/api/matplotlib_configuration_api.html?highlight=rcdefaults#matplotlib.rcdefaults +.. _@owena11: https://github.com/owena11 +.. _GitHub: https://github.com/SciTools/iris/issues/new/choose +.. _readthedocs: https://readthedocs.org/ +.. _CF Conventions and Metadata: https://cfconventions.org/ +.. _flake8: https://flake8.pycqa.org/en/stable/ +.. _nox: https://nox.thea.codes/en/stable/ +.. _Title Case Capitalization: https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case +.. _travis-ci: https://travis-ci.org/github/SciTools/iris +.. _stickler-ci: https://stickler-ci.com/ diff --git a/docs/iris/src/whatsnew/index.rst b/docs/iris/src/whatsnew/index.rst index f10e73554a..ab5b22b030 100644 --- a/docs/iris/src/whatsnew/index.rst +++ b/docs/iris/src/whatsnew/index.rst @@ -10,6 +10,7 @@ Iris versions. .. toctree:: :maxdepth: 1 + 3.0.1.rst 3.0.rst 2.4.rst 2.3.rst diff --git a/lib/iris/__init__.py b/lib/iris/__init__.py index e858a10566..0d960c2c30 100644 --- a/lib/iris/__init__.py +++ b/lib/iris/__init__.py @@ -106,7 +106,7 @@ def callback(cube, field, filename): # Iris revision. -__version__ = "3.0.0" +__version__ = "3.0.1" # Restrict the names imported when using "from iris import *" __all__ = [ diff --git a/lib/iris/aux_factory.py b/lib/iris/aux_factory.py index 8fca2f087a..962b46e9e2 100644 --- a/lib/iris/aux_factory.py +++ b/lib/iris/aux_factory.py @@ -11,6 +11,7 @@ from abc import ABCMeta, abstractmethod import warnings +import cf_units import dask.array as da import numpy as np @@ -621,7 +622,7 @@ def _check_dependencies(delta, sigma, surface_air_pressure): # Check units. if sigma is not None and sigma.units.is_unknown(): # Be graceful, and promote unknown to dimensionless units. - sigma.units = "1" + sigma.units = cf_units.Unit("1") if sigma is not None and not sigma.units.is_dimensionless(): raise ValueError("Invalid units: sigma must be dimensionless.") @@ -869,7 +870,7 @@ def _check_dependencies(sigma, eta, depth, depth_c, nsigma, zlev): if sigma is not None and sigma.units.is_unknown(): # Be graceful, and promote unknown to dimensionless units. - sigma.units = "1" + sigma.units = cf_units.Unit("1") if sigma is not None and not sigma.units.is_dimensionless(): msg = ( @@ -1137,7 +1138,7 @@ def _check_dependencies(sigma, eta, depth): # Check units. if sigma is not None and sigma.units.is_unknown(): # Be graceful, and promote unknown to dimensionless units. - sigma.units = "1" + sigma.units = cf_units.Unit("1") if sigma is not None and not sigma.units.is_dimensionless(): msg = ( @@ -1349,7 +1350,7 @@ def _check_dependencies(s, c, eta, depth, depth_c): for coord, term in coords: if coord is not None and coord.units.is_unknown(): # Be graceful, and promote unknown to dimensionless units. - coord.units = "1" + coord.units = cf_units.Unit("1") if coord is not None and not coord.units.is_dimensionless(): msg = ( @@ -1569,7 +1570,7 @@ def _check_dependencies(s, eta, depth, a, b, depth_c): # Check units. if s is not None and s.units.is_unknown(): # Be graceful, and promote unknown to dimensionless units. - s.units = "1" + s.units = cf_units.Unit("1") if s is not None and not s.units.is_dimensionless(): msg = ( @@ -1798,7 +1799,7 @@ def _check_dependencies(s, c, eta, depth, depth_c): for coord, term in coords: if coord is not None and coord.units.is_unknown(): # Be graceful, and promote unknown to dimensionless units. - coord.units = "1" + coord.units = cf_units.Unit("1") if coord is not None and not coord.units.is_dimensionless(): msg = ( diff --git a/lib/iris/fileformats/netcdf.py b/lib/iris/fileformats/netcdf.py index 98f712a970..bb7a870d58 100644 --- a/lib/iris/fileformats/netcdf.py +++ b/lib/iris/fileformats/netcdf.py @@ -719,6 +719,9 @@ def coord_from_term(term): 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") diff --git a/lib/iris/tests/unit/aux_factory/test_HybridPressureFactory.py b/lib/iris/tests/unit/aux_factory/test_HybridPressureFactory.py index 14944891f2..32091c7d63 100644 --- a/lib/iris/tests/unit/aux_factory/test_HybridPressureFactory.py +++ b/lib/iris/tests/unit/aux_factory/test_HybridPressureFactory.py @@ -113,6 +113,15 @@ def test_factory_metadata(self): 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): diff --git a/lib/iris/tests/unit/aux_factory/test_OceanSFactory.py b/lib/iris/tests/unit/aux_factory/test_OceanSFactory.py index caf9d303c6..6e8e40cd1b 100644 --- a/lib/iris/tests/unit/aux_factory/test_OceanSFactory.py +++ b/lib/iris/tests/unit/aux_factory/test_OceanSFactory.py @@ -137,6 +137,12 @@ def test_depth_incompatible_units(self): 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): diff --git a/lib/iris/tests/unit/aux_factory/test_OceanSg1Factory.py b/lib/iris/tests/unit/aux_factory/test_OceanSg1Factory.py index 99a4fe1732..238df2f073 100644 --- a/lib/iris/tests/unit/aux_factory/test_OceanSg1Factory.py +++ b/lib/iris/tests/unit/aux_factory/test_OceanSg1Factory.py @@ -121,6 +121,15 @@ def test_depth_incompatible_units(self): 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): diff --git a/lib/iris/tests/unit/aux_factory/test_OceanSg2Factory.py b/lib/iris/tests/unit/aux_factory/test_OceanSg2Factory.py index 387f0e48d1..fb3ada382e 100644 --- a/lib/iris/tests/unit/aux_factory/test_OceanSg2Factory.py +++ b/lib/iris/tests/unit/aux_factory/test_OceanSg2Factory.py @@ -121,6 +121,15 @@ def test_depth_incompatible_units(self): 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): diff --git a/lib/iris/tests/unit/aux_factory/test_OceanSigmaFactory.py b/lib/iris/tests/unit/aux_factory/test_OceanSigmaFactory.py index 07c970ad7e..69a8a32c6e 100644 --- a/lib/iris/tests/unit/aux_factory/test_OceanSigmaFactory.py +++ b/lib/iris/tests/unit/aux_factory/test_OceanSigmaFactory.py @@ -59,6 +59,12 @@ def test_depth_incompatible_units(self): 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): diff --git a/lib/iris/tests/unit/aux_factory/test_OceanSigmaZFactory.py b/lib/iris/tests/unit/aux_factory/test_OceanSigmaZFactory.py index 6f1e8cd57a..4a4e30b9ca 100644 --- a/lib/iris/tests/unit/aux_factory/test_OceanSigmaZFactory.py +++ b/lib/iris/tests/unit/aux_factory/test_OceanSigmaZFactory.py @@ -138,6 +138,12 @@ def test_depth_incompatible_units(self): 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): diff --git a/lib/iris/tests/unit/fileformats/netcdf/test__load_aux_factory.py b/lib/iris/tests/unit/fileformats/netcdf/test__load_aux_factory.py index 48cc9c0d1a..c8f9460e0f 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/test__load_aux_factory.py +++ b/lib/iris/tests/unit/fileformats/netcdf/test__load_aux_factory.py @@ -53,8 +53,44 @@ def test_formula_terms_ap(self): self.assertEqual(factory.surface_air_pressure, self.ps) def test_formula_terms_a_p0(self): - coord_a = DimCoord(np.arange(5), units="Pa") - coord_p0 = DimCoord(10, units="1") + 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", @@ -71,6 +107,7 @@ def test_formula_terms_a_p0(self): 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 From 86335036c7b05a23121509999cfc22d354aae172 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Wed, 27 Jan 2021 15:53:53 +0000 Subject: [PATCH 3/3] add whatnew PR for entry --- docs/iris/src/whatsnew/3.0.1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/iris/src/whatsnew/3.0.1.rst b/docs/iris/src/whatsnew/3.0.1.rst index 408381f466..597e235ebe 100644 --- a/docs/iris/src/whatsnew/3.0.1.rst +++ b/docs/iris/src/whatsnew/3.0.1.rst @@ -21,7 +21,7 @@ This document explains the changes made to Iris for this release * `@bjlittle`_ gracefully promote formula terms within :mod:`~iris.aux_factory` that have ``units`` of ``unknown`` to ``units`` of ``1`` (dimensionless), where the formula term **must** have dimensionless ``units``. Without this graceful treatment of ``units`` the resulting :class:`~iris.cube.Cube` will **not** contain the expected auxiliary - factory, and the associated derived coordinate will be missing. + factory, and the associated derived coordinate will be missing. (:pull:`3965`) .. dropdown:: :opticon:`report` Release Highlights