Skip to content

Commit a12c0c8

Browse files
stephenworsleypre-commit-ci[bot]pp-mo
authored
Temporary fix to ensure coordinate realisation during pp load (#6767)
* put in a temporary fix to allow data realisation with derived coordinates during pp load * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * move and rename context manager * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add documentation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add whatsnew * Update lib/iris/loading.py Co-authored-by: Patrick Peglar <patrick.peglar@metoffice.gov.uk> * address review comments * refactor tests * lint fix --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Patrick Peglar <patrick.peglar@metoffice.gov.uk>
1 parent 2ef5940 commit a12c0c8

File tree

4 files changed

+105
-3
lines changed

4 files changed

+105
-3
lines changed

docs/src/whatsnew/latest.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,12 @@ This document explains the changes made to Iris for this release
168168
#. `@ukmo-ccbunney`_ ``assert_CML`` now uses stricter array formatting to avoid
169169
changes in tests due to Numpy version changes. (:pull:`6743`)
170170

171+
#. `@stephenworsley`_ added a private switch :obj:`~iris.loading._CONCRETE_DERIVED_LOADING`
172+
for controlling laziness of coordinates from pp loading, avoiding a
173+
slowdown due to merging. Note: this object is temporary and is likely
174+
to be replaced by a permanent solution or else be renamed.
175+
(:issue:`6755`, :pull:`6767`)
176+
171177

172178
.. comment
173179
Whatsnew author names (@github name) in alphabetical order. Note that,

lib/iris/fileformats/rules.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import iris.cube
1515
import iris.exceptions
1616
import iris.fileformats.um_cf_map
17+
from iris.loading import _CONCRETE_DERIVED_LOADING
1718
import iris.warnings
1819

1920
Factory = collections.namedtuple("Factory", ["factory_class", "args"])
@@ -158,8 +159,12 @@ def _dereference_args(factory, reference_targets, regrid_cache, cube):
158159
# match the grid of this cube.
159160
src, cube = _ensure_aligned(regrid_cache, src, cube)
160161
if src is not None:
162+
if _CONCRETE_DERIVED_LOADING:
163+
data = src.data
164+
else:
165+
data = src.core_data()
161166
new_coord = iris.coords.AuxCoord(
162-
src.core_data(),
167+
data,
163168
src.standard_name,
164169
src.long_name,
165170
src.var_name,

lib/iris/loading.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# See LICENSE in the root of the repository for full licensing details.
55
"""Iris general file loading mechanism."""
66

7+
from contextlib import contextmanager
78
from dataclasses import dataclass
89
import itertools
910
import threading
@@ -15,6 +16,55 @@
1516
from iris.warnings import IrisLoadWarning
1617

1718

19+
class _ConcreteDerivedLoading(threading.local):
20+
"""An object to control whether factory references are loaded with lazy or real data.
21+
22+
Use via the run-time switch :const:`~iris.loading._CONCRETE_DERIVED_LOADING`.
23+
Use :meth:`context` to temporarily activate or assign the property
24+
`load_real_references` to True.
25+
26+
Notes
27+
-----
28+
This is intended as a temporary fix for a problem with PP loading causing
29+
performance issues during loading (see https://github.com/SciTools/iris/issues/6755)
30+
This is expected to be either removed or renamed in a future version.
31+
"""
32+
33+
def __init__(self):
34+
self.load_real_references = False
35+
36+
def __bool__(self):
37+
return self.load_real_references
38+
39+
@contextmanager
40+
def context(self, load_real=True):
41+
"""Control whether references are loaded lazily.
42+
43+
Defines a context block within which the setting is applied.
44+
45+
Parameters
46+
----------
47+
load_real : bool, default True
48+
Sets whether references are loaded as concrete data, within the context.
49+
50+
Example
51+
-------
52+
>>> with _CONCRETE_DERIVED_LOADING.context(load_real=True):
53+
... <code>
54+
"""
55+
try:
56+
old_value = self.load_real_references
57+
self.load_real_references = load_real
58+
yield
59+
finally:
60+
self.load_real_references = old_value
61+
62+
63+
# TODO: this is a temporary fix, either remove this when a permanent fix exists
64+
# or else make this public if this is deemed necessary.
65+
_CONCRETE_DERIVED_LOADING = _ConcreteDerivedLoading()
66+
67+
1868
def _generate_cubes(uris, callback, constraints):
1969
import iris.io
2070

lib/iris/tests/integration/test_pp.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from unittest import mock
1313

1414
from cf_units import Unit
15+
import dask.array as da
1516
import numpy as np
1617

1718
from iris.aux_factory import HybridHeightFactory, HybridPressureFactory
@@ -22,6 +23,7 @@
2223
from iris.fileformats.pp import load_pairs_from_fields
2324
import iris.fileformats.pp_load_rules
2425
from iris.fileformats.pp_save_rules import verify
26+
from iris.loading import _CONCRETE_DERIVED_LOADING
2527
import iris.util
2628
from iris.warnings import IrisUserWarning
2729

@@ -144,9 +146,12 @@ def test_potential_temperature_level_round_trip(self):
144146
self.assertEqual(field.blev, potm_value)
145147

146148
@staticmethod
147-
def _field_with_data(scale=1, **kwargs):
149+
def _field_with_data(scale=1, lazy=False, **kwargs):
148150
x, y = 40, 30
149-
mock_data = np.arange(1200).reshape(y, x) * scale
151+
if lazy:
152+
mock_data = da.arange(1200).reshape(y, x) * scale
153+
else:
154+
mock_data = np.arange(1200).reshape(y, x) * scale
150155
mock_core_data = mock.MagicMock(return_value=mock_data)
151156
field = mock.MagicMock(
152157
core_data=mock_core_data,
@@ -253,6 +258,42 @@ def test_hybrid_pressure_round_trip(self):
253258
self.assertEqual(data_field.brlev, sigma_lower)
254259
self.assertEqual(data_field.brsvd, [sigma_upper, delta_upper])
255260

261+
def test_hybrid_pressure_lazy_load(self):
262+
pressure_field = self._field_with_data(
263+
10,
264+
lazy=True,
265+
stash=iris.fileformats.pp.STASH(1, 0, 409),
266+
lbuser=[0, 0, 0, 409, 0, 0, 0],
267+
)
268+
269+
# Make a fake data field which needs the reference surface.
270+
model_level = 5678
271+
sigma_lower, sigma, sigma_upper = 0.85, 0.9, 0.95
272+
delta_lower, delta, delta_upper = 0.05, 0.1, 0.15
273+
data_field = self._field_with_data(
274+
lbvc=9,
275+
lblev=model_level,
276+
bhlev=delta,
277+
bhrlev=delta_lower,
278+
blev=sigma,
279+
brlev=sigma_lower,
280+
brsvd=[sigma_upper, delta_upper],
281+
)
282+
283+
# Convert both fields to cubes.
284+
load = mock.Mock(return_value=iter([pressure_field, data_field]))
285+
with mock.patch("iris.fileformats.pp.load", new=load) as load:
286+
pressure_cube, data_cube = iris.fileformats.pp.load_cubes("DUMMY")
287+
288+
assert data_cube.coord("surface_air_pressure").has_lazy_points()
289+
290+
# TODO: _CONCRETE_DERIVED_LOADING is a temporary fix, remove from test when a permanent fix exists
291+
load = mock.Mock(return_value=iter([pressure_field, data_field]))
292+
with mock.patch("iris.fileformats.pp.load", new=load) as load:
293+
with _CONCRETE_DERIVED_LOADING.context():
294+
_, realised_data_cube = iris.fileformats.pp.load_cubes("DUMMY")
295+
assert not realised_data_cube.coord("surface_air_pressure").has_lazy_points()
296+
256297
def test_hybrid_pressure_with_duplicate_references(self):
257298
# Make a fake reference surface field.
258299
pressure_field = self._field_with_data(

0 commit comments

Comments
 (0)