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
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), )))
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')
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))
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
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
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
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
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
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
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)