Source code for iris._combine

# Copyright Iris contributors
#
# This file is part of Iris and is released under the BSD license.
# See LICENSE in the root of the repository for full licensing details.
"""Generalised mechanism for combining cubes into larger ones.

Integrates merge and concatenate with the cube-equalisation options and the promotion of
hybrid reference dimensions on loading.

This is effectively a generalised "combine cubes" operation, but it is not (yet)
publicly available.
"""

import threading
from typing import Mapping


[docs] class CombineOptions(threading.local): """A container for cube combination options. Controls for generalised merge/concatenate options. These are used as controls for both the :func:`iris.util.combine_cubes` utility method and the core Iris loading functions : see also :data:`iris.loading.LoadPolicy`. It specifies a number of possible operations which may be applied to a list of cubes, in a definite sequence, all of which tend to combine cubes into a smaller number of larger or higher-dimensional cubes. Notes ----- The individual configurable options are : * ``equalise_cube_kwargs`` = dict or None If not None, this enables and provides keyword controls for a call to the :func:`iris.util.equalise_cubes` utility. If active, this always occurs **before** any merge/concatenate phase. * ``merge_concat_sequence`` = "m" / "c" / "cm" / "mc" Specifies whether to apply :meth:`~iris.cube.CubeList.merge`, or :meth:`~iris.cube.CubeList.concatenate` operations, or both, in either order. * ``merge_uses_unique`` = True / False When True, any merge operation will error if its result contains multiple identical cubes. Otherwise (unique=False), that is a permitted result. .. Note:: By default, in a normal :meth:`~iris.cube.CubeList.merge` operation on a :class:`~iris.cube.CubeList`, unique is ``True`` unless specified otherwise. For loading operations, however, the default is ``unique=False``, as this is required to make sense when making for multiple * ``repeat_until_unchanged`` = True / False When enabled, the configured "combine" operation will be repeated until the result is stable (no more cubes are combined). Several common sets of options are provided in :data:`~iris.LOAD_POLICY.SETTINGS` : * ``"legacy"`` Produces loading behaviour identical to Iris versions < 3.11, i.e. before the varying hybrid references were supported. * ``"default"`` As "legacy" except that ``support_multiple_references=True``. This differs from "legacy" only when multiple mergeable reference fields are encountered, in which case incoming cubes are extended into the extra dimension, and a concatenate step is added. * ``"recommended"`` Enables multiple reference handling, *and* applies a merge step followed by a concatenate step. * ``"comprehensive"`` Like "recommended", but will also *repeat* the merge+concatenate steps until no further change is produced. .. note :: The 'comprehensive' policy makes a maximum effort to reduce the number of cubes to a minimum. However, it still cannot combine cubes with a mixture of matching dimension and scalar coordinates. This may be supported at some later date, but for now is not possible without specific user actions. .. Note :: See also : :ref:`controlling_merge`. """ # Useful constants OPTION_KEYS = ( # "support_multiple_references", "merge_concat_sequence", "repeat_until_unchanged", ) _OPTIONS_ALLOWED_VALUES = { # "support_multiple_references": (False, True), "merge_concat_sequence": ("", "m", "c", "mc", "cm"), "repeat_until_unchanged": (False, True), } SETTING_NAMES = ("legacy", "default", "recommended", "comprehensive") SETTINGS = { "legacy": dict( # support_multiple_references=False, merge_concat_sequence="m", repeat_until_unchanged=False, ), "default": dict( # support_multiple_references=True, merge_concat_sequence="m", repeat_until_unchanged=False, ), "recommended": dict( # support_multiple_references=True, merge_concat_sequence="mc", repeat_until_unchanged=False, ), "comprehensive": dict( # support_multiple_references=True, merge_concat_sequence="mc", repeat_until_unchanged=True, ), } def __init__(self, options: str | dict | None = None, **kwargs): """Create loading strategy control object.""" self.set("default") self.set(options, **kwargs) def __setattr__(self, key, value): if key not in self.OPTION_KEYS: raise KeyError(f"LoadPolicy object has no property '{key}'.") allowed_values = self._OPTIONS_ALLOWED_VALUES[key] if value not in allowed_values: msg = ( f"{value!r} is not a valid setting for LoadPolicy.{key} : " f"must be one of '{allowed_values}'." ) raise ValueError(msg) self.__dict__[key] = value
[docs] def set(self, options: str | dict | None = None, **kwargs): """Set new options. Parameters ---------- * options : str or dict, optional A dictionary of options values, or the name of one of the :data:`~iris.LoadPolicy.SETTINGS` standard option sets, e.g. "legacy" or "comprehensive". * kwargs : dict Individual option settings, from :data:`~iris.LoadPolicy.OPTION_KEYS`. Note ---- Keyword arguments are applied after the 'options' arg, and so will take precedence. """ if options is None: options = {} elif isinstance(options, str) and options in self.SETTINGS: options = self.SETTINGS[options] elif not isinstance(options, Mapping): msg = ( f"Invalid arg options={options!r} : " f"must be a dict, or one of {tuple(self.SETTINGS.keys())}" ) raise TypeError(msg) # Override any options with keywords options.update(**kwargs) bad_keys = [key for key in options if key not in self.OPTION_KEYS] if bad_keys: msg = f"Unknown options {bad_keys} : valid options are {self.OPTION_KEYS}." raise ValueError(msg) # Implement all options by changing own content. for key, value in options.items(): setattr(self, key, value)
[docs] def settings(self): """Return an options dict containing the current settings.""" return {key: getattr(self, key) for key in self.OPTION_KEYS}
def __repr__(self): msg = f"{self.__class__.__name__}(" msg += ", ".join(f"{key}={getattr(self, key)!r}" for key in self.OPTION_KEYS) msg += ")" return msg