return self._data.get_index(dim)  # type: ignore         else:             indexes = [self._data.get_index(k) for k in ordered_dims]  # type: ignore            names = list(ordered_dims)            return pd.MultiIndex.from_product(indexes, names=names)            # compute the sizes of the repeat and tile for the cartesian product            # (taken from pandas.core.reshape.util)            index_lengths = np.fromiter(                (len(index) for index in indexes), dtype=np.intp            )            cumprod_lengths = np.cumproduct(index_lengths)            if cumprod_lengths[-1] != 0:                # sizes of the repeats                repeat_counts = cumprod_lengths[-1] / cumprod_lengths            else:                # if any factor is empty, the cartesian product is empty                repeat_counts = np.zeros_like(cumprod_lengths)            # sizes of the tiles            tile_counts = np.roll(cumprod_lengths, 1)            tile_counts[0] = 1            # loop over the indexes            # for each MultiIndex or Index compute the cartesian product of the codes            code_list = []            level_list = []            names = []            for i, index in enumerate(indexes):                if isinstance(index, pd.MultiIndex):                    codes, levels = index.codes, index.levels                else:                    code, level = pd.factorize(index)                    codes = [code]                    levels = [level]                # compute the cartesian product                code_list += [                    np.tile(np.repeat(code, repeat_counts[i]), tile_counts[i])                    for code in codes                ]                level_list += levels                names += index.names            return pd.MultiIndex(level_list, code_list, names=names)      def update(self, other: Mapping[Hashable, Any]) -> None:         other_vars = getattr(other, "variables", other)


if isinstance(obj, Variable):         obj = obj.copy(deep=False)     elif isinstance(obj, tuple):<ins>        if isinstance(obj[1], DataArray):            # TODO: change into TypeError            warnings.warn(                (                    "Using a DataArray object to construct a variable is"                    " ambiguous, please extract the data using the .data property."                    " This will raise a TypeError in 0.19.0."                ),                DeprecationWarning,            )</ins>         try:             obj = Variable(*obj)         except (TypeError, ValueError) as error:


if combine_attrs == "drop":         return {}     elif combine_attrs == "override":        return variable_attrs[0]        return dict(variable_attrs[0])     elif combine_attrs == "no_conflicts":         result = dict(variable_attrs[0])         for attrs in variable_attrs[1:]:


"matplotlib": ("https://matplotlib.org", None),     "dask": ("https://docs.dask.org/en/latest", None),     "cftime": ("https://unidata.github.io/cftime", None),    "sparse": ("https://sparse.pydata.org/en/latest/", None), }  diff --git a/xarray/core/common.py b/xarray/core/common.py-- a/xarray/core/common.py++ b/xarray/core/common.py


dask="allowed",         )     def astype(self, dtype, casting="unsafe", copy=True, keep_attrs=True):    def astype(        self: T,        dtype,        *,        order=None,        casting=None,        subok=None,        copy=None,        keep_attrs=True,    ) -> T:         """         Copy of the xarray object, with data cast to a specified type.         Leaves coordinate dtype unchanged.


----------         dtype : str or dtype             Typecode or data-type to which the array is cast.        order : {'C', 'F', 'A', 'K'}, optional            Controls the memory layout order of the result. ‘C’ means C order,            ‘F’ means Fortran order, ‘A’ means ‘F’ order if all the arrays are            Fortran contiguous, ‘C’ order otherwise, and ‘K’ means as close to            the order the array elements appear in memory as possible.         casting : {'no', 'equiv', 'safe', 'same_kind', 'unsafe'}, optional            Controls what kind of data casting may occur. Defaults to 'unsafe'            for backwards compatibility.            Controls what kind of data casting may occur.              * 'no' means the data types should not be cast at all.             * 'equiv' means only byte-order changes are allowed.             * 'safe' means only casts which can preserve values are allowed.             * 'same_kind' means only safe casts or casts within a kind,                like float64 to float32, are allowed.              like float64 to float32, are allowed.             * 'unsafe' means any data conversions may be done.        subok : bool, optional            If True, then sub-classes will be passed-through, otherwise the            returned array will be forced to be a base-class array.         copy : bool, optional             By default, astype always returns a newly allocated array. If this             is set to False and the `dtype` requirement is satisfied, the input


out : same as object             New object with data cast to the specified type.         Notes        -----        The ``order``, ``casting``, ``subok`` and ``copy`` arguments are only passed        through to the ``astype`` method of the underlying array when a value        different than ``None`` is supplied.        Make sure to only supply these arguments if the underlying array class        supports them.         See also         --------        np.ndarray.astype        numpy.ndarray.astype         dask.array.Array.astype        sparse.COO.astype         """         from .computation import apply_ufunc         kwargs = dict(order=order, casting=casting, subok=subok, copy=copy)        kwargs = {k: v for k, v in kwargs.items() if v is not None}         return apply_ufunc(             duck_array_ops.astype,             self,            kwargs=dict(dtype=dtype, casting=casting, copy=copy),            dtype,            kwargs=kwargs,             keep_attrs=keep_attrs,             dask="allowed",         )diff --git a/xarray/core/duck_array_ops.py b/xarray/core/duck_array_ops.py-- a/xarray/core/duck_array_ops.py++ b/xarray/core/duck_array_ops.py


)  def astype(data, **kwargs):def astype(data, dtype, **kwargs):     try:         import sparse     except ImportError:


)         kwargs.pop("casting")     return data.astype(**kwargs)    return data.astype(dtype, **kwargs)   def asarray(data, xp=np):diff --git a/xarray/core/variable.py b/xarray/core/variable.py-- a/xarray/core/variable.py++ b/xarray/core/variable.py


)         self._data = data     def astype(self, dtype, casting="unsafe", copy=True, keep_attrs=True):    def astype(        self: VariableType,        dtype,        *,        order=None,        casting=None,        subok=None,        copy=None,        keep_attrs=True,    ) -> VariableType:         """         Copy of the Variable object, with data cast to a specified type.          Parameters         ----------         dtype : str or dtype             Typecode or data-type to which the array is cast.            Typecode or data-type to which the array is cast.        order : {'C', 'F', 'A', 'K'}, optional            Controls the memory layout order of the result. ‘C’ means C order,            ‘F’ means Fortran order, ‘A’ means ‘F’ order if all the arrays are            Fortran contiguous, ‘C’ order otherwise, and ‘K’ means as close to            the order the array elements appear in memory as possible.         casting : {'no', 'equiv', 'safe', 'same_kind', 'unsafe'}, optional             Controls what kind of data casting may occur. Defaults to 'unsafe'             for backwards compatibility.             * 'no' means the data types should not be cast at all.             * 'equiv' means only byte-order changes are allowed.             * 'safe' means only casts which can preserve values are allowed.             * 'same_kind' means only safe casts or casts within a kind,                 like float64 to float32, are allowed.             * 'unsafe' means any data conversions may be done.            Controls what kind of data casting may occur.            * 'no' means the data types should not be cast at all.            * 'equiv' means only byte-order changes are allowed.            * 'safe' means only casts which can preserve values are allowed.            * 'same_kind' means only safe casts or casts within a kind,              like float64 to float32, are allowed.            * 'unsafe' means any data conversions may be done.        subok : bool, optional            If True, then sub-classes will be passed-through, otherwise the            returned array will be forced to be a base-class array.         copy : bool, optional             By default, astype always returns a newly allocated array. If this             is set to False and the `dtype` requirement is satisfied, the input             array is returned instead of a copy.            By default, astype always returns a newly allocated array. If this            is set to False and the `dtype` requirement is satisfied, the input            array is returned instead of a copy.         keep_attrs : bool, optional             By default, astype keeps attributes. Set to False to remove             attributes in the returned object.


out : same as object             New object with data cast to the specified type.         Notes        -----        The ``order``, ``casting``, ``subok`` and ``copy`` arguments are only passed        through to the ``astype`` method of the underlying array when a value        different than ``None`` is supplied.        Make sure to only supply these arguments if the underlying array class        supports them.         See also         --------        np.ndarray.astype        numpy.ndarray.astype         dask.array.Array.astype        sparse.COO.astype         """         from .computation import apply_ufunc         kwargs = dict(order=order, casting=casting, subok=subok, copy=copy)        kwargs = {k: v for k, v in kwargs.items() if v is not None}         return apply_ufunc(             duck_array_ops.astype,             self,            kwargs=dict(dtype=dtype, casting=casting, copy=copy),            dtype,            kwargs=kwargs,             keep_attrs=keep_attrs,             dask="allowed",         )


_STANDARD_CALENDARS = {"standard", "gregorian", "proleptic_gregorian"}  _NS_PER_TIME_DELTA = {<ins>    "ns": 1,</ins>     "us": int(1e3),     "ms": int(1e6),     "s": int(1e9),


}  TIME_UNITS = frozenset(    ["days", "hours", "minutes", "seconds", "milliseconds", "microseconds"]    [        "days",        "hours",        "minutes",        "seconds",        "milliseconds",        "microseconds",        "nanoseconds",    ] )


if not units.endswith("s"):         units = "%ss" % units     return {<ins>        "nanoseconds": "ns",</ins>         "microseconds": "us",         "milliseconds": "ms",         "seconds": "s",


# strings, in which case we fall back to using cftime         raise OutOfBoundsDatetime     # fixes: https://github.com/pydata/pandas/issues/14068    # these lines check if the the lowest or the highest value in dates    # cause an OutOfBoundsDatetime (Overflow) error    with warnings.catch_warnings():        warnings.filterwarnings("ignore", "invalid value encountered", RuntimeWarning)        pd.to_timedelta(flat_num_dates.min(), delta) + ref_date        pd.to_timedelta(flat_num_dates.max(), delta) + ref_date    # Cast input dates to integers of nanoseconds because `pd.to_datetime`    # works much faster when dealing with integers    # make _NS_PER_TIME_DELTA an array to ensure type upcasting    flat_num_dates_ns_int = (        flat_num_dates.astype(np.float64) * _NS_PER_TIME_DELTA[delta]    ).astype(np.int64)    # To avoid integer overflow when converting to nanosecond units for integer    # dtypes smaller than np.int64 cast all integer-dtype arrays to np.int64    # (GH 2002).    if flat_num_dates.dtype.kind == "i":        flat_num_dates = flat_num_dates.astype(np.int64)     # Cast input ordinals to integers of nanoseconds because pd.to_timedelta    # works much faster when dealing with integers (GH 1399).    flat_num_dates_ns_int = (flat_num_dates * _NS_PER_TIME_DELTA[delta]).astype(        np.int64    )    # Use pd.to_timedelta to safely cast integer values to timedeltas,    # and add those to a Timestamp to safely produce a DatetimeIndex.  This    # ensures that we do not encounter integer overflow at any point in the    # process without raising OutOfBoundsDatetime.     return (pd.to_timedelta(flat_num_dates_ns_int, "ns") + ref_date).values


def _infer_time_units_from_diff(unique_timedeltas):    for time_unit in ["days", "hours", "minutes", "seconds"]:    # Note that the modulus operator was only implemented for np.timedelta64    # arrays as of NumPy version 1.16.0.  Once our minimum version of NumPy    # supported is greater than or equal to this we will no longer need to cast    # unique_timedeltas to a TimedeltaIndex.  In the meantime, however, the    # modulus operator works for TimedeltaIndex objects.    unique_deltas_as_index = pd.TimedeltaIndex(unique_timedeltas)    for time_unit in [        "days",        "hours",        "minutes",        "seconds",        "milliseconds",        "microseconds",        "nanoseconds",    ]:         delta_ns = _NS_PER_TIME_DELTA[_netcdf_to_numpy_timeunit(time_unit)]         unit_delta = np.timedelta64(delta_ns, "ns")        diffs = unique_timedeltas / unit_delta        if np.all(diffs == diffs.astype(int)):        if np.all(unique_deltas_as_index % unit_delta == np.timedelta64(0, "ns")):             return time_unit     return "seconds"


# Wrap the dates in a DatetimeIndex to do the subtraction to ensure         # an OverflowError is raised if the ref_date is too far away from         # dates to be encoded (GH 2272).        num = (pd.DatetimeIndex(dates.ravel()) - ref_date) / time_delta        dates_as_index = pd.DatetimeIndex(dates.ravel())        time_deltas = dates_as_index - ref_date        # Use floor division if time_delta evenly divides all differences        # to preserve integer dtype if possible (GH 4045).        if np.all(time_deltas % time_delta == np.timedelta64(0, "ns")):            num = time_deltas // time_delta        else:            num = time_deltas / time_delta         num = num.values.reshape(dates.shape)      except (OutOfBoundsDatetime, OverflowError):


return result.transpose(*all_dims, missing_dims="ignore")  def where(cond, x, y):def where(cond, x, y, keep_attrs=None):     """Return elements from `x` or `y` depending on `cond`.      Performs xarray-like broadcasting across input arguments.


values to choose from where `cond` is True     y : scalar, array, Variable, DataArray or Dataset         values to choose from where `cond` is False<ins>    keep_attrs : bool or str or callable, optional        How to treat attrs. If True, keep the attrs of `x`.</ins>      Returns     -------


Dataset.where, DataArray.where :         equivalent methods     """<ins>    if keep_attrs is None:        keep_attrs = _get_keep_attrs(default=False)    if keep_attrs is True:        # keep the attributes of x, the second parameter, by default to        # be consistent with the `where` method of `DataArray` and `Dataset`        keep_attrs = lambda attrs, context: attrs[1]</ins>     # alignment for three arguments is complicated, so don't support it yet     return apply_ufunc(         duck_array_ops.where,


join="exact",         dataset_join="exact",         dask="allowed",<ins>        keep_attrs=keep_attrs,</ins>     )


# expand the indexer so we can handle Ellipsis             labels = indexing.expanded_indexer(key, self.data_array.ndim)             key = dict(zip(self.data_array.dims, labels))        return self.data_array.sel(**key)        return self.data_array.sel(key)      def __setitem__(self, key, value) -> None:         if not utils.is_dict_like(key):


return col_width  def _mapping_repr(mapping, title, summarizer, col_width=None):def _mapping_repr(mapping, title, summarizer, col_width=None, max_rows=None):     if col_width is None:         col_width = _calculate_col_width(mapping)    if max_rows is None:        max_rows = OPTIONS["display_max_rows"]     summary = [f"{title}:"]     if mapping:        summary += [summarizer(k, v, col_width) for k, v in mapping.items()]        if len(mapping) > max_rows:            first_rows = max_rows // 2 + max_rows % 2            items = list(mapping.items())            summary += [summarizer(k, v, col_width) for k, v in items[:first_rows]]            if max_rows > 1:                last_rows = max_rows // 2                summary += [pretty_print("    ...", col_width) + " ..."]                summary += [summarizer(k, v, col_width) for k, v in items[-last_rows:]]        else:            summary += [summarizer(k, v, col_width) for k, v in mapping.items()]     else:         summary += [EMPTY_REPR]     return "\n".join(summary)diff --git a/xarray/core/options.py b/xarray/core/options.py-- a/xarray/core/options.py++ b/xarray/core/options.py


import warnings DISPLAY_WIDTH = "display_width" ARITHMETIC_JOIN = "arithmetic_join"CMAP_DIVERGENT = "cmap_divergent"CMAP_SEQUENTIAL = "cmap_sequential"DISPLAY_MAX_ROWS = "display_max_rows"DISPLAY_STYLE = "display_style"DISPLAY_WIDTH = "display_width" ENABLE_CFTIMEINDEX = "enable_cftimeindex" FILE_CACHE_MAXSIZE = "file_cache_maxsize"WARN_FOR_UNCLOSED_FILES = "warn_for_unclosed_files"CMAP_SEQUENTIAL = "cmap_sequential"CMAP_DIVERGENT = "cmap_divergent" KEEP_ATTRS = "keep_attrs"DISPLAY_STYLE = "display_style"WARN_FOR_UNCLOSED_FILES = "warn_for_unclosed_files"   OPTIONS = {    DISPLAY_WIDTH: 80,     ARITHMETIC_JOIN: "inner",    CMAP_DIVERGENT: "RdBu_r",    CMAP_SEQUENTIAL: "viridis",    DISPLAY_MAX_ROWS: 12,    DISPLAY_STYLE: "html",    DISPLAY_WIDTH: 80,     ENABLE_CFTIMEINDEX: True,     FILE_CACHE_MAXSIZE: 128,    WARN_FOR_UNCLOSED_FILES: False,    CMAP_SEQUENTIAL: "viridis",    CMAP_DIVERGENT: "RdBu_r",     KEEP_ATTRS: "default",    DISPLAY_STYLE: "html",    WARN_FOR_UNCLOSED_FILES: False, }  _JOIN_OPTIONS = frozenset(["inner", "outer", "left", "right", "exact"])


_VALIDATORS = {    DISPLAY_WIDTH: _positive_integer,     ARITHMETIC_JOIN: _JOIN_OPTIONS.__contains__,    DISPLAY_MAX_ROWS: _positive_integer,    DISPLAY_STYLE: _DISPLAY_OPTIONS.__contains__,    DISPLAY_WIDTH: _positive_integer,     ENABLE_CFTIMEINDEX: lambda value: isinstance(value, bool),     FILE_CACHE_MAXSIZE: _positive_integer,    WARN_FOR_UNCLOSED_FILES: lambda value: isinstance(value, bool),     KEEP_ATTRS: lambda choice: choice in [True, False, "default"],    DISPLAY_STYLE: _DISPLAY_OPTIONS.__contains__,    WARN_FOR_UNCLOSED_FILES: lambda value: isinstance(value, bool), }


_SETTERS = {    FILE_CACHE_MAXSIZE: _set_file_cache_maxsize,     ENABLE_CFTIMEINDEX: _warn_on_setting_enable_cftimeindex,    FILE_CACHE_MAXSIZE: _set_file_cache_maxsize, }


return other + self.as_timedelta()  <ins>class Millisecond(BaseCFTimeOffset):    _freq = "L"    def as_timedelta(self):        return timedelta(milliseconds=self.n)    def __apply__(self, other):        return other + self.as_timedelta()class Microsecond(BaseCFTimeOffset):    _freq = "U"    def as_timedelta(self):        return timedelta(microseconds=self.n)    def __apply__(self, other):        return other + self.as_timedelta()</ins> _FREQUENCIES = {     "A": YearEnd,     "AS": YearBegin,


"T": Minute,     "min": Minute,     "S": Second,<ins>    "L": Millisecond,    "ms": Millisecond,    "U": Microsecond,    "us": Microsecond,</ins>     "AS-JAN": partial(YearBegin, month=1),     "AS-FEB": partial(YearBegin, month=2),     "AS-MAR": partial(YearBegin, month=3),


`ISO-8601 format <https://en.wikipedia.org/wiki/ISO_8601>`_.     - It supports many, but not all, frequencies supported by       ``pandas.date_range``.  For example it does not currently support any of      the business-related, semi-monthly, or sub-second frequencies.      the business-related or semi-monthly frequencies.     - Compound sub-monthly frequencies are not supported, e.g. '1H1min', as       these can easily be written in terms of the finest common resolution,       e.g. '61min'.


+--------+--------------------------+     | S      | Second frequency         |     +--------+--------------------------+    | L, ms  | Millisecond frequency    |    +--------+--------------------------+    | U, us  | Microsecond frequency    |    +--------+--------------------------+      Any multiples of the following anchored offsets are also supported. diff --git a/xarray/coding/times.py b/xarray/coding/times.py-- a/xarray/coding/times.py++ b/xarray/coding/times.py


import re import warningsfrom datetime import datetimefrom datetime import datetime, timedelta from distutils.version import LooseVersion from functools import partial


"D": int(1e9) * 60 * 60 * 24, } <ins>_US_PER_TIME_DELTA = {    "microseconds": 1,    "milliseconds": 1_000,    "seconds": 1_000_000,    "minutes": 60 * 1_000_000,    "hours": 60 * 60 * 1_000_000,    "days": 24 * 60 * 60 * 1_000_000,}_NETCDF_TIME_UNITS_CFTIME = [    "days",    "hours",    "minutes",    "seconds",    "milliseconds",    "microseconds",]_NETCDF_TIME_UNITS_NUMPY = _NETCDF_TIME_UNITS_CFTIME + ["nanoseconds"]</ins> TIME_UNITS = frozenset(     [         "days",


if calendar in _STANDARD_CALENDARS:                     dates = cftime_to_nptime(dates)     elif use_cftime:        dates = _decode_datetime_with_cftime(            flat_num_dates.astype(float), units, calendar        )        dates = _decode_datetime_with_cftime(flat_num_dates, units, calendar)     else:         dates = _decode_datetime_with_pandas(flat_num_dates, units, calendar)


return result.reshape(num_timedeltas.shape)  def _unit_timedelta_cftime(units):    return timedelta(microseconds=_US_PER_TIME_DELTA[units])def _unit_timedelta_numpy(units):    numpy_units = _netcdf_to_numpy_timeunit(units)    return np.timedelta64(_NS_PER_TIME_DELTA[numpy_units], "ns") def _infer_time_units_from_diff(unique_timedeltas):    # Note that the modulus operator was only implemented for np.timedelta64    # arrays as of NumPy version 1.16.0.  Once our minimum version of NumPy    # supported is greater than or equal to this we will no longer need to cast    # unique_timedeltas to a TimedeltaIndex.  In the meantime, however, the    # modulus operator works for TimedeltaIndex objects.    unique_deltas_as_index = pd.TimedeltaIndex(unique_timedeltas)    for time_unit in [        "days",        "hours",        "minutes",        "seconds",        "milliseconds",        "microseconds",        "nanoseconds",    ]:        delta_ns = _NS_PER_TIME_DELTA[_netcdf_to_numpy_timeunit(time_unit)]        unit_delta = np.timedelta64(delta_ns, "ns")        if np.all(unique_deltas_as_index % unit_delta == np.timedelta64(0, "ns")):    if unique_timedeltas.dtype == np.dtype("O"):        time_units = _NETCDF_TIME_UNITS_CFTIME        unit_timedelta = _unit_timedelta_cftime        zero_timedelta = timedelta(microseconds=0)        timedeltas = unique_timedeltas    else:        time_units = _NETCDF_TIME_UNITS_NUMPY        unit_timedelta = _unit_timedelta_numpy        zero_timedelta = np.timedelta64(0, "ns")        # Note that the modulus operator was only implemented for np.timedelta64        # arrays as of NumPy version 1.16.0.  Once our minimum version of NumPy        # supported is greater than or equal to this we will no longer need to cast        # unique_timedeltas to a TimedeltaIndex.  In the meantime, however, the        # modulus operator works for TimedeltaIndex objects.        timedeltas = pd.TimedeltaIndex(unique_timedeltas)    for time_unit in time_units:        if np.all(timedeltas % unit_timedelta(time_unit) == zero_timedelta):             return time_unit     return "seconds"


reference_date = dates[0] if len(dates) > 0 else "1970-01-01"         reference_date = format_cftime_datetime(reference_date)     unique_timedeltas = np.unique(np.diff(dates))<del>    if unique_timedeltas.dtype == np.dtype("O"):        # Convert to np.timedelta64 objects using pandas to work around a        # NumPy casting bug: https://github.com/numpy/numpy/issues/11096        unique_timedeltas = to_timedelta_unboxed(unique_timedeltas)</del>     units = _infer_time_units_from_diff(unique_timedeltas)     return f"{units} since {reference_date}"


# unstacked dataset         return Dataset(data_dict)     def transpose(self, *dims: Hashable, transpose_coords: bool = True) -> "DataArray":    def transpose(        self,        *dims: Hashable,        transpose_coords: bool = True,        missing_dims: str = "raise",    ) -> "DataArray":         """Return a new DataArray object with transposed dimensions.          Parameters


dimensions to this order.         transpose_coords : bool, default: True             If True, also transpose the coordinates of this DataArray.<ins>        missing_dims : {"raise", "warn", "ignore"}, default: "raise"            What to do if dimensions that should be selected from are not present in the            DataArray:            - "raise": raise an exception            - "warning": raise a warning, and ignore the missing dimensions            - "ignore": ignore the missing dimensions</ins>          Returns         -------


Dataset.transpose         """         if dims:            dims = tuple(utils.infix_dims(dims, self.dims))            dims = tuple(utils.infix_dims(dims, self.dims, missing_dims))         variable = self.variable.transpose(*dims)         if transpose_coords:             coords: Dict[Hashable, Variable] = {}diff --git a/xarray/core/utils.py b/xarray/core/utils.py-- a/xarray/core/utils.py++ b/xarray/core/utils.py


return len(self._data) - num_hidden  def infix_dims(dims_supplied: Collection, dims_all: Collection) -> Iterator:def infix_dims(    dims_supplied: Collection, dims_all: Collection, missing_dims: str = "raise") -> Iterator:     """    Resolves a supplied list containing an ellispsis representing other items, to    Resolves a supplied list containing an ellipsis representing other items, to     a generator with the 'realized' list of all items     """     if ... in dims_supplied:         if len(set(dims_all)) != len(dims_all):             raise ValueError("Cannot use ellipsis with repeated dims")        if len([d for d in dims_supplied if d == ...]) > 1:        if list(dims_supplied).count(...) > 1:             raise ValueError("More than one ellipsis supplied")         other_dims = [d for d in dims_all if d not in dims_supplied]        for d in dims_supplied:            if d == ...:        existing_dims = drop_missing_dims(dims_supplied, dims_all, missing_dims)        for d in existing_dims:            if d is ...:                 yield from other_dims             else:                 yield d     else:        if set(dims_supplied) ^ set(dims_all):        existing_dims = drop_missing_dims(dims_supplied, dims_all, missing_dims)        if set(existing_dims) ^ set(dims_all):             raise ValueError(                 f"{dims_supplied} must be a permuted list of {dims_all}, unless `...` is included"             )        yield from dims_supplied        yield from existing_dims   def get_temp_dimname(dims: Container[Hashable], new_dim: Hashable) -> Hashable:


invalid = indexers.keys() - set(dims)         if invalid:             raise ValueError(                f"dimensions {invalid} do not exist. Expected one or more of {dims}"                f"Dimensions {invalid} do not exist. Expected one or more of {dims}"             )          return indexers


invalid = indexers.keys() - set(dims)         if invalid:             warnings.warn(                f"dimensions {invalid} do not exist. Expected one or more of {dims}"                f"Dimensions {invalid} do not exist. Expected one or more of {dims}"             )         for key in invalid:             indexers.pop(key)


)  <ins>def drop_missing_dims(    supplied_dims: Collection, dims: Collection, missing_dims: str) -> Collection:    """Depending on the setting of missing_dims, drop any dimensions from supplied_dims that    are not present in dims.    Parameters    ----------    supplied_dims : dict    dims : sequence    missing_dims : {"raise", "warn", "ignore"}    """    if missing_dims == "raise":        supplied_dims_set = set(val for val in supplied_dims if val is not ...)        invalid = supplied_dims_set - set(dims)        if invalid:            raise ValueError(                f"Dimensions {invalid} do not exist. Expected one or more of {dims}"            )        return supplied_dims    elif missing_dims == "warn":        invalid = set(supplied_dims) - set(dims)        if invalid:            warnings.warn(                f"Dimensions {invalid} do not exist. Expected one or more of {dims}"            )        return [val for val in supplied_dims if val in dims or val is ...]    elif missing_dims == "ignore":        return [val for val in supplied_dims if val in dims or val is ...]    else:        raise ValueError(            f"Unrecognised option {missing_dims} for missing_dims argument"        )</ins> class UncachedAccessor:     """Acts like a property, but on both classes and class instances


add_offset = pop_to(attrs, encoding, "add_offset", name=name)             dtype = _choose_float_dtype(data.dtype, "add_offset" in attrs)             if np.ndim(scale_factor) > 0:                scale_factor = scale_factor.item()                scale_factor = np.asarray(scale_factor).item()             if np.ndim(add_offset) > 0:                add_offset = add_offset.item()                add_offset = np.asarray(add_offset).item()             transform = partial(                 _scale_offset_decoding,                 scale_factor=scale_factor,


ds = self._to_temp_dataset().drop_sel(labels, errors=errors)         return self._from_temp_dataset(ds)     def drop_isel(self, indexers=None, **indexers_kwargs):        """Drop index positions from this DataArray.        Parameters        ----------        indexers : mapping of hashable to Any            Index locations to drop        **indexers_kwargs : {dim: position, ...}, optional            The keyword arguments form of ``dim`` and ``positions``        Returns        -------        dropped : DataArray        Raises        ------        IndexError        """        dataset = self._to_temp_dataset()        dataset = dataset.drop_isel(indexers=indexers, **indexers_kwargs)        return self._from_temp_dataset(dataset)     def dropna(         self, dim: Hashable, how: str = "any", thresh: int = None     ) -> "DataArray":diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py-- a/xarray/core/dataset.py++ b/xarray/core/dataset.py


labels_for_dim = [labels_for_dim]             labels_for_dim = np.asarray(labels_for_dim)             try:                index = self.indexes[dim]                index = self.get_index(dim)             except KeyError:                 raise ValueError("dimension %r does not have coordinate labels" % dim)             new_index = index.drop(labels_for_dim, errors=errors)             ds = ds.loc[{dim: new_index}]         return ds     def drop_isel(self, indexers=None, **indexers_kwargs):        """Drop index positions from this Dataset.        Parameters        ----------        indexers : mapping of hashable to Any            Index locations to drop        **indexers_kwargs : {dim: position, ...}, optional            The keyword arguments form of ``dim`` and ``positions``        Returns        -------        dropped : Dataset        Raises        ------        IndexError        Examples        --------        >>> data = np.arange(6).reshape(2, 3)        >>> labels = ["a", "b", "c"]        >>> ds = xr.Dataset({"A": (["x", "y"], data), "y": labels})        >>> ds        <xarray.Dataset>        Dimensions:  (x: 2, y: 3)        Coordinates:          * y        (y) <U1 'a' 'b' 'c'        Dimensions without coordinates: x        Data variables:            A        (x, y) int64 0 1 2 3 4 5        >>> ds.drop_isel(y=[0, 2])        <xarray.Dataset>        Dimensions:  (x: 2, y: 1)        Coordinates:          * y        (y) <U1 'b'        Dimensions without coordinates: x        Data variables:            A        (x, y) int64 1 4        >>> ds.drop_isel(y=1)        <xarray.Dataset>        Dimensions:  (x: 2, y: 2)        Coordinates:          * y        (y) <U1 'a' 'c'        Dimensions without coordinates: x        Data variables:            A        (x, y) int64 0 2 3 5        """        indexers = either_dict_or_kwargs(indexers, indexers_kwargs, "drop")        ds = self        dimension_index = {}        for dim, pos_for_dim in indexers.items():            # Don't cast to set, as it would harm performance when labels            # is a large numpy array            if utils.is_scalar(pos_for_dim):                pos_for_dim = [pos_for_dim]            pos_for_dim = np.asarray(pos_for_dim)            index = self.get_index(dim)            new_index = index.delete(pos_for_dim)            dimension_index[dim] = new_index        ds = ds.loc[dimension_index]        return ds     def drop_dims(         self, drop_dims: Union[Hashable, Iterable[Hashable]], *, errors: str = "raise"     ) -> "Dataset":


- "override": if indexes are of same size, rewrite indexes to be           those of the first object with that dimension. Indexes for the same           dimension must have the same size in all objects.    combine_attrs : {"drop", "identical", "no_conflicts", "override"}, \                    default: "drop"    combine_attrs : {"drop", "identical", "no_conflicts", "drop_conflicts", \                     "override"}, default: "drop"         String indicating how to combine attrs of the objects being merged:          - "drop": empty attrs on returned Dataset.         - "identical": all attrs must be the same on every object.         - "no_conflicts": attrs from all objects are combined, any that have           the same name must also have the same value.        - "drop_conflicts": attrs from all objects are combined, any that have          the same name but different values are dropped.         - "override": skip comparing and copy attrs from the first dataset to           the result.


- "override": if indexes are of same size, rewrite indexes to be           those of the first object with that dimension. Indexes for the same           dimension must have the same size in all objects.    combine_attrs : {"drop", "identical", "no_conflicts", "override"}, \                    default: "drop"    combine_attrs : {"drop", "identical", "no_conflicts", "drop_conflicts", \                     "override"}, default: "drop"         String indicating how to combine attrs of the objects being merged:          - "drop": empty attrs on returned Dataset.         - "identical": all attrs must be the same on every object.         - "no_conflicts": attrs from all objects are combined, any that have           the same name must also have the same value.        - "drop_conflicts": attrs from all objects are combined, any that have          the same name but different values are dropped.         - "override": skip comparing and copy attrs from the first dataset to           the result. diff --git a/xarray/core/concat.py b/xarray/core/concat.py-- a/xarray/core/concat.py++ b/xarray/core/concat.py


- "override": if indexes are of same size, rewrite indexes to be           those of the first object with that dimension. Indexes for the same           dimension must have the same size in all objects.    combine_attrs : {"drop", "identical", "no_conflicts", "override"}, \                    default: "override"    combine_attrs : {"drop", "identical", "no_conflicts", "drop_conflicts", \                     "override"}, default: "override"         String indicating how to combine attrs of the objects being merged:          - "drop": empty attrs on returned Dataset.         - "identical": all attrs must be the same on every object.         - "no_conflicts": attrs from all objects are combined, any that have           the same name must also have the same value.        - "drop_conflicts": attrs from all objects are combined, any that have          the same name but different values are dropped.         - "override": skip comparing and copy attrs from the first dataset to           the result. diff --git a/xarray/core/merge.py b/xarray/core/merge.py-- a/xarray/core/merge.py++ b/xarray/core/merge.py


from . import dtypes, pdcompat from .alignment import deep_align from .duck_array_ops import lazy_array_equivfrom .utils import Frozen, compat_dict_union, dict_equivfrom .utils import Frozen, compat_dict_union, dict_equiv, equivalent from .variable import Variable, as_variable, assert_unique_multiindex_level_names  if TYPE_CHECKING:


"the same. Merging %s with %s" % (str(result), str(attrs))                 )         return result<ins>    elif combine_attrs == "drop_conflicts":        result = {}        dropped_keys = set()        for attrs in variable_attrs:            result.update(                {                    key: value                    for key, value in attrs.items()                    if key not in result and key not in dropped_keys                }            )            result = {                key: value                for key, value in result.items()                if key not in attrs or equivalent(attrs[key], value)            }            dropped_keys |= {key for key in attrs if key not in result}        return result</ins>     elif combine_attrs == "identical":         result = dict(variable_attrs[0])         for attrs in variable_attrs[1:]:


Compatibility checks to use when merging variables.     join : {"outer", "inner", "left", "right"}, optional         How to combine objects with different indexes.    combine_attrs : {"drop", "identical", "no_conflicts", "override"}, optional    combine_attrs : {"drop", "identical", "no_conflicts", "drop_conflicts", \                     "override"}, optional         How to combine attributes of objects     priority_arg : int, optional         Optional argument in `objects` that takes precedence over the others.


Value to use for newly missing values. If a dict-like, maps         variable names to fill values. Use a data array's name to         refer to its values.    combine_attrs : {"drop", "identical", "no_conflicts", "override"}, \                    default: "drop"    combine_attrs : {"drop", "identical", "no_conflicts", "drop_conflicts", \                     "override"}, default: "drop"         String indicating how to combine attrs of the objects being merged:          - "drop": empty attrs on returned Dataset.         - "identical": all attrs must be the same on every object.         - "no_conflicts": attrs from all objects are combined, any that have           the same name must also have the same value.        - "drop_conflicts": attrs from all objects are combined, any that have          the same name but different values are dropped.         - "override": skip comparing and copy attrs from the first dataset to           the result.


import contextlib import io import threadingimport uuid import warningsfrom typing import Anyfrom typing import Any, Hashable  from ..core import utils from ..core.options import OPTIONS


from .lru_cache import LRUCache  # Global cache for storing open files.FILE_CACHE: LRUCache[str, io.IOBase] = LRUCache(FILE_CACHE: LRUCache[Any, io.IOBase] = LRUCache(     maxsize=OPTIONS["file_cache_maxsize"], on_evict=lambda k, v: v.close() ) assert FILE_CACHE.maxsize, "file cache must be at least size one"  REF_COUNTS: dict[Any, int] = {}  _DEFAULT_MODE = utils.ReprObject("<unused>")


kwargs=None,         lock=None,         cache=None,        manager_id: Hashable | None = None,         ref_counts=None,     ):        """Initialize a FileManager.        """Initialize a CachingFileManager.         The cache and ref_counts arguments exist solely to facilitate        dependency injection, and should only be set for tests.        The cache, manager_id and ref_counts arguments exist solely to        facilitate dependency injection, and should only be set for tests.          Parameters         ----------


global variable and contains non-picklable file objects, an             unpickled FileManager objects will be restored with the default             cache.<ins>        manager_id : hashable, optional            Identifier for this CachingFileManager.</ins>         ref_counts : dict, optional             Optional dict to use for keeping track the number of references to             the same file.


self._mode = mode         self._kwargs = {} if kwargs is None else dict(kwargs)         self._default_lock = lock is None or lock is False        self._lock = threading.Lock() if self._default_lock else lock        self._use_default_lock = lock is None or lock is False        self._lock = threading.Lock() if self._use_default_lock else lock          # cache[self._key] stores the file associated with this object.         if cache is None:             cache = FILE_CACHE         self._cache = cache        if manager_id is None:            # Each call to CachingFileManager should separately open files.            manager_id = str(uuid.uuid4())        self._manager_id = manager_id         self._key = self._make_key()          # ref_counts[self._key] stores the number of CachingFileManager objects


self._args,             "a" if self._mode == "w" else self._mode,             tuple(sorted(self._kwargs.items())),<ins>            self._manager_id,</ins>         )         return _HashedSequence(value)


if file is not None:                 file.close()     def __del__(self):        # If we're the only CachingFileManger referencing a unclosed file, we        # should remove it from the cache upon garbage collection.    def __del__(self) -> None:        # If we're the only CachingFileManger referencing a unclosed file,        # remove it from the cache upon garbage collection.         #        # Keeping our own count of file references might seem like overkill,        # but it's actually pretty common to reopen files with the same        # variable name in a notebook or command line environment, e.g., to        # fix the parameters used when opening a file:        #    >>> ds = xarray.open_dataset('myfile.nc')        #    >>> ds = xarray.open_dataset('myfile.nc', decode_times=False)        # This second assignment to "ds" drops CPython's ref-count on the first        # "ds" argument to zero, which can trigger garbage collections. So if        # we didn't check whether another object is referencing 'myfile.nc',        # the newly opened file would actually be immediately closed!        # We keep track of our own reference count because we don't want to        # close files if another identical file manager needs it. This can        # happen if a CachingFileManager is pickled and unpickled without        # closing the original file.         ref_count = self._ref_counter.decrement(self._key)          if not ref_count and self._key in self._cache:


if OPTIONS["warn_for_unclosed_files"]:                 warnings.warn(                    "deallocating {}, but file is not already closed. "                    "This may indicate a bug.".format(self),                    f"deallocating {self}, but file is not already closed. "                    "This may indicate a bug.",                     RuntimeWarning,                     stacklevel=2,                 )      def __getstate__(self):         """State for pickling."""        # cache and ref_counts are intentionally omitted: we don't want to try        # to serialize these global objects.        lock = None if self._default_lock else self._lock        return (self._opener, self._args, self._mode, self._kwargs, lock)        # cache is intentionally omitted: we don't want to try to serialize        # these global objects.        lock = None if self._use_default_lock else self._lock        return (            self._opener,            self._args,            self._mode,            self._kwargs,            lock,            self._manager_id,        )     def __setstate__(self, state):    def __setstate__(self, state) -> None:         """Restore from a pickle."""        opener, args, mode, kwargs, lock = state        self.__init__(opener, *args, mode=mode, kwargs=kwargs, lock=lock)        opener, args, mode, kwargs, lock, manager_id = state        self.__init__(  # type: ignore            opener, *args, mode=mode, kwargs=kwargs, lock=lock, manager_id=manager_id        )     def __repr__(self):    def __repr__(self) -> str:         args_string = ", ".join(map(repr, self._args))         if self._mode is not _DEFAULT_MODE:             args_string += f", mode={self._mode!r}"        return "{}({!r}, {}, kwargs={})".format(            type(self).__name__, self._opener, args_string, self._kwargs        return (            f"{type(self).__name__}({self._opener!r}, {args_string}, "            f"kwargs={self._kwargs}, manager_id={self._manager_id!r})"         )


return np.dtype(dtype), fill_value  NAT_TYPES = (np.datetime64("NaT"), np.timedelta64("NaT"))NAT_TYPES = {np.datetime64("NaT").dtype, np.timedelta64("NaT").dtype}   def get_fill_value(dtype):diff --git a/xarray/core/nanops.py b/xarray/core/nanops.py-- a/xarray/core/nanops.py++ b/xarray/core/nanops.py


import numpy as np  from . import dtypes, nputils, utilsfrom .duck_array_ops import _dask_or_eager_func, count, fillna, isnull, where_methodfrom .duck_array_ops import (    _dask_or_eager_func,    count,    fillna,    isnull,    where,    where_method,) from .pycompat import dask_array_type  try:


"""     xarray version of pandas.core.nanops._maybe_null_out     """     if axis is not None and getattr(result, "ndim", False):         null_mask = (np.take(mask.shape, axis).prod() - mask.sum(axis) - min_count) < 0        if null_mask.any():            dtype, fill_value = dtypes.maybe_promote(result.dtype)            result = result.astype(dtype)            result[null_mask] = fill_value        dtype, fill_value = dtypes.maybe_promote(result.dtype)        result = where(null_mask, fill_value, result.astype(dtype))      elif getattr(result, "dtype", None) not in dtypes.NAT_TYPES:         null_mask = mask.size - mask.sum()        if null_mask < min_count:            result = np.nan        result = where(null_mask < min_count, np.nan, result)      return result diff --git a/xarray/core/ops.py b/xarray/core/ops.py-- a/xarray/core/ops.py++ b/xarray/core/ops.py


_MINCOUNT_DOCSTRING = """ min_count : int, default: None    The required number of valid values to perform the operation.    If fewer than min_count non-NA values are present the result will    be NA. New in version 0.10.8: Added with the default being None."""    The required number of valid values to perform the operation. If    fewer than min_count non-NA values are present the result will be    NA. Only used if skipna is set to True or defaults to True for the    array's dtype. New in version 0.10.8: Added with the default being    None. Changed in version 0.17.0: if specified on an integer array    and skipna=True, the result will be a float array."""  _COARSEN_REDUCE_DOCSTRING_TEMPLATE = """\ Coarsen this object by applying `{name}` along its dimensions.


_reduce_extra_args_docstring = """"""      @classmethod    def _reduce_method(cls, func: Callable, include_skipna: bool, numeric_only: bool):    def _reduce_method(        cls, func: Callable, include_skipna: bool = False, numeric_only: bool = False    ):         """         Return a wrapped function for injecting reduction methods.         see ops.inject_reduce_methods


return wrapped_func <ins>    def reduce(self, func: Callable, **kwargs):        """Reduce the items in this group by applying `func` along some        dimension(s).        Parameters        ----------        func : callable            Function which can be called in the form `func(x, axis, **kwargs)`            to return the result of collapsing an np.ndarray over the coarsening            dimensions.  It must be possible to provide the `axis` argument            with a tuple of integers.        **kwargs : dict            Additional keyword arguments passed on to `func`.        Returns        -------        reduced : DataArray            Array with summarized data.        Examples        --------        >>> da = xr.DataArray(np.arange(8).reshape(2, 4), dims=("a", "b"))        >>> coarsen = da.coarsen(b=2)        >>> coarsen.reduce(np.sum)        <xarray.DataArray (a: 2, b: 2)>        array([[ 1,  5],               [ 9, 13]])        Dimensions without coordinates: a, b        """        wrapped_func = self._reduce_method(func)        return wrapped_func(self, **kwargs)</ins>  class DatasetCoarsen(Coarsen):     __slots__ = ()


return wrapped_func <ins>    def reduce(self, func: Callable, **kwargs):        """Reduce the items in this group by applying `func` along some        dimension(s).        Parameters        ----------        func : callable            Function which can be called in the form `func(x, axis, **kwargs)`            to return the result of collapsing an np.ndarray over the coarsening            dimensions.  It must be possible to provide the `axis` argument with            a tuple of integers.        **kwargs : dict            Additional keyword arguments passed on to `func`.        Returns        -------        reduced : Dataset            Arrays with summarized data.        """        wrapped_func = self._reduce_method(func)        return wrapped_func(self, **kwargs)</ins>  inject_reduce_methods(DataArrayCoarsen) inject_reduce_methods(DatasetCoarsen)


Dataset with this object's DataArrays replaced with new DataArrays             of summarized data and the indicated dimension(s) removed.         """<ins>        if "axis" in kwargs:            raise ValueError(                "passing 'axis' to Dataset reduce methods is ambiguous."                " Please use 'dim' instead."            )</ins>         if dim is None or dim is ...:             dims = set(self.dims)         elif isinstance(dim, str) or not isinstance(dim, Iterable):


)         )     def argmin(self, dim=None, axis=None, **kwargs):    def argmin(self, dim=None, **kwargs):         """Indices of the minima of the member variables.          If there are multiple minima, the indices of the first one found will be


this is deprecated, in future will be an error, since DataArray.argmin will             return a dict with indices for all dimensions, which does not make sense for             a Dataset.<del>        axis : int, optional            Axis over which to apply `argmin`. Only one of the 'dim' and 'axis' arguments            can be supplied.</del>         keep_attrs : bool, optional             If True, the attributes (`attrs`) will be copied from the original             object to the new one.  If False (default), the new object will be


See Also         --------         DataArray.argmin         """        if dim is None and axis is None:        if dim is None:             warnings.warn(                "Once the behaviour of DataArray.argmin() and Variable.argmin() with "                "neither dim nor axis argument changes to return a dict of indices of "                "each dimension, for consistency it will be an error to call "                "Dataset.argmin() with no argument, since we don't return a dict of "                "Datasets.",                "Once the behaviour of DataArray.argmin() and Variable.argmin() without "                "dim changes to return a dict of indices of each dimension, for "                "consistency it will be an error to call Dataset.argmin() with no argument,"                "since we don't return a dict of Datasets.",                 DeprecationWarning,                 stacklevel=2,             )         if (             dim is None            or axis is not None             or (not isinstance(dim, Sequence) and dim is not ...)             or isinstance(dim, str)         ):             # Return int index if single dimension is passed, and is not part of a             # sequence             argmin_func = getattr(duck_array_ops, "argmin")            return self.reduce(argmin_func, dim=dim, axis=axis, **kwargs)            return self.reduce(argmin_func, dim=dim, **kwargs)         else:             raise ValueError(                 "When dim is a sequence or ..., DataArray.argmin() returns a dict. "


"Dataset.argmin() with a sequence or ... for dim"             )     def argmax(self, dim=None, axis=None, **kwargs):    def argmax(self, dim=None, **kwargs):         """Indices of the maxima of the member variables.          If there are multiple maxima, the indices of the first one found will be


this is deprecated, in future will be an error, since DataArray.argmax will             return a dict with indices for all dimensions, which does not make sense for             a Dataset.<del>        axis : int, optional            Axis over which to apply `argmax`. Only one of the 'dim' and 'axis' arguments            can be supplied.</del>         keep_attrs : bool, optional             If True, the attributes (`attrs`) will be copied from the original             object to the new one.  If False (default), the new object will be


DataArray.argmax          """        if dim is None and axis is None:        if dim is None:             warnings.warn(                "Once the behaviour of DataArray.argmax() and Variable.argmax() with "                "neither dim nor axis argument changes to return a dict of indices of "                "each dimension, for consistency it will be an error to call "                "Dataset.argmax() with no argument, since we don't return a dict of "                "Datasets.",                "Once the behaviour of DataArray.argmin() and Variable.argmin() without "                "dim changes to return a dict of indices of each dimension, for "                "consistency it will be an error to call Dataset.argmin() with no argument,"                "since we don't return a dict of Datasets.",                 DeprecationWarning,                 stacklevel=2,             )         if (             dim is None            or axis is not None             or (not isinstance(dim, Sequence) and dim is not ...)             or isinstance(dim, str)         ):             # Return int index if single dimension is passed, and is not part of a             # sequence             argmax_func = getattr(duck_array_ops, "argmax")            return self.reduce(argmax_func, dim=dim, axis=axis, **kwargs)            return self.reduce(argmax_func, dim=dim, **kwargs)         else:             raise ValueError(                 "When dim is a sequence or ..., DataArray.argmin() returns a dict. "


if "_FillValue" in attrs:                         new_fill = unsigned_dtype.type(attrs["_FillValue"])                         attrs["_FillValue"] = new_fill<ins>            elif data.dtype.kind == "u":                if unsigned == "false":                    signed_dtype = np.dtype("i%s" % data.dtype.itemsize)                    transform = partial(np.asarray, dtype=signed_dtype)                    data = lazy_elemwise_func(data, transform, signed_dtype)                    if "_FillValue" in attrs:                        new_fill = signed_dtype.type(attrs["_FillValue"])                        attrs["_FillValue"] = new_fill</ins>             else:                 warnings.warn(                     "variable %r has _Unsigned attribute but is not "


if name == "season":         months = values_as_cftimeindex.month         field_values = _season_from_months(months)<ins>    elif name == "date":        raise AttributeError(            "'CFTimeIndex' object has no attribute `date`. Consider using the floor method instead, for instance: `.time.dt.floor('D')`."        )</ins>     else:         field_values = getattr(values_as_cftimeindex, name)     return field_values.reshape(values.shape)


"time", "Timestamps corresponding to datetimes", object     ) <ins>    date = Properties._tslib_field_accessor(        "date", "Date corresponding to datetimes", object    )</ins>     is_month_start = Properties._tslib_field_accessor(         "is_month_start",         "Indicates whether the date is the first day of the month.",


scipy.io.netcdf (only netCDF3 supported). Byte-strings or file-like         objects are opened by scipy.io.netcdf (netCDF3) or h5py (netCDF4/HDF).     engine : {"netcdf4", "scipy", "pydap", "h5netcdf", "pynio", "cfgrib", \        "pseudonetcdf", "zarr"}, optional        "pseudonetcdf", "zarr"} or subclass of xarray.backends.BackendEntrypoint, optional         Engine to use when reading files. If not provided, the default engine         is chosen based on available dependencies, with a preference for        "netcdf4".        "netcdf4". A custom backend class (a subclass of ``BackendEntrypoint``)        can also be used.     chunks : int or dict, optional         If chunks is provided, it is used to load the new dataset into dask         arrays. ``chunks=-1`` loads the dataset with dask using a singlediff --git a/xarray/backends/plugins.py b/xarray/backends/plugins.py-- a/xarray/backends/plugins.py++ b/xarray/backends/plugins.py


import pkg_resources from .common import BACKEND_ENTRYPOINTSfrom .common import BACKEND_ENTRYPOINTS, BackendEntrypoint  STANDARD_BACKENDS_ORDER = ["netcdf4", "h5netcdf", "scipy"]


def get_backend(engine):    """Select open_dataset method based on current engine"""    engines = list_engines()    if engine not in engines:        raise ValueError(            f"unrecognized engine {engine} must be one of: {list(engines)}"    """Select open_dataset method based on current engine."""    if isinstance(engine, str):        engines = list_engines()        if engine not in engines:            raise ValueError(                f"unrecognized engine {engine} must be one of: {list(engines)}"            )        backend = engines[engine]    elif isinstance(engine, type) and issubclass(engine, BackendEntrypoint):        backend = engine    else:        raise TypeError(            (                "engine must be a string or a subclass of "                f"xarray.backends.BackendEntrypoint: {engine}"            )         )    return engines[engine]    return backend


return zip(self._unique_coord.values, self._iter_grouped())      def __repr__(self):        return "{}, grouped over {!r} \n{!r} groups with labels {}.".format(        return "{}, grouped over {!r}\n{!r} groups with labels {}.".format(             self.__class__.__name__,             self._unique_coord.name,             self._unique_coord.size,


)  <ins>def _is_standard_calendar(calendar):    return calendar.lower() in _STANDARD_CALENDARS</ins> def _netcdf_to_numpy_timeunit(units):     units = units.lower()     if not units.endswith("s"):


def _decode_datetime_with_pandas(flat_num_dates, units, calendar):    if calendar not in _STANDARD_CALENDARS:    if not _is_standard_calendar(calendar):         raise OutOfBoundsDatetime(             "Cannot decode times from a non-standard calendar, {!r}, using "             "pandas.".format(calendar)


dates[np.nanargmin(num_dates)].year < 1678                 or dates[np.nanargmax(num_dates)].year >= 2262             ):                if calendar in _STANDARD_CALENDARS:                if _is_standard_calendar(calendar):                     warnings.warn(                         "Unable to decode time axis into full "                         "numpy.datetime64 objects, continuing using "


stacklevel=3,                     )             else:                if calendar in _STANDARD_CALENDARS:                if _is_standard_calendar(calendar):                     dates = cftime_to_nptime(dates)     elif use_cftime:         dates = _decode_datetime_with_cftime(flat_num_dates, units, calendar)


delta, ref_date = _unpack_netcdf_time_units(units)     try:        if calendar not in _STANDARD_CALENDARS or dates.dtype.kind == "O":        if not _is_standard_calendar(calendar) or dates.dtype.kind == "O":             # parse with cftime instead             raise OutOfBoundsDatetime         assert dates.dtype == "datetime64[ns]"


# See issue dask/dask#6516         coeffs, residuals, _, _ = da.linalg.lstsq(lhs_da, rhs)     return coeffs, residualsdef push(array, n, axis):    """    Dask-aware bottleneck.push    """    from bottleneck import push    if len(array.chunks[axis]) > 1 and n is not None and n < array.shape[axis]:        raise NotImplementedError(            "Cannot fill along a chunked axis when limit is not None."            "Either rechunk to a single chunk along this axis or call .compute() or .load() first."        )    if all(c == 1 for c in array.chunks[axis]):        array = array.rechunk({axis: 2})    pushed = array.map_blocks(push, axis=axis, n=n)    if len(array.chunks[axis]) > 1:        pushed = pushed.map_overlap(            push, axis=axis, n=n, depth={axis: (1, 0)}, boundary="none"        )    return pusheddiff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py-- a/xarray/core/dataarray.py++ b/xarray/core/dataarray.py


The maximum number of consecutive NaN values to forward fill. In             other words, if there is a gap with more than this number of             consecutive NaNs, it will only be partially filled. Must be greater            than 0 or None for no limit.            than 0 or None for no limit. Must be None or greater than or equal            to axis length if filling along chunked axes (dimensions).          Returns         -------


The maximum number of consecutive NaN values to backward fill. In             other words, if there is a gap with more than this number of             consecutive NaNs, it will only be partially filled. Must be greater            than 0 or None for no limit.            than 0 or None for no limit. Must be None or greater than or equal            to axis length if filling along chunked axes (dimensions).          Returns         -------diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py-- a/xarray/core/dataset.py++ b/xarray/core/dataset.py


The maximum number of consecutive NaN values to backward fill. In             other words, if there is a gap with more than this number of             consecutive NaNs, it will only be partially filled. Must be greater            than 0 or None for no limit.            than 0 or None for no limit. Must be None or greater than or equal            to axis length if filling along chunked axes (dimensions).          Returns         -------diff --git a/xarray/core/duck_array_ops.py b/xarray/core/duck_array_ops.py-- a/xarray/core/duck_array_ops.py++ b/xarray/core/duck_array_ops.py


return dask_array_ops.least_squares(lhs, rhs, rcond=rcond, skipna=skipna)     else:         return nputils.least_squares(lhs, rhs, rcond=rcond, skipna=skipna)def push(array, n, axis):    from bottleneck import push    if is_duck_dask_array(array):        return dask_array_ops.push(array, n, axis)    else:        return push(array, n, axis)diff --git a/xarray/core/missing.py b/xarray/core/missing.py-- a/xarray/core/missing.py++ b/xarray/core/missing.py


from . import utils from .common import _contains_datetime_like_objects, ones_like from .computation import apply_ufuncfrom .duck_array_ops import datetime_to_numeric, timedelta_to_numericfrom .duck_array_ops import datetime_to_numeric, push, timedelta_to_numeric from .options import _get_keep_attrs from .pycompat import is_duck_dask_array from .utils import OrderedSet, is_scalar


def _bfill(arr, n=None, axis=-1):     """inverse of ffill"""    import bottleneck as bn     arr = np.flip(arr, axis=axis)      # fill    arr = bn.push(arr, axis=axis, n=n)    arr = push(arr, axis=axis, n=n)      # reverse back to original     return np.flip(arr, axis=axis)


def ffill(arr, dim=None, limit=None):     """forward fill missing values"""    import bottleneck as bn     axis = arr.get_axis_num(dim)      # work around for bottleneck 178     _limit = limit if limit is not None else arr.shape[axis]      return apply_ufunc(        bn.push,        push,         arr,        dask="parallelized",        dask="allowed",         keep_attrs=True,         output_dtypes=[arr.dtype],         kwargs=dict(n=_limit, axis=axis),


return apply_ufunc(         _bfill,         arr,        dask="parallelized",        dask="allowed",         keep_attrs=True,         output_dtypes=[arr.dtype],         kwargs=dict(n=_limit, axis=axis),


self.obj: T_DataWithCoords = obj         self.weights: "DataArray" = weights <ins>    def _check_dim(self, dim: Optional[Union[Hashable, Iterable[Hashable]]]):        """raise an error if any dimension is missing"""        if isinstance(dim, str) or not isinstance(dim, Iterable):            dims = [dim] if dim else []        else:            dims = list(dim)        missing_dims = set(dims) - set(self.obj.dims) - set(self.weights.dims)        if missing_dims:            raise ValueError(                f"{self.__class__.__name__} does not contain the dimensions: {missing_dims}"            )</ins>     @staticmethod     def _reduce(         da: "DataArray",


class DataArrayWeighted(Weighted["DataArray"]):     def _implementation(self, func, dim, **kwargs) -> "DataArray": <ins>        self._check_dim(dim)</ins>         dataset = self.obj._to_temp_dataset()         dataset = dataset.map(func, dim=dim, **kwargs)         return self.obj._from_temp_dataset(dataset)


class DatasetWeighted(Weighted["Dataset"]):     def _implementation(self, func, dim, **kwargs) -> "Dataset": <ins>        self._check_dim(dim)</ins>         return self.obj.map(func, dim=dim, **kwargs)


from .core.alignment import align, broadcast from .core.combine import combine_by_coords, combine_nested from .core.common import ALL_DIMS, full_like, ones_like, zeros_likefrom .core.computation import apply_ufunc, corr, cov, dot, polyval, unify_chunks, wherefrom .core.computation import (    apply_ufunc,    corr,    cov,    cross,    dot,    polyval,    unify_chunks,    where,) from .core.concat import concat from .core.dataarray import DataArray from .core.dataset import Dataset


"dot",     "cov",     "corr",    "cross",     "full_like",     "get_options",     "infer_freq",diff --git a/xarray/core/computation.py b/xarray/core/computation.py-- a/xarray/core/computation.py++ b/xarray/core/computation.py


if TYPE_CHECKING:     from .coordinates import Coordinates<ins>    from .dataarray import DataArray</ins>     from .dataset import Dataset     from .types import T_Xarray


return corr  <ins>def cross(    a: Union[DataArray, Variable], b: Union[DataArray, Variable], *, dim: Hashable) -> Union[DataArray, Variable]:    """    Compute the cross product of two (arrays of) vectors.    The cross product of `a` and `b` in :math:`R^3` is a vector    perpendicular to both `a` and `b`. The vectors in `a` and `b` are    defined by the values along the dimension `dim` and can have sizes    1, 2 or 3. Where the size of either `a` or `b` is    1 or 2, the remaining components of the input vector is assumed to    be zero and the cross product calculated accordingly. In cases where    both input vectors have dimension 2, the z-component of the cross    product is returned.    Parameters    ----------    a, b : DataArray or Variable        Components of the first and second vector(s).    dim : hashable        The dimension along which the cross product will be computed.        Must be available in both vectors.    Examples    --------    Vector cross-product with 3 dimensions:    >>> a = xr.DataArray([1, 2, 3])    >>> b = xr.DataArray([4, 5, 6])    >>> xr.cross(a, b, dim="dim_0")    <xarray.DataArray (dim_0: 3)>    array([-3,  6, -3])    Dimensions without coordinates: dim_0    Vector cross-product with 2 dimensions, returns in the perpendicular    direction:    >>> a = xr.DataArray([1, 2])    >>> b = xr.DataArray([4, 5])    >>> xr.cross(a, b, dim="dim_0")    <xarray.DataArray ()>    array(-3)    Vector cross-product with 3 dimensions but zeros at the last axis    yields the same results as with 2 dimensions:    >>> a = xr.DataArray([1, 2, 0])    >>> b = xr.DataArray([4, 5, 0])    >>> xr.cross(a, b, dim="dim_0")    <xarray.DataArray (dim_0: 3)>    array([ 0,  0, -3])    Dimensions without coordinates: dim_0    One vector with dimension 2:    >>> a = xr.DataArray(    ...     [1, 2],    ...     dims=["cartesian"],    ...     coords=dict(cartesian=(["cartesian"], ["x", "y"])),    ... )    >>> b = xr.DataArray(    ...     [4, 5, 6],    ...     dims=["cartesian"],    ...     coords=dict(cartesian=(["cartesian"], ["x", "y", "z"])),    ... )    >>> xr.cross(a, b, dim="cartesian")    <xarray.DataArray (cartesian: 3)>    array([12, -6, -3])    Coordinates:      * cartesian  (cartesian) <U1 'x' 'y' 'z'    One vector with dimension 2 but coords in other positions:    >>> a = xr.DataArray(    ...     [1, 2],    ...     dims=["cartesian"],    ...     coords=dict(cartesian=(["cartesian"], ["x", "z"])),    ... )    >>> b = xr.DataArray(    ...     [4, 5, 6],    ...     dims=["cartesian"],    ...     coords=dict(cartesian=(["cartesian"], ["x", "y", "z"])),    ... )    >>> xr.cross(a, b, dim="cartesian")    <xarray.DataArray (cartesian: 3)>    array([-10,   2,   5])    Coordinates:      * cartesian  (cartesian) <U1 'x' 'y' 'z'    Multiple vector cross-products. Note that the direction of the    cross product vector is defined by the right-hand rule:    >>> a = xr.DataArray(    ...     [[1, 2, 3], [4, 5, 6]],    ...     dims=("time", "cartesian"),    ...     coords=dict(    ...         time=(["time"], [0, 1]),    ...         cartesian=(["cartesian"], ["x", "y", "z"]),    ...     ),    ... )    >>> b = xr.DataArray(    ...     [[4, 5, 6], [1, 2, 3]],    ...     dims=("time", "cartesian"),    ...     coords=dict(    ...         time=(["time"], [0, 1]),    ...         cartesian=(["cartesian"], ["x", "y", "z"]),    ...     ),    ... )    >>> xr.cross(a, b, dim="cartesian")    <xarray.DataArray (time: 2, cartesian: 3)>    array([[-3,  6, -3],           [ 3, -6,  3]])    Coordinates:      * time       (time) int64 0 1      * cartesian  (cartesian) <U1 'x' 'y' 'z'    Cross can be called on Datasets by converting to DataArrays and later    back to a Dataset:    >>> ds_a = xr.Dataset(dict(x=("dim_0", [1]), y=("dim_0", [2]), z=("dim_0", [3])))    >>> ds_b = xr.Dataset(dict(x=("dim_0", [4]), y=("dim_0", [5]), z=("dim_0", [6])))    >>> c = xr.cross(    ...     ds_a.to_array("cartesian"), ds_b.to_array("cartesian"), dim="cartesian"    ... )    >>> c.to_dataset(dim="cartesian")    <xarray.Dataset>    Dimensions:  (dim_0: 1)    Dimensions without coordinates: dim_0    Data variables:        x        (dim_0) int64 -3        y        (dim_0) int64 6        z        (dim_0) int64 -3    See Also    --------    numpy.cross : Corresponding numpy function    """    if dim not in a.dims:        raise ValueError(f"Dimension {dim!r} not on a")    elif dim not in b.dims:        raise ValueError(f"Dimension {dim!r} not on b")    if not 1 <= a.sizes[dim] <= 3:        raise ValueError(            f"The size of {dim!r} on a must be 1, 2, or 3 to be "            f"compatible with a cross product but is {a.sizes[dim]}"        )    elif not 1 <= b.sizes[dim] <= 3:        raise ValueError(            f"The size of {dim!r} on b must be 1, 2, or 3 to be "            f"compatible with a cross product but is {b.sizes[dim]}"        )    all_dims = list(dict.fromkeys(a.dims + b.dims))    if a.sizes[dim] != b.sizes[dim]:        # Arrays have different sizes. Append zeros where the smaller        # array is missing a value, zeros will not affect np.cross:        if (            not isinstance(a, Variable)  # Only used to make mypy happy.            and dim in getattr(a, "coords", {})            and not isinstance(b, Variable)  # Only used to make mypy happy.            and dim in getattr(b, "coords", {})        ):            # If the arrays have coords we know which indexes to fill            # with zeros:            a, b = align(                a,                b,                fill_value=0,                join="outer",                exclude=set(all_dims) - {dim},            )        elif min(a.sizes[dim], b.sizes[dim]) == 2:            # If the array doesn't have coords we can only infer            # that it has composite values if the size is at least 2.            # Once padded, rechunk the padded array because apply_ufunc            # requires core dimensions not to be chunked:            if a.sizes[dim] < b.sizes[dim]:                a = a.pad({dim: (0, 1)}, constant_values=0)                # TODO: Should pad or apply_ufunc handle correct chunking?                a = a.chunk({dim: -1}) if is_duck_dask_array(a.data) else a            else:                b = b.pad({dim: (0, 1)}, constant_values=0)                # TODO: Should pad or apply_ufunc handle correct chunking?                b = b.chunk({dim: -1}) if is_duck_dask_array(b.data) else b        else:            raise ValueError(                f"{dim!r} on {'a' if a.sizes[dim] == 1 else 'b'} is incompatible:"                " dimensions without coordinates must have have a length of 2 or 3"            )    c = apply_ufunc(        np.cross,        a,        b,        input_core_dims=[[dim], [dim]],        output_core_dims=[[dim] if a.sizes[dim] == 3 else []],        dask="parallelized",        output_dtypes=[np.result_type(a, b)],    )    c = c.transpose(*all_dims, missing_dims="ignore")    return c</ins> def dot(*arrays, dims=None, **kwargs):     """Generalized dot product for xarray objects. Like np.einsum, but     provides a simpler interface based on array dimensions.


):     if col_width is None:         col_width = _calculate_col_width(mapping)    if max_rows is None:        max_rows = OPTIONS["display_max_rows"]     summary = [f"{title}:"]     if mapping:         len_mapping = len(mapping)         if not _get_boolean_with_default(expand_option_name, default=True):             summary = [f"{summary[0]} ({len_mapping})"]        elif len_mapping > max_rows:        elif max_rows is not None and len_mapping > max_rows:             summary = [f"{summary[0]} ({max_rows}/{len_mapping})"]             first_rows = max_rows // 2 + max_rows % 2             keys = list(mapping.keys())


)  def coords_repr(coords, col_width=None):def coords_repr(coords, col_width=None, max_rows=None):     if col_width is None:         col_width = _calculate_col_width(_get_col_items(coords))     return _mapping_repr(


summarizer=summarize_coord,         expand_option_name="display_expand_coords",         col_width=col_width,<ins>        max_rows=max_rows,</ins>     )


summary = ["<xarray.{}>".format(type(ds).__name__)]      col_width = _calculate_col_width(_get_col_items(ds.variables))    max_rows = OPTIONS["display_max_rows"]      dims_start = pretty_print("Dimensions:", col_width)     summary.append("{}({})".format(dims_start, dim_summary(ds)))      if ds.coords:        summary.append(coords_repr(ds.coords, col_width=col_width))        summary.append(coords_repr(ds.coords, col_width=col_width, max_rows=max_rows))      unindexed_dims_str = unindexed_dims_repr(ds.dims, ds.coords)     if unindexed_dims_str:         summary.append(unindexed_dims_str)     summary.append(data_vars_repr(ds.data_vars, col_width=col_width))    summary.append(data_vars_repr(ds.data_vars, col_width=col_width, max_rows=max_rows))      if ds.attrs:        summary.append(attrs_repr(ds.attrs))        summary.append(attrs_repr(ds.attrs, max_rows=max_rows))      return "\n".join(summary)


import functools from datetime import datetime, timedelta from itertools import chain, zip_longestfrom typing import Hashablefrom typing import Collection, Hashable, Optional  import numpy as np import pandas as pd


return np.ravel(np.asarray(array[indexer])).tolist()  <ins>def calc_max_rows_first(max_rows: int) -> int:    """Calculate the first rows to maintain the max number of rows."""    return max_rows // 2 + max_rows % 2def calc_max_rows_last(max_rows: int) -> int:    """Calculate the last rows to maintain the max number of rows."""    return max_rows // 2</ins> def format_timestamp(t):     """Cast given object to a Timestamp and return a nicely formatted string"""     # Timestamp is only valid for 1678 to 2262


summary = [f"{summary[0]} ({len_mapping})"]         elif max_rows is not None and len_mapping > max_rows:             summary = [f"{summary[0]} ({max_rows}/{len_mapping})"]            first_rows = max_rows // 2 + max_rows % 2            first_rows = calc_max_rows_first(max_rows)             keys = list(mapping.keys())             summary += [summarizer(k, mapping[k], col_width) for k in keys[:first_rows]]             if max_rows > 1:                last_rows = max_rows // 2                last_rows = calc_max_rows_last(max_rows)                 summary += [pretty_print("    ...", col_width) + " ..."]                 summary += [                     summarizer(k, mapping[k], col_width) for k in keys[-last_rows:]


return ", ".join(elements)  def unindexed_dims_repr(dims, coords):def _element_formatter(    elements: Collection[Hashable],    col_width: int,    max_rows: Optional[int] = None,    delimiter: str = ", ",) -> str:    """    Formats elements for better readability.    Once it becomes wider than the display width it will create a newline and    continue indented to col_width.    Once there are more rows than the maximum displayed rows it will start    removing rows.    Parameters    ----------    elements : Collection of hashable        Elements to join together.    col_width : int        The width to indent to if a newline has been made.    max_rows : int, optional        The maximum number of allowed rows. The default is None.    delimiter : str, optional        Delimiter to use between each element. The default is ", ".    """    elements_len = len(elements)    out = [""]    length_row = 0    for i, v in enumerate(elements):        delim = delimiter if i < elements_len - 1 else ""        v_delim = f"{v}{delim}"        length_element = len(v_delim)        length_row += length_element        # Create a new row if the next elements makes the print wider than        # the maximum display width:        if col_width + length_row > OPTIONS["display_width"]:            out[-1] = out[-1].rstrip()  # Remove trailing whitespace.            out.append("\n" + pretty_print("", col_width) + v_delim)            length_row = length_element        else:            out[-1] += v_delim    # If there are too many rows of dimensions trim some away:    if max_rows and (len(out) > max_rows):        first_rows = calc_max_rows_first(max_rows)        last_rows = calc_max_rows_last(max_rows)        out = (            out[:first_rows]            + ["\n" + pretty_print("", col_width) + "..."]            + (out[-last_rows:] if max_rows > 1 else [])        )    return "".join(out)def dim_summary_limited(obj, col_width: int, max_rows: Optional[int] = None) -> str:    elements = [f"{k}: {v}" for k, v in obj.sizes.items()]    return _element_formatter(elements, col_width, max_rows)def unindexed_dims_repr(dims, coords, max_rows: Optional[int] = None):     unindexed_dims = [d for d in dims if d not in coords]     if unindexed_dims:        dims_str = ", ".join(f"{d}" for d in unindexed_dims)        return "Dimensions without coordinates: " + dims_str        dims_start = "Dimensions without coordinates: "        dims_str = _element_formatter(            unindexed_dims, col_width=len(dims_start), max_rows=max_rows        )        return dims_start + dims_str     else:         return None


def array_repr(arr):     from .variable import Variable <ins>    max_rows = OPTIONS["display_max_rows"]</ins>     # used for DataArray, Variable and IndexVariable     if hasattr(arr, "name") and arr.name is not None:         name_str = f"{arr.name!r} "


else:         data_repr = inline_variable_array_repr(arr.variable, OPTIONS["display_width"])     start = f"<xarray.{type(arr).__name__} {name_str}"    dims = dim_summary_limited(arr, col_width=len(start) + 1, max_rows=max_rows)     summary = [        "<xarray.{} {}({})>".format(type(arr).__name__, name_str, dim_summary(arr)),        f"{start}({dims})>",         data_repr,     ]      if hasattr(arr, "coords"):         if arr.coords:            summary.append(repr(arr.coords))            col_width = _calculate_col_width(_get_col_items(arr.coords))            summary.append(                coords_repr(arr.coords, col_width=col_width, max_rows=max_rows)            )         unindexed_dims_str = unindexed_dims_repr(arr.dims, arr.coords)        unindexed_dims_str = unindexed_dims_repr(            arr.dims, arr.coords, max_rows=max_rows        )         if unindexed_dims_str:             summary.append(unindexed_dims_str)


max_rows = OPTIONS["display_max_rows"]      dims_start = pretty_print("Dimensions:", col_width)    summary.append("{}({})".format(dims_start, dim_summary(ds)))    dims_values = dim_summary_limited(ds, col_width=col_width + 1, max_rows=max_rows)    summary.append(f"{dims_start}({dims_values})")      if ds.coords:         summary.append(coords_repr(ds.coords, col_width=col_width, max_rows=max_rows))     unindexed_dims_str = unindexed_dims_repr(ds.dims, ds.coords)    unindexed_dims_str = unindexed_dims_repr(ds.dims, ds.coords, max_rows=max_rows)     if unindexed_dims_str:         summary.append(unindexed_dims_str)


else:         units = _get_units_from_attrs(da)     return "\n".join(textwrap.wrap(name + extra + units, 30))    # Treat `name` differently if it's a latex sequence    if name.startswith("$") and (name.count("$") % 2 == 0):        return "$\n$".join(            textwrap.wrap(name + extra + units, 60, break_long_words=False)        )    else:        return "\n".join(textwrap.wrap(name + extra + units, 30))   def _interval_to_mid_points(array):


da = da.where(~missing_vals)             return da         else:            return da            # ensure consistent return dtype            return da.astype(float)      da_a = da_a.map_blocks(_get_valid_values, args=[da_b])     da_b = da_b.map_blocks(_get_valid_values, args=[da_a])diff --git a/xarray/core/parallel.py b/xarray/core/parallel.py-- a/xarray/core/parallel.py++ b/xarray/core/parallel.py


from .alignment import align from .dataarray import DataArray from .dataset import Dataset<ins>from .pycompat import is_dask_collection</ins>  try:     import dask


raise TypeError("kwargs must be a mapping (for example, a dict)")      for value in kwargs.values():        if dask.is_dask_collection(value):        if is_dask_collection(value):             raise TypeError(                 "Cannot pass dask collections in kwargs yet. Please compute or "                 "load values before passing to map_blocks."             )     if not dask.is_dask_collection(obj):    if not is_dask_collection(obj):         return func(obj, *args, **kwargs)      all_args = [obj] + list(args)diff --git a/xarray/core/pycompat.py b/xarray/core/pycompat.py-- a/xarray/core/pycompat.py++ b/xarray/core/pycompat.py


self.available = duck_array_module is not None  def is_duck_dask_array(x):def is_dask_collection(x):     if DuckArrayModule("dask").available:         from dask.base import is_dask_collection         return is_duck_array(x) and is_dask_collection(x)        return is_dask_collection(x)     else:         return False  def is_duck_dask_array(x):    return is_duck_array(x) and is_dask_collection(x) dsk = DuckArrayModule("dask") dask_version = dsk.version dask_array_type = dsk.type


if coord is not None and dim not in applied_example.dims:             index, index_vars = create_default_index_implicit(coord)             indexes = {k: index for k in index_vars}            combined = combined._overwrite_indexes(indexes, variables=index_vars)            combined = combined._overwrite_indexes(indexes, index_vars)         combined = self._maybe_restore_empty_groups(combined)         combined = self._maybe_unstack(combined)         return combined


if coord is not None and dim not in applied_example.dims:             index, index_vars = create_default_index_implicit(coord)             indexes = {k: index for k in index_vars}            combined = combined._overwrite_indexes(indexes, coords=index_vars)            combined = combined._overwrite_indexes(indexes, index_vars)         combined = self._maybe_restore_empty_groups(combined)         combined = self._maybe_unstack(combined)         return combined


# default to lower precision so a full (abbreviated) line can fit on     # one line with the default display_width    options = {"precision": 6, "linewidth": OPTIONS["display_width"], "threshold": 200}    options = {        "precision": 6,        "linewidth": OPTIONS["display_width"],        "threshold": OPTIONS["display_values_threshold"],    }     if array.ndim < 3:         edgeitems = 3     elif array.ndim == 3:diff --git a/xarray/core/indexing.py b/xarray/core/indexing.py-- a/xarray/core/indexing.py++ b/xarray/core/indexing.py


from contextlib import suppress from dataclasses import dataclass, field from datetime import timedelta<ins>from html import escape</ins> from typing import (     TYPE_CHECKING,     Any,


from . import duck_array_ops, nputils, utils from .npcompat import DTypeLike<ins>from .options import OPTIONS</ins> from .pycompat import dask_version, integer_types, is_duck_dask_array, sparse_array_type from .types import T_Xarray from .utils import either_dict_or_kwargs, get_valid_numpy_dtype


)             return f"{type(self).__name__}{props}"     def _repr_inline_(self, max_width) -> str:        # special implementation to speed-up the repr for big multi-indexes    def _get_array_subset(self) -> np.ndarray:        # used to speed-up the repr for big multi-indexes        threshold = max(100, OPTIONS["display_values_threshold"] + 2)        if self.size > threshold:            pos = threshold // 2            indices = np.concatenate([np.arange(0, pos), np.arange(-pos, 0)])            subset = self[OuterIndexer((indices,))]        else:            subset = self        return np.asarray(subset)    def _repr_inline_(self, max_width: int) -> str:        from .formatting import format_array_flat         if self.level is None:             return "MultiIndex"         else:            from .formatting import format_array_flat            return format_array_flat(self._get_array_subset(), max_width)             if self.size > 100 and max_width < self.size:                n_values = max_width                indices = np.concatenate(                    [np.arange(0, n_values), np.arange(-n_values, 0)]                )                subset = self[OuterIndexer((indices,))]            else:                subset = self    def _repr_html_(self) -> str:        from .formatting import short_numpy_repr             return format_array_flat(np.asarray(subset), max_width)        array_repr = short_numpy_repr(self._get_array_subset())        return f"
{escape(array_repr)}
"      def copy(self, deep: bool = True) -> "PandasMultiIndexingAdapter":         # see PandasIndexingAdapter.copydiff --git a/xarray/core/options.py b/xarray/core/options.py-- a/xarray/core/options.py++ b/xarray/core/options.py


cmap_divergent: Union[str, "Colormap"]     cmap_sequential: Union[str, "Colormap"]     display_max_rows: int<ins>    display_values_threshold: int</ins>     display_style: Literal["text", "html"]     display_width: int     display_expand_attrs: Literal["default", True, False]


"cmap_divergent": "RdBu_r",     "cmap_sequential": "viridis",     "display_max_rows": 12,<ins>    "display_values_threshold": 200,</ins>     "display_style": "html",     "display_width": 80,     "display_expand_attrs": "default",


_VALIDATORS = {     "arithmetic_join": _JOIN_OPTIONS.__contains__,     "display_max_rows": _positive_integer,<ins>    "display_values_threshold": _positive_integer,</ins>     "display_style": _DISPLAY_OPTIONS.__contains__,     "display_width": _positive_integer,     "display_expand_attrs": lambda choice: choice in [True, False, "default"],


* ``default`` : to expand unless over a pre-defined limit     display_max_rows : int, default: 12         Maximum display rows.<ins>    display_values_threshold : int, default: 200        Total number of array elements which trigger summarization rather        than full repr for variable data views (numpy arrays).</ins>     display_style : {"text", "html"}, default: "html"         Display style to use in jupyter for xarray objects.     display_width : int, default: 80


"""     if keep_attrs is None:         keep_attrs = _get_keep_attrs(default=False)     if keep_attrs is True:         # keep the attributes of x, the second parameter, by default to         # be consistent with the `where` method of `DataArray` and `Dataset`        keep_attrs = lambda attrs, context: attrs[1]        keep_attrs = lambda attrs, context: getattr(x, "attrs", {})      # alignment for three arguments is complicated, so don't support it yet     return apply_ufunc(


import numpy as npimport xarray as xrfrom . import parameterized, randn, requires_daskNDEGS = (2, 5, 20)NX = (10**2, 10**6)class Polyval:    def setup(self, *args, **kwargs):        self.xs = {nx: xr.DataArray(randn((nx,)), dims="x", name="x") for nx in NX}        self.coeffs = {            ndeg: xr.DataArray(                randn((ndeg,)), dims="degree", coords={"degree": np.arange(ndeg)}            )            for ndeg in NDEGS        }    @parameterized(["nx", "ndeg"], [NX, NDEGS])    def time_polyval(self, nx, ndeg):        x = self.xs[nx]        c = self.coeffs[ndeg]        xr.polyval(x, c).compute()    @parameterized(["nx", "ndeg"], [NX, NDEGS])    def peakmem_polyval(self, nx, ndeg):        x = self.xs[nx]        c = self.coeffs[ndeg]        xr.polyval(x, c).compute()class PolyvalDask(Polyval):    def setup(self, *args, **kwargs):        requires_dask()        super().setup(*args, **kwargs)        self.xs = {k: v.chunk({"x": 10000}) for k, v in self.xs.items()}diff --git a/xarray/core/computation.py b/xarray/core/computation.py-- a/xarray/core/computation.py++ b/xarray/core/computation.py


Iterable,     Mapping,     Sequence,    overload, )  import numpy as np  from . import dtypes, duck_array_ops, utils from .alignment import align, deep_alignfrom .common import zeros_likefrom .duck_array_ops import datetime_to_numeric from .indexes import Index, filter_indexes_from_coords from .merge import merge_attrs, merge_coordinates_without_align from .options import OPTIONS, _get_keep_attrs


)  def polyval(coord, coeffs, degree_dim="degree"):@overloaddef polyval(coord: DataArray, coeffs: DataArray, degree_dim: Hashable) -> DataArray:    ...@overloaddef polyval(coord: T_Xarray, coeffs: Dataset, degree_dim: Hashable) -> Dataset:    ...@overloaddef polyval(coord: Dataset, coeffs: T_Xarray, degree_dim: Hashable) -> Dataset:    ...def polyval(    coord: T_Xarray, coeffs: T_Xarray, degree_dim: Hashable = "degree") -> T_Xarray:     """Evaluate a polynomial at specific values      Parameters     ----------    coord : DataArray        The 1D coordinate along which to evaluate the polynomial.    coeffs : DataArray        Coefficients of the polynomials.    degree_dim : str, default: "degree"    coord : DataArray or Dataset        Values at which to evaluate the polynomial.    coeffs : DataArray or Dataset        Coefficients of the polynomial.    degree_dim : Hashable, default: "degree"         Name of the polynomial degree dimension in `coeffs`.     Returns    -------    DataArray or Dataset        Evaluated polynomial.     See Also     --------     xarray.DataArray.polyfit    numpy.polyval    numpy.polynomial.polynomial.polyval     """    from .dataarray import DataArray    from .missing import get_clean_interp_index     x = get_clean_interp_index(coord, coord.name, strict=False)    if degree_dim not in coeffs._indexes:        raise ValueError(            f"Dimension `{degree_dim}` should be a coordinate variable with labels."        )    if not np.issubdtype(coeffs[degree_dim].dtype, int):        raise ValueError(            f"Dimension `{degree_dim}` should be of integer dtype. Received {coeffs[degree_dim].dtype} instead."        )    max_deg = coeffs[degree_dim].max().item()    coeffs = coeffs.reindex(        {degree_dim: np.arange(max_deg + 1)}, fill_value=0, copy=False    )    coord = _ensure_numeric(coord)    # using Horner's method    # https://en.wikipedia.org/wiki/Horner%27s_method    res = coeffs.isel({degree_dim: max_deg}, drop=True) + zeros_like(coord)    for deg in range(max_deg - 1, -1, -1):        res *= coord        res += coeffs.isel({degree_dim: deg}, drop=True)     deg_coord = coeffs[degree_dim]    return res     lhs = DataArray(        np.vander(x, int(deg_coord.max()) + 1),        dims=(coord.name, degree_dim),        coords={coord.name: coord, degree_dim: np.arange(deg_coord.max() + 1)[::-1]},    )    return (lhs * coeffs).sum(degree_dim)def _ensure_numeric(data: T_Xarray) -> T_Xarray:    """Converts all datetime64 variables to float64    Parameters    ----------    data : DataArray or Dataset        Variables with possible datetime dtypes.    Returns    -------    DataArray or Dataset        Variables with datetime64 dtypes converted to float64.    """    from .dataset import Dataset    def to_floatable(x: DataArray) -> DataArray:        if x.dtype.kind in "mM":            return x.copy(                data=datetime_to_numeric(                    x.data,                    offset=np.datetime64("1970-01-01"),                    datetime_unit="ns",                ),            )        return x    if isinstance(data, Dataset):        return data.map(to_floatable)    else:        return to_floatable(data)   def _calc_idxminmax(


pd.to_timedelta(flat_num_dates.max(), delta) + ref_date      # To avoid integer overflow when converting to nanosecond units for integer    # dtypes smaller than np.int64 cast all integer-dtype arrays to np.int64    # (GH 2002).    if flat_num_dates.dtype.kind == "i":    # dtypes smaller than np.int64 cast all integer and unsigned integer dtype    # arrays to np.int64 (GH 2002, GH 6589).  Note this is safe even in the case    # of np.uint64 values, because any np.uint64 value that would lead to    # overflow when converting to np.int64 would not be representable with a    # timedelta64 value, and therefore would raise an error in the lines above.    if flat_num_dates.dtype.kind in "iu":         flat_num_dates = flat_num_dates.astype(np.int64)      # Cast input ordinals to integers of nanoseconds because pd.to_timedelta


from .dataset import Dataset      def to_floatable(x: DataArray) -> DataArray:        if x.dtype.kind in "mM":        if x.dtype.kind == "M":            # datetimes             return x.copy(                 data=datetime_to_numeric(                     x.data,


datetime_unit="ns",                 ),             )<ins>        elif x.dtype.kind == "m":            # timedeltas            return x.astype(float)</ins>         return x      if isinstance(data, Dataset):


# using Horner's method     # https://en.wikipedia.org/wiki/Horner%27s_method    res = coeffs.isel({degree_dim: max_deg}, drop=True) + zeros_like(coord)    res = zeros_like(coord) + coeffs.isel({degree_dim: max_deg}, drop=True)     for deg in range(max_deg - 1, -1, -1):         res *= coord         res += coeffs.isel({degree_dim: deg}, drop=True)


chunks: dict[Any, tuple[int, ...]] = {}     for v in variables:        if hasattr(v.data, "chunks"):        if hasattr(v._data, "chunks"):             for dim, c in v.chunksizes.items():                 if dim in chunks and c != chunks[dim]:                     raise ValueError(


# TODO legacy attribute         self.window_labels = self.obj[self.dim[0]]     def __iter__(self) -> Iterator[tuple[RollingKey, DataArray]]:    def __iter__(self) -> Iterator[tuple[DataArray, DataArray]]:         if self.ndim > 1:             raise ValueError("__iter__ is only supported for 1d-rolling")        stops = np.arange(1, len(self.window_labels) + 1)        starts = stops - int(self.window[0])        starts[: int(self.window[0])] = 0        dim0 = self.dim[0]        window0 = int(self.window[0])        offset = (window0 + 1) // 2 if self.center[0] else 1        stops = np.arange(offset, self.obj.sizes[dim0] + offset)        starts = stops - window0        starts[: window0 - offset] = 0         for (label, start, stop) in zip(self.window_labels, starts, stops):            window = self.obj.isel({self.dim[0]: slice(start, stop)})            window = self.obj.isel({dim0: slice(start, stop)})             counts = window.count(dim=self.dim[0])            counts = window.count(dim=dim0)             window = window.where(counts >= self.min_periods)              yield (label, window)


from __future__ import annotations <ins>import warnings</ins> from contextlib import contextmanager from typing import TYPE_CHECKING, Any, Hashable, Iterator, Mapping, Sequence, cast


import pandas as pd  from . import formattingfrom .indexes import Index, Indexes, assert_no_index_corruptedfrom .indexes import Index, Indexes, PandasMultiIndex, assert_no_index_corrupted from .merge import merge_coordinates_without_align, merge_coords from .utils import Frozen, ReprObject from .variable import Variable, calculate_dimensions


def _update_coords(self, coords, indexes):         raise NotImplementedError() <ins>    def _maybe_drop_multiindex_coords(self, coords):        raise NotImplementedError()</ins>     def __iter__(self) -> Iterator[Hashable]:         # needs to be in the same order as the dataset variables         for k in self.variables:


def update(self, other: Mapping[Any, Any]) -> None:         other_vars = getattr(other, "variables", other)<ins>        self._maybe_drop_multiindex_coords(set(other_vars))</ins>         coords, indexes = merge_coords(             [self.variables, other_vars], priority_arg=1, indexes=self.xindexes         )


original_indexes.update(indexes)         self._data._indexes = original_indexes <ins>    def _maybe_drop_multiindex_coords(self, coords: set[Hashable]) -> None:        """Drops variables in coords, and any associated variables as well."""        assert self._data.xindexes is not None        variables, indexes = drop_coords(            coords, self._data._variables, self._data.xindexes        )        self._data._variables = variables        self._data._indexes = indexes</ins>     def __delitem__(self, key: Hashable) -> None:         if key in self:             del self._data[key]


original_indexes.update(indexes)         self._data._indexes = original_indexes <ins>    def _maybe_drop_multiindex_coords(self, coords: set[Hashable]) -> None:        """Drops variables in coords, and any associated variables as well."""        variables, indexes = drop_coords(            coords, self._data._coords, self._data.xindexes        )        self._data._coords = variables        self._data._indexes = indexes</ins>     @property     def variables(self):         return Frozen(self._data._coords)