Skip to content

Commit 73c666e

Browse files
committed
Move CombineOptions class into iris.loading to avoid circularity.
1 parent ed1c722 commit 73c666e

File tree

2 files changed

+234
-236
lines changed

2 files changed

+234
-236
lines changed

lib/iris/_combine.py

Lines changed: 1 addition & 234 deletions
Original file line numberDiff line numberDiff line change
@@ -11,244 +11,11 @@
1111
publicly available.
1212
"""
1313

14-
import contextlib
15-
import threading
16-
from typing import List, Mapping
14+
from typing import List
1715

1816
import iris
1917

2018

21-
class CombineOptions(threading.local):
22-
"""A container for cube combination options.
23-
24-
Controls for generalised merge/concatenate options : see :data:`iris.LOAD_POLICY`
25-
and :func:`iris.util.combine_cubes`.
26-
27-
Also controls the detection and handling of cases where a hybrid coordinate
28-
uses multiple reference fields during loading : for example, a UM file which
29-
contains a series of fields describing time-varying orography.
30-
31-
Options can be set directly, or via :meth:`~iris.LoadPolicy.set`, or changed for
32-
the scope of a code block with :meth:`~iris.LoadPolicy.context`.
33-
34-
.. note ::
35-
36-
The default behaviour will "fix" loading for cases like the time-varying
37-
orography case described above. However, this is not strictly
38-
backwards-compatible. If this causes problems, you can force identical loading
39-
behaviour to earlier Iris versions with ``LOAD_POLICY.set("legacy")`` or
40-
equivalent.
41-
42-
.. testsetup::
43-
44-
from iris import LOAD_POLICY
45-
46-
Notes
47-
-----
48-
The individual configurable options are :
49-
50-
* ``support_multiple_references`` = True / False
51-
When enabled, the presence of multiple aux-factory reference cubes, which merge
52-
to define a extra dimension, will add that dimension to the loaded cubes.
53-
This is essential for correct support of time-dependent hybrid coordinates (i.e.
54-
aux factories) when loading from fields-based data (e.g. PP or GRIB).
55-
For example (notably) time-dependent orography in UM data on hybrid-heights.
56-
57-
In addition, when such multiple references are detected, an extra concatenate
58-
step is added to the 'merge_concat_sequence' (see below), if none is already
59-
configured there.
60-
61-
* ``merge_concat_sequence`` = "m" / "c" / "cm" / "mc"
62-
Specifies whether to merge, or concatenate, or both in either order.
63-
This is the "combine" operation which is applied to loaded data.
64-
65-
* ``repeat_until_unchanged`` = True / False
66-
When enabled, the configured "combine" operation will be repeated until the
67-
result is stable (no more cubes are combined).
68-
69-
Several common sets of options are provided in :data:`~iris.LOAD_POLICY.SETTINGS` :
70-
71-
* ``"legacy"``
72-
Produces loading behaviour identical to Iris versions < 3.11, i.e. before the
73-
varying hybrid references were supported.
74-
75-
* ``"default"``
76-
As "legacy" except that ``support_multiple_references=True``. This differs
77-
from "legacy" only when multiple mergeable reference fields are encountered,
78-
in which case incoming cubes are extended into the extra dimension, and a
79-
concatenate step is added.
80-
81-
* ``"recommended"``
82-
Enables multiple reference handling, *and* applies a merge step followed by
83-
a concatenate step.
84-
85-
* ``"comprehensive"``
86-
Like "recommended", but will also *repeat* the merge+concatenate steps until no
87-
further change is produced.
88-
89-
.. note ::
90-
91-
The 'comprehensive' policy makes a maximum effort to reduce the number of
92-
cubes to a minimum. However, it still cannot combine cubes with a mixture
93-
of matching dimension and scalar coordinates. This may be supported at
94-
some later date, but for now is not possible without specific user actions.
95-
96-
.. Note ::
97-
98-
See also : :ref:`controlling_merge`.
99-
100-
"""
101-
102-
# Useful constants
103-
OPTION_KEYS = (
104-
"support_multiple_references",
105-
"merge_concat_sequence",
106-
"repeat_until_unchanged",
107-
)
108-
_OPTIONS_ALLOWED_VALUES = {
109-
"support_multiple_references": (False, True),
110-
"merge_concat_sequence": ("", "m", "c", "mc", "cm"),
111-
"repeat_until_unchanged": (False, True),
112-
}
113-
SETTINGS = {
114-
"legacy": dict(
115-
support_multiple_references=False,
116-
merge_concat_sequence="m",
117-
repeat_until_unchanged=False,
118-
),
119-
"default": dict(
120-
support_multiple_references=True,
121-
merge_concat_sequence="m",
122-
repeat_until_unchanged=False,
123-
),
124-
"recommended": dict(
125-
support_multiple_references=True,
126-
merge_concat_sequence="mc",
127-
repeat_until_unchanged=False,
128-
),
129-
"comprehensive": dict(
130-
support_multiple_references=True,
131-
merge_concat_sequence="mc",
132-
repeat_until_unchanged=True,
133-
),
134-
}
135-
136-
def __init__(self, options: str | dict | None = None, **kwargs):
137-
"""Create loading strategy control object."""
138-
self.set("default")
139-
self.set(options, **kwargs)
140-
141-
def __setattr__(self, key, value):
142-
if key not in self.OPTION_KEYS:
143-
raise KeyError(f"LoadPolicy object has no property '{key}'.")
144-
145-
allowed_values = self._OPTIONS_ALLOWED_VALUES[key]
146-
if value not in allowed_values:
147-
msg = (
148-
f"{value!r} is not a valid setting for LoadPolicy.{key} : "
149-
f"must be one of '{allowed_values}'."
150-
)
151-
raise ValueError(msg)
152-
153-
self.__dict__[key] = value
154-
155-
def set(self, options: str | dict | None = None, **kwargs):
156-
"""Set new options.
157-
158-
Parameters
159-
----------
160-
* options : str or dict, optional
161-
A dictionary of options values, or the name of one of the
162-
:data:`~iris.LoadPolicy.SETTINGS` standard option sets,
163-
e.g. "legacy" or "comprehensive".
164-
* kwargs : dict
165-
Individual option settings, from :data:`~iris.LoadPolicy.OPTION_KEYS`.
166-
167-
Note
168-
----
169-
Keyword arguments are applied after the 'options' arg, and
170-
so will take precedence.
171-
172-
"""
173-
if options is None:
174-
options = {}
175-
elif isinstance(options, str) and options in self.SETTINGS:
176-
options = self.SETTINGS[options]
177-
elif not isinstance(options, Mapping):
178-
msg = (
179-
f"Invalid arg options={options!r} : "
180-
f"must be a dict, or one of {tuple(self.SETTINGS.keys())}"
181-
)
182-
raise TypeError(msg)
183-
184-
# Override any options with keywords
185-
options.update(**kwargs)
186-
bad_keys = [key for key in options if key not in self.OPTION_KEYS]
187-
if bad_keys:
188-
msg = f"Unknown options {bad_keys} : valid options are {self.OPTION_KEYS}."
189-
raise ValueError(msg)
190-
191-
# Implement all options by changing own content.
192-
for key, value in options.items():
193-
setattr(self, key, value)
194-
195-
def settings(self):
196-
"""Return an options dict containing the current settings."""
197-
return {key: getattr(self, key) for key in self.OPTION_KEYS}
198-
199-
def __repr__(self):
200-
msg = f"{self.__class__.__name__}("
201-
msg += ", ".join(f"{key}={getattr(self, key)!r}" for key in self.OPTION_KEYS)
202-
msg += ")"
203-
return msg
204-
205-
@contextlib.contextmanager
206-
def context(self, settings=None, **kwargs):
207-
"""Return a context manager applying given options.
208-
209-
Parameters
210-
----------
211-
settings : str or dict
212-
Options dictionary or name, as for :meth:`~LoadPolicy.set`.
213-
kwargs : dict
214-
Option values, as for :meth:`~LoadPolicy.set`.
215-
216-
Examples
217-
--------
218-
.. testsetup::
219-
220-
import iris
221-
from iris import LOAD_POLICY, sample_data_path
222-
223-
>>> # Show how a CombineOptions acts in the context of a load operation
224-
>>> # (N.B. the LOAD_POLICY actually *is* a CombineOptions type object)
225-
>>> path = sample_data_path("time_varying_hybrid_height", "*.pp")
226-
>>> # "legacy" load behaviour allows merge but not concatenate
227-
>>> with LOAD_POLICY.context("legacy"):
228-
... cubes = iris.load(path, "x_wind")
229-
>>> print(cubes)
230-
0: x_wind / (m s-1) (time: 2; model_level_number: 5; latitude: 144; longitude: 192)
231-
1: x_wind / (m s-1) (time: 12; model_level_number: 5; latitude: 144; longitude: 192)
232-
2: x_wind / (m s-1) (model_level_number: 5; latitude: 144; longitude: 192)
233-
>>>
234-
>>> # "recommended" behaviour enables concatenation
235-
>>> with LOAD_POLICY.context("recommended"):
236-
... cubes = iris.load(path, "x_wind")
237-
>>> print(cubes)
238-
0: x_wind / (m s-1) (model_level_number: 5; time: 15; latitude: 144; longitude: 192)
239-
"""
240-
# Save the current state
241-
saved_settings = self.settings()
242-
243-
# Apply the new options and execute the context
244-
try:
245-
self.set(settings, **kwargs)
246-
yield
247-
finally:
248-
# Re-establish the former state
249-
self.set(saved_settings)
250-
251-
25219
def _combine_cubes_inner(
25320
cubes: List[iris.cube.Cube], options: dict
25421
) -> iris.cube.CubeList:

0 commit comments

Comments
 (0)