Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/src/common_links.inc
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
.. _SciTools Contributor's License Agreement (CLA): https://cla-assistant.io/SciTools/
.. _extlinks: https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html
.. _Diataxis: https://diataxis.fr/
.. _CF Conventions: https://cfconventions.org/


.. comment
Expand Down
2 changes: 1 addition & 1 deletion docs/src/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ 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 <https://cfconventions.org>`_
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.
Expand Down
2 changes: 2 additions & 0 deletions docs/src/user_manual/explanation/um_files_loading.rst
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,8 @@ Time Information

**Details**

More about units: :doc:`../explanation/units`.

In Iris (as in CF) times and time intervals are both expressed as simple
numbers, following the approach of the
`UDUNITS project <https://www.unidata.ucar.edu/software/udunits/>`_.
Expand Down
150 changes: 150 additions & 0 deletions docs/src/user_manual/explanation/units.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
.. explanation:: Units
:tags: topic_data_model

Read about how Iris objects such as Cubes and Coordinates are assigned scientific units.

.. include:: /common_links.inc

=====
Units
=====

.. todo:: cross reference in navigating_a_cube, why_iris, cube_maths, um_files_loading, glossary

A measure such as 'temperature' cannot just be described by a number - e.g.
273.15 - it must also be associated with a unit - e.g. 'Kelvin' - to give it
meaning. This is a core element of the :term:`CF Conventions`, and so is
fundamental to the design of Iris:

- All data model objects - e.g. :term:`Cube`, :term:`Coordinate`; anything
based on :class:`~iris.common.mixin.CFVariableMixin` - have a
:attr:`~iris.common.mixin.CFVariableMixin.units` attribute.
- Operations on Iris objects are 'recorded' in their units attribute. E.g.
a :class:`~iris.cube.Cube` with units of ``m/s`` multiplied by a
:class:`~iris.cube.Cube` with units of ``s`` will have units of ``m`` after
the operation. Read more: :ref:`cube_maths_combining_units`.
- Operations between :class:`~iris.cube.Cube` objects - e.g. arithmetic or
merging - are only permitted if the units are compatible. Read more:
:doc:`/user_manual/section_indexes/metadata_arithmetic`,
:ref:`merge_and_concat`.

In addition to the CF Conventions, the Iris ecosystem defines two further units:

- ``no-unit`` - the associated data is not suitable for describing with a unit.
- ``unknown`` - the unit describing the associated data cannot be determined.
If a calculation is prevented because it would result in inappropriate units,
it may be forced by setting the units of the original cubes to be
``"unknown"``.

The Units API
-------------

.. testsetup::

from iris.cube import Cube
from iris.experimental.units import USE_CFPINT
my_cube = Cube([0, 1, 2])

Setting the :attr:`~iris.common.mixin.CFVariableMixin.units` on an object is
simple - you provide a string and Iris will parse and store it appropriately:

>>> my_cube.units = 'm/s'
>>> print(my_cube.units)
m/s
>>> print(repr(my_cube.units))
Unit('m/s')

As well as a string, you can assign objects directly from the unit-handling
library and these will also be parsed. If you want to test this: the function
:func:`iris.common.units.make_unit` outputs the same object that would be
created when assigning an input to the
:attr:`~iris.common.mixin.CFVariableMixin.units` attribute.

You can choose which library you want Iris to use for unit parsing and operations:

.. list-table:: Choice of Cf-units or Pint in Iris
:header-rows: 1
:stub-columns: 1

* - Library
- Iris Class
- Parent Class
- Status @ ``2026-04-13``
* - `Cf-units`_
- :class:`iris.common.units.CfUnit`
- :class:`cf_units.Unit`
- Default
* - `Pint`_
- :class:`iris.common.units.PintUnit`
- :class:`pint.Unit`
- Experimental

You make this choice using the :data:`iris.experimental.units.USE_CFPINT` flag:

>>> with USE_CFPINT.context():
... my_cube.units = "m/s"
>>> print(my_cube.units)
m s-1
>>> print(repr(my_cube.units))
Unit('meter / second')

In both cases Iris internals ensure the unit is CF-compliant. CF-compliance is
standard behaviour for Cf-units; while the Pint case currently (``2026-04-13``)
uses the `Cfpint`_ library with some Iris-specific modifications. The intent is a
**seamless user experience**
regardless of the underlying library, hopefully allowing a
**gradual transition to `Pint`_**,
bringing Iris users closer to the wider scientific Python ecosystem.

To aid the above transition, any compatibility features have been marked as
deprecated, with advice on how to update your code. Example:
:attr:`iris.common.units.PintUnit.is_dimensionless`.

The Libraries Underneath
------------------------

The :term:`CF Conventions` officially define unit behaviour as follows:

- Reference time units: e.g. ``days since 2000-12-01``, behaviour
described directly in the `CF Conventions`_ pages; section 4.4.
- All other units: behaviour provided by the `UDUNITS2`_ package, with a small
number of modifications described in the `CF Conventions`_ pages; section 3.1.

Since reference time units are not based on existing software, the rules given
by the CF Conventions are implemented by the `Cftime`_ Python package.

A full software implementation of CF Conventions units must therefore combine:

- cftime
- UDUNITS2
- The specific UDUNITS2 modifications

The two most established packages for this are: `Cf-units`_ and `Cfunits`_.

Both of these packages have
struggled to find ways of combining Python with UDUNITS2, especially when it
comes to installation. These struggles have inspired attempts to implement
UDUNITS2 in Python or via the `Pint`_ Python package, all of which are experimental
at time of writing (``2026-04-13``).

Iris' hope for the future is for a mature and well maintained implementation of
**CF-compliant Pint**. The CF Conventions are the de facto standard for storing
atmospheric/oceanographic data, while Pint is the most widely accepted Python
package for units. Being Pint-based (rather than UDUNITS2-based), should
improve the Iris user experience:

- Better interoperability/collaboration with the wider scientific Python ecosystem.
- A better maintained units library - more features, more support.
- Simpler installation. Specifically: this would allow Iris to be fully
installed via Pip (UDUNITS2 needs Conda for installation).

This is why we are future-proofing Iris to support both Cf-units and Pint, and
why we are working with international collaborators to establish a
CF-compliant Pint implementation.

.. _Cf-units: https://github.com/SciTools/cf-units
.. _Cfunits: https://github.com/NCAS-CMS/cfunits
.. _Pint: https://github.com/hgrecco/pint
.. _Cfpint: https://github.com/SciTools/cfpint
.. _UDUNITS2: https://www.unidata.ucar.edu/software/udunits
.. _Cftime: https://github.com/unidata/cftime
2 changes: 1 addition & 1 deletion docs/src/user_manual/explanation/why_iris.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ It excels when working with multi-dimensional Earth Science data, where tabular
representations become unwieldy and inefficient.

`CF Standard names <https://cfconventions.org/Data/cf-standard-names/current/build/cf-standard-name-table.html>`_,
`units <https://github.com/SciTools/cf_units>`_, and coordinate metadata
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:
Expand Down
4 changes: 2 additions & 2 deletions docs/src/user_manual/how_to/navigating_a_cube.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ and :attr:`Cube.units <iris.cube.Cube.units>` respectively::
print(cube.units)

Interrogating these with the standard :func:`type` function will tell you that ``standard_name`` and ``long_name``
are either a string or ``None``, and ``units`` is an instance of :class:`iris.unit.Unit`. A more in depth discussion on
the cube units and their functional effects can be found at the end of :doc:`../tutorial/cube_maths`.
are either a string or ``None``, and ``units`` is a specialised unit-handling
object. Read more about units here: :doc:`../explanation/units`.

You can access a string representing the "name" of a cube with the :meth:`Cube.name() <iris.cube.Cube.name>` method::

Expand Down
2 changes: 1 addition & 1 deletion docs/src/user_manual/reference/glossary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ Glossary
The unit with which the :term:`phenomenon` is measured e.g. m / sec.

| **Related:** :term:`Cube`
| **More information:** :doc:`../explanation/iris_cubes`
| **More information:** :doc:`../explanation/units` :doc:`../explanation/iris_cubes`
|

Xarray
Expand Down
1 change: 1 addition & 0 deletions docs/src/user_manual/section_indexes/general.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ Below are any pages not belonging to any other User Manual section.
../explanation/um_files_loading
../explanation/ux_guide
../explanation/which_regridder_to_use
../explanation/units
4 changes: 3 additions & 1 deletion docs/src/user_manual/tutorial/cube_maths.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ this attribute can then be manipulated directly::
cube.data -= 273.15

The problem with manipulating the data directly is that other metadata may
become inconsistent; in this case the units of the cube are no longer what was
become inconsistent; in this case the :term:`unit` of the cube are no longer what was
intended. This example could be rectified by changing the units attribute::

cube.units = 'celsius'
Expand Down Expand Up @@ -237,6 +237,8 @@ The result could now be plotted using the guidance provided in the
Combining Units
---------------

More about units: :doc:`../explanation/units`.

It should be noted that when combining cubes by multiplication, division or
power operations, the resulting cube will have a unit which is an appropriate
combination of the constituent units. In the above example, since ``pressure``
Expand Down
2 changes: 1 addition & 1 deletion docs/src/user_manual/tutorial/merge_and_concat.rst
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ Concatenate

**Time Units**

Differences in the units of the time coordinates of the input cubes probably cause
Differences in the :term:`unit` of the time coordinates of the input cubes probably cause
the greatest amount of concatenate-related difficulties.
In recognition of this, Iris has a helper function,
:func:`~iris.util.unify_time_units`, to apply a common time unit to all the input cubes.
Expand Down
12 changes: 8 additions & 4 deletions lib/iris/common/mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from collections.abc import Mapping
from functools import wraps
from typing import Any
from typing import TYPE_CHECKING, Any

# Optional imports : actually only needed for type hints
try:
Expand All @@ -34,6 +34,9 @@
from .metadata import BaseMetadata
from .units import make_unit

if TYPE_CHECKING:
from units import CfUnit, PintUnit

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

❤️ thanks, I keep forgetting this is the way

__all__ = ["CFVariableMixin", "LimitedAttributeDict"]


Expand Down Expand Up @@ -229,11 +232,12 @@ def var_name(self, name: str | None) -> None:
self._metadata_manager.var_name = name

@property
def units(self) -> cf_units.Unit | cfpint.Unit:
def units(self) -> CfUnit | PintUnit:
"""The S.I. unit of the object.

If not ``None``, this is always an Iris Unit type - either :class:`CfUnit` or
:class:`cfpint.Unit`. See :func:`iris.common.units.make_unit`.
If not ``None``, this is always an Iris Unit type - either
:class:`~iris.common.units.CfUnit` or :class:`~iris.common.units.PintUnit`.
See :func:`iris.common.units.make_unit`.
"""
return self._metadata_manager.units

Expand Down
8 changes: 7 additions & 1 deletion lib/iris/common/units/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@
#
# This file is part of Iris and is released under the BSD license.
# See LICENSE in the root of the repository for full licensing details.
"""Generic definition of units as used in Iris."""
"""Generic definition of units as used in Iris.

.. z_reference:: iris.common.units
:tags: topic_data_model

API reference
"""

from typing import Any

Expand Down
2 changes: 1 addition & 1 deletion lib/iris/cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -1293,7 +1293,7 @@ def __init__(
#: The "standard name" for the Cube's phenomenon.
self.standard_name = standard_name

#: An instance of :class:`cf_units.Unit` describing the Cube's data.
#: An instance of :class:`~iris.common.units.CfUnit` or :class:`~iris.common.units.PintUnit` describing the Cube's data.
self.units = units

#: The "long name" for the Cube's phenomenon.
Expand Down
8 changes: 7 additions & 1 deletion lib/iris/experimental/units.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
# This file is part of Iris and is released under the BSD license.
# See LICENSE in the root of the repository for full licensing details.

"""Control for unit types."""
"""Control for unit types.

.. z_reference:: iris.experimental.units
:tags: topic_experimental;topic_data_model

API reference
"""

from contextlib import contextmanager
import threading
Expand Down
Loading