Skip to content

Commit ed1c722

Browse files
committed
Add a public 'combine_cubes'.
1 parent e499d38 commit ed1c722

File tree

3 files changed

+105
-41
lines changed

3 files changed

+105
-41
lines changed

lib/iris/_combine.py

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@
1313

1414
import contextlib
1515
import threading
16-
from typing import Mapping
16+
from typing import List, Mapping
17+
18+
import iris
1719

1820

1921
class CombineOptions(threading.local):
2022
"""A container for cube combination options.
2123
22-
Controls for generalised merge/concatenate options.
24+
Controls for generalised merge/concatenate options : see :data:`iris.LOAD_POLICY`
25+
and :func:`iris.util.combine_cubes`.
2326
2427
Also controls the detection and handling of cases where a hybrid coordinate
2528
uses multiple reference fields during loading : for example, a UM file which
@@ -246,64 +249,57 @@ def context(self, settings=None, **kwargs):
246249
self.set(saved_settings)
247250

248251

249-
def _combine_cubes(cubes, options):
250-
"""Combine cubes as for load, according to "loading policy" options.
252+
def _combine_cubes_inner(
253+
cubes: List[iris.cube.Cube], options: dict
254+
) -> iris.cube.CubeList:
255+
"""Combine cubes, according to "combine options".
251256
252-
Applies :meth:`~iris.cube.CubeList.merge`/:meth:`~iris.cube.CubeList.concatenate`
253-
steps to the given cubes, as determined by the 'settings'.
257+
As described for the main "iris.utils.combine_cubes".
254258
255259
Parameters
256260
----------
257-
cubes : list of :class:`~iris.cube.Cube`
258-
A list of cubes to combine.
261+
cubes : list of Cube
262+
Cubes to combine.
263+
259264
options : dict
260-
Settings, as described for :class:`iris.CombineOptions`.
265+
A list of options, as described in CombineOptions.
261266
262267
Returns
263268
-------
264-
:class:`~iris.cube.CubeList`
265-
266-
.. Note::
267-
The ``support_multiple_references`` keyword/property has no effect on the
268-
:func:`_combine_cubes` operation : it only takes effect during a load operation.
269-
270-
Notes
271-
-----
272-
TODO: make this public API in future.
273-
At that point, change the API to support (options=None, **kwargs) + add testing of
274-
those modes (notably arg type = None / str / dict).
275-
269+
CubeList
276270
"""
277271
from iris.cube import CubeList
278272

279-
if not isinstance(cubes, CubeList):
280-
cubes = CubeList(cubes)
273+
if isinstance(cubes, CubeList):
274+
cubelist = cubes
275+
else:
276+
cubelist = CubeList(cubes)
281277

278+
sequence = options["merge_concat_sequence"]
282279
while True:
283-
n_original_cubes = len(cubes)
284-
sequence = options["merge_concat_sequence"]
280+
n_original_cubes = len(cubelist)
285281

286282
if sequence[0] == "c":
287283
# concat if it comes first
288-
cubes = cubes.concatenate()
284+
cubelist = cubelist.concatenate()
289285
if "m" in sequence:
290286
# merge if requested
291287
# NOTE: this needs "unique=False" to make "iris.load()" work correctly.
292288
# TODO: make configurable via options.
293-
cubes = cubes.merge(unique=False)
289+
cubelist = cubelist.merge(unique=False)
294290
if sequence[-1] == "c":
295291
# concat if it comes last
296-
cubes = cubes.concatenate()
292+
cubelist = cubelist.concatenate()
297293

298294
# Repeat if requested, *and* this step reduced the number of cubes
299-
if not options["repeat_until_unchanged"] or len(cubes) >= n_original_cubes:
295+
if not options["repeat_until_unchanged"] or len(cubelist) >= n_original_cubes:
300296
break
301297

302-
return cubes
298+
return cubelist
303299

304300

305301
def _combine_load_cubes(cubes):
306-
# A special version to call _combine_cubes while also implementing the
302+
# A special version to call _combine_cubes_inner while also implementing the
307303
# _MULTIREF_DETECTION behaviour
308304
from iris import LOAD_POLICY
309305

@@ -318,4 +314,4 @@ def _combine_load_cubes(cubes):
318314
if _MULTIREF_DETECTION.found_multiple_refs:
319315
options["merge_concat_sequence"] += "c"
320316

321-
return _combine_cubes(cubes, options)
317+
return _combine_cubes_inner(cubes, options)

lib/iris/tests/unit/test_combine_cubes.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#
33
# This file is part of Iris and is released under the BSD license.
44
# See LICENSE in the root of the repository for full licensing details.
5-
"""Unit tests for the :func:`iris.io.loading.combine_cubes` function.
5+
"""Unit tests for the :func:`iris.loading.combine_cubes` function.
66
77
Note: These tests are fairly extensive to cover functional uses within the loading
88
operations.
@@ -13,8 +13,8 @@
1313
import pytest
1414

1515
from iris import LoadPolicy
16-
from iris._combine import _combine_cubes
1716
from iris.tests.unit.fileformats.test_load_functions import cu
17+
from iris.util import combine_cubes
1818

1919

2020
@pytest.fixture(params=list(LoadPolicy.SETTINGS.keys()))
@@ -23,12 +23,12 @@ def options(request):
2323
return request.param # Return the name of the attribute to test.
2424

2525

26-
# Interface to convert settings-name / kwargs into an options dict,
27-
# TODO: remove this wrapper when the API of "combine_cubes" is opened up.
28-
def combine_cubes(cubes, settings_name="default", **kwargs):
29-
options = LoadPolicy.SETTINGS[settings_name]
30-
options.update(kwargs)
31-
return _combine_cubes(cubes, options)
26+
# # Interface to convert settings-name / kwargs into an options dict,
27+
# # TODO: remove this wrapper when the API of "combine_cubes" is opened up.
28+
# def combine_cubes(cubes, settings_name="default", **kwargs):
29+
# options = LoadPolicy.SETTINGS[settings_name]
30+
# options.update(kwargs)
31+
# return _combine_cubes(cubes, options)
3232

3333

3434
class Test:

lib/iris/util.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@
1515
import os.path
1616
import sys
1717
import tempfile
18-
from typing import Literal
18+
from typing import List, Literal
1919
from warnings import warn
2020

2121
import cf_units
2222
from dask import array as da
2323
import numpy as np
2424
import numpy.ma as ma
2525

26+
import iris
2627
from iris._deprecation import warn_deprecated
2728
from iris._lazy_data import is_lazy_data, is_lazy_masked_data
2829
from iris._shapefiles import create_shapefile_mask
@@ -2320,3 +2321,70 @@ def equalise_cubes(
23202321
# Return a CubeList result = the *original* cubes, as modified
23212322
result = CubeList(cubes)
23222323
return result
2324+
2325+
2326+
def combine_cubes(
2327+
cubes: List[iris.cube.Cube],
2328+
options: str | dict | None = None,
2329+
**kwargs,
2330+
):
2331+
"""Combine cubes, according to "combine options".
2332+
2333+
Applies a combination of :meth:`~iris.cube.CubeList.merge` and/or
2334+
:meth:`~iris.cube.CubeList.concatenate` steps to the given cubes,
2335+
as determined by the given settings (from 'options' and 'kwargs').
2336+
2337+
Parameters
2338+
----------
2339+
cubes : list of :class:`~iris.cube.Cube`
2340+
A list of cubes to combine.
2341+
2342+
options : str or dict, optional
2343+
Name of a CombineOptions "setting", or a dictionary of settings options, as
2344+
described for :class:`~iris.CombineOptions`.
2345+
Defaults to :meth:`iris.LOAD_POLICY.settings`.
2346+
2347+
kwargs : dict
2348+
Individual option setting values. These take precedence over those defined by
2349+
the 'options' arg, as described for :meth:`~iris.CombineOptions.set`.
2350+
2351+
Returns
2352+
-------
2353+
:class:`~iris.cube.CubeList`
2354+
2355+
.. Note::
2356+
The ``support_multiple_references`` keyword/property has **no** effect on
2357+
:func:`_combine_cubes` : this only acts during load operations.
2358+
2359+
Examples
2360+
--------
2361+
>>> results = combine_cubes(cubes)
2362+
>>> results = combine_cubes(cubes, options=CombineOptions("recommended"))
2363+
>>> results = combine_cubes(cubes, repeat_until_unchanged=True)
2364+
2365+
"""
2366+
# TODO: somehow make a real + useful example
2367+
2368+
from iris import LOAD_POLICY, CombineOptions
2369+
from iris._combine import _combine_cubes_inner
2370+
2371+
err = None
2372+
opts_dict = {}
2373+
if options is None:
2374+
opts_dict = LOAD_POLICY.settings().copy()
2375+
elif isinstance(options, str):
2376+
if options in CombineOptions.SETTINGS:
2377+
opts_dict = CombineOptions.SETTINGS[options].copy()
2378+
else:
2379+
err = (
2380+
"Unrecognised settings name : expected one of "
2381+
f"{tuple(CombineOptions.SETTINGS)}."
2382+
)
2383+
2384+
if err:
2385+
raise ValueError(err)
2386+
2387+
if kwargs is not None:
2388+
opts_dict.update(kwargs)
2389+
2390+
return _combine_cubes_inner(cubes, opts_dict)

0 commit comments

Comments
 (0)