Skip to content

Commit 033d0a8

Browse files
committed
Move loading-specific parts from CombineOptions to LoadPolicy.
1 parent b0c2fc7 commit 033d0a8

File tree

2 files changed

+117
-98
lines changed

2 files changed

+117
-98
lines changed

lib/iris/_combine.py

Lines changed: 30 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -11,57 +11,44 @@
1111
publicly available.
1212
"""
1313

14-
import contextlib
14+
from __future__ import annotations
15+
1516
import threading
16-
import typing
17-
from typing import List
17+
from typing import TYPE_CHECKING, List
1818

19-
if typing.TYPE_CHECKING:
19+
if TYPE_CHECKING:
2020
from iris.cube import Cube, CubeList
2121

2222

2323
class CombineOptions(threading.local):
2424
"""A container for cube combination options.
2525
26-
Controls for generalised merge/concatenate options.
27-
28-
Also controls the detection and handling of cases where a hybrid coordinate
29-
uses multiple reference fields during loading : for example, a UM file which
30-
contains a series of fields describing time-varying orography.
31-
32-
Options can be set directly, or via :meth:`~iris.LoadPolicy.set`, or changed for
33-
the scope of a code block with :meth:`~iris.LoadPolicy.context`.
34-
35-
.. note ::
26+
Controls for generalised merge/concatenate options. These are used as controls for
27+
both the :func:`iris.util.combine_cubes` utility method and the core Iris loading
28+
functions : see also :data:`iris.loading.LoadPolicy`.
3629
37-
The default behaviour will "fix" loading for cases like the time-varying
38-
orography case described above. However, this is not strictly
39-
backwards-compatible. If this causes problems, you can force identical loading
40-
behaviour to earlier Iris versions with ``LOAD_POLICY.set("legacy")`` or
41-
equivalent.
42-
43-
.. testsetup::
44-
45-
from iris import LOAD_POLICY
30+
It specifies a number of possible operations which may be applied to a list of
31+
cubes, in a definite sequence, all of which tend to combine cubes into a smaller
32+
number of larger or higher-dimensional cubes.
4633
4734
Notes
4835
-----
4936
The individual configurable options are :
5037
51-
* ``support_multiple_references`` = True / False
52-
When enabled, the presence of multiple aux-factory reference cubes, which merge
53-
to define a extra dimension, will add that dimension to the loaded cubes.
54-
This is essential for correct support of time-dependent hybrid coordinates (i.e.
55-
aux factories) when loading from fields-based data (e.g. PP or GRIB).
56-
For example (notably) time-dependent orography in UM data on hybrid-heights.
38+
* ``merge_concat_sequence`` = "m" / "c" / "cm" / "mc"
39+
Specifies whether to apply :meth:`~iris.cube.CubeList.merge`, or
40+
:meth:`~iris.cube.CubeList.concatenate` operations, or both, in either order.
5741
58-
In addition, when such multiple references are detected, an extra concatenate
59-
step is added to the 'merge_concat_sequence' (see below), if none is already
60-
configured there.
42+
* ``merge_uses_unique`` = True / False
43+
When True, any merge operation will error if its result contains multiple
44+
identical cubes. Otherwise (unique=False), that is a permitted result.
6145
62-
* ``merge_concat_sequence`` = "m" / "c" / "cm" / "mc"
63-
Specifies whether to merge, or concatenate, or both in either order.
64-
This is the "combine" operation which is applied to loaded data.
46+
.. Note::
47+
48+
By default, in a normal :meth:`~iris.cube.CubeList.merge` operation on a
49+
:class:`~iris.cube.CubeList`, ``unique`` defaults to ``True``.
50+
For loading operations, however, the default is ``unique=False``, as this
51+
produces the intended behaviour when loading with multiple constraints.
6552
6653
* ``repeat_until_unchanged`` = True / False
6754
When enabled, the configured "combine" operation will be repeated until the
@@ -102,35 +89,29 @@ class CombineOptions(threading.local):
10289

10390
# Useful constants
10491
#: Valid option names
105-
OPTION_KEYS = (
106-
"support_multiple_references",
92+
OPTION_KEYS = [
10793
"merge_concat_sequence",
10894
"repeat_until_unchanged",
109-
)
95+
] # this is a list, so we can update it in an inheriting class
11096
_OPTIONS_ALLOWED_VALUES = {
111-
"support_multiple_references": (False, True),
11297
"merge_concat_sequence": ("", "m", "c", "mc", "cm"),
11398
"repeat_until_unchanged": (False, True),
11499
}
115100
#: Settings content
116101
SETTINGS = {
117102
"legacy": dict(
118-
support_multiple_references=False,
119103
merge_concat_sequence="m",
120104
repeat_until_unchanged=False,
121105
),
122106
"default": dict(
123-
support_multiple_references=True,
124107
merge_concat_sequence="m",
125108
repeat_until_unchanged=False,
126109
),
127110
"recommended": dict(
128-
support_multiple_references=True,
129111
merge_concat_sequence="mc",
130112
repeat_until_unchanged=False,
131113
),
132114
"comprehensive": dict(
133-
support_multiple_references=True,
134115
merge_concat_sequence="mc",
135116
repeat_until_unchanged=True,
136117
),
@@ -163,8 +144,8 @@ def set(self, options: str | dict | None = None, **kwargs):
163144
Parameters
164145
----------
165146
* options : str or dict, optional
166-
A dictionary of options values, or the name of one of the
167-
:data:`~iris.LoadPolicy.SETTINGS` standard option sets,
147+
A dictionary of options values, or one of the
148+
:data:`~iris.LoadPolicy.SETTINGS_NAMES` standard settings names,
168149
e.g. "legacy" or "comprehensive".
169150
* kwargs : dict
170151
Individual option settings, from :data:`~iris.LoadPolicy.OPTION_KEYS`.
@@ -183,13 +164,13 @@ def set(self, options: str | dict | None = None, **kwargs):
183164
options_dict = options
184165
else:
185166
msg = (
186-
f"'options' arg has unexpected type {type(options)!r}, "
187-
"expected (None | str | dict)."
167+
f"arg `options` has unexpected type {type(options)!r}, "
168+
f"expected one of (None | str | dcit)."
188169
)
189-
raise ValueError(msg)
170+
raise TypeError(msg)
190171

191172
# Override any options with keywords
192-
options_dict = options_dict.copy() # do not modify source (!)
173+
options_dict = options_dict.copy() # don't modify original
193174
options_dict.update(**kwargs)
194175
bad_keys = [key for key in options_dict if key not in self.OPTION_KEYS]
195176
if bad_keys:
@@ -210,52 +191,6 @@ def __repr__(self):
210191
msg += ")"
211192
return msg
212193

213-
@contextlib.contextmanager
214-
def context(self, settings: str | dict | None = None, **kwargs):
215-
"""Return a context manager applying given options.
216-
217-
Parameters
218-
----------
219-
settings : str or dict
220-
Options dictionary or name, as for :meth:`~LoadPolicy.set`.
221-
kwargs : dict
222-
Option values, as for :meth:`~LoadPolicy.set`.
223-
224-
Examples
225-
--------
226-
.. testsetup::
227-
228-
import iris
229-
from iris import LOAD_POLICY, sample_data_path
230-
231-
>>> # Show how a CombineOptions acts in the context of a load operation
232-
>>> # (N.B. the LOAD_POLICY actually *is* a CombineOptions type object)
233-
>>> path = sample_data_path("time_varying_hybrid_height", "*.pp")
234-
>>> # "legacy" load behaviour allows merge but not concatenate
235-
>>> with LOAD_POLICY.context("legacy"):
236-
... cubes = iris.load(path, "x_wind")
237-
>>> print(cubes)
238-
0: x_wind / (m s-1) (time: 2; model_level_number: 5; latitude: 144; longitude: 192)
239-
1: x_wind / (m s-1) (time: 12; model_level_number: 5; latitude: 144; longitude: 192)
240-
2: x_wind / (m s-1) (model_level_number: 5; latitude: 144; longitude: 192)
241-
>>>
242-
>>> # "recommended" behaviour enables concatenation
243-
>>> with LOAD_POLICY.context("recommended"):
244-
... cubes = iris.load(path, "x_wind")
245-
>>> print(cubes)
246-
0: x_wind / (m s-1) (model_level_number: 5; time: 15; latitude: 144; longitude: 192)
247-
"""
248-
# Save the current state
249-
saved_settings = self.settings()
250-
251-
# Apply the new options and execute the context
252-
try:
253-
self.set(settings, **kwargs)
254-
yield
255-
finally:
256-
# Re-establish the former state
257-
self.set(saved_settings)
258-
259194

260195
def _combine_cubes(cubes: List[Cube], options: dict) -> CubeList:
261196
"""Combine cubes as for load, according to "loading policy" options.

lib/iris/loading.py

Lines changed: 87 additions & 3 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+
import contextlib
78
import itertools
89
from typing import Iterable
910

@@ -284,7 +285,32 @@ def load_raw(uris, constraints=None, callback=None):
284285
class LoadPolicy(CombineOptions):
285286
"""A control object for Iris loading options.
286287
287-
Incorporates all the settings of a :class:`~iris.CombineOptions`.
288+
Incorporates all the settings of a :class:`~iris.CombineOptions`, and adds an
289+
additional ``support_multiple_references`` option.
290+
291+
Also adds :meth:`~iris.LoadPolicy.context`, allowing loading behaviours to be
292+
modified for the duration of a code block.
293+
294+
In addition to controlling the "combine" operations during loading, LoadPolicy also
295+
provides a ``support_multiple_references`` option to manage the detection and
296+
handling of cases where a hybrid coordinate has multiple reference fields : for
297+
example, a UM file which contains a series of fields describing a time-varying
298+
orography.
299+
300+
The ``support_multiple_references`` option takes the value ``True`` or ``False`` to
301+
enable or disable this. The default is ``True``.
302+
303+
.. note ::
304+
305+
The default behaviour will "fix" loading for cases like the time-varying
306+
orography case described above. However, this is not strictly
307+
backwards-compatible. If this causes problems, you can force identical loading
308+
behaviour to earlier Iris versions with ``LOAD_POLICY.set("legacy")`` or
309+
equivalent.
310+
311+
.. testsetup::
312+
313+
from iris import LOAD_POLICY
288314
289315
Examples
290316
--------
@@ -302,10 +328,68 @@ class LoadPolicy(CombineOptions):
302328
LoadPolicy(support_multiple_references=True, merge_concat_sequence='mc', repeat_until_unchanged=True)
303329
>>> print(LOAD_POLICY)
304330
LoadPolicy(support_multiple_references=True, merge_concat_sequence='cm', repeat_until_unchanged=False)
305-
306331
"""
307332

308-
pass
333+
# Option keys are as for CombineOptions, plus the multiple-refs control
334+
OPTION_KEYS = CombineOptions.OPTION_KEYS + ["support_multiple_references"]
335+
336+
# Allowed values are as for CombineOptions, plus boolean values for multiple-refs
337+
_OPTIONS_ALLOWED_VALUES = dict(
338+
list(CombineOptions._OPTIONS_ALLOWED_VALUES.items())
339+
+ [("support_multiple_references", (True, False))]
340+
)
341+
342+
# Settings dicts are as for CombineOptions, but all have multiple load refs enabled
343+
SETTINGS = {
344+
key: dict(list(settings.items()) + [("support_multiple_references", True)])
345+
for key, settings in CombineOptions.SETTINGS.items()
346+
}
347+
348+
@contextlib.contextmanager
349+
def context(self, settings: str | dict, **kwargs):
350+
"""Return a context manager applying given options changes during a scope.
351+
352+
Parameters
353+
----------
354+
settings : str or dict
355+
A settings name or options dictionary, as for :meth:`~LoadPolicy.set`.
356+
kwargs : dict
357+
Option values, as for :meth:`~LoadPolicy.set`.
358+
359+
Examples
360+
--------
361+
.. testsetup::
362+
363+
import iris
364+
from iris import LOAD_POLICY, sample_data_path
365+
366+
>>> # Show how a CombineOptions acts in the context of a load operation
367+
>>> path = sample_data_path("time_varying_hybrid_height", "*.pp")
368+
>>>
369+
>>> # Show that "legacy" load behaviour allows merge but not concatenate
370+
>>> with LOAD_POLICY.context("legacy"):
371+
... cubes = iris.load(path, "x_wind")
372+
>>> print(cubes)
373+
0: x_wind / (m s-1) (time: 2; model_level_number: 5; latitude: 144; longitude: 192)
374+
1: x_wind / (m s-1) (time: 12; model_level_number: 5; latitude: 144; longitude: 192)
375+
2: x_wind / (m s-1) (model_level_number: 5; latitude: 144; longitude: 192)
376+
>>>
377+
>>> # Show how "recommended" behaviour enables concatenation also
378+
>>> with LOAD_POLICY.context("recommended"):
379+
... cubes = iris.load(path, "x_wind")
380+
>>> print(cubes)
381+
0: x_wind / (m s-1) (model_level_number: 5; time: 15; latitude: 144; longitude: 192)
382+
"""
383+
# Save the current state
384+
saved_settings = self.settings()
385+
386+
# Apply the new options and execute the context
387+
try:
388+
self.set(settings, **kwargs)
389+
yield
390+
finally:
391+
# Re-establish the former state
392+
self.set(saved_settings)
309393

310394

311395
#: A control object containing the current file loading strategy options.

0 commit comments

Comments
 (0)