def test_hourly_mean(self): # lbtim.ia = 1 -> hourly field = mock.MagicMock(lbproc=128, lbtim=mock.Mock(ia=1, ib=2, ic=3)) res = _all_other_rules(field)[CELL_METHODS_INDEX] expected = [CellMethod('mean', 'time', '1 hour')] self.assertEqual(res, expected)
def test_other_lbtim_ib(self): # lbtim.ib = 5 -> non-specific aggregation field = mock.MagicMock(lbproc=4096, lbtim=mock.Mock(ia=24, ib=5, ic=3)) res = _all_other_rules(field)[CELL_METHODS_INDEX] expected = [CellMethod('minimum', 'time')] self.assertEqual(res, expected)
def test_lbcode3x23(self): time_bounds = np.array([[0.875, 1.125], [1.125, 1.375], [1.375, 1.625], [1.625, 1.875]]) field = mock.MagicMock( lbproc=0, bzx=0, bdx=0, lbnpt=3, lbrow=4, t1=nc_datetime(2000, 1, 2, hour=0, minute=0, second=0), t2=nc_datetime(2000, 1, 3, hour=0, minute=0, second=0), lbtim=mock.Mock(ia=1, ib=2, ic=2), lbcode=SplittableInt(31323, {'iy': slice(0, 2), 'ix': slice(2, 4)}), x_bounds=None, y_bounds=time_bounds, _x_coord_name=lambda: 'longitude', _y_coord_name=lambda: 'latitude') spec = ['lbtim', 'lbcode', 'lbrow', 'lbnpt', 'lbproc', 'lbsrce', 'lbuser', 'bzx', 'bdx', 'bdy', 'bmdi', 't1', 't2', 'stash', 'x_bounds', 'y_bounds', '_x_coord_name', '_y_coord_name'] field.mock_add_spec(spec) res = _all_other_rules(field)[DIM_COORDS_INDEX] expected_time_points = np.array([1, 1.25, 1.5, 1.75]) + (2000 * 360) expected_unit = Unit('days since 0000-01-01 00:00:00', calendar=CALENDAR_360_DAY) expected = [(DimCoord(expected_time_points, standard_name='time', units=expected_unit, bounds=time_bounds), 0)] self.assertCoordsAndDimsListsMatch(res, expected)
def test_daily_min(self): # lbproc = 4096 -> min field = mock.MagicMock(lbproc=4096, lbtim=mock.Mock(ia=24, ib=2, ic=3)) res = _all_other_rules(field)[CELL_METHODS_INDEX] expected = [CellMethod('minimum', 'time', '24 hour')] self.assertEqual(res, expected)
def test_hourly_mean_over_multiple_years(self): field = mock.MagicMock(lbproc=128, lbtim=mock.Mock(ia=1, ib=3, ic=3)) res = _all_other_rules(field)[CELL_METHODS_INDEX] expected = [CellMethod('mean within years', 'time', '1 hour'), CellMethod('mean over years', 'time')] self.assertEqual(res, expected)
def test_time_mean(self): # lbproc = 128 -> mean # lbtim.ib = 2 -> simple t1 to t2 interval. field = mock.MagicMock(lbproc=128, lbtim=mock.Mock(ia=0, ib=2, ic=3)) res = _all_other_rules(field)[CELL_METHODS_INDEX] expected = [CellMethod('mean', 'time')] self.assertEqual(res, expected)
def test_time_mean_over_multiple_years(self): # lbtim.ib = 3 -> interval within a year, over multiple years. field = mock.MagicMock(lbproc=128, lbtim=mock.Mock(ia=0, ib=3, ic=3)) res = _all_other_rules(field)[CELL_METHODS_INDEX] expected = [CellMethod('mean within years', 'time'), CellMethod('mean over years', 'time')] self.assertEqual(res, expected)
def test_multiple_unordered_rotated_lbprocs(self): field = mock.MagicMock(lbproc=192, bzx=0, bdx=1, lbnpt=3, lbrow=3, lbtim=mock.Mock(ia=24, ib=5, ic=3), lbcode=SplittableInt(101), x_bounds=None, _x_coord_name=lambda: 'grid_longitude', _y_coord_name=lambda: 'grid_latitude') res = _all_other_rules(field)[CELL_METHODS_INDEX] expected = [CellMethod('mean', 'time'), CellMethod('mean', 'grid_longitude')] self.assertEqual(res, expected)
def test_multiple_unordered_lbprocs(self): field = mock.MagicMock(lbproc=192, lbtim=mock.Mock(ia=24, ib=5, ic=3), lbcode=SplittableInt(1), _x_coord_name=lambda: 'longitude', _y_coord_name=lambda: 'latitude') res = _all_other_rules(field)[CELL_METHODS_INDEX] expected = [CellMethod('mean', 'time'), CellMethod('mean', 'longitude')] self.assertEqual(res, expected)
def test_month_coord(self): field = self._make_field() field.mock_add_spec(self._spec) res = _all_other_rules(field)[AUX_COORDS_INDEX] expected = [(AuxCoord(3, long_name='month_number'), None), (AuxCoord('Mar', long_name='month', units=Unit('no unit')), None), (DimCoord(points=0, standard_name='forecast_period', units=Unit('hours')), None)] self.assertCoordsAndDimsListsMatch(res, expected)
def test_nonzero_yeard(self): field = self._make_field(lbyrd=1) field.mock_add_spec(self._spec) res = _all_other_rules(field)[AUX_COORDS_INDEX] self.assertCoordsAndDimsListsMatch(res, [])
def test_climatology_max(self): field = mock.MagicMock(lbproc=4096, lbtim=mock.Mock(ia=24, ib=3, ic=3)) res = _all_other_rules(field)[CELL_METHODS_INDEX] expected = [CellMethod('minimum', 'time')] self.assertEqual(res, expected)
def test_custom_max(self): field = mock.MagicMock(lbproc=8192, lbtim=mock.Mock(ia=47, ib=2, ic=3)) res = _all_other_rules(field)[CELL_METHODS_INDEX] expected = [CellMethod('maximum', 'time', '47 hour')] self.assertEqual(res, expected)
def _convert_collation(collation): """ Converts a FieldCollation into the corresponding items of Cube metadata. Args: * collation: A FieldCollation object. Returns: A :class:`iris.fileformats.rules.ConversionMetadata` object. .. note: This is the 'loader.converter', in the control structure passed to the generic rules code, :meth:`iris.fileformats.rules.load_cubes`. """ from iris.fileformats.rules import ConversionMetadata from iris.fileformats.pp_rules import (_convert_time_coords, _convert_vertical_coords, _convert_scalar_realization_coords, _convert_scalar_pseudo_level_coords, _all_other_rules) # For all the scalar conversions, all fields in the collation will # give the same result, so the choice is arbitrary. field = collation.fields[0] # Call "all other" rules. (references, standard_name, long_name, units, attributes, cell_methods, dim_coords_and_dims, aux_coords_and_dims) = _all_other_rules(field) # Adjust any dimension bindings to account for the extra leading # dimensions added by the collation. if collation.vector_dims_shape: def _adjust_dims(coords_and_dims, n_dims): def adjust(dims): if dims is not None: dims += n_dims return dims return [(coord, adjust(dims)) for coord, dims in coords_and_dims] n_collation_dims = len(collation.vector_dims_shape) dim_coords_and_dims = _adjust_dims(dim_coords_and_dims, n_collation_dims) aux_coords_and_dims = _adjust_dims(aux_coords_and_dims, n_collation_dims) # Dimensions to which we've already assigned dimension coordinates. dim_coord_dims = set() # Helper call to choose which coords are dimensions and which auxiliary. def _bind_coords(coords_and_dims, dim_coord_dims, dim_coords_and_dims, aux_coords_and_dims): def key_func(item): return _HINTS.get(item[0].name(), len(_HINTS)) # Target the first DimCoord for a dimension at dim_coords, # and target everything else at aux_coords. for coord, dims in sorted(coords_and_dims, key=key_func): if (isinstance(coord, DimCoord) and dims is not None and len(dims) == 1 and dims[0] not in dim_coord_dims): dim_coords_and_dims.append((coord, dims)) dim_coord_dims.add(dims[0]) else: aux_coords_and_dims.append((coord, dims)) # Call "time" rules. # # For "normal" (non-cross-sectional) time values. vector_headers = collation.element_arrays_and_dims # If the collation doesn't define a vector of values for a # particular header then it must be constant over all fields in the # collation. In which case it's safe to get the value from any field. t1, t1_dims = vector_headers.get('t1', (field.t1, ())) t2, t2_dims = vector_headers.get('t2', (field.t2, ())) lbft, lbft_dims = vector_headers.get('lbft', (field.lbft, ())) coords_and_dims = _convert_time_coords(field.lbcode, field.lbtim, field.time_unit('hours'), t1, t2, lbft, t1_dims, t2_dims, lbft_dims) # Bind resulting coordinates to dimensions, where suitable. _bind_coords(coords_and_dims, dim_coord_dims, dim_coords_and_dims, aux_coords_and_dims) # Call "vertical" rules. # # "Normal" (non-cross-sectional) vertical levels blev, blev_dims = vector_headers.get('blev', (field.blev, ())) lblev, lblev_dims = vector_headers.get('lblev', (field.lblev, ())) bhlev, bhlev_dims = vector_headers.get('bhlev', (field.bhlev, ())) bhrlev, bhrlev_dims = vector_headers.get('bhrlev', (field.bhrlev, ())) brsvd1, brsvd1_dims = vector_headers.get('brsvd1', (field.brsvd[0], ())) brsvd2, brsvd2_dims = vector_headers.get('brsvd2', (field.brsvd[1], ())) brlev, brlev_dims = vector_headers.get('brlev', (field.brlev, ())) # Find all the non-trivial dimension values dims = set( filter(None, [ blev_dims, lblev_dims, bhlev_dims, bhrlev_dims, brsvd1_dims, brsvd2_dims, brlev_dims ])) if len(dims) > 1: raise TranslationError('Unsupported multiple values for vertical ' 'dimension.') if dims: v_dims = dims.pop() if len(v_dims) > 1: raise TranslationError('Unsupported multi-dimension vertical ' 'headers.') else: v_dims = () coords_and_dims, factories = _convert_vertical_coords( field.lbcode, field.lbvc, blev, lblev, field.stash, bhlev, bhrlev, brsvd1, brsvd2, brlev, v_dims) # Bind resulting coordinates to dimensions, where suitable. _bind_coords(coords_and_dims, dim_coord_dims, dim_coords_and_dims, aux_coords_and_dims) # Realization (aka ensemble) (--> scalar coordinates) aux_coords_and_dims.extend( _convert_scalar_realization_coords(lbrsvd4=field.lbrsvd[3])) # Pseudo-level coordinate (--> scalar coordinates) aux_coords_and_dims.extend( _convert_scalar_pseudo_level_coords(lbuser5=field.lbuser[4])) return ConversionMetadata(factories, references, standard_name, long_name, units, attributes, cell_methods, dim_coords_and_dims, aux_coords_and_dims)
def _convert_collation(collation): """ Converts a FieldCollation into the corresponding items of Cube metadata. Args: * collation: A FieldCollation object. Returns: A :class:`iris.fileformats.rules.ConversionMetadata` object. """ # For all the scalar conversions all fields in the collation will # give the same result, so the choice is arbitrary. field = collation.fields[0] # All the "other" rules. (references, standard_name, long_name, units, attributes, cell_methods, dim_coords_and_dims, aux_coords_and_dims) = _all_other_rules(field) # Adjust any dimension bindings to account for the extra leading # dimensions added by the collation. if collation.vector_dims_shape: n_collation_dims = len(collation.vector_dims_shape) dim_coords_and_dims = _adjust_dims(dim_coords_and_dims, n_collation_dims) aux_coords_and_dims = _adjust_dims(aux_coords_and_dims, n_collation_dims) # "Normal" (non-cross-sectional) time values vector_headers = collation.element_arrays_and_dims # If the collation doesn't define a vector of values for a # particular header then it must be constant over all fields in the # collation. In which case it's safe to get the value from any field. t1, t1_dims = vector_headers.get('t1', (field.t1, ())) t2, t2_dims = vector_headers.get('t2', (field.t2, ())) lbft, lbft_dims = vector_headers.get('lbft', (field.lbft, ())) coords_and_dims = _convert_time_coords(field.lbcode, field.lbtim, field.time_unit('hours'), t1, t2, lbft, t1_dims, t2_dims, lbft_dims) dim_coord_dims = set() _bind_coords(coords_and_dims, dim_coord_dims, dim_coords_and_dims, aux_coords_and_dims) # "Normal" (non-cross-sectional) vertical levels blev, blev_dims = vector_headers.get('blev', (field.blev, ())) lblev, lblev_dims = vector_headers.get('lblev', (field.lblev, ())) bhlev, bhlev_dims = vector_headers.get('bhlev', (field.bhlev, ())) bhrlev, bhrlev_dims = vector_headers.get('bhrlev', (field.bhrlev, ())) brsvd1, brsvd1_dims = vector_headers.get('brsvd1', (field.brsvd[0], ())) brsvd2, brsvd2_dims = vector_headers.get('brsvd2', (field.brsvd[1], ())) brlev, brlev_dims = vector_headers.get('brlev', (field.brlev, ())) # Find all the non-trivial dimension values dims = set( filter(None, [ blev_dims, lblev_dims, bhlev_dims, bhrlev_dims, brsvd1_dims, brsvd2_dims, brlev_dims ])) if len(dims) > 1: raise TranslationError('Unsupported multiple values for vertical ' 'dimension.') if dims: v_dims = dims.pop() if len(v_dims) > 1: raise TranslationError('Unsupported multi-dimension vertical ' 'headers.') else: v_dims = () coords_and_dims, factories = _convert_vertical_coords( field.lbcode, field.lbvc, blev, lblev, field.stash, bhlev, bhrlev, brsvd1, brsvd2, brlev, v_dims) _bind_coords(coords_and_dims, dim_coord_dims, dim_coords_and_dims, aux_coords_and_dims) # Realization (aka ensemble) (--> scalar coordinates) aux_coords_and_dims.extend( _convert_scalar_realization_coords(lbrsvd4=field.lbrsvd[3])) # Pseudo-level coordinate (--> scalar coordinates) aux_coords_and_dims.extend( _convert_scalar_pseudo_level_coords(lbuser5=field.lbuser[4])) return ConversionMetadata(factories, references, standard_name, long_name, units, attributes, cell_methods, dim_coords_and_dims, aux_coords_and_dims)
def _convert_collation(collation): """ Converts a FieldCollation into the corresponding items of Cube metadata. Args: * collation: A FieldCollation object. Returns: A :class:`iris.fileformats.rules.ConversionMetadata` object. .. note: This is the 'loader.converter', in the control structure passed to the generic rules code, :meth:`iris.fileformats.rules.load_cubes`. """ from iris.fileformats.rules import ConversionMetadata from iris.fileformats.pp_rules import (_convert_time_coords, _convert_vertical_coords, _convert_scalar_realization_coords, _convert_scalar_pseudo_level_coords, _all_other_rules) # For all the scalar conversions, all fields in the collation will # give the same result, so the choice is arbitrary. field = collation.fields[0] # Call "all other" rules. (references, standard_name, long_name, units, attributes, cell_methods, dim_coords_and_dims, aux_coords_and_dims) = _all_other_rules(field) # Adjust any dimension bindings to account for the extra leading # dimensions added by the collation. if collation.vector_dims_shape: def _adjust_dims(coords_and_dims, n_dims): def adjust(dims): if dims is not None: dims += n_dims return dims return [(coord, adjust(dims)) for coord, dims in coords_and_dims] n_collation_dims = len(collation.vector_dims_shape) dim_coords_and_dims = _adjust_dims(dim_coords_and_dims, n_collation_dims) aux_coords_and_dims = _adjust_dims(aux_coords_and_dims, n_collation_dims) # Dimensions to which we've already assigned dimension coordinates. dim_coord_dims = set() # Helper call to choose which coords are dimensions and which auxiliary. def _bind_coords(coords_and_dims, dim_coord_dims, dim_coords_and_dims, aux_coords_and_dims): def key_func(item): return _HINTS.get(item[0].name(), len(_HINTS)) # Target the first DimCoord for a dimension at dim_coords, # and target everything else at aux_coords. for coord, dims in sorted(coords_and_dims, key=key_func): if (isinstance(coord, DimCoord) and dims is not None and len(dims) == 1 and dims[0] not in dim_coord_dims): dim_coords_and_dims.append((coord, dims)) dim_coord_dims.add(dims[0]) else: aux_coords_and_dims.append((coord, dims)) # Call "time" rules. # # For "normal" (non-cross-sectional) time values. vector_headers = collation.element_arrays_and_dims # If the collation doesn't define a vector of values for a # particular header then it must be constant over all fields in the # collation. In which case it's safe to get the value from any field. t1, t1_dims = vector_headers.get('t1', (field.t1, ())) t2, t2_dims = vector_headers.get('t2', (field.t2, ())) lbft, lbft_dims = vector_headers.get('lbft', (field.lbft, ())) coords_and_dims = _convert_time_coords(field.lbcode, field.lbtim, field.time_unit('hours'), t1, t2, lbft, t1_dims, t2_dims, lbft_dims) # Bind resulting coordinates to dimensions, where suitable. _bind_coords(coords_and_dims, dim_coord_dims, dim_coords_and_dims, aux_coords_and_dims) # Call "vertical" rules. # # "Normal" (non-cross-sectional) vertical levels blev, blev_dims = vector_headers.get('blev', (field.blev, ())) lblev, lblev_dims = vector_headers.get('lblev', (field.lblev, ())) bhlev, bhlev_dims = vector_headers.get('bhlev', (field.bhlev, ())) bhrlev, bhrlev_dims = vector_headers.get('bhrlev', (field.bhrlev, ())) brsvd1, brsvd1_dims = vector_headers.get('brsvd1', (field.brsvd[0], ())) brsvd2, brsvd2_dims = vector_headers.get('brsvd2', (field.brsvd[1], ())) brlev, brlev_dims = vector_headers.get('brlev', (field.brlev, ())) # Find all the non-trivial dimension values dims = set(filter(None, [blev_dims, lblev_dims, bhlev_dims, bhrlev_dims, brsvd1_dims, brsvd2_dims, brlev_dims])) if len(dims) > 1: raise TranslationError('Unsupported multiple values for vertical ' 'dimension.') if dims: v_dims = dims.pop() if len(v_dims) > 1: raise TranslationError('Unsupported multi-dimension vertical ' 'headers.') else: v_dims = () coords_and_dims, factories = _convert_vertical_coords(field.lbcode, field.lbvc, blev, lblev, field.stash, bhlev, bhrlev, brsvd1, brsvd2, brlev, v_dims) # Bind resulting coordinates to dimensions, where suitable. _bind_coords(coords_and_dims, dim_coord_dims, dim_coords_and_dims, aux_coords_and_dims) # Realization (aka ensemble) (--> scalar coordinates) aux_coords_and_dims.extend(_convert_scalar_realization_coords( lbrsvd4=field.lbrsvd[3])) # Pseudo-level coordinate (--> scalar coordinates) aux_coords_and_dims.extend(_convert_scalar_pseudo_level_coords( lbuser5=field.lbuser[4])) return ConversionMetadata(factories, references, standard_name, long_name, units, attributes, cell_methods, dim_coords_and_dims, aux_coords_and_dims)
def _convert_collation(collation): """ Converts a FieldCollation into the corresponding items of Cube metadata. Args: * collation: A FieldCollation object. Returns: A :class:`iris.fileformats.rules.ConversionMetadata` object. """ # For all the scalar conversions all fields in the collation will # give the same result, so the choice is arbitrary. field = collation.fields[0] # All the "other" rules. (references, standard_name, long_name, units, attributes, cell_methods, dim_coords_and_dims, aux_coords_and_dims) = _all_other_rules(field) # Adjust any dimension bindings to account for the extra leading # dimensions added by the collation. if collation.vector_dims_shape: n_collation_dims = len(collation.vector_dims_shape) dim_coords_and_dims = _adjust_dims(dim_coords_and_dims, n_collation_dims) aux_coords_and_dims = _adjust_dims(aux_coords_and_dims, n_collation_dims) # "Normal" (non-cross-sectional) time values vector_headers = collation.element_arrays_and_dims # If the collation doesn't define a vector of values for a # particular header then it must be constant over all fields in the # collation. In which case it's safe to get the value from any field. t1, t1_dims = vector_headers.get('t1', (field.t1, ())) t2, t2_dims = vector_headers.get('t2', (field.t2, ())) lbft, lbft_dims = vector_headers.get('lbft', (field.lbft, ())) coords_and_dims = _convert_time_coords(field.lbcode, field.lbtim, field.time_unit('hours'), t1, t2, lbft, t1_dims, t2_dims, lbft_dims) dim_coord_dims = set() _bind_coords(coords_and_dims, dim_coord_dims, dim_coords_and_dims, aux_coords_and_dims) # "Normal" (non-cross-sectional) vertical levels blev, blev_dims = vector_headers.get('blev', (field.blev, ())) lblev, lblev_dims = vector_headers.get('lblev', (field.lblev, ())) bhlev, bhlev_dims = vector_headers.get('bhlev', (field.bhlev, ())) bhrlev, bhrlev_dims = vector_headers.get('bhrlev', (field.bhrlev, ())) brsvd1, brsvd1_dims = vector_headers.get('brsvd1', (field.brsvd[0], ())) brsvd2, brsvd2_dims = vector_headers.get('brsvd2', (field.brsvd[1], ())) brlev, brlev_dims = vector_headers.get('brlev', (field.brlev, ())) # Find all the non-trivial dimension values dims = set(filter(None, [blev_dims, lblev_dims, bhlev_dims, bhrlev_dims, brsvd1_dims, brsvd2_dims, brlev_dims])) if len(dims) > 1: raise TranslationError('Unsupported multiple values for vertical ' 'dimension.') if dims: v_dims = dims.pop() if len(v_dims) > 1: raise TranslationError('Unsupported multi-dimension vertical ' 'headers.') else: v_dims = () coords_and_dims, factories = _convert_vertical_coords(field.lbcode, field.lbvc, blev, lblev, field.stash, bhlev, bhrlev, brsvd1, brsvd2, brlev, v_dims) _bind_coords(coords_and_dims, dim_coord_dims, dim_coords_and_dims, aux_coords_and_dims) # Realization (aka ensemble) (--> scalar coordinates) aux_coords_and_dims.extend(_convert_scalar_realization_coords( lbrsvd4=field.lbrsvd[3])) # Pseudo-level coordinate (--> scalar coordinates) aux_coords_and_dims.extend(_convert_scalar_pseudo_level_coords( lbuser5=field.lbuser[4])) return ConversionMetadata(factories, references, standard_name, long_name, units, attributes, cell_methods, dim_coords_and_dims, aux_coords_and_dims)
def test_diff_month(self): field = self._make_field(lbmon=3, lbmond=4) field.mock_add_spec(self._spec) res = _all_other_rules(field)[AUX_COORDS_INDEX] self.assertCoordsAndDimsListsMatch(res, [])