text: import logging
  from numbers import Integral
  
  import numpy as np
  
  try:
      from matplotlib.path import Path
  except ImportError:
      pass
  
  from ..data import Data
  from ..decorators import (
      _deprecated_kwarg_check,
      _inplace_enabled,
      _inplace_enabled_define_and_cleanup,
      _manage_log_level_via_verbosity,
  )
  from ..functions import (
      _DEPRECATION_ERROR_KWARGS,
      bounds_combination_mode,
      parse_indices,
  )
  from ..query import Query
  from ..units import Units
  
  logger = logging.getLogger(__name__)
  
  
  _units_degrees = Units("degrees")
  
  _empty_set = set()
  
  
  class FieldDomain:
      """Mixin class for methods common to both field and domain
      constructs.
  
      .. versionadded:: 3.9.0
  
      """
  
      @property
      def _cyclic(self):
          """Storage for axis cyclicity.
  
          Do not change in-place.
  
          """
          return self._custom.get("_cyclic", _empty_set)
  
      @_cyclic.setter
      def _cyclic(self, value):
          """value must be a set.
  
          Do not change in-place.
  
          """
          self._custom["_cyclic"] = value
  
      @_cyclic.deleter
      def _cyclic(self):
          self._custom["_cyclic"] = _empty_set
  
      def _coordinate_reference_axes(self, key):
          """Returns the set of coordinate reference axes for a key.
  
          :Parameters:
  
              key: `str`
                  Coordinate reference construct key.
  
          :Returns:
  
              `set`
  
          **Examples:**
  
          >>> f._coordinate_reference_axes('coordinatereference0')
  
          """
          ref = self.constructs[key]
  
          axes = []
  
          for c_key in ref.coordinates():
              axes.extend(self.get_data_axes(c_key))
  
          for da_key in ref.coordinate_conversion.domain_ancillaries().values():
              axes.extend(self.get_data_axes(da_key))
  
          return set(axes)
  
      def _conform_coordinate_references(self, key, coordref=None):
          """Where possible replace the content of coordinate reference
          construct coordinates with coordinate construct keys.
  
          .. versionadded:: 3.0.0
  
          :Parameters:
  
              key: `str`
                  Coordinate construct key.
  
              coordref: `CoordinateReference`, optional
  
                  .. versionadded:: 3.6.0
  
          :Returns:
  
              `None`
  
          **Examples:**
  
          >>> f._conform_coordinate_references('auxiliarycoordinate1')
          >>> f._conform_coordinate_references('auxiliarycoordinate1',
          ...                                  coordref=cr)
  
          """
          identity = self.constructs[key].identity(strict=True)
  
          if coordref is None:
              refs = self.coordinate_references(todict=True).values()
          else:
              refs = (coordref,)
  
          for ref in refs:
              if identity in ref.coordinates():
                  ref.del_coordinate(identity, None)
                  ref.set_coordinate(key)
  
      def _construct(
          self,
          _method,
          _constructs_method,
          identities,
          key=False,
          item=False,
          default=ValueError(),
          **filter_kwargs,
      ):
          """An interface to `Constructs.filter`.
  
          {{unique construct}}
  
          .. versionadded:: 3.9.0
  
          :Parameters:
  
              _method: `str`
                  The name of the calling method.
  
              _constructs_method: `str`
                  The name of the corresponding method that can return
                  any number of constructs.
  
              identities: sequence
                  As for the *identities* parameter of the calling
                  method.
  
              {{key: `bool`, optional}}
  
              {{item: `bool`, optional}}
  
              default: optional
                  Return the value of the *default* parameter if there
                  is no unique construct.
  
                  {{default Exception}}
  
              {{filter_kwargs: optional}}
  
          :Returns:
  
                  {{Returns construct}}
  
          """
          cached = filter_kwargs.get("cached")
          if cached is not None:
              return cached
  
          filter_kwargs["todict"] = True
  
          c = getattr(self, _constructs_method)(*identities, **filter_kwargs)
  
          # Return construct, or key, or both, or default
          n = len(c)
          if n == 1:
              k, construct = c.popitem()
              if key:
                  return k
  
              if item:
                  return k, construct
  
              return construct
  
          if default is None:
              return default
  
          return self._default(
              default,
              f"{self.__class__.__name__}.{_method}() can't return {n} "
              "constructs",
          )
  
      @_manage_log_level_via_verbosity
      def _equivalent_coordinate_references(
          self,
          field1,
          key0,
          key1,
          atol=None,
          rtol=None,
          s=None,
          t=None,
          verbose=None,
          axis_map=None,
      ):
          """True if coordinate reference constructs are equivalent.
  
          Two real numbers ``x`` and ``y`` are considered equal if
          ``|x-y|<=atol+rtol|y|``, where ``atol`` (the tolerance on absolute
          differences) and ``rtol`` (the tolerance on relative differences)
          are positive, typically very small numbers. See the *atol* and
          *rtol* parameters.
  
          :Parameters:
  
              ref0: `CoordinateReference`
  
              ref1: `CoordinateReference`
  
              field1: `Field`
                  The field which contains *ref1*.
  
          :Returns:
  
              `bool`
  
          """
          ref0 = self.coordinate_references(todict=True)[key0]
          ref1 = field1.coordinate_references(todict=True)[key1]
  
          if not ref0.equivalent(ref1, rtol=rtol, atol=atol, verbose=verbose):
              logger.info(
                  f"{self.__class__.__name__}: Non-equivalent coordinate "
                  f"references ({ref0!r}, {ref1!r})"
              )  # pragma: no cover
              return False
  
          # Compare the domain ancillaries
          # TODO consider case of None key ?
          for (
              term,
              identifier0,
          ) in ref0.coordinate_conversion.domain_ancillaries().items():
              if identifier0 is None:
                  continue
  
              identifier1 = ref1.coordinate_conversion.domain_ancillaries()[term]
  
              #            key0 = domain_ancillaries.filter_by_key(identifier0).key()
              #            key1 = field1_domain_ancillaries.filter_by_key(identifier1).key()
  
              if not self._equivalent_construct_data(
                  field1,
                  key0=identifier0,  # key0,
                  key1=identifier1,  # key1,
                  rtol=rtol,
                  atol=atol,
                  s=s,
                  t=t,
                  verbose=verbose,
                  axis_map=axis_map,
              ):
                  # add traceback TODO
                  return False
  
          return True
  
      def _indices(self, mode, data_axes, auxiliary_mask, **kwargs):
          """Create indices that define a subspace of the field or domain
          construct.
  
          This method is intended to be called by the `indices` method.
  
          See the `indices` method for more details.
  
          .. versionadded:: 3.9.0
  
          :Parameters:
  
              mode: `str`
                  The mode of operation. See the *mode* parameter of
                  `indices` for details.
  
              data_axes: sequence of `str`, or `None`
                  The domain axis identifiers of the data axes, or
                  `None` if there is no data array.
  
              auxiliary_mask: `bool`
                  Whether or not to create an auxiliary mask. See
                  `indices` for details.
  
              kwargs: *optional*
                  See the **kwargs** parameters of `indices` for
                  details.
  
          :Returns:
  
              `dict`
                   The dictionary has two keys: ``'indices'`` and
                   ``'mask'``.
  
                   The ``'indices'`` key stores a dictionary keyed by
                   domain axis identifiers, each of which has a value of
                   the index for that domain axis.
  
                   The ``'mask'`` key stores a dictionary in keyed by
                   tuples of domain axis identifier combinations, each
                   of which has of a `Data` object containing the
                   auxiliary mask to apply to those domain axes at time
                   of the indices being used to create a subspace. This
                   dictionary will always be empty if *auxiliary_mask*
                   is False.
  
          """
          compress = mode == "compress"
          envelope = mode == "envelope"
          full = mode == "full"
  
          #        logger.debug(
          #            f"{self.__class__.__name__}._indices:\n"
          #            f"  mode         = {mode!r}\n"
          #            f"  input kwargs = {kwargs!r}"
          #        )  # pragma: no cover
  
          domain_axes = self.domain_axes(todict=True)
          #        constructs = self.constructs.filter_by_data()
  
          # Initialize indices
          indices = {axis: slice(None) for axis in domain_axes}
  
          parsed = {}
          unique_axes = set()
          n_axes = 0
  
          for identity, value in kwargs.items():
              key, construct = self.construct(
                  identity,
                  filter_by_data=True,
                  item=True,
                  default=(None, None),
              )
              if construct is not None:
                  axes = self.get_data_axes(key)
              else:
                  da_key = self.domain_axis(identity, key=True, default=None)
                  if da_key is not None:
                      axes = (da_key,)
                      key = None
                      construct = None
                  else:
                      raise ValueError(
                          f"Can't find indices. Ambiguous axis or axes "
                          f"defined by {identity!r}"
                      )
  
              if axes in parsed:
                  # The axes are the same as an existing key
                  parsed[axes].append((axes, key, construct, value))
              else:
                  new_key = True
                  y = set(axes)
                  for x in parsed:
                      if set(x) == set(y):
                          # The axes are the same but in a different
                          # order, so we don't need a new key.
                          parsed[x].append((axes, key, construct, value))
                          new_key = False
                          break
  
                  if new_key:
                      # The axes, taken in any order, are not the same
                      # as any keys, so create an new key.
                      n_axes += len(axes)
                      parsed[axes] = [(axes, key, construct, value)]
  
              unique_axes.update(axes)
  
          logger.debug(
              f"  parsed       = {parsed!r}\n"
              f"  unique_axes  = {unique_axes!r}\n"
              f"  n_axes       = {n_axes!r}"
          )  # pragma: no cover
  
          if len(unique_axes) < n_axes:
              raise ValueError(
                  "Can't find indices: Multiple constructs with incompatible "
                  "domain axes"
              )
  
          auxiliary_mask = {}
  
          for canonical_axes, axes_key_construct_value in parsed.items():
              axes, keys, constructs, points = list(
                  zip(*axes_key_construct_value)
              )
  
              n_items = len(constructs)
              n_axes = len(canonical_axes)
  
              if n_items > n_axes:
                  if n_axes == 1:
                      a = "axis"
                  else:
                      a = "axes"
  
                  raise ValueError(
                      f"Error: Can't specify {n_items} conditions for "
                      f"{n_axes} {a}: {points}"
                  )
  
              create_mask = False
  
              item_axes = axes[0]
  
              logger.debug(
                  f"  item_axes    = {item_axes!r}\n  keys         = {keys!r}"
              )  # pragma: no cover
  
              if n_axes == 1:
                  # ----------------------------------------------------
                  # 1-d construct
                  # ----------------------------------------------------
                  ind = None
  
                  axis = item_axes[0]
                  item = constructs[0]
                  value = points[0]
  
                  logger.debug(
                      f"  {n_items} 1-d constructs: {constructs!r}\n"
                      f"  axis         = {axis!r}\n"
                      f"  value        = {value!r}"
                  )  # pragma: no cover
  
                  if isinstance(value, (list, slice, tuple, np.ndarray)):
                      # ------------------------------------------------
                      # 1-dimensional CASE 1: Value is already an index,
                      #                       e.g. [0], [7,4,2],
                      #                       slice(0,4,2),
                      #                       numpy.array([2,4,7]),
                      #                       [True, False, True]
                      # ------------------------------------------------
                      logger.debug("  1-d CASE 1:")  # pragma: no cover
  
                      index = value
  
                      if envelope or full:
                          size = domain_axes[axis].get_size()
                          # TODODASK - consider using dask.arange here
                          d = np.arange(size)  # self._Data(range(size))
                          ind = (d[value],)  # .array,)
                          index = slice(None)
  
                  elif (
                      item is not None
                      and isinstance(value, Query)
                      and value.operator in ("wi", "wo")
                      and item.construct_type == "dimension_coordinate"
                      and self.iscyclic(axis)
                  ):
                      # ------------------------------------------------
                      # 1-dimensional CASE 2: Axis is cyclic and
                      #                       subspace criterion is a
                      #                       'within' or 'without'
                      #                       Query instance
                      # ------------------------------------------------
                      logger.debug("  1-d CASE 2:")  # pragma: no cover
  
                      if item.increasing:
                          anchor0 = value.value[0]
                          anchor1 = value.value[1]
                      else:
                          anchor0 = value.value[1]
                          anchor1 = value.value[0]
  
                      a = self.anchor(axis, anchor0, dry_run=True)["roll"]
                      b = self.flip(axis).anchor(axis, anchor1, dry_run=True)[
                          "roll"
                      ]
  
                      size = item.size
                      if abs(anchor1 - anchor0) >= item.period():
                          if value.operator == "wo":
                              set_start_stop = 0
                          else:
                              set_start_stop = -a
  
                          start = set_start_stop
                          stop = set_start_stop
                      elif a + b == size:
                          b = self.anchor(axis, anchor1, dry_run=True)["roll"]
                          if (b == a and value.operator == "wo") or not (
                              b == a or value.operator == "wo"
                          ):
                              set_start_stop = -a
                          else:
                              set_start_stop = 0
  
                          start = set_start_stop
                          stop = set_start_stop
                      else:
                          if value.operator == "wo":
                              start = b - size
                              stop = -a + size
                          else:
                              start = -a
                              stop = b - size
  
                      index = slice(start, stop, 1)
  
                      if full:
                          # TODODASK - consider using some sort of
                          #            dask.arange here
                          d = self._Data(list(range(size)))
                          d.cyclic(0)
                          ind = (d[index].array,)
                          index = slice(None)
  
                  elif item is not None:
                      # ------------------------------------------------
                      # 1-dimensional CASE 3: All other 1-d cases
                      # ------------------------------------------------
                      logger.debug("  1-d CASE 3:")  # pragma: no cover
  
                      item_match = value == item
  
                      if not item_match.any():
                          raise ValueError(
                              f"No {identity!r} axis indices found "
                              f"from: {value}"
                          )
  
                      index = np.asanyarray(item_match)
  
                      if envelope or full:
                          if np.ma.isMA(index):
                              ind = np.ma.where(index)
                          else:
                              ind = np.where(index)
  
                          index = slice(None)
  
                  else:
                      raise ValueError(
                          "Must specify a domain axis construct or a "
                          "construct with data for which to create indices"
                      )
  
                  logger.debug(
                      f"  index        = {index}\n" f"  ind          = {ind}"
                  )  # pragma: no cover
  
                  # Put the index into the correct place in the list of
                  # indices.
                  #
                  # Note that we might overwrite it later if there's an
                  # auxiliary mask for this axis.
                  indices[axis] = index
  
              else:
                  # ----------------------------------------------------
                  # N-dimensional constructs
                  # ----------------------------------------------------
                  logger.debug(
                      f"  {n_items} N-d constructs: {constructs!r}\n"
                      f"  {len(points)} points        : {points!r}\n"
                  )  # pragma: no cover
  
                  # Make sure that each N-d item has the same axis order
                  transposed_constructs = []
  
                  for construct, construct_axes in zip(constructs, axes):
                      if construct_axes != canonical_axes:
                          iaxes = [
                              construct_axes.index(axis)
                              for axis in canonical_axes
                          ]
                          construct = construct.transpose(iaxes)
  
                      transposed_constructs.append(construct)
  
                  logger.debug(
                      f"  transposed N-d constructs: {transposed_constructs!r}"
                  )  # pragma: no cover
  
                  item_matches = [
                      (value == construct).data
                      for value, construct in zip(points, transposed_constructs)
                  ]
  
                  item_match = item_matches.pop()
  
                  for m in item_matches:
                      item_match &= m
  
                  item_match = item_match.array  # LAMA alert
  
                  if np.ma.isMA:
                      ind = np.ma.where(item_match)
                  else:
                      ind = np.where(item_match)
  
                  logger.debug(
                      f"  item_match  = {item_match}\n" f"  ind         = {ind}"
                  )  # pragma: no cover
  
                  for i in ind:
                      if not i.size:
                          raise ValueError(
                              f"No {canonical_axes!r} axis indices found "
                              f"from: {value!r}"
                          )
  
                  bounds = [
                      item.bounds.array[ind]
                      for item in transposed_constructs
                      if item.has_bounds()
                  ]
  
                  contains = False
                  if bounds:
                      points2 = []
                      for v, construct in zip(points, transposed_constructs):
                          if isinstance(v, Query):
                              if v.operator == "contains":
                                  contains = True
                                  v = v.value
                              elif v.operator == "eq":
                                  v = v.value
                              else:
                                  contains = False
                                  break
  
                          v = self._Data.asdata(v)
                          if v.Units:
                              v.Units = construct.Units
  
                          points2.append(v.datum())
  
                  if contains:
                      # The coordinates have bounds and the condition is
                      # a 'contains' Query object. Check each
                      # potentially matching cell for actually including
                      # the point.
                      try:
                          Path
                      except NameError:
                          raise ImportError(
                              "Need to install matplotlib to create indices "
                              f"based on {transposed_constructs[0].ndim}-d "
                              "constructs and a 'contains' Query object"
                          )
  
                      if n_items != 2:
                          raise ValueError(
                              f"Can't index for cell from {n_axes}-d "
                              "coordinate objects"
                          )
  
                      if 0 < len(bounds) < n_items:
                          raise ValueError(
                              "Cell to derive index from must have unique "
                              f"bounds, but got {bounds}"
                          )
  
                      # Remove grid cells if, upon closer inspection,
                      # they do actually contain the point.
                      delete = [
                          n
                          for n, vertices in enumerate(zip(*zip(*bounds)))
                          if not Path(zip(*vertices)).contains_point(points2)
                      ]
  
                      if delete:
                          ind = [np.delete(ind_1d, delete) for ind_1d in ind]
  
              if ind is not None:
                  mask_shape = []
                  masked_subspace_size = 1
                  ind = np.array(ind)
  
                  for i, (axis, start, stop) in enumerate(
                      zip(canonical_axes, ind.min(axis=1), ind.max(axis=1))
                  ):
                      if data_axes and axis not in data_axes:
                          continue
  
                      if indices[axis] == slice(None):
                          if compress:
                              # Create a compressed index for this axis
                              size = stop - start + 1
                              index = sorted(set(ind[i]))
                          elif envelope:
                              # Create an envelope index for this axis
                              stop += 1
                              size = stop - start
                              index = slice(start, stop)
                          elif full:
                              # Create a full index for this axis
                              start = 0
                              stop = domain_axes[axis].get_size()
                              size = stop - start
                              index = slice(start, stop)
                          else:
                              raise ValueError(
                                  "Must have full, envelope or compress"
                              )  # pragma: no cover
  
                          indices[axis] = index
  
                      mask_shape.append(size)
                      masked_subspace_size *= size
                      ind[i] -= start
  
                  create_mask = data_axes and ind.shape[1] < masked_subspace_size
              else:
                  create_mask = False
  
              # TODODASK - if we have 2 list of integers then we need to
              #            apply different auxiliary masks (if any)
              #            after different __getitems__. SCRUB THAT! if
              #            we have an auxiliary mask, then by definition
              #            we do _not_ have a list(s) of integers
  
              # --------------------------------------------------------
              # Create an auxiliary mask for these axes
              # --------------------------------------------------------
              logger.debug(f"  create_mask  = {create_mask}")  # pragma: no cover
  
              if create_mask:
                  mask = _create_auxiliary_mask_component(
                      mask_shape, ind, compress
                  )
                  auxiliary_mask[canonical_axes] = mask
                  logger.debug(
                      f"  mask_shape   = {mask_shape}\n"
                      f"  mask.shape   = {mask.shape}"
                  )  # pragma: no cover
  
          for axis, index in tuple(indices.items()):
              indices[axis] = parse_indices(
                  (domain_axes[axis].get_size(),), (index,)
              )[0]
  
          # Include the auxiliary mask
          indices = {
              "indices": indices,
              "mask": auxiliary_mask,
          }
  
          logger.debug(f"  indices      = {indices!r}")  # pragma: no cover
  
          # Return the indices and the auxiliary mask
          return indices
  
      def _roll_constructs(self, axis, shift):
          """Roll the metadata constructs in-place along axes.
  
          If a roll axis is spanned by a dimension coordinate construct
          then it must be a periodic dimension coordinate construct.
  
          .. versionadded:: 3.9.0
  
          :Parameters:
  
              axis: sequence of `str`
                  The axis or axes along which elements are to be
                  shifted, defined by their domain axis identifiers.
  
              shift: (sequence of) `int`
                  The number of places by which elements are shifted.
                  If a sequence, then *axis* must be a sequence of the
                  same size, and each of the given axes is shifted by
                  the corresponding number.  If an `int` while *axis* is
                  a sequence, then the same value is used for all given
                  axes.
  
          :Returns:
  
              `list`
  
                  The shifts corresponding to each rolled axis.
  
          **Examples:**
  
          """
          if isinstance(shift, Integral):
              if axis:
                  shift = [shift] * len(axis)
              else:
                  shift = [shift]
          else:
              shift = list(shift)
  
          if len(shift) != len(axis):
              raise ValueError(
                  f"Can't roll {self.__class__.__name__}: "
                  f"Must have the same number of shifts ({len(shift)}) "
                  f"as axes ({len(axis)})."
              )
  
          for a in axis:
              dim = self.dimension_coordinate(filter_by_axis=(a,), default=None)
              if dim is not None and dim.period() is None:
                  raise ValueError(
                      f"Can't roll {self.__class__.__name__}. "
                      f"{dim.identity()!r} axis has a non-periodic "
                      "dimension coordinate construct"
                  )
  
          data_axes = self.constructs.data_axes()
          for key, construct in self.constructs.filter_by_data(
              todict=True
          ).items():
              construct_axes = data_axes.get(key, ())
  
              c_axes = []
              c_shifts = []
              for a, s in zip(axis, shift):
                  if a in construct_axes:
                      c_axes.append(construct_axes.index(a))
                      c_shifts.append(s)
  
              if not c_axes:
                  # This construct does not span the roll axes
                  continue
  
              # TODODASK - remove these two lines when multiaxis rolls
              #            are allowed at v4.0.0
              c_axes = c_axes[0]
              c_shifts = c_shifts[0]
  
              construct.roll(c_axes, shift=c_shifts, inplace=True)
  
          return shift
  
      def _set_construct_parse_axes(self, item, axes=None, allow_scalar=True):
          """Parse axes for the set_construct method.
  
          :Parameters:
  
              item: metadata construct
  
              axes: (sequence of) `str or `int`, optional
  
              allow_scalar: `bool`, optional
  
          :Returns:
  
              `list`
  
          """
          data = item.get_data(None, _fill_value=False)
  
          if axes is None:
              # --------------------------------------------------------
              # The axes have not been set => infer the axes.
              # --------------------------------------------------------
              if data is not None:
                  shape = item.shape
                  if allow_scalar and shape == ():
                      axes = []
                  else:
                      if not allow_scalar and not shape:
                          shape = (1,)
  
                      if not shape or len(shape) != len(set(shape)):
                          raise ValueError(
                              f"Can't insert {item!r}: Ambiguous shape: "
                              f"{shape}. Consider setting the 'axes' parameter."
                          )
  
                      domain_axes = self.domain_axes(todict=True)
                      axes = []
                      axes_sizes = [
                          domain_axis.get_size(None)
                          for domain_axis in domain_axes.values()
                      ]
  
                      for n in shape:
                          if not axes_sizes.count(n):
                              raise ValueError(
                                  f"Can't insert {item!r}: There is no "
                                  f"domain axis construct with size {n}."
                              )
  
                          if axes_sizes.count(n) != 1:
                              raise ValueError(
                                  f"Can't insert {item!r}: Ambiguous shape: "
                                  "f{shape}. Consider setting the 'axes' "
                                  "parameter."
                              )
  
                          da_key = self.domain_axis(
                              filter_by_size=(n,), key=True
                          )
                          axes.append(da_key)
          else:
              # --------------------------------------------------------
              # Axes have been provided
              # --------------------------------------------------------
              if isinstance(axes, (str, int)):
                  axes = (axes,)
  
              if axes and data is not None:
                  ndim = item.ndim
                  if not ndim and not allow_scalar:
                      ndim = 1
  
                  if isinstance(axes, (str, int)):
                      axes = (axes,)
  
                  if len(axes) != ndim or len(set(axes)) != ndim:
                      raise ValueError(
                          f"Can't insert {item!r}: Incorrect number of given "
                          f"axes (got {len(set(axes))}, expected {ndim})"
                      )
  
                  axes2 = []
                  for axis, size in zip(axes, item.data.shape):
                      da_key, domain_axis = self.domain_axis(
                          axis,
                          item=True,
                          default=ValueError(f"Unknown axis: {axis!r}"),
                      )
  
                      axis_size = domain_axis.get_size(None)
                      if size != axis_size:
                          raise ValueError(
                              f"Can't insert {item!r}: Mismatched axis size "
                              f"({size} != {axis_size})"
                          )
  
                      axes2.append(da_key)
  
                  axes = axes2
  
                  if ndim != len(set(axes)):
                      raise ValueError(
                          f"Can't insert {item!r}: Mismatched number of axes "
                          f"({len(set(axes))} != {ndim})"
                      )
  
          return axes
  
      @property
      def rank(self):
          """The number of axes in the domain.
  
          **Examples:**
  
          TODO
  
          """
          return len(self.domain_axes(todict=True))
  
      @_deprecated_kwarg_check("i")
      @_inplace_enabled(default=False)
      def anchor(
          self, axis, value, inplace=False, dry_run=False, i=False, **kwargs
      ):
          """Roll a cyclic axis so that the given value lies in the first
          coordinate cell.
  
          A unique axis is selected with the *axes* and *kwargs*
          parameters.
  
          .. versionadded:: 3.9.0
  
          .. seealso:: `axis`, `cyclic`, `iscyclic`, `roll`
  
          :Parameters:
  
              axis:
                  The cyclic axis to be anchored.
  
                  domain axis selection TODO.
  
              value:
                  Anchor the dimension coordinate values for the
                  selected cyclic axis to the *value*. May be any
                  numeric scalar object that can be converted to a
                  `Data` object (which includes `numpy` and `Data`
                  objects). If *value* has units then they must be
                  compatible with those of the dimension coordinates,
                  otherwise it is assumed to have the same units as the
                  dimension coordinates. The coordinate values are
                  transformed so that *value* is "equal to or just
                  before" the new first coordinate value. More
                  specifically:
  
                    * Increasing dimension coordinates with positive
                      period, P, are transformed so that *value* lies in
                      the half-open range (L-P, F], where F and L are
                      the transformed first and last coordinate values,
                      respectively.
  
              ..
  
                    * Decreasing dimension coordinates with positive
                      period, P, are transformed so that *value* lies in
                      the half-open range (L+P, F], where F and L are
                      the transformed first and last coordinate values,
                      respectively.
  
                  *Parameter example:*
                    If the original dimension coordinates are ``0, 5,
                    ..., 355`` (evenly spaced) and the period is ``360``
                    then ``value=0`` implies transformed coordinates of
                    ``0, 5, ..., 355``; ``value=-12`` implies
                    transformed coordinates of ``-10, -5, ..., 345``;
                    ``value=380`` implies transformed coordinates of
                    ``380, 385, ..., 715``.
  
                  *Parameter example:*
                    If the original dimension coordinates are ``355,
                    350, ..., 0`` (evenly spaced) and the period is
                    ``360`` then ``value=355`` implies transformed
                    coordinates of ``355, 350, ..., 0``; ``value=0``
                    implies transformed coordinates of ``0, -5, ...,
                    -355``; ``value=392`` implies transformed
                    coordinates of ``390, 385, ..., 30``.
  
              {{inplace: `bool`, optional}}
  
              dry_run: `bool`, optional
                  Return a dictionary of parameters which describe the
                  anchoring process. The construct is not changed, even
                  if *inplace* is True.
  
              {{i: deprecated at version 3.0.0}}
  
              kwargs: deprecated at version 3.0.0
  
          :Returns:
  
              `dict`
  
          **Examples:**
  
          >>> f.iscyclic('X')
          True
          >>> f.dimension_coordinate('X').data
          <CF Data(8): [0, ..., 315] degrees_east> TODO
          >>> print(f.dimension_coordinate('X').array)
          [  0  45  90 135 180 225 270 315]
          >>> g = f.anchor('X', 230)
          >>> print(g.dimension_coordinate('X').array)
          [270 315   0  45  90 135 180 225]
          >>> g = f.anchor('X', cf.Data(590, 'degreesE'))
          >>> print(g.dimension_coordinate('X').array)
          [630 675 360 405 450 495 540 585]
          >>> g = f.anchor('X', cf.Data(-490, 'degreesE'))
          >>> print(g.dimension_coordinate('X').array)
          [-450 -405 -720 -675 -630 -585 -540 -495]
  
          >>> f.iscyclic('X')
          True
          >>> f.dimension_coordinate('X').data
          <CF Data(8): [0.0, ..., 357.1875] degrees_east>
          >>> f.anchor('X', 10000).dimension_coordinate('X').data
          <CF Data(8): [10001.25, ..., 10358.4375] degrees_east>
          >>> d = f.anchor('X', 10000, dry_run=True)
          >>> d
          {'axis': 'domainaxis2',
           'nperiod': <CF Data(1): [10080.0] 0.0174532925199433 rad>,
           'roll': 28}
          >>> (f.roll(d['axis'], d['roll']).dimension_coordinate(
          ...     d['axis']) + d['nperiod']).data
          <CF Data(8): [10001.25, ..., 10358.4375] degrees_east>
  
          """
          if kwargs:
              _DEPRECATION_ERROR_KWARGS(
                  self, "anchor", kwargs
              )  # pragma: no cover
  
          da_key, axis = self.domain_axis(axis, item=True)
  
          if dry_run:
              f = self
          else:
              f = _inplace_enabled_define_and_cleanup(self)
  
          dim = f.dimension_coordinate(filter_by_axis=(da_key,), default=None)
          if dim is None:
              raise ValueError(
                  "Can't shift non-cyclic "
                  f"{f.constructs.domain_axis_identity(da_key)!r} axis"
              )
  
          period = dim.period()
          if period is None:
              raise ValueError(f"Cyclic {dim.identity()!r} axis has no period")
  
          value = f._Data.asdata(value)
          if not value.Units:
              value = value.override_units(dim.Units)
          elif not value.Units.equivalent(dim.Units):
              raise ValueError(
                  f"Anchor value has incompatible units: {value.Units!r}"
              )
  
          axis_size = axis.get_size()
  
          if axis_size <= 1:
              # Don't need to roll a size one axis
              if dry_run:
                  return {"axis": da_key, "roll": 0, "nperiod": 0}
  
              return f
  
          c = dim.get_data(_fill_value=False)
  
          if dim.increasing:
              # Adjust value so it's in the range [c[0], c[0]+period)
              n = ((c[0] - value) / period).ceil()
              value1 = value + n * period
  
              shift = axis_size - np.argmax((c - value1 >= 0).array)
              if not dry_run:
                  f.roll(da_key, shift, inplace=True)
  
              # Re-get dim
              dim = f.dimension_coordinate(filter_by_axis=(da_key,))
              # TODO CHECK n for dry run or not
              n = ((value - dim.data[0]) / period).ceil()
          else:
              # Adjust value so it's in the range (c[0]-period, c[0]]
              n = ((c[0] - value) / period).floor()
              value1 = value + n * period
  
              shift = axis_size - np.argmax((value1 - c >= 0).array)
  
              if not dry_run:
                  f.roll(da_key, shift, inplace=True)
  
              # Re-get dim
              dim = f.dimension_coordinate(filter_by_axis=(da_key,))
              # TODO CHECK n for dry run or not
              n = ((value - dim.data[0]) / period).floor()
  
          if dry_run:
              return {"axis": da_key, "roll": shift, "nperiod": n * period}
  
          if n:
              with bounds_combination_mode("OR"):
                  dim += n * period
  
          return f
  
      @_manage_log_level_via_verbosity
      def autocyclic(self, key=None, coord=None, verbose=None, config={}):
          """Set dimensions to be cyclic.
  
          A dimension is set to be cyclic if it has a unique longitude
          (or grid longitude) dimension coordinate construct with bounds
          and the first and last bounds values differ by 360 degrees (or
          an equivalent amount in other units).
  
          .. versionadded:: 1.0
  
          .. seealso:: `cyclic`, `iscyclic`, `period`
  
          :Parameters:
  
              {{verbose: `int` or `str` or `None`, optional}}
  
              config: `dict`
                  Additional parameters for optimizing the
                  operation. See the code for details.
  
                  .. versionadded:: 3.9.0
  
          :Returns:
  
             `bool`
  
          """
          noop = config.get("no-op")
  
          if "cyclic" in config:
              if not config["cyclic"]:
                  if not noop:
                      self.cyclic(key, iscyclic=False, config=config)
                  return False
              else:
                  period = coord.period()
                  if period is not None:
                      period = None
                  else:
                      period = config.get("period")
  
                  self.cyclic(key, iscyclic=True, period=period, config=config)
                  return True
  
          if coord is None:
              key, coord = self.dimension_coordinate(
                  "X", item=True, default=(None, None)
              )
              if coord is None:
                  return False
          elif "X" in config:
              if not config["X"]:
                  if not noop:
                      self.cyclic(key, iscyclic=False, config=config)
                  return False
          elif not coord.X:
              if not noop:
                  self.cyclic(key, iscyclic=False, config=config)
              return False
  
          bounds_range = config.get("bounds_range")
          if bounds_range is not None:
              bounds_units = config["bounds_units"]
          else:
              bounds = coord.get_bounds(None)
              if bounds is None:
                  if not noop:
                      self.cyclic(key, iscyclic=False, config=config)
                  return False
  
              data = bounds.get_data(None, _fill_value=False)
              if data is None:
                  if not noop:
                      self.cyclic(key, iscyclic=False, config=config)
                  return False
  
              bounds_units = bounds.Units
  
          period = coord.period()
  
          if period is not None:
              has_period = True
          else:
              period = config.get("period")
              if period is None:
                  has_period = False
              else:
                  has_period = True
  
          if not has_period:
              if bounds_units.islongitude:
                  period = Data(360.0, units="degrees_east")
              elif bounds_units.equivalent(_units_degrees):
                  period = Data(360.0, units="degrees")
              else:
                  self.cyclic(key, iscyclic=False, config=config)
                  return False
  
              period.Units = bounds_units
  
          if bounds_range is None:
              bounds_range = abs(data.last_element() - data.first_element())
  
          if bounds_range != period:
              if not noop:
                  self.cyclic(key, iscyclic=False, config=config)
              return False
  
          config = config.copy()
          config["axis"] = self.get_data_axes(key, default=(None,))[0]
  
          self.cyclic(key, iscyclic=True, period=period, config=config)
  
          return True
  
      def del_coordinate_reference(
          self, identity=None, construct=None, default=ValueError()
      ):
          """Remove a coordinate reference construct and all of its domain
          ancillary constructs.
  
                  .. versionadded:: 3.0.0
  
                  .. seealso:: `del_construct`
  
                  :Parameters:
  
                      identity: optional
                          Select the coordinate reference construct by one of:
  
                          * The identity of a coordinate reference construct.
  
                            {{construct selection identity}}
  
                          * The key of a coordinate reference construct
  
                          * `None`. This is the default, which selects the
                            coordinate reference construct when there is only
                            one of them.
  
                          *Parameter example:*
                            ``identity='standard_name:atmosphere_hybrid_height_coordinate'``
  
                          *Parameter example:*
                            ``identity='grid_mapping_name:rotated_latitude_longitude'``
  
                          *Parameter example:*
                            ``identity='transverse_mercator'``
  
                          *Parameter example:*
                            ``identity='coordinatereference1'``
  
                          *Parameter example:*
                            ``identity='key%coordinatereference1'``
  
                          *Parameter example:*
                            ``identity='ncvar%lat_lon'``
  
                          *Parameter example:*
                            ``identity=cf.eq('rotated_pole')'``
  
                          *Parameter example:*
                            ``identity=re.compile('^rotated')``
  
                      construct: optional
                          TODO
  
                      default: optional
                          Return the value of the *default* parameter if the
                          construct can not be removed, or does not exist.
  
                          {{default Exception}}
  
                  :Returns:
  
                          The removed coordinate reference construct.
  
                  **Examples:**
  
                  >>> f.del_coordinate_reference('rotated_latitude_longitude')
                  <CF CoordinateReference: rotated_latitude_longitude>
  
          """
          if construct is None:
              if identity is None:
                  raise ValueError(
                      "An identity or construct must be provided in order to "
                      "determine the coordinate reference to select."
                  )
  
              key = self.coordinate_reference(identity, key=True, default=None)
              if key is None:
                  if default is None:
                      return
  
                  return self._default(
                      default,
                      f"Can't identify construct from {identity!r}",
                  )
  
              ref = self.del_construct(key)
  
              for (
                  da_key
              ) in ref.coordinate_conversion.domain_ancillaries().values():
                  self.del_construct(da_key, default=None)
  
              return ref
          elif identity is not None:
              raise ValueError(
                  "Provide only one of the identity and construct parameters "
                  "to select a coordinate reference."
              )
  
          out = []
  
          c_key = self.construct(construct, key=True, default=None)
          if c_key is None:
              if default is None:
                  return
  
              return self._default(
                  default, f"Can't identify construct from {construct!r}"
              )
  
          for key, ref in tuple(self.coordinate_references(todict=True).items()):
              if c_key in ref.coordinates():
                  self.del_coordinate_reference(
                      key, construct=None, default=default
                  )
                  out.append(ref)
                  continue
  
              if (
                  c_key
                  in ref.coordinate_conversion.domain_ancillaries().values()
              ):
                  self.del_coordinate_reference(
                      key, construct=None, default=default
                  )
                  out.append(ref)
                  continue
  
          return out
  
      def del_domain_axis(
          self, identity=None, squeeze=False, default=ValueError()
      ):
          """Remove a domain axis construct.
  
          In general, a domain axis construct can only be removed if it
          is not spanned by any construct's data. However, a size 1
          domain axis construct can be removed in any case if the
          *squeeze* parameter is set to `True`. In this case, a metadata
          construct whose data spans only the removed domain axis
          construct will also be removed.
  
          .. versionadded:: 3.6.0
  
          .. seealso:: `del_construct`
  
          :Parameters:
  
              identity: optional
                  Select the domain axis construct by one of:
  
                  * An identity or key of a 1-d dimension or auxiliary
                    coordinate construct that whose data spans the
                    domain axis construct.
  
                    {{construct selection identity}}
  
                  * A domain axis construct identity.
  
                    {{domain axis selection identity}}
  
                  * The key of a domain axis construct.
  
                  * `None`. This is the default, which selects the
                    domain axis construct when there is only one of
                    them.  ``'key%dimensioncoordinate2'`` are both
                    acceptable keys.
  
                  *Parameter example:*
                    ``identity='long_name=Latitude'``
  
                  *Parameter example:*
                    ``identity='dimensioncoordinate1'``
  
                  *Parameter example:*
                    ``identity='domainaxis2'``
  
                  *Parameter example:*
                    ``identity='key%domainaxis2'``
  
                  *Parameter example:*
                    ``identity='ncdim%y'``
  
              squeeze: `bool`, optional
                  If True then allow the removal of a size 1 domain axis
                  construct that is spanned by any data array and
                  squeeze the corresponding dimension from those arrays.
  
              default: optional
                  Return the value of the *default* parameter if the
                  construct can not be removed, or does not exist.
  
                  {{default Exception}}
  
          :Returns:
  
              `DomainAxis`
                  The removed domain axis construct.
  
          **Examples:**
  
          >>> f = cf.example_field(0)
          >>> g = f[0]
          Field: specific_humidity (ncvar%q)
          ----------------------------------
          Data            : specific_humidity(latitude(1), longitude(8)) 1
          Cell methods    : area: mean
          Dimension coords: latitude(1) = [-75.0] degrees_north
                          : longitude(8) = [22.5, ..., 337.5] degrees_east
                          : time(1) = [2019-01-01 00:00:00]
          >>> g.del_domain_axis('Y', squeeze=True)
          <CF DomainAxis: size(1)>
          >>> print(g)
          Field: specific_humidity (ncvar%q)
          ----------------------------------
          Data            : specific_humidity(longitude(8)) 1
          Cell methods    : area: mean
          Dimension coords: longitude(8) = [22.5, ..., 337.5] degrees_east
                          : time(1) = [2019-01-01 00:00:00]
          >>> g.del_domain_axis('T', squeeze=True)
          <CF DomainAxis: size(1)>
          >>> print(g)
          Field: specific_humidity (ncvar%q)
          ----------------------------------
          Data            : specific_humidity(longitude(8)) 1
          Cell methods    : area: mean
          Dimension coords: longitude(8) = [22.5, ..., 337.5] degrees_east
  
          """
          dakey, domain_axis = self.domain_axis(identity, item=True)
  
          if not squeeze:
              return self.del_construct(dakey)
  
          if dakey in self.get_data_axes(default=()):
              self.squeeze(dakey, inplace=True)
  
          for ckey, construct in self.constructs.filter_by_data(
              todict=True
          ).items():
              data = construct.get_data(None, _fill_value=False)
              if data is None:
                  continue
  
              construct_axes = self.get_data_axes(ckey)
              if dakey not in construct_axes:
                  continue
  
              i = construct_axes.index(dakey)
              construct.squeeze(i, inplace=True)
              construct_axes = list(construct_axes)
              construct_axes.remove(dakey)
              self.set_data_axes(axes=construct_axes, key=ckey)
  
              if not construct_axes:
                  self.del_construct(ckey)
  
          return domain_axis
  
      def auxiliary_coordinate(
          self,
          *identity,
          default=ValueError(),
          key=False,
          item=False,
          **filter_kwargs,
      ):
          """Select an auxiliary coordinate construct.
  
          {{unique construct}}
  
          .. versionadded:: 3.0.0
  
          .. seealso:: `construct`, `auxiliary_coordinates`
  
          :Parameters:
  
              identity: optional
                  Select auxiliary coordinate constructs that have an
                  identity, defined by their `!identities` methods, that
                  matches any of the given values.
  
                  Additionally, the values are matched against construct
                  identifiers, with or without the ``'key%'`` prefix.
  
                  If no values are provided then all auxiliary
                  coordinate constructs are selected.
  
                  {{value match}}
  
                  {{displayed identity}}
  
              {{key: `bool`, optional}}
  
              {{item: `bool`, optional}}
  
                  .. versionadded:: (cfdm) 3.9.0
  
              default: optional
                  Return the value of the *default* parameter if there
                  is no unique construct.
  
                  {{default Exception}}
  
              {{filter_kwargs: optional}}
  
                  .. versionadded:: (cfdm) 3.9.0
  
          :Returns:
  
                  {{Returns construct}}
  
          **Examples:**
  
          """
          return self._construct(
              "auxiliary_coordinate",
              "auxiliary_coordinates",
              identity,
              key=key,
              item=item,
              default=default,
              **filter_kwargs,
          )
  
      def construct(
          self,
          *identity,
          default=ValueError(),
          key=False,
          item=False,
          **filter_kwargs,
      ):
          """Select a metadata construct by its identity.
  
          .. seealso:: `del_construct`, `get_construct`, `has_construct`,
                       `set_construct`
  
          :Parameters:
  
              identity: optional
                  Select constructs that have an identity, defined by
                  their `!identities` methods, that matches any of the
                  given values.
  
                  Additionally, the values are matched against construct
                  identifiers, with or without the ``'key%'`` prefix.
  
                  If no values are provided then all constructs are
                  selected.
  
                  {{value match}}
  
                  {{displayed identity}}
  
              {{key: `bool`, optional}}
  
              {{item: `bool`, optional}}
  
                  .. versionadded:: (cfdm) 3.9.0
  
              default: optional
                  Return the value of the *default* parameter if there
                  is no unique construct.
  
                  {{default Exception}}
  
              {{filter_kwargs: optional}}
  
                  .. versionadded:: (cfdm) 3.9.0
  
          :Returns:
  
                  {{Returns construct}}
  
          **Examples:**
  
          """
          return self._construct(
              "construct",
              "constructs",
              identity,
              key=key,
              item=item,
              default=default,
              **filter_kwargs,
          )
  
      def cell_measure(
          self,
          *identity,
          default=ValueError(),
          key=False,
          item=False,
          **filter_kwargs,
      ):
          """Select a cell measure construct.
  
          {{unique construct}}
  
          .. versionadded:: 3.0.0
  
          .. seealso:: `construct`, `cell_measures`
  
          :Parameters:
  
              identity: optional
                  Select dimension coordinate constructs that have an
                  identity, defined by their `!identities` methods, that
                  matches any of the given values.
  
                  Additionally, the values are matched against construct
                  identifiers, with or without the ``'key%'`` prefix.
  
                  If no values are provided then all dimension
                  coordinate constructs are selected.
  
                  {{value match}}
  
                  {{displayed identity}}
  
              {{key: `bool`, optional}}
  
              {{item: `bool`, optional}}
  
                  .. versionadded:: (cfdm) 3.9.0
  
              default: optional
                  Return the value of the *default* parameter if there
                  is no unique construct.
  
                  {{default Exception}}
  
              {{filter_kwargs: optional}}
  
                  .. versionadded:: (cfdm) 3.9.0
  
          :Returns:
  
                  {{Returns construct}}
  
          **Examples:**
  
          """
          return self._construct(
              "cell_measure",
              "cell_measures",
              identity,
              key=key,
              item=item,
              default=default,
              **filter_kwargs,
          )
  
      def coordinate(
          self,
          *identity,
          default=ValueError(),
          key=False,
          item=False,
          **filter_kwargs,
      ):
          """Select a dimension or auxiliary coordinate construct.
  
          {{unique construct}}
  
          .. versionadded:: 3.0.0
  
          .. seealso:: `construct`, `coordinates`
  
          :Parameters:
  
              identity: optional
                  Select dimension or auxiliary coordinate constructs
                  that have an identity, defined by their `!identities`
                  methods, that matches any of the given values.
  
                  Additionally, the values are matched against construct
                  identifiers, with or without the ``'key%'`` prefix.
  
                  If no values are provided then all dimension or
                  auxiliary coordinate constructs are selected.
  
                  {{value match}}
  
                  {{displayed identity}}
  
              {{key: `bool`, optional}}
  
              {{item: `bool`, optional}}
  
                  .. versionadded:: (cfdm) 3.9.0
  
              default: optional
                  Return the value of the *default* parameter if there
                  is no unique construct.
  
                  {{default Exception}}
  
              {{filter_kwargs: optional}}
  
                  .. versionadded:: (cfdm) 3.9.0
  
          :Returns:
  
                  {{Returns construct}}
  
          **Examples:**
  
          """
          return self._construct(
              "coordinate",
              "coordinates",
              identity,
              key=key,
              item=item,
              default=default,
              **filter_kwargs,
          )
  
      def coordinate_reference(
          self,
          *identity,
          default=ValueError(),
          key=False,
          item=False,
          **filter_kwargs,
      ):
          """Return a coordinate reference construct, or its key.
  
          .. versionadded:: 3.0.0
  
          .. seealso:: `construct`, `auxiliary_coordinate`,
                       `cell_measure`, `cell_method`, `coordinate`,
                       `coordinate_references`, `dimension_coordinate`,
                       `domain_ancillary`, `domain_axis`,
                       `field_ancillary`
  
          :Parameters:
  
              identity: optional
                  Select coordinate reference constructs that have an
                  identity, defined by their `!identities` methods, that
                  matches any of the given values.
  
                  Additionally, the values are matched against construct
                  identifiers, with or without the ``'key%'`` prefix.
  
                  If no identities are provided then all coordinate
                  reference constructs are selected.
  
                  {{value match}}
  
                  {{displayed identity}}
  
              {{key: `bool`, optional}}
  
              {{item: `bool`, optional}}
  
                  .. versionadded:: (cfdm) 3.9.0
  
              default: optional
                  Return the value of the *default* parameter if there
                  is no unique construct.
  
                  {{default Exception}}
  
              {{filter_kwargs: optional}}
  
                  .. versionadded:: (cfdm) 3.9.0
  
          :Returns:
  
                  {{Returns construct}}
  
          **Examples:**
  
          """
          return self._construct(
              "coordinate_reference",
              "coordinate_references",
              identity,
              key=key,
              item=item,
              default=default,
              **filter_kwargs,
          )
  
      def coordinate_reference_domain_axes(self, identity=None):
          """Return the domain axes that apply to a coordinate reference
          construct.
  
                  :Parameters:
  
                      identity: optional
                          Select the coordinate reference construct by one of:
  
                          * The identity of a coordinate reference construct.
  
                            {{construct selection identity}}
  
                          * The key of a coordinate reference construct
  
                          * `None`. This is the default, which selects the
                            coordinate reference construct when there is only
                            one of them.
  
                          *Parameter example:*
                            ``identity='standard_name:atmosphere_hybrid_height_coordinate'``
  
                          *Parameter example:*
                            ``identity='grid_mapping_name:rotated_latitude_longitude'``
  
                          *Parameter example:*
                            ``identity='transverse_mercator'``
  
                          *Parameter example:*
                            ``identity='coordinatereference1'``
  
                          *Parameter example:*
                            ``identity='key%coordinatereference1'``
  
                          *Parameter example:*
                            ``identity='ncvar%lat_lon'``
  
                          *Parameter example:*
                            ``identity=cf.eq('rotated_pole')'``
  
                          *Parameter example:*
                            ``identity=re.compile('^rotated')``
  
                  :Returns:
  
                      `set`
                          The identifiers of the domain axis constructs that span
                          the data of all coordinate and domain ancillary
                          constructs used by the selected coordinate reference
                          construct.
  
                  **Examples:**
  
                  >>> f.coordinate_reference_domain_axes('coordinatereference0')
                  {'domainaxis0', 'domainaxis1', 'domainaxis2'}
  
                  >>> f.coordinate_reference_domain_axes(
                  ...     'atmosphere_hybrid_height_coordinate')
                  {'domainaxis0', 'domainaxis1', 'domainaxis2'}
  
          """
          cr = self.coordinate_reference(identity)
  
          data_axes = self.constructs.data_axes()
  
          axes = []
          for i in cr.coordinates() | set(
              cr.coordinate_conversion.domain_ancillaries().values()
          ):
              key = self.construct(i, key=True, default=None)
              axes.extend(data_axes.get(key, ()))
  
          return set(axes)
  
      def cyclic(
          self, *identity, iscyclic=True, period=None, config={}, **filter_kwargs
      ):
          """Set the cyclicity of an axis.
  
          .. versionadded:: 1.0
  
          .. seealso:: `autocyclic`, `iscyclic`, `period`, `domain_axis`
  
          :Parameters:
  
              identity, filter_kwargs: optional
                  Select the unique domain axis construct returned by
                  ``f.domain_axis(*identity, **filter_kwargs)``. See
                  `domain_axis` for details.
  
              iscyclic: `bool`, optional
                  If False then the axis is set to be non-cyclic. By
                  default the selected axis is set to be cyclic.
  
              period: optional
                  The period for a dimension coordinate construct which
                  spans the selected axis. May be any numeric scalar
                  object that can be converted to a `Data` object (which
                  includes numpy array and `Data` objects). The absolute
                  value of *period* is used. If *period* has units then
                  they must be compatible with those of the dimension
                  coordinates, otherwise it is assumed to have the same
                  units as the dimension coordinates.
  
              config: `dict`
                  Additional parameters for optimizing the
                  operation. See the code for details.
  
                  .. versionadded:: 3.9.0
  
              axes: deprecated at version 3.0.0
                  Use the *identity* and **filter_kwargs* parameters
                  instead.
  
          :Returns:
  
              `set`
                  The construct keys of the domain axes which were
                  cyclic prior to the new setting, or the current cyclic
                  domain axes if no axis was specified.
  
          **Examples:**
  
          >>> f.cyclic()
          set()
          >>> f.cyclic('X', period=360)
          set()
          >>> f.cyclic()
          {'domainaxis2'}
          >>> f.cyclic('X', iscyclic=False)
          {'domainaxis2'}
          >>> f.cyclic()
          set()
  
          """
          if not iscyclic and config.get("no-op"):
              return self._cyclic.copy()
  
          old = None
          cyclic = self._cyclic
  
          if not identity and not filter_kwargs:
              return cyclic.copy()
  
          axis = config.get("axis")
          if axis is None:
              axis = self.domain_axis(*identity, key=True, **filter_kwargs)
  
          data = self.get_data(None, _fill_value=False)
          if data is not None:
              try:
                  data_axes = self.get_data_axes()
                  data.cyclic(data_axes.index(axis), iscyclic)
              except ValueError:
                  pass
  
          if iscyclic:
              dim = config.get("coord")
              if dim is None:
                  dim = self.dimension_coordinate(
                      filter_by_axis=(axis,), default=None
                  )
  
              if dim is not None:
                  if config.get("period") is not None:
                      dim.period(**config)
                  elif period is not None:
                      dim.period(period, **config)
                  elif dim.period() is None:
                      raise ValueError(
                          "A cyclic dimension coordinate must have a period"
                      )
  
              if axis not in cyclic:
                  # Never change _cyclic in-place
                  old = cyclic.copy()
                  cyclic = cyclic.copy()
                  cyclic.add(axis)
                  self._cyclic = cyclic
  
          elif axis in cyclic:
              # Never change _cyclic in-place
              old = cyclic.copy()
              cyclic = cyclic.copy()
              cyclic.discard(axis)
              self._cyclic = cyclic
  
          if old is None:
              old = cyclic.copy()
  
          return old
  
      def dimension_coordinate(
          self,
          *identity,
          key=False,
          default=ValueError(),
          item=False,
          **filter_kwargs,
      ):
          """Select a dimension coordinate construct.
  
          {{unique construct}}
  
          .. versionadded:: 3.0.0
  
          .. seealso:: `construct`, `dimension_coordinates`
  
          :Parameters:
  
              identity: optional
                  Select dimension coordinate constructs that have an
                  identity, defined by their `!identities` methods, that
                  matches any of the given values.
  
                  Additionally, the values are matched against construct
                  identifiers, with or without the ``'key%'`` prefix.
  
                  If no values are provided then all dimension
                  coordinate constructs are selected.
  
                  {{value match}}
  
                  {{displayed identity}}
  
              {{key: `bool`, optional}}
  
              {{item: `bool`, optional}}
  
                  .. versionadded:: (cfdm) 3.9.0
  
              default: optional
                  Return the value of the *default* parameter if there
                  is no unique construct.
  
                  {{default Exception}}
  
              {{filter_kwargs: optional}}
  
                  .. versionadded:: (cfdm) 3.9.0
  
          :Returns:
  
                  {{Returns construct}}
  
          **Examples:**
  
          """
          return self._construct(
              "dimension_coordinate",
              "dimension_coordinates",
              identity,
              key=key,
              item=item,
              default=default,
              **filter_kwargs,
          )
  
      @_deprecated_kwarg_check("axes")
      def direction(self, identity, axes=None, **kwargs):
          """Whether or not a domain axis is increasing.
  
          An domain axis is considered to be increasing if its dimension
          coordinate values are increasing in index space or if it has
          no dimension coordinate.
  
          .. seealso:: `directions`
  
          :Parameters:
  
              identity: optional
                  Select the domain axis construct by one of:
  
                  * An identity or key of a 1-d dimension or auxiliary
                    coordinate construct that whose data spans the
                    domain axis construct.
  
                    {{construct selection identity}}
  
                  * A domain axis construct identity
  
                    The domain axis is that which would be selected by
                    passing the given axis description to a call of the
                    construct's `domain_axis` method. For example, for a
                    value of ``'X'``, the domain axis construct returned
                    by ``f.domain_axis('X')`` is selected.
  
                  * `None`. This is the default, which selects the
                     domain construct when there is only one of them.
  
              axes: deprecated at version 3.0.0
                  Use the *identity* parameter instead.
  
              size: deprecated at version 3.0.0
  
              kwargs: deprecated at version 3.0.0
  
          :Returns:
  
              `bool`
                  Whether or not the domain axis is increasing.
  
          **Examples:**
  
          >>> print(f.dimension_coordinate('X').array)
          array([  0  30  60])
          >>> f.direction('X')
          True
          >>> g = f.flip('X')
          >>> g.direction('X')
          False
  
          """
          if kwargs:
              _DEPRECATION_ERROR_KWARGS(
                  self, "direction", kwargs
              )  # pragma: no cover
  
          #        axis = self.domain_axis(identity, key=True, default=None)
          #       if axis is None:
          #            return True
  
          for coord in self.dimension_coordinates(
              filter_by_axis=(identity,), todict=True
          ).values():
              return coord.direction()
  
          return True
  
      def directions(self):
          """Return a dictionary mapping all domain axes to their
          directions.
  
          .. seealso:: `direction`
  
          :Returns:
  
              `dict`
                  A dictionary whose key/value pairs are domain axis
                  keys and their directions.
  
          **Examples:**
  
          >>> d.directions()
          {'domainaxis1': True, 'domainaxis1': False}
  
          """
          out = {key: True for key in self.domain_axes(todict=True)}
  
          data_axes = self.constructs.data_axes()
  
          for key, coord in self.dimension_coordinates(todict=True).items():
              axis = data_axes[key][0]
              out[axis] = coord.direction()
  
          return out
  
      def domain_ancillary(
          self,
          *identity,
          default=ValueError(),
          key=False,
          item=False,
          **filter_kwargs,
      ):
          """Select a domain ancillary construct.
  
          {{unique construct}}
  
          .. versionadded:: 3.0.0
  
          .. seealso:: `construct`, `domain_ancillaries`
  
          :Parameters:
  
              identity: optional
                  Select domain ancillary constructs that have an
                  identity, defined by their `!identities` methods, that
                  matches any of the given values.
  
                  Additionally, the values are matched against construct
                  identifiers, with or without the ``'key%'`` prefix.
  
                  If no values are provided then all domain ancillary
                  constructs are selected.
  
                  {{value match}}
  
                  {{displayed identity}}
  
              {{key: `bool`, optional}}
  
              {{item: `bool`, optional}}
  
                  .. versionadded:: (cfdm) 3.9.0
  
              default: optional
                  Return the value of the *default* parameter if there
                  is no unique construct.
  
                  {{default Exception}}
  
              {{filter_kwargs: optional}}
  
                  .. versionadded:: (cfdm) 3.9.0
  
          :Returns:
  
                  {{Returns construct}}
  
          **Examples:**
  
          """
          return self._construct(
              "domain_ancillary",
              "domain_ancillaries",
              identity,
              key=key,
              item=item,
              default=default,
              **filter_kwargs,
          )
  
      def domain_axis(
          self,
          *identity,
          key=False,
          default=ValueError(),
          item=False,
          **filter_kwargs,
      ):
          """Select a domain axis construct.
  
          {{unique construct}}
  
          .. versionadded:: 3.0.0
  
          .. seealso:: `construct`, `domain_axes`
  
          :Parameters:
  
              identities: `tuple`, optional
                  Select domain axis constructs that have an identity,
                  defined by their `!identities` methods, that matches
                  any of the given values.
  
                  Additionally, the values are matched against construct
                  identifiers, with or without the ``'key%'`` prefix.
  
                  Additionally, if for a given `value``,
                  ``f.coordinates(value, filter_by_naxes=(1,))`` returns
                  1-d coordinate constructs that all span the same
                  domain axis construct then that domain axis construct
                  is selected. See `coordinates` for details.
  
                  Additionally, if there is a `Field` data array and a
                  value matches the integer position of an array
                  dimension, then the corresponding domain axis
                  construct is selected.
  
                  If no values are provided then all domain axis
                  constructs are selected.
  
                  {{value match}}
  
                  {{displayed identity}}
  
              {{key: `bool`, optional}}
  
              {{item: `bool`, optional}}
  
                  .. versionadded:: (cfdm) 3.9.0
  
              default: optional
                  Return the value of the *default* parameter if there
                  is no unique construct.
  
                  {{default Exception}}
  
              {{filter_kwargs: optional}}
  
                  .. versionadded:: (cfdm) 3.9.0
  
          :Returns:
  
                  {{Returns construct}}
  
  
          **Examples:**
  
          """
          return self._construct(
              "domain_axis",
              "domain_axes",
              identity,
              key=key,
              item=item,
              default=default,
              **filter_kwargs,
          )
  
      def get_coordinate_reference(
          self, *identity, key=False, construct=None, default=ValueError()
      ):
          """Return a coordinate reference construct.
  
          .. versionadded:: 3.0.2
  
          .. seealso:: `construct`
  
          :Parameters:
  
              identity: optional
                  Select the coordinate reference construct by one of:
  
                  * The identity of a coordinate reference construct.
  
                    {{construct selection identity}}
  
                  * The key of a coordinate reference construct
  
                  * `None`. This is the default, which selects the
                    coordinate reference construct when there is only
                    one of them.
  
                  *Parameter example:*
                    ``identity='standard_name:atmosphere_hybrid_height_coordinate'``
  
                  *Parameter example:*
                    ``identity='grid_mapping_name:rotated_latitude_longitude'``
  
                  *Parameter example:*
                    ``identity='transverse_mercator'``
  
                  *Parameter example:*
                    ``identity='coordinatereference1'``
  
                  *Parameter example:*
                    ``identity='key%coordinatereference1'``
  
                  *Parameter example:*
                    ``identity='ncvar%lat_lon'``
  
                  *Parameter example:*
                    ``identity=cf.eq('rotated_pole')'``
  
                  *Parameter example:*
                    ``identity=re.compile('^rotated')``
  
              construct: optional
                  TODO
  
              key: `bool`, optional
                  If True then return the selected construct key. By
                  default the construct itself is returned.
  
              default: optional
                  Return the value of the *default* parameter if a
                  construct can not be found.
  
                  {{default Exception}}
  
          :Returns:
  
              `CoordinateReference` or `str`
                  The selected coordinate reference construct, or its
                  key.
  
          **Examples:**
  
          """
          if construct is None:
              return self.coordinate_reference(
                  *identity, key=key, default=default
              )
  
          out = []
  
          c_key = self.construct(construct, key=True, default=None)
          if c_key is None:
              if default is None:
                  return
              return self._default(
                  default, f"Can't identify construct from {construct!r}"
              )
  
          for cr_key, ref in tuple(
              self.coordinate_references(todict=True).items()
          ):
              if c_key in [
                  ref.coordinates(),
                  ref.coordinate_conversion.domain_ancillaries().values(),
              ]:
                  if key:
                      if cr_key not in out:
                          out.append(cr_key)
                  elif ref not in out:
                      out.append(ref)
  
                  continue
  
          return out
  
      def has_construct(self, *identity, **filter_kwargs):
          """Whether a metadata construct exists.
  
          .. versionadded:: 3.4.0
  
          .. seealso:: `construct`, `del_construct`, `get_construct`,
                       `set_construct`
          :Parameters:
  
              identity, filter_kwargs: optional
                  Select the unique construct returned by
                  ``f.construct(*identity, **filter_kwargs)``. See
                  `construct` for details.
  
          :Returns:
  
              `bool`
                  `True` if the construct exists, otherwise `False`.
  
          **Examples:**
  
          >>> f = cf.example_field(0)
          >>> print(f)
          Field: specific_humidity (ncvar%q)
          ----------------------------------
          Data            : specific_humidity(latitude(5), longitude(8)) 1
          Cell methods    : area: mean
          Dimension coords: latitude(5) = [-75.0, ..., 75.0] degrees_north
                          : longitude(8) = [22.5, ..., 337.5] degrees_east
                          : time(1) = [2019-01-01 00:00:00]
          >>> f.has_construct('T')
          True
          >>> f.has_construct('longitude')
          True
          >>> f.has_construct('Z')
          False
  
          """
          return (
              self.construct(*identity, default=None, **filter_kwargs)
              is not None
          )
  
      def iscyclic(self, *identity, **filter_kwargs):
          """Returns True if the given axis is cyclic.
  
          {{unique construct}}
  
          .. versionadded:: 1.0
  
          .. seealso:: `cyclic`, `period`, `domain_axis`
  
          :Parameters:
  
              identity: optional
                  Select the unique domain axis construct returned by
                  ``f.domain_axis(*identity, **filter_kwargs)``. See
                  `domain_axis` for details.
  
              {{filter_kwargs: optional}}
  
                  .. versionadded:: (cfdm) 3.9.0
  
          :Returns:
  
              `bool`
                  True if the selected axis is cyclic, otherwise False.
  
          **Examples:**
  
          >>> f.iscyclic('X')
          True
          >>> f.iscyclic('latitude')
          False
  
          >>> x = f.iscyclic('long_name=Latitude')
          >>> x = f.iscyclic('dimensioncoordinate1')
          >>> x = f.iscyclic('domainaxis2')
          >>> x = f.iscyclic('key%domainaxis2')
          >>> x = f.iscyclic('ncdim%y')
          >>> x = f.iscyclic(2)
  
          """
          axis = self.domain_axis(
              *identity, key=True, default=None, **filter_kwargs
          )
          if axis is None:
              raise ValueError("Can't identify unique axis")
  
          return axis in self.cyclic()
  
      def match_by_rank(self, *ranks):
          """Whether or not the number of domain axis constructs satisfies
          conditions.
  
          .. versionadded:: 3.0.0
  
          .. seealso:: `match`, `match_by_property`,
                       `match_by_identity`, `match_by_ncvar`,
                       `match_by_construct`
  
          :Parameters:
  
              ranks: optional
                  Define conditions on the number of domain axis
                  constructs.
  
                  A condition is one of:
  
                  * `int`
                  * a `Query` object
  
                  The condition is satisfied if the number of domain
                  axis constructs equals the condition value.
  
                  *Parameter example:*
                    To see if the field construct has 4 domain axis
                    constructs: ``4``
  
                  *Parameter example:*
                    To see if the field construct has at least 3 domain
                    axis constructs: ``cf.ge(3)``
  
          :Returns:
  
              `bool`
                  Whether or not at least one of the conditions are met.
  
          **Examples:**
  
          >>> f.match_by_rank(3, 4)
  
          >>> f.match_by_rank(cf.wi(2, 4))
  
          >>> f.match_by_rank(1, cf.gt(3))
  
          """
          if not ranks:
              return True
  
          n_domain_axes = len(self.domain_axes(todict=True))
          for rank in ranks:
              ok = rank == n_domain_axes
              if ok:
                  return True
  
          return False
  
      def _parse_axes(self, axes):
          """Convert the given axes to their domain axis identifiers.
  
          .. versionadded:: 3.9.0
  
          :Parameters:
  
              axes:
                  One or more axis specifications.
  
                  If *axes* is a sequence then the returned identifiers
                  are in the same order.
  
          :Returns:
  
              `list`
                  The domain axis identifiers.
  
          """
          if isinstance(axes, str):
              axes = (axes,)
          else:
              try:
                  len(axes)
              except TypeError:
                  axes = (axes,)
  
          return [self.domain_axis(x, key=True) for x in axes]
  
      def replace_construct(
          self, *identity, new=None, copy=True, **filter_kwargs
      ):
          """Replace a metadata construct.
  
          Replacement assigns the same construct key and, if applicable, the
          domain axes of the original construct to the new, replacing
          construct.
  
          .. versionadded:: 3.0.0
  
          .. seealso:: `set_construct`, `construct`
  
          :Parameters:
  
              identity: optional
                  Select the unique construct returned by
                  ``f.construct(*identity, **filter_kwargs)``. See
                  `construct` for details.
  
              new:
                 The new construct to replace that selected by the
                 *identity* parameter.
  
              copy: `bool`, optional
                  If True then set a copy of the new construct. By default
                  the construct is copied.
  
              {{filter_kwargs: optional}}
  
                  .. versionadded:: 3.9.0
  
              construct:
                  Deprecated at version 3.9.0
  
          :Returns:
  
                  The construct that was replaced.
  
          **Examples:**
  
          >>> f.replace_construct('X', new=X_construct)
  
          """
          key, c = self.construct(*identity, item=True, **filter_kwargs)
  
          if not isinstance(new, c.__class__):
              raise ValueError(
                  f"Can't replace a {c.__class__.__name__} construct "
                  f"with a {new.__class__.__name__} object"
              )
  
          axes = self.get_data_axes(key, default=None)
          if axes is not None:
              shape0 = getattr(c, "shape", None)
              shape1 = getattr(new, "shape", None)
              if shape0 != shape1:
                  raise ValueError(
                      f"Can't replace {c.__class__.__name__} construct "
                      f"with a {new.__class__.__name__} object of different "
                      "shape."
                  )
  
          self.set_construct(new, key=key, axes=axes, copy=copy)
  
          return c
  
      def set_construct(
          self,
          construct,
          key=None,
          axes=None,
          set_axes=True,
          copy=True,
          autocyclic={},
      ):
          """Set a metadata construct.
  
          When inserting a construct with data, the domain axes constructs
          spanned by the data are either inferred, or specified with the
          *axes* parameter.
  
          For a dimension coordinate construct, an existing dimension
          coordinate construct is discarded if it spans the same domain axis
          construct (since only one dimension coordinate construct can be
          associated with a given domain axis construct).
  
          .. versionadded:: 3.0.0
  
          .. seealso:: `constructs`, `creation_commands`, `del_construct`,
                       `get_construct`, `set_coordinate_reference`,
                       `set_data_axes`
  
          :Parameters:
  
              construct:
                  The metadata construct to be inserted.
  
              key: `str`, optional
                  The construct identifier to be used for the construct. If
                  not set then a new, unique identifier is created
                  automatically. If the identifier already exists then the
                  existing construct will be replaced.
  
                  *Parameter example:*
                    ``key='cellmeasure0'``
  
              axes: (sequence of) `str` or `int`, optional
                  Set the domain axes constructs that are spanned by the
                  construct's data. If unset, and the *set_axes* parameter
                  is True, then an attempt will be made to assign existing
                  domain axis constructs to the data.
  
                  The contents of the *axes* parameter is mapped to domain
                  axis constructs by translating each element into a domain
                  axis construct key via the `domain_axis` method.
  
                  *Parameter example:*
                    ``axes='domainaxis1'``
  
                  *Parameter example:*
                    ``axes='X'``
  
                  *Parameter example:*
                    ``axes=['latitude']``
  
                  *Parameter example:*
                    ``axes=['X', 'longitude']``
  
                  *Parameter example:*
                    ``axes=[1, 0]``
  
              set_axes: `bool`, optional
                  If False then do not set the domain axes constructs that
                  are spanned by the data, even if the *axes* parameter has
                  been set. By default the axes are set either according to
                  the *axes* parameter, or an attempt will be made to assign
                  existing domain axis constructs to the data.
  
              copy: `bool`, optional
                  If True then set a copy of the construct. By default the
                  construct is copied.
  
              autocyclic: `dict`, optional
                  Additional parameters for optimizing the operation,
                  relating to coordinate periodicity and cyclicity. See
                  the code for details.
  
                  .. versionadded:: 3.9.0
  
          :Returns:
  
              `str`
                  The construct identifier for the construct.
  
          **Examples:**
  
          >>> key = f.set_construct(c)
          >>> key = f.set_construct(c, copy=False)
          >>> key = f.set_construct(c, axes='domainaxis2')
          >>> key = f.set_construct(c, key='cellmeasure0')
  
          """
          construct_type = construct.construct_type
  
          if not set_axes:
              axes = None
  
          if construct_type in (
              "dimension_coordinate",
              "auxiliary_coordinate",
              "cell_measure",
          ):
              if construct.isscalar:
                  # Turn a scalar object into 1-d
                  if copy:
                      construct = construct.insert_dimension(0)
                      copy = False
                  else:
                      construct.insert_dimension(0, inplace=True)
  
              if set_axes:
                  axes = self._set_construct_parse_axes(
                      construct, axes, allow_scalar=False
                  )
  
              if construct_type == "dimension_coordinate":
                  data_axes = self.constructs.data_axes()
                  for dim in self.dimension_coordinates(todict=True):
                      if dim == key:
                          continue
  
                      if data_axes.get(dim) == tuple(axes):
                          self.del_construct(dim, default=None)
  
          elif construct_type in ("domain_ancillary", "field_ancillary"):
              if set_axes:
                  axes = self._set_construct_parse_axes(
                      construct, axes, allow_scalar=True
                  )
  
          out = super().set_construct(construct, key=key, axes=axes, copy=copy)
  
          if construct_type == "dimension_coordinate":
              construct.autoperiod(inplace=True, config=autocyclic)
              self._conform_coordinate_references(out)
              self.autocyclic(key=out, coord=construct, config=autocyclic)
              try:
                  self._conform_cell_methods()
              except AttributeError:
                  pass
  
          elif construct_type == "auxiliary_coordinate":
              construct.autoperiod(inplace=True, config=autocyclic)
              self._conform_coordinate_references(out)
              try:
                  self._conform_cell_methods()
              except AttributeError:
                  pass
  
          elif construct_type == "cell_method":
              self._conform_cell_methods()
  
          elif construct_type == "coordinate_reference":
              for ckey in self.coordinates(todict=True):
                  self._conform_coordinate_references(ckey, coordref=construct)
  
          # Return the construct key
          return out
  
      def set_coordinate_reference(
          self, coordinate_reference, key=None, parent=None, strict=True
      ):
          """Set a coordinate reference construct.
  
          By default, this is equivalent to using the `set_construct`
          method. If, however, the *parent* parameter has been set to be
          a field or domain construct that contains the new coordinate
          reference construct then copies of its coordinate and domain
          ancillary constructs will be referenced by the inserted
          coordinate reference construct.
  
          .. versionadded:: 3.0.0
  
          .. seealso:: `set_construct`
  
          :Parameters:
  
              coordinate_reference: `CoordinateReference`
                  The coordinate reference construct to be inserted.
  
              key: `str`, optional
                  The construct identifier to be used for the
                  construct. If not set then a new, unique identifier is
                  created automatically. If the identifier already
                  exists then the existing construct will be replaced.
  
                  *Parameter example:*
                    ``key='coordinatereference1'``
  
              parent: `Field` or `Domain`, optional
                  A field or domain construct that contains the new
                  coordinate reference construct.
  
              strict: `bool`, optional
                  If False then allow non-strict identities for
                  identifying coordinate and domain ancillary metadata
                  constructs.
  
          :Returns:
  
              `str`
                  The construct identifier for the coordinate reference
                  construct.
  
          """
          if parent is None:
              return self.set_construct(coordinate_reference, key=key, copy=True)
  
          # Still here?
          ref = coordinate_reference.copy()
  
          coordinates = parent.coordinates(todict=True)
          domain_ancillaries = parent.domain_ancillaries(todict=True)
  
          ckeys = []
          for value in coordinate_reference.coordinates():
              if value in coordinates:
                  identity = coordinates[value].identity(strict=strict)
                  ckeys.append(self.coordinate(identity, key=True, default=None))
  
          ref.clear_coordinates()
          ref.set_coordinates(ckeys)
  
          coordinate_conversion = coordinate_reference.coordinate_conversion
  
          dakeys = {}
          for term, value in coordinate_conversion.domain_ancillaries().items():
              if value in domain_ancillaries:
                  identity = domain_ancillaries[value].identity(strict=strict)
                  dakeys[term] = self.domain_ancillary(
                      identity, key=True, default=None
                  )
              else:
                  dakeys[term] = None
  
          ref.coordinate_conversion.clear_domain_ancillaries()
          ref.coordinate_conversion.set_domain_ancillaries(dakeys)
  
          return self.set_construct(ref, key=key, copy=False)
  
      # ----------------------------------------------------------------
      # Aliases
      # ----------------------------------------------------------------
      def aux(
          self,
          *identity,
          key=False,
          default=ValueError(),
          item=False,
          **filter_kwargs,
      ):
          """Alias for `auxiliary_coordinate`."""
          return self.auxiliary_coordinate(
              *identity, key=key, default=default, item=item, **filter_kwargs
          )
  
      def auxs(self, *identities, **filter_kwargs):
          """Alias for `coordinates`."""
          return self.auxiliary_coordinates(*identities, **filter_kwargs)
  
      def axes(self, *identities, **filter_kwargs):
          """Alias for `domain_axes`."""
          return self.domain_axes(*identities, **filter_kwargs)
  
      def axis(
          self,
          *identity,
          key=False,
          default=ValueError(),
          item=False,
          **filter_kwargs,
      ):
          """Alias for `domain_axis`."""
          return self.domain_axis(
              *identity, key=key, default=default, item=item, **filter_kwargs
          )
  
      def coord(
          self,
          *identity,
          key=False,
          default=ValueError(),
          item=False,
          **filter_kwargs,
      ):
          """Alias for `coordinate`."""
          return self.coordinate(
              *identity, key=key, default=default, item=item, **filter_kwargs
          )
  
      def coords(self, *identities, **filter_kwargs):
          """Alias for `coordinates`."""
          return self.coordinates(*identities, **filter_kwargs)
  
      def dim(
          self,
          *identity,
          key=False,
          default=ValueError(),
          item=False,
          **filter_kwargs,
      ):
          """Alias for `dimension_coordinate`."""
          return self.dimension_coordinate(
              *identity, key=key, default=default, item=item, **filter_kwargs
          )
  
      def dims(self, *identities, **filter_kwargs):
          """Alias for `dimension_coordinates`."""
          return self.dimension_coordinates(*identities, **filter_kwargs)
  
      def domain_anc(
          self,
          *identity,
          key=False,
          default=ValueError(),
          item=False,
          **filter_kwargs,
      ):
          """Alias for `domain_ancillary`."""
          return self.domain_ancillary(
              *identity, key=key, default=default, item=item, **filter_kwargs
          )
  
      def domain_ancs(self, *identities, **filter_kwargs):
          """Alias for `domain_ancillaries`."""
          return self.domain_ancillaries(*identities, **filter_kwargs)
  
      def key(self, *identity, default=ValueError(), **filter_kwargs):
          """Alias for `construct_key`."""
          return self.construct(
              *identity, default=default, key=True, **filter_kwargs
          )
  
      def measure(
          self,
          *identity,
          key=False,
          default=ValueError(),
          item=False,
          **filter_kwargs,
      ):
          """Alias for `cell_measure`."""
          return self.cell_measure(
              *identity,
              key=key,
              default=default,
              item=item,
              **filter_kwargs,
          )
  
      def measures(self, *identities, **filter_kwargs):
          """Alias for `cell_measures`."""
          return self.cell_measures(*identities, **filter_kwargs)
  
      def ref(
          self,
          *identity,
          default=ValueError(),
          key=False,
          item=False,
          **filter_kwargs,
      ):
          """Alias for `coordinate_reference`."""
          return self.coordinate_reference(
              *identity,
              key=key,
              default=default,
              item=item,
              **filter_kwargs,
          )
  
      def refs(self, *identities, **filter_kwargs):
          """Alias for `coordinate_references`."""
          return self.coordinate_references(*identities, **filter_kwargs)
  
  
  def _create_auxiliary_mask_component(mask_shape, ind, compress):
      """Create an auxiliary mask component.
  
      .. versionadded:: 3.9.0
  
      :Parameters:
  
          mask_shape: `tuple`
              The shape of the mask component to be created.
  
                *Parameter example*
                  ``mask_shape=(3,)``
  
                *Parameter example*
                  ``mask_shape=(9, 10)``
  
          ind: sequnce of `list`
              As returned by a single argument call of
              ``np[.ma].where(....)``.
  
          compress: `bool`
              If True then remove whole slices which only contain masked
              points.
  
      :Returns:
  
          `Data`
              The mask array.
  
      **Examples:**
  
      >>> f = cf.{{class}}()
      >>> d = _create_auxiliary_mask_component(
      ...     (4,), ([0, 3, 1],)
      ... )
      >>> print(d.array)
      [False False  True False]
      >>> d = f._create_auxiliary_mask_component(
      ...     (4, 6), ([0, 3, 1], [5, 3, 2])
      ... )
      >>> print(d.array)
      [[ True  True  True  True  True False]
       [ True  True False  True  True  True]
       [ True  True  True  True  True  True]
       [ True  True  True False  True  True]]
  
      """
      # Note that, for now, auxiliary_mask has to be numpy array (rather
      # than a cf.Data object) because we're going to index it with
      # fancy indexing which a cf.Data object might not support - namely
      # a non-monotonic list of integers.
      auxiliary_mask = np.ones(mask_shape, dtype=bool)
  
      auxiliary_mask[tuple(ind)] = False
  
      # For compressed indices, remove slices which only contain masked
      # points.
      if compress:
          for i, (index, n) in enumerate(zip(ind, mask_shape)):
              index = set(index)
              if len(index) == n:
                  continue
  
              auxiliary_mask = auxiliary_mask.take(sorted(index), axis=i)
  
      return Data(auxiliary_mask)
  