Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 10 additions & 26 deletions lib/iris/_concatenate.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ def __new__(mcs, coord, dims):
kwargs["circular"] = coord.circular
if isinstance(coord, iris.coords.DimCoord):
# Mix the monotonic ordering into the metadata.
if coord.core_points()[0] == coord.core_points()[-1]:
if coord.points[0] == coord.points[-1]:
order = _CONSTANT
elif coord.core_points()[-1] > coord.core_points()[0]:
elif coord.points[-1] > coord.points[0]:
order = _INCREASING
else:
order = _DECREASING
Expand Down Expand Up @@ -775,37 +775,21 @@ def _calculate_extents(self):
self.dim_extents = []
for coord, order in zip(self.dim_coords, self.dim_order):
if order == _CONSTANT or order == _INCREASING:
points = _Extent(
coord.core_points()[0], coord.core_points()[-1]
)
if coord.core_bounds() is not None:
points = _Extent(coord.points[0], coord.points[-1])
if coord.bounds is not None:
bounds = (
_Extent(
coord.core_bounds()[0, 0],
coord.core_bounds()[-1, 0],
),
_Extent(
coord.core_bounds()[0, 1],
coord.core_bounds()[-1, 1],
),
_Extent(coord.bounds[0, 0], coord.bounds[-1, 0]),
_Extent(coord.bounds[0, 1], coord.bounds[-1, 1]),
)
else:
bounds = None
else:
# The order must be decreasing ...
points = _Extent(
coord.core_points()[-1], coord.core_points()[0]
)
if coord.core_bounds() is not None:
points = _Extent(coord.points[-1], coord.points[0])
if coord.bounds is not None:
bounds = (
_Extent(
coord.core_bounds()[-1, 0],
coord.core_bounds()[0, 0],
),
_Extent(
coord.core_bounds()[-1, 1],
coord.core_bounds()[0, 1],
),
_Extent(coord.bounds[-1, 0], coord.bounds[0, 0]),
_Extent(coord.bounds[-1, 1], coord.bounds[0, 1]),
)
else:
bounds = None
Expand Down
136 changes: 135 additions & 1 deletion lib/iris/tests/unit/concatenate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,138 @@
# This file is part of Iris and is released under the LGPL license.
# See COPYING and COPYING.LESSER in the root of the repository for full
# licensing details.
"""Unit tests for the :mod:`iris._concatenate` package."""
"""Unit-test infrastructure for the :mod:`iris._concatenate` package."""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any

import dask.array as da
import numpy as np

from iris._concatenate import _CONSTANT, _DECREASING, _INCREASING
import iris.common
from iris.coords import AuxCoord, DimCoord

__all__ = ["ExpectedItem", "N_POINTS", "SCALE_FACTOR", "create_metadata"]

# number of coordinate points
N_POINTS: int = 10

# coordinate points multiplication scale factor
SCALE_FACTOR: int = 10


METADATA = {
"standard_name": "air_temperature",
"long_name": "air temperature",
"var_name": "atemp",
"units": "kelvin",
"attributes": {},
"coord_system": None,
"climatological": False,
"circular": False,
}


@dataclass
class ExpectedItem:
"""Expected test result components of :class:`iris._concatenate._CoordMetaData`."""

defn: iris.common.DimCoordMetadata | iris.common.CoordMetadata
dims: tuple[int, ...]
points_dtype: np.dtype
bounds_dtype: np.dtype | None = None
kwargs: dict[str, Any] = field(default_factory=dict)


@dataclass
class MetaDataItem:
"""Test input and expected output from :class:`iris._concatenate._CoordMetaData`."""

coord: AuxCoord | DimCoord
dims: tuple[int, ...]
expected: ExpectedItem


def create_metadata(
dim_coord: bool = True,
scalar: bool = False,
order: int = None,
circular: bool | None = False,
coord_dtype: np.dtype = None,
lazy: bool = True,
with_bounds: bool | None = False,
) -> MetaDataItem:
"""Construct payload for :class:`iris._concatenate.CoordMetaData` testing."""
if coord_dtype is None:
coord_dtype = np.float32

if order is None:
order = _INCREASING

array_lib = da if lazy else np
bounds = None

if scalar:
points = array_lib.ones(1, dtype=coord_dtype)
order = _CONSTANT

if with_bounds:
bounds = array_lib.array([0, 2], dtype=coord_dtype).reshape(1, 2)
else:
if order == _CONSTANT:
points = array_lib.ones(N_POINTS, dtype=coord_dtype)
else:
if order == _DECREASING:
start, stop, step = N_POINTS - 1, -1, -1
else:
start, stop, step = 0, N_POINTS, 1
points = (
array_lib.arange(start, stop, step, dtype=coord_dtype)
* SCALE_FACTOR
)

if with_bounds:
offset = SCALE_FACTOR // 2
bounds = array_lib.vstack(
[points.copy() - offset, points.copy() + offset]
).T

bounds_dtype = coord_dtype if with_bounds else None

values = METADATA.copy()
values["circular"] = circular
CoordClass = DimCoord if dim_coord else AuxCoord
coord = CoordClass(points, bounds=bounds)
if dim_coord and lazy:
# creating a DimCoord *always* results in realized points/bounds.
assert not coord.has_lazy_points()
if with_bounds:
assert not coord.has_lazy_bounds()
metadata = iris.common.DimCoordMetadata(**values)

if dim_coord:
coord.metadata = metadata
else:
# convert the DimCoordMetadata to a CoordMetadata instance
# and assign to the AuxCoord
coord.metadata = iris.common.CoordMetadata.from_metadata(metadata)

dims = tuple([dim for dim in range(coord.ndim)])
kwargs = {"scalar": scalar}

if dim_coord:
kwargs["circular"] = circular
kwargs["order"] = order

expected = ExpectedItem(
defn=metadata,
dims=dims,
points_dtype=coord_dtype,
bounds_dtype=bounds_dtype,
kwargs=kwargs,
)

return MetaDataItem(coord=coord, dims=dims, expected=expected)
117 changes: 117 additions & 0 deletions lib/iris/tests/unit/concatenate/test__CoordMetaData.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Copyright Iris contributors
#
# This file is part of Iris and is released under the LGPL license.
# See COPYING and COPYING.LESSER in the root of the repository for full
# licensing details.
"""Unit-tests for :class:`iris._concatenate._CoordMetaData`."""

from __future__ import annotations

import numpy as np
import pytest

from iris._concatenate import (
_CONSTANT,
_DECREASING,
_INCREASING,
_CoordMetaData,
)

from . import ExpectedItem, create_metadata


def check(actual: _CoordMetaData, expected: ExpectedItem) -> None:
"""Assert actual and expected results."""
assert actual.defn == expected.defn
assert actual.dims == expected.dims
assert actual.points_dtype == expected.points_dtype
assert actual.bounds_dtype == expected.bounds_dtype
assert actual.kwargs == expected.kwargs


@pytest.mark.parametrize("order", [_DECREASING, _INCREASING])
@pytest.mark.parametrize("circular", [False, True])
@pytest.mark.parametrize("coord_dtype", [np.int32, np.float32])
@pytest.mark.parametrize("lazy", [False, True])
@pytest.mark.parametrize("with_bounds", [False, True])
def test_dim(
order: int,
circular: bool,
coord_dtype: np.dtype,
lazy: bool,
with_bounds: bool,
) -> None:
"""Test :class:`iris._concatenate._CoordMetaData` with dim coord."""
metadata = create_metadata(
dim_coord=True,
scalar=False,
order=order,
circular=circular,
coord_dtype=coord_dtype,
lazy=lazy,
with_bounds=with_bounds,
)
actual = _CoordMetaData(coord=metadata.coord, dims=metadata.dims)
check(actual, metadata.expected)


@pytest.mark.parametrize("circular", [False, True])
@pytest.mark.parametrize("coord_dtype", [np.int32, np.float32])
@pytest.mark.parametrize("lazy", [False, True])
@pytest.mark.parametrize("with_bounds", [False, True])
def test_dim__scalar(
circular: bool, coord_dtype: np.dtype, lazy: bool, with_bounds: bool
) -> None:
"""Test :class:`iris._concatenate._CoordMetaData` with scalar dim coord."""
metadata = create_metadata(
dim_coord=True,
scalar=True,
order=_CONSTANT,
circular=circular,
coord_dtype=coord_dtype,
lazy=lazy,
with_bounds=with_bounds,
)
actual = _CoordMetaData(coord=metadata.coord, dims=metadata.dims)
check(actual, metadata.expected)


@pytest.mark.parametrize("order", [_DECREASING, _INCREASING])
@pytest.mark.parametrize("coord_dtype", [np.int32, np.float32])
@pytest.mark.parametrize("lazy", [False, True])
@pytest.mark.parametrize("with_bounds", [False, True])
def test_aux(
order: int, coord_dtype: np.dtype, lazy: bool, with_bounds: bool
) -> None:
"""Test :class:`iris._concatenate._CoordMetaData` with aux coord."""
metadata = create_metadata(
dim_coord=False,
scalar=False,
order=order,
circular=None,
coord_dtype=coord_dtype,
lazy=lazy,
with_bounds=with_bounds,
)
actual = _CoordMetaData(coord=metadata.coord, dims=metadata.dims)
check(actual, metadata.expected)


@pytest.mark.parametrize("coord_dtype", [np.int32, np.float32])
@pytest.mark.parametrize("lazy", [False, True])
@pytest.mark.parametrize("with_bounds", [False, True])
def test_aux__scalar(
coord_dtype: np.dtype, lazy: bool, with_bounds: bool
) -> None:
"""Test :class:`iris._concatenate._CoordMetaData` with scalar aux coord."""
metadata = create_metadata(
dim_coord=False,
scalar=True,
order=_CONSTANT,
circular=None,
coord_dtype=coord_dtype,
lazy=lazy,
with_bounds=with_bounds,
)
actual = _CoordMetaData(coord=metadata.coord, dims=metadata.dims)
check(actual, metadata.expected)
Loading