Exemple #1
0
def uv_cubes(x=None, y=None):
    """Return u, v cubes with a grid in a rotated pole CRS."""
    cs = iris.coord_systems.RotatedGeogCS(grid_north_pole_latitude=37.5,
                                          grid_north_pole_longitude=177.5)
    if x is None:
        x = np.linspace(311.9, 391.1, 6)
    if y is None:
        y = np.linspace(-23.6, 24.8, 5)

    x2d, y2d = np.meshgrid(x, y)
    u = 10 * (2 * np.cos(2 * np.deg2rad(x2d) + 3 * np.deg2rad(y2d + 30))**2)
    v = 20 * np.cos(6 * np.deg2rad(x2d))
    lon = DimCoord(x,
                   standard_name="grid_longitude",
                   units="degrees",
                   coord_system=cs)
    lat = DimCoord(y,
                   standard_name="grid_latitude",
                   units="degrees",
                   coord_system=cs)
    u_cube = Cube(u, standard_name="x_wind", units="m/s")
    v_cube = Cube(v, standard_name="y_wind", units="m/s")
    for cube in (u_cube, v_cube):
        cube.add_dim_coord(lat.copy(), 0)
        cube.add_dim_coord(lon.copy(), 1)
    return u_cube, v_cube
Exemple #2
0
    def test_attributes_subtle_differences(self):
        cube = Cube([0])

        # Add a pair that differ only in having a list instead of an array.
        co1a = DimCoord(
            [0],
            long_name="co1_list_or_array",
            attributes=dict(x=1, arr1=np.array(2), arr2=np.array([1, 2])),
        )
        co1b = co1a.copy()
        co1b.attributes.update(dict(arr2=[1, 2]))
        for co in (co1a, co1b):
            cube.add_aux_coord(co)

        # Add a pair that differ only in an attribute array dtype.
        co2a = AuxCoord(
            [0],
            long_name="co2_dtype",
            attributes=dict(x=1, arr1=np.array(2), arr2=np.array([3, 4])),
        )
        co2b = co2a.copy()
        co2b.attributes.update(dict(arr2=np.array([3.0, 4.0])))
        assert co2b != co2a
        for co in (co2a, co2b):
            cube.add_aux_coord(co)

        # Add a pair that differ only in an attribute array shape.
        co3a = DimCoord(
            [0],
            long_name="co3_shape",
            attributes=dict(x=1, arr1=np.array([5, 6]), arr2=np.array([3, 4])),
        )
        co3b = co3a.copy()
        co3b.attributes.update(dict(arr1=np.array([[5], [6]])))
        for co in (co3a, co3b):
            cube.add_aux_coord(co)

        rep = iris._representation.CubeSummary(cube)
        co_summs = rep.scalar_sections["Scalar coordinates:"].contents
        co1a_summ, co1b_summ = co_summs[0:2]
        self.assertEqual(co1a_summ.extra, "arr2=array([1, 2])")
        self.assertEqual(co1b_summ.extra, "arr2=[1, 2]")
        co2a_summ, co2b_summ = co_summs[2:4]
        self.assertEqual(co2a_summ.extra, "arr2=array([3, 4])")
        self.assertEqual(co2b_summ.extra, "arr2=array([3., 4.])")
        co3a_summ, co3b_summ = co_summs[4:6]
        self.assertEqual(co3a_summ.extra, "arr1=array([5, 6])")
        self.assertEqual(co3b_summ.extra, "arr1=array([[5], [6]])")
 def setUp(self):
     """Start tests."""
     coord = DimCoord([1, 2], var_name='coord')
     second_coord = coord.copy([3, 4])
     third_coord = coord.copy([5, 6])
     self.raw_cubes = []
     self.raw_cubes.append(
         Cube([1, 2], var_name='sample',
              dim_coords_and_dims=((coord, 0), )))
     self.raw_cubes.append(
         Cube([3, 4],
              var_name='sample',
              dim_coords_and_dims=((second_coord, 0), )))
     self.raw_cubes.append(
         Cube([5, 6],
              var_name='sample',
              dim_coords_and_dims=((third_coord, 0), )))
Exemple #4
0
class TestCoordsValues(tests.IrisTest):
    def setUp(self):
        self.ref_1d = DimCoord([1, 2, 3, 4], long_name='a')
        self.ref_1d_bounded = self.ref_1d.copy()
        self.ref_1d_bounded.guess_bounds()
        self.ref_multidim = AuxCoord(
            [[[1, 2, 3], [6, 5, 4]], [[0, 0, 1], [9, 9, 9]]], long_name='b')
        self.value_msg = "significantly different values"

    def _coords_eq(self, c1, c2, msg='', err=''):
        _check_compare_result(self, compare_coords, c1, c2, msg=msg, err=err)

    def test_1d_invert(self):
        c1 = self.ref_1d
        c2 = c1.copy()
        c2 = c2[::-1]
        self._coords_eq(c1, c2, msg='different points arrays')

    def test_fail_1d_points_differ(self):
        c1 = self.ref_1d
        c2 = c1.copy()
        pts = c2.points.copy()
        pts[-1] += 5
        c2.points = pts
        self._coords_eq(c1, c2, err=self.value_msg)

    def test_fail_1d_bounds_differ(self):
        c1 = self.ref_1d_bounded
        c2 = c1.copy()
        bds = c2.bounds.copy()
        bds[0, 0] -= 5.0
        c2.bounds = bds
        self._coords_eq(c1, c2, err=self.value_msg)

    def test_multidim_invert_0(self):
        c1 = self.ref_multidim
        c2 = c1.copy()
        pts = c2.points.copy()
        pts = pts[::-1]
        c2.points = pts
        self._coords_eq(c1, c2, msg='different points arrays')

    def test_multidim_invert_1(self):
        c1 = self.ref_multidim
        c2 = c1.copy()
        pts = c2.points.copy()
        pts = pts[:, ::-1, :]
        c2.points = pts
        self._coords_eq(c1, c2, msg='different points arrays')

    def test_multidim_invert_02(self):
        c1 = self.ref_multidim
        c2 = c1.copy()
        pts = c2.points.copy()
        pts = pts[::-1, :, ::-1]
        c2.points = pts
        self._coords_eq(c1, c2, msg='different points arrays')
Exemple #5
0
    def _coords_are_broadcastable(coord1: DimCoord, coord2: DimCoord) -> bool:
        """
        Broadcastable coords will differ only in length, so create a copy of one with
        the points and bounds of the other and compare. Also ensure length of at least
        one of the coords is 1.
        """
        coord_copy = coord1.copy(coord2.points, bounds=coord2.bounds)

        return (coord_copy == coord2) and ((len(coord1.points) == 1) or
                                           (len(coord2.points) == 1))
Exemple #6
0
def uv_cubes(x=None, y=None):
    """Return u, v cubes with a grid in a rotated pole CRS."""
    cs = iris.coord_systems.RotatedGeogCS(grid_north_pole_latitude=37.5,
                                          grid_north_pole_longitude=177.5)
    if x is None:
        x = np.linspace(311.9, 391.1, 6)
    if y is None:
        y = np.linspace(-23.6, 24.8, 5)

    x2d, y2d = np.meshgrid(x, y)
    u = 10 * (2 * np.cos(2 * np.deg2rad(x2d) + 3 * np.deg2rad(y2d + 30)) ** 2)
    v = 20 * np.cos(6 * np.deg2rad(x2d))
    lon = DimCoord(x, standard_name='grid_longitude', units='degrees',
                   coord_system=cs)
    lat = DimCoord(y, standard_name='grid_latitude', units='degrees',
                   coord_system=cs)
    u_cube = Cube(u, standard_name='x_wind', units='m/s')
    v_cube = Cube(v, standard_name='y_wind', units='m/s')
    for cube in (u_cube, v_cube):
        cube.add_dim_coord(lat.copy(), 0)
        cube.add_dim_coord(lon.copy(), 1)
    return u_cube, v_cube
Exemple #7
0
    def test_writable_points(self):
        coord1 = DimCoord(np.arange(5),
                          bounds=[[0, 1], [1, 2], [2, 3], [3, 4], [4, 5]])
        coord2 = coord1.copy()
        msg = 'destination is read-only'

        with self.assertRaisesRegexp(ValueError, msg):
            coord1.points[:] = 0

        with self.assertRaisesRegexp(ValueError, msg):
            coord2.points[:] = 0

        with self.assertRaisesRegexp(ValueError, msg):
            coord1.bounds[:] = 0

        with self.assertRaisesRegexp(ValueError, msg):
            coord2.bounds[:] = 0
Exemple #8
0
    def test_writable_points(self):
        coord1 = DimCoord(np.arange(5),
                          bounds=[[0, 1], [1, 2], [2, 3], [3, 4], [4, 5]])
        coord2 = coord1.copy()
        msg = 'destination is read-only'

        with self.assertRaisesRegexp(ValueError, msg):
            coord1.points[:] = 0

        with self.assertRaisesRegexp(ValueError, msg):
            coord2.points[:] = 0

        with self.assertRaisesRegexp(ValueError, msg):
            coord1.bounds[:] = 0

        with self.assertRaisesRegexp(ValueError, msg):
            coord2.bounds[:] = 0
Exemple #9
0
def uv_cubes_3d(ref_cube, n_realization=3):
    """
    Return 3d u, v cubes with a grid in a rotated pole CRS taken from
    the provided 2d cube, by adding a realization dimension
    coordinate bound to teh zeroth dimension.

    """
    lat = ref_cube.coord("grid_latitude")
    lon = ref_cube.coord("grid_longitude")
    x2d, y2d = np.meshgrid(lon.points, lat.points)
    u = 10 * (2 * np.cos(2 * np.deg2rad(x2d) + 3 * np.deg2rad(y2d + 30))**2)
    v = 20 * np.cos(6 * np.deg2rad(x2d))
    # Multiply slices by factor to give variation over 0th dim.
    factor = np.arange(1, n_realization + 1).reshape(n_realization, 1, 1)
    u = factor * u
    v = factor * v
    realization = DimCoord(np.arange(n_realization), "realization")
    u_cube = Cube(u, standard_name="x_wind", units="m/s")
    v_cube = Cube(v, standard_name="y_wind", units="m/s")
    for cube in (u_cube, v_cube):
        cube.add_dim_coord(realization.copy(), 0)
        cube.add_dim_coord(lat.copy(), 1)
        cube.add_dim_coord(lon.copy(), 2)
    return u_cube, v_cube
Exemple #10
0
def uv_cubes_3d(ref_cube, n_realization=3):
    """
    Return 3d u, v cubes with a grid in a rotated pole CRS taken from
    the provided 2d cube, by adding a realization dimension
    coordinate bound to teh zeroth dimension.

    """
    lat = ref_cube.coord('grid_latitude')
    lon = ref_cube.coord('grid_longitude')
    x2d, y2d = np.meshgrid(lon.points, lat.points)
    u = 10 * (2 * np.cos(2 * np.deg2rad(x2d) + 3 * np.deg2rad(y2d + 30)) ** 2)
    v = 20 * np.cos(6 * np.deg2rad(x2d))
    # Multiply slices by factor to give variation over 0th dim.
    factor = np.arange(1, n_realization + 1).reshape(n_realization, 1, 1)
    u = factor * u
    v = factor * v
    realization = DimCoord(np.arange(n_realization), 'realization')
    u_cube = Cube(u, standard_name='x_wind', units='m/s')
    v_cube = Cube(v, standard_name='y_wind', units='m/s')
    for cube in (u_cube, v_cube):
        cube.add_dim_coord(realization.copy(), 0)
        cube.add_dim_coord(lat.copy(), 1)
        cube.add_dim_coord(lon.copy(), 2)
    return u_cube, v_cube
Exemple #11
0
def load_NAMEIII_trajectory(filename):
    """
    Load a NAME III trajectory file returning a
    generator of :class:`iris.cube.Cube` instances.

    Args:

    * filename (string):
        Name of file to load.

    Returns:
        A generator :class:`iris.cube.Cube` instances.

    """
    time_unit = iris.unit.Unit('hours since epoch',
                               calendar=iris.unit.CALENDAR_GREGORIAN)

    with open(filename, 'r') as infile:
        header = read_header(infile)

        # read the column headings
        for line in infile:
            if line.startswith("    "):
                break
        headings = [heading.strip() for heading in line.split(",")]

        # read the columns
        columns = [[] for i in range(len(headings))]
        for line in infile:
            values = [v.strip() for v in line.split(",")]
            for c, v in enumerate(values):
                if "UTC" in v:
                    v = v.replace(":00 ", " ")  # Strip out milliseconds.
                    v = datetime.datetime.strptime(v, NAMEIII_DATETIME_FORMAT)
                else:
                    try:
                        v = float(v)
                    except ValueError:
                        pass
                columns[c].append(v)

    # Where's the Z column?
    z_column = None
    for i, heading in enumerate(headings):
        if heading.startswith("Z "):
            z_column = i
            break
    if z_column is None:
        raise iris.exceptions.TranslationError("Expected a Z column")

    # Every column up to Z becomes a coordinate.
    coords = []
    for name, values in izip(headings[:z_column + 1], columns[:z_column + 1]):
        values = np.array(values)
        if np.all(np.array(values) == values[0]):
            values = [values[0]]

        standard_name = long_name = units = None
        if isinstance(values[0], datetime.datetime):
            values = time_unit.date2num(values)
            units = time_unit
            if name == "Time":
                name = "time"
        elif " (Lat-Long)" in name:
            if name.startswith("X"):
                name = "longitude"
            elif name.startswith("Y"):
                name = "latitude"
            units = "degrees"
        elif name == "Z (m asl)":
            name = "height"
            units = "m"

        try:
            coord = DimCoord(values, units=units)
        except ValueError:
            coord = AuxCoord(values, units=units)
        coord.rename(name)
        coords.append(coord)

    # Every numerical column after the Z becomes a cube.
    for name, values in izip(headings[z_column + 1:], columns[z_column + 1:]):
        try:
            float(values[0])
        except ValueError:
            continue
        # units embedded in column heading?
        name, units = _split_name_and_units(name)
        cube = iris.cube.Cube(values, units=units)
        cube.rename(name)
        for coord in coords:
            dim = 0 if len(coord.points) > 1 else None
            if isinstance(coord, DimCoord) and coord.name() == "time":
                cube.add_dim_coord(coord.copy(), dim)
            else:
                cube.add_aux_coord(coord.copy(), dim)
        yield cube
Exemple #12
0
def load_NAMEIII_trajectory(filename):
    """
    Load a NAME III trajectory file returning a
    generator of :class:`iris.cube.Cube` instances.

    Args:

    * filename (string):
        Name of file to load.

    Returns:
        A generator :class:`iris.cube.Cube` instances.

    """
    time_unit = cf_units.Unit('hours since epoch',
                              calendar=cf_units.CALENDAR_GREGORIAN)

    with open(filename, 'r') as infile:
        header = read_header(infile)

        # read the column headings
        for line in infile:
            if line.startswith("    "):
                break
        headings = [heading.strip() for heading in line.split(",")]

        # read the columns
        columns = [[] for i in range(len(headings))]
        for line in infile:
            values = [v.strip() for v in line.split(",")]
            for c, v in enumerate(values):
                if "UTC" in v:
                    v = v.replace(":00 ", " ")  # Strip out milliseconds.
                    v = datetime.datetime.strptime(v, NAMEIII_DATETIME_FORMAT)
                else:
                    try:
                        v = float(v)
                    except ValueError:
                        pass
                columns[c].append(v)

    # Where's the Z column?
    z_column = None
    for i, heading in enumerate(headings):
        if heading.startswith("Z "):
            z_column = i
            break
    if z_column is None:
        raise TranslationError("Expected a Z column")

    # Every column up to Z becomes a coordinate.
    coords = []
    for name, values in zip(headings[:z_column+1], columns[:z_column+1]):
        values = np.array(values)
        if np.all(np.array(values) == values[0]):
            values = [values[0]]

        standard_name = long_name = units = None
        if isinstance(values[0], datetime.datetime):
            values = time_unit.date2num(values)
            units = time_unit
            if name == "Time":
                name = "time"
        elif " (Lat-Long)" in name:
            if name.startswith("X"):
                name = "longitude"
            elif name.startswith("Y"):
                name = "latitude"
            units = "degrees"
        elif name == "Z (m asl)":
            name = "altitude"
            units = "m"
            long_name = "altitude above sea level"
        elif name == "Z (m agl)":
            name = 'height'
            units = "m"
            long_name = "height above ground level"
        elif name == "Z (FL)":
            name = "flight_level"
            long_name = name

        try:
            coord = DimCoord(values, units=units)
        except ValueError:
            coord = AuxCoord(values, units=units)
        coord.rename(name)
        if coord.long_name is None and long_name is not None:
            coord.long_name = long_name
        coords.append(coord)

    # Every numerical column after the Z becomes a cube.
    for name, values in zip(headings[z_column+1:], columns[z_column+1:]):
        try:
            float(values[0])
        except ValueError:
            continue
        # units embedded in column heading?
        name, units = _split_name_and_units(name)
        cube = iris.cube.Cube(values, units=units)
        cube.rename(name)
        for coord in coords:
            dim = 0 if len(coord.points) > 1 else None
            if isinstance(coord, DimCoord) and coord.name() == "time":
                cube.add_dim_coord(coord.copy(), dim)
            else:
                cube.add_aux_coord(coord.copy(), dim)
        yield cube
Exemple #13
0
def load_NAMEIII_trajectory(filename):
    """
    Load a NAME III trajectory file returning a
    generator of :class:`iris.cube.Cube` instances.

    Args:

    * filename (string):
        Name of file to load.

    Returns:
        A generator :class:`iris.cube.Cube` instances.

    """
    time_unit = cf_units.Unit("hours since epoch",
                              calendar=cf_units.CALENDAR_GREGORIAN)

    with open(filename, "r") as infile:
        header = read_header(infile)

        # read the column headings
        for line in infile:
            if line.startswith("    "):
                break
        headings = [heading.strip() for heading in line.split(",")]

        # read the columns
        columns = [[] for i in range(len(headings))]
        for line in infile:
            values = [v.strip() for v in line.split(",")]
            for c, v in enumerate(values):
                if "UTC" in v:
                    v = datetime.datetime.strptime(v, NAMETRAJ_DATETIME_FORMAT)
                else:
                    try:
                        v = float(v)
                    except ValueError:
                        pass
                columns[c].append(v)

    # Sort columns according to PP Index
    columns_t = list(map(list, zip(*columns)))
    columns_t.sort(key=itemgetter(1))
    columns = list(map(list, zip(*columns_t)))

    # Where's the Z column?
    z_column = None
    for i, heading in enumerate(headings):
        if heading.startswith("Z "):
            z_column = i
            break
    if z_column is None:
        raise TranslationError("Expected a Z column")

    # Every column up to Z becomes a coordinate.
    coords = []
    for name, values in zip(headings[:z_column + 1], columns[:z_column + 1]):
        values = np.array(values)
        if np.all(np.array(values) == values[0]):
            values = [values[0]]

        long_name = units = None
        if isinstance(values[0], datetime.datetime):
            values = time_unit.date2num(values).astype(float)
            units = time_unit
            if name == "Time":
                name = "time"
        elif " (Lat-Long)" in name:
            if name.startswith("X"):
                name = "longitude"
            elif name.startswith("Y"):
                name = "latitude"
            units = "degrees"
        elif name == "Z (m asl)":
            name = "altitude"
            units = "m"
            long_name = "altitude above sea level"
        elif name == "Z (m agl)":
            name = "height"
            units = "m"
            long_name = "height above ground level"
        elif name == "Z (FL)":
            name = "flight_level"
            long_name = name

        try:
            coord = DimCoord(values, units=units)
        except ValueError:
            coord = AuxCoord(values, units=units)
        coord.rename(name)
        if coord.long_name is None and long_name is not None:
            coord.long_name = long_name
        coords.append(coord)

    # Every numerical column after the Z becomes a cube.
    for name, values in zip(headings[z_column + 1:], columns[z_column + 1:]):
        try:
            float(values[0])
        except ValueError:
            continue
        # units embedded in column heading?
        name, units = _split_name_and_units(name)
        cube = iris.cube.Cube(values, units=units)
        cube.rename(name)
        # Add the Main Headings as attributes.
        for key, value in header.items():
            if value is not None and value != "" and key not in headings:
                cube.attributes[key] = value
        # Add coordinates
        for coord in coords:
            dim = 0 if len(coord.points) > 1 else None
            if dim == 0 and coord.name() == "time":
                cube.add_dim_coord(coord.copy(), dim)
            elif dim == 0 and coord.name() == "PP Index":
                cube.add_dim_coord(coord.copy(), dim)
            else:
                cube.add_aux_coord(coord.copy(), dim)
        yield cube
class TestConcatenate(unittest.TestCase):
    """Tests for :func:`esmvalcore.preprocessor._io.concatenate`."""
    def setUp(self):
        """Start tests."""
        self._model_coord = DimCoord([1., 2.],
                                     var_name='time',
                                     standard_name='time',
                                     units='days since 1950-01-01')
        self.raw_cubes = []
        self._add_cube([1., 2.], [1., 2.])
        self._add_cube([3., 4.], [3., 4.])
        self._add_cube([5., 6.], [5., 6.])

    def _add_cube(self, data, coord):
        self.raw_cubes.append(
            Cube(data,
                 var_name='sample',
                 dim_coords_and_dims=((self._model_coord.copy(coord), 0), )))

    def test_concatenate(self):
        """Test concatenation of two cubes."""
        concatenated = _io.concatenate(self.raw_cubes)
        np.testing.assert_array_equal(
            concatenated.coord('time').points, np.array([1, 2, 3, 4, 5, 6]))

    def test_concatenate_empty_cubes(self):
        """Test concatenation with empty :class:`iris.cube.CubeList`."""
        empty_cubes = CubeList([])
        result = _io.concatenate(empty_cubes)
        assert result is empty_cubes

    def test_concatenate_noop(self):
        """Test concatenation of a single cube."""
        concatenated = _io.concatenate([self.raw_cubes[0]])
        np.testing.assert_array_equal(
            concatenated.coord('time').points, np.array([1, 2]))

    def test_concatenate_with_overlap(self):
        """Test concatenation of time overalapping cubes."""
        self._add_cube([6.5, 7.5], [6., 7.])
        concatenated = _io.concatenate(self.raw_cubes)
        np.testing.assert_array_equal(
            concatenated.coord('time').points,
            np.array([1., 2., 3., 4., 5., 6., 7.]))
        np.testing.assert_array_equal(concatenated.data,
                                      np.array([1., 2., 3., 4., 5., 6.5, 7.5]))

    def test_concatenate_with_overlap_2(self):
        """Test a more generic case."""
        self._add_cube([65., 75., 100.], [9., 10., 11.])
        self._add_cube([65., 75., 100.], [7., 8., 9.])
        concatenated = _io.concatenate(self.raw_cubes)
        np.testing.assert_array_equal(
            concatenated.coord('time').points,
            np.array([1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11.]))

    def test_concatenate_with_overlap_3(self):
        """Test a more generic case."""
        self._add_cube([65., 75., 100.], [9., 10., 11.])
        self._add_cube([65., 75., 100., 100., 100., 112.],
                       [7., 8., 9., 10., 11., 12.])
        concatenated = _io.concatenate(self.raw_cubes)
        np.testing.assert_array_equal(
            concatenated.coord('time').points,
            np.array([1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12.]))

    def test_concatenate_with_overlap_same_start(self):
        """Test a more generic case."""
        cube1 = self.raw_cubes[0]
        raw_cubes = [
            cube1,
        ]
        time_coord = DimCoord([1., 7.],
                              var_name='time',
                              standard_name='time',
                              units='days since 1950-01-01')
        raw_cubes.append(
            Cube([33., 55.],
                 var_name='sample',
                 dim_coords_and_dims=((time_coord, 0), )))
        concatenated = _io.concatenate(raw_cubes)
        np.testing.assert_array_equal(
            concatenated.coord('time').points, np.array([1., 7.]))
        raw_cubes.reverse()
        concatenated = _io.concatenate(raw_cubes)
        np.testing.assert_array_equal(
            concatenated.coord('time').points, np.array([1., 7.]))

    def test_concatenate_with_iris_exception(self):
        """Test a more generic case."""
        time_coord_1 = DimCoord([1.5, 5., 7.],
                                var_name='time',
                                standard_name='time',
                                units='days since 1950-01-01')
        cube1 = Cube([33., 55., 77.],
                     var_name='sample',
                     dim_coords_and_dims=((time_coord_1, 0), ))
        time_coord_2 = DimCoord([1., 5., 7.],
                                var_name='time',
                                standard_name='time',
                                units='days since 1950-01-01')
        cube2 = Cube([33., 55., 77.],
                     var_name='sample',
                     dim_coords_and_dims=((time_coord_2, 0), ))
        cubes_single_ovlp = [cube2, cube1]
        with self.assertRaises(ConcatenateError):
            _io.concatenate(cubes_single_ovlp)

    def test_concatenate_no_time_coords(self):
        """Test a more generic case."""
        time_coord_1 = DimCoord([1.5, 5., 7.],
                                var_name='time',
                                standard_name='time',
                                units='days since 1950-01-01')
        cube1 = Cube([33., 55., 77.],
                     var_name='sample',
                     dim_coords_and_dims=((time_coord_1, 0), ))
        ap_coord_2 = DimCoord([1., 5., 7.],
                              var_name='air_pressure',
                              standard_name='air_pressure',
                              units='m',
                              attributes={'positive': 'down'})
        cube2 = Cube([33., 55., 77.],
                     var_name='sample',
                     dim_coords_and_dims=((ap_coord_2, 0), ))
        with self.assertRaises(ValueError):
            _io.concatenate([cube1, cube2])

    def test_concatenate_with_order(self):
        """Test a more generic case."""
        time_coord_1 = DimCoord([1.5, 2., 5., 7.],
                                var_name='time',
                                standard_name='time',
                                units='days since 1950-01-01')
        cube1 = Cube([33., 44., 55., 77.],
                     var_name='sample',
                     dim_coords_and_dims=((time_coord_1, 0), ))
        time_coord_2 = DimCoord([1., 2., 5., 7., 100.],
                                var_name='time',
                                standard_name='time',
                                units='days since 1950-01-01')
        cube2 = Cube([33., 44., 55., 77., 1000.],
                     var_name='sample',
                     dim_coords_and_dims=((time_coord_2, 0), ))
        cubes_ordered = [cube2, cube1]
        concatenated = _io.concatenate(cubes_ordered)
        np.testing.assert_array_equal(
            concatenated.coord('time').points, np.array([1., 2., 5., 7.,
                                                         100.]))
        cubes_reverse = [cube1, cube2]
        concatenated = _io.concatenate(cubes_reverse)
        np.testing.assert_array_equal(
            concatenated.coord('time').points, np.array([1., 2., 5., 7.,
                                                         100.]))

    def test_fail_on_calendar_concatenate_with_overlap(self):
        """Test fail of concatenation with overlap."""
        time_coord = DimCoord([3., 7000.],
                              var_name='time',
                              standard_name='time',
                              units=Unit('days since 1950-01-01',
                                         calendar='360_day'))
        self.raw_cubes.append(
            Cube([33., 55.],
                 var_name='sample',
                 dim_coords_and_dims=((time_coord, 0), )))
        with self.assertRaises(TypeError):
            _io.concatenate(self.raw_cubes)

    def test_fail_on_units_concatenate_with_overlap(self):
        """Test fail of concatenation with overlap."""
        time_coord_1 = DimCoord([3., 7000.],
                                var_name='time',
                                standard_name='time',
                                units=Unit('days since 1950-01-01',
                                           calendar='360_day'))
        time_coord_2 = DimCoord([3., 9000.],
                                var_name='time',
                                standard_name='time',
                                units=Unit('days since 1950-01-01',
                                           calendar='360_day'))
        time_coord_3 = DimCoord([3., 9000.],
                                var_name='time',
                                standard_name='time',
                                units=Unit('days since 1850-01-01',
                                           calendar='360_day'))
        raw_cubes = []
        raw_cubes.append(
            Cube([33., 55.],
                 var_name='sample',
                 dim_coords_and_dims=((time_coord_1, 0), )))
        raw_cubes.append(
            Cube([33., 55.],
                 var_name='sample',
                 dim_coords_and_dims=((time_coord_2, 0), )))
        raw_cubes.append(
            Cube([33., 55.],
                 var_name='sample',
                 dim_coords_and_dims=((time_coord_3, 0), )))
        with self.assertRaises(ValueError):
            _io.concatenate(raw_cubes)

    def test_fail_metadata_differs(self):
        """Test exception raised if two cubes have different metadata."""
        self.raw_cubes[0].units = 'm'
        self.raw_cubes[1].units = 'K'
        with self.assertRaises(ValueError):
            _io.concatenate(self.raw_cubes)

    def test_fix_attributes(self):
        """Test fixing attributes for concatenation."""
        identical_attrs = {
            'int': 42,
            'float': 3.1415,
            'bool': True,
            'str': 'Hello, world',
            'list': [1, 1, 2, 3, 5, 8, 13],
            'tuple': (1, 2, 3, 4, 5),
            'dict': {
                1: 'one',
                2: 'two',
                3: 'three'
            },
            'nparray': np.arange(42),
        }
        differing_attrs = [
            {
                'new_int': 0,
                'new_str': 'hello',
                'new_nparray': np.arange(3),
                'mix': np.arange(2),
            },
            {
                'new_int': 1,
                'new_str': 'world',
                'new_list': [1, 1, 2],
                'new_tuple': (0, 1),
                'new_dict': {
                    0: 'zero',
                },
                'mix': {
                    1: 'one',
                },
            },
            {
                'new_str': '!',
                'new_list': [1, 1, 2, 3],
                'new_tuple': (1, 2, 3),
                'new_dict': {
                    0: 'zeroo',
                    1: 'one',
                },
                'new_nparray': np.arange(2),
                'mix': False,
            },
        ]
        resulting_attrs = {
            'new_int': '0;1',
            'new_str': 'hello;world;!',
            'new_nparray': '[0 1 2];[0 1]',
            'new_list': '[1, 1, 2];[1, 1, 2, 3]',
            'new_tuple': '(0, 1);(1, 2, 3)',
            'new_dict': "{0: 'zero'};{0: 'zeroo', 1: 'one'}",
            'mix': "[0 1];{1: 'one'};False",
        }
        resulting_attrs.update(identical_attrs)

        for idx in range(3):
            self.raw_cubes[idx].attributes = identical_attrs
            self.raw_cubes[idx].attributes.update(differing_attrs[idx])
        _io._fix_cube_attributes(self.raw_cubes)  # noqa
        for cube in self.raw_cubes:
            self.assertEqual(cube.attributes, resulting_attrs)
class Test1Dim(tests.IrisTest):
    def setUp(self):
        self.lon = DimCoord(
            points=[0.5, 1.5, 2.5],
            bounds=[[0, 1], [1, 2], [2, 3]],
            standard_name="longitude",
            long_name="edge longitudes",
            var_name="lon",
            units="degrees",
            attributes={"test": 1},
        )
        # Should be fine with either a DimCoord or an AuxCoord.
        self.lat = AuxCoord(
            points=[0.5, 2.5, 1.5],
            bounds=[[0, 1], [2, 3], [1, 2]],
            standard_name="latitude",
            long_name="edge_latitudes",
            var_name="lat",
            units="degrees",
            attributes={"test": 1},
        )

    def create(self):
        return Mesh.from_coords(self.lon, self.lat)

    def test_dimensionality(self):
        mesh = self.create()
        self.assertEqual(1, mesh.topology_dimension)

        self.assertArrayEqual([0, 1, 1, 2, 2, 3],
                              mesh.node_coords.node_x.points)
        self.assertArrayEqual([0, 1, 2, 3, 1, 2],
                              mesh.node_coords.node_y.points)
        self.assertArrayEqual([0.5, 1.5, 2.5], mesh.edge_coords.edge_x.points)
        self.assertArrayEqual([0.5, 2.5, 1.5], mesh.edge_coords.edge_y.points)
        self.assertIsNone(getattr(mesh, "face_coords", None))

        for conn_name in Connectivity.UGRID_CF_ROLES:
            conn = getattr(mesh, conn_name, None)
            if conn_name == "edge_node_connectivity":
                self.assertArrayEqual([[0, 1], [2, 3], [4, 5]], conn.indices)
            else:
                self.assertIsNone(conn)

    def test_node_metadata(self):
        mesh = self.create()
        pairs = [
            (self.lon, mesh.node_coords.node_x),
            (self.lat, mesh.node_coords.node_y),
        ]
        for expected_coord, actual_coord in pairs:
            for attr in ("standard_name", "long_name", "units", "attributes"):
                expected = getattr(expected_coord, attr)
                actual = getattr(actual_coord, attr)
                self.assertEqual(expected, actual)
            self.assertIsNone(actual_coord.var_name)

    def test_centre_metadata(self):
        mesh = self.create()
        pairs = [
            (self.lon, mesh.edge_coords.edge_x),
            (self.lat, mesh.edge_coords.edge_y),
        ]
        for expected_coord, actual_coord in pairs:
            for attr in ("standard_name", "long_name", "units", "attributes"):
                expected = getattr(expected_coord, attr)
                actual = getattr(actual_coord, attr)
                self.assertEqual(expected, actual)
            self.assertIsNone(actual_coord.var_name)

    def test_mesh_metadata(self):
        # Inappropriate to guess these values from the input coords.
        mesh = self.create()
        for attr in (
                "standard_name",
                "long_name",
                "var_name",
        ):
            self.assertIsNone(getattr(mesh, attr))
        self.assertTrue(mesh.units.is_unknown())
        self.assertDictEqual({}, mesh.attributes)

    def test_lazy(self):
        self.lon = AuxCoord.from_coord(self.lon)
        self.lon = self.lon.copy(self.lon.lazy_points(),
                                 self.lon.lazy_bounds())
        self.lat = self.lat.copy(self.lat.lazy_points(),
                                 self.lat.lazy_bounds())

        mesh = self.create()
        for coord in list(mesh.all_coords):
            if coord is not None:
                self.assertTrue(coord.has_lazy_points())
        for conn in list(mesh.all_connectivities):
            if conn is not None:
                self.assertTrue(conn.has_lazy_indices())

    def test_coord_shape_mismatch(self):
        lat_orig = self.lat.copy(self.lat.points, self.lat.bounds)
        self.lat = lat_orig.copy(points=lat_orig.points,
                                 bounds=np.tile(lat_orig.bounds, 2))
        with self.assertRaisesRegex(ValueError,
                                    "bounds shapes are not identical"):
            _ = self.create()

        self.lat = lat_orig.copy(points=lat_orig.points[-1],
                                 bounds=lat_orig.bounds[-1])
        with self.assertRaisesRegex(ValueError,
                                    "points shapes are not identical"):
            _ = self.create()

    def test_reorder(self):
        # Swap the coords.
        self.lat, self.lon = self.lon, self.lat
        mesh = self.create()
        # Confirm that the coords have been swapped back to the 'correct' order.
        self.assertEqual("longitude", mesh.node_coords.node_x.standard_name)
        self.assertEqual("latitude", mesh.node_coords.node_y.standard_name)

    def test_non_xy(self):
        for coord in self.lon, self.lat:
            coord.standard_name = None
        lon_name, lat_name = [
            coord.long_name for coord in (self.lon, self.lat)
        ]
        # Swap the coords.
        self.lat, self.lon = self.lon, self.lat
        with self.assertLogs(logger, "INFO", "Unable to find 'X' and 'Y'"):
            mesh = self.create()
        # Confirm that the coords have not been swapped back.
        self.assertEqual(lat_name, mesh.node_coords.node_x.long_name)
        self.assertEqual(lon_name, mesh.node_coords.node_y.long_name)