You are viewing the latest unreleased documentation v3.3.dev0. You may prefer a stable version.

# Lenient Cube Maths#

This section provides an overview of lenient cube maths. In particular, it explains what lenient maths involves, clarifies how it differs from normal or strict cube maths, and demonstrates how you can exercise fine control over whether your cube maths operations are lenient or strict.

Note that, lenient cube maths is the default behaviour of Iris from version `3.0.0`.

## Introduction#

Lenient maths stands somewhat on the shoulders of giants. If you’ve not already done so, you may want to recap the material discussed in the following sections,

In addition to this, cube maths leans heavily on the `resolve` module, which provides the necessary infrastructure required by Iris to analyse and combine each `Cube` operand involved in a maths operation into the resultant `Cube`. It may be worth while investing some time to understand how the `Resolve` class underpins cube maths, and consider how it may be used in general to combine or resolve cubes together.

Given these prerequisites, recall that lenient behaviour introduced and discussed the concept of lenient metadata; a more pragmatic and forgiving approach to comparing, combining and understanding the differences between your metadata (Table 1). The lenient metadata philosophy introduced there is extended to cube maths, with the view to also preserving as much common coordinate (Table 2) information, as well as common metadata, between the participating `Cube` operands as possible.

Let’s consolidate our understanding of lenient and strict cube maths through a practical worked example, which we’ll explore together next.

## Lenient Example#

Consider the following `Cube` of `air_potential_temperature`, which has an atmosphere hybrid height parametric vertical coordinate, and represents the output of an low-resolution global atmospheric `experiment`,

```>>> print(experiment)
air_potential_temperature / (K)             (model_level_number: 15; grid_latitude: 100; grid_longitude: 100)
Dimension coordinates:
model_level_number                                     x                  -                    -
grid_latitude                                          -                  x                    -
grid_longitude                                         -                  -                    x
Auxiliary coordinates:
atmosphere_hybrid_height_coordinate                    x                  -                    -
sigma                                                  x                  -                    -
surface_altitude                                       -                  x                    x
Derived coordinates:
altitude                                               x                  x                    x
Scalar coordinates:
forecast_period                     0.0 hours
forecast_reference_time             2009-09-09 17:10:00
time                                2009-09-09 17:10:00
Attributes:
Conventions                         'CF-1.5'
STASH                               m01s00i004
experiment-id                       'RT3 50'
source                              'Data from Met Office Unified Model 7.04'
```

Consider also the following `Cube`, which has the same global spatial extent, and acts as a `control`,

```>>> print(control)
air_potential_temperature / (K)     (grid_latitude: 100; grid_longitude: 100)
Dimension coordinates:
grid_latitude                             x                    -
grid_longitude                            -                    x
Scalar coordinates:
model_level_number          1
time                        2009-09-09 17:10:00
Attributes:
Conventions                 'CF-1.7'
STASH                       m01s00i004
source                      'Data from Met Office Unified Model 7.04'
```

Now let’s subtract these cubes in order to calculate a simple `difference`,

```>>> difference = experiment - control
>>> print(difference)
unknown / (K)                               (model_level_number: 15; grid_latitude: 100; grid_longitude: 100)
Dimension coordinates:
model_level_number                                     x                  -                    -
grid_latitude                                          -                  x                    -
grid_longitude                                         -                  -                    x
Auxiliary coordinates:
atmosphere_hybrid_height_coordinate                    x                  -                    -
sigma                                                  x                  -                    -
surface_altitude                                       -                  x                    x
Derived coordinates:
altitude                                               x                  x                    x
Scalar coordinates:
forecast_period                     0.0 hours
forecast_reference_time             2009-09-09 17:10:00
time                                2009-09-09 17:10:00
Attributes:
experiment-id                       'RT3 50'
source                              'Data from Met Office Unified Model 7.04'
```

Note that, cube maths automatically takes care of broadcasting the dimensionality of the `control` up to that of the `experiment`, in order to calculate the `difference`. This is performed only after ensuring that both the dimension coordinates `grid_latitude` and `grid_longitude` are first leniently equivalent.

As expected, the resultant `difference` contains the `HybridHeightFactory` and all it’s associated auxiliary coordinates. However, the scalar coordinates have been leniently combined to preserve as much coordinate information as possible, and the `attributes` dictionaries have also been leniently combined. In addition, see what further rationalisation is always performed by cube maths on the resultant metadata and coordinates.

Also, note that the `model_level_number` scalar coordinate from the `control` has be superseded by the similarly named dimension coordinate from the `experiment` in the resultant `difference`.

Now let’s compare and contrast this lenient result with the strict alternative. But before we do so, let’s first clarify how to control the behaviour of cube maths.

## Control the Behaviour#

As stated earlier, lenient cube maths is the default behaviour from Iris `3.0.0`. However, this behaviour may be controlled via the thread-safe `LENIENT["maths"]` runtime option,

```>>> from iris.common import LENIENT
>>> print(LENIENT)
Lenient(maths=True)
```

Which may be set and applied globally thereafter for Iris within the current thread of execution,

```>>> LENIENT["maths"] = False
>>> print(LENIENT)
Lenient(maths=False)
```

Or alternatively, temporarily alter the behaviour of cube maths only within the scope of the `LENIENT` context manager,

```>>> print(LENIENT)
Lenient(maths=True)
>>> with LENIENT.context(maths=False):
...     print(LENIENT)
...
Lenient(maths=False)
>>> print(LENIENT)
Lenient(maths=True)
```

## Strict Example#

Now that we know how to control the underlying behaviour of cube maths, let’s return to our lenient example, but this time perform strict cube maths instead,

```>>> with LENIENT.context(maths=False):
...     difference = experiment - control
...
>>> print(difference)
unknown / (K)                               (model_level_number: 15; grid_latitude: 100; grid_longitude: 100)
Dimension coordinates:
model_level_number                                     x                  -                    -
grid_latitude                                          -                  x                    -
grid_longitude                                         -                  -                    x
Auxiliary coordinates:
atmosphere_hybrid_height_coordinate                    x                  -                    -
sigma                                                  x                  -                    -
surface_altitude                                       -                  x                    x
Derived coordinates:
altitude                                               x                  x                    x
Scalar coordinates:
time                                2009-09-09 17:10:00
Attributes:
source                              'Data from Met Office Unified Model 7.04'
```

Although the numerical result of this strict cube maths operation is identical, it is not as rich in metadata as the lenient alternative. In particular, it does not contain the `forecast_period` and `forecast_reference_time` scalar coordinates, or the `experiment-id` in the `attributes` dictionary.

This is because strict cube maths, in general, will only return common metadata and common coordinates that are strictly equivalent.

## Finer Detail#

In general, if you want to preserve as much metadata and coordinate information as possible during cube maths, then opt to use the default lenient behaviour. Otherwise, favour the strict alternative if you require to enforce precise metadata and coordinate commonality.

The following information may also help you decide whether lenient cube maths best suits your use case,

Additionally, cube maths will always perform the following rationalisation of the resultant `Cube`,