|
11 | 11 | publicly available. |
12 | 12 | """ |
13 | 13 |
|
14 | | -import contextlib |
15 | | -import threading |
16 | | -from typing import List, Mapping |
| 14 | +from typing import List |
17 | 15 |
|
18 | 16 | import iris |
19 | 17 |
|
20 | 18 |
|
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 | | - |
252 | 19 | def _combine_cubes_inner( |
253 | 20 | cubes: List[iris.cube.Cube], options: dict |
254 | 21 | ) -> iris.cube.CubeList: |
|
0 commit comments