def test_dim_location_beyond_bounds(input_cube, realization_coord): """Test ValueError raised when new_dim_location is outside expected bounds.""" # Beyond expected dimension bounds with pytest.raises(ValueError): add_coordinate_to_cube(input_cube, realization_coord, new_dim_location=3) with pytest.raises(ValueError): add_coordinate_to_cube(input_cube, realization_coord, new_dim_location=-1)
def test_add_unit_dimension(input_cube, expected_cube): """Test case where added dimension is of length 1.""" realization_coord = DimCoord([0], standard_name="realization", units=1) expected_cube = new_axis(expected_cube.extract(Constraint(realization=0)), "realization") output_cube = add_coordinate_to_cube(input_cube, realization_coord) assert output_cube == expected_cube
def _prepare_error_probability_cube(self, forecast_cube): """Initialise a cube with the same dimensions as the input forecast_cube, with an additional threshold dimension added as the leading dimension. Args: forecast_cube: Cube containing the forecast to be calibrated. Returns: An empty probability cube. """ # Create a template for error CDF, with threshold the leading dimension. forecast_error_variable = f"forecast_error_of_{forecast_cube.name()}" error_probability_cube = create_new_diagnostic_cube( name=f"probability_of_{forecast_error_variable}_above_threshold", units="1", template_cube=forecast_cube, mandatory_attributes=generate_mandatory_attributes([forecast_cube ]), ) error_threshold_coord = DimCoord( self.error_thresholds, long_name=forecast_error_variable, var_name="threshold", units=forecast_cube.units, attributes={"spp__relative_to_threshold": "above"}, ) error_probability_cube = add_coordinate_to_cube( error_probability_cube, new_coord=error_threshold_coord, ) return error_probability_cube
def test_dim_location(input_cube, expected_cube, realization_coord, new_dim_location, expected_order): """Test the usage when new_dim_location is provided.""" # leading position expected_cube.transpose(expected_order) output_cube = add_coordinate_to_cube(input_cube, realization_coord, new_dim_location=new_dim_location) assert output_cube == expected_cube
def test_no_copy_metadata(input_cube, expected_cube, realization_coord): """Test case where copy_metadata is not carried over.""" output_cube = add_coordinate_to_cube(input_cube, realization_coord, copy_metadata=False) # Check data matches np.testing.assert_equal(output_cube.data, expected_cube.data) # Check coords match assert output_cube.coords() == expected_cube.coords() # Check that cube metadata is empty assert output_cube.metadata == Cube(np.array([])).metadata
def test_non_float32_data(input_cube, expected_cube, realization_coord): """Test the case where input data is not float32.""" input_cube.data = input_cube.data.astype(int) expected_cube.data = expected_cube.data.astype(int) output_cube = add_coordinate_to_cube(input_cube, realization_coord) assert output_cube == expected_cube
def test_basic(input_cube, expected_cube, realization_coord): """Test the basic usage of cube and coordinate arguments.""" output_cube = add_coordinate_to_cube(input_cube, realization_coord) assert output_cube == expected_cube
def _align_feature_variables(self, feature_cubes: CubeList, forecast_cube: Cube) -> Tuple[CubeList, Cube]: """Ensure that feature cubes have consistent dimension coordinates. If realization dimension present in any cube, all cubes lacking this dimension will have realization dimension added and broadcast along this new dimension. This situation occurs when derived fields (such as accumulated solar radiation) are used as predictors. As these fields do not contain a realization dimension, they must be broadcast to match the NWP fields that do contain realization, so that all features have consistent shape. In the case of deterministic models (those without a realization dimension), a realization dimension is added to allow consistent behaviour between ensemble and deterministic models. Args: feature_cubes: Cubelist containing feature variables to align. forecast_cube: Cube containing the forecast variable to align. Returns: - feature_cubes with realization coordinate added to each cube if absent - forecast_cube with realization coordinate added if absent Raises: ValueError: if feature/forecast variables have inconsistent dimension coordinates (excluding realization dimension), or if feature/forecast variables have different length realization coordinate over cubes containing a realization coordinate. """ combined_cubes = CubeList(list([*feature_cubes, forecast_cube])) # Compare feature cube coordinates, raise error if dim-coords don't match compare_feature_coords = compare_coords(combined_cubes, ignored_coords=["realization"]) for misaligned_coords in compare_feature_coords: for coord_info in misaligned_coords.values(): if coord_info["data_dims"] is not None: raise ValueError( "Mismatch between non-realization dimension coords.") # Compare realization coordinates across cubes where present; # raise error if realization coordinates don't match, otherwise set # common_realization_coord to broadcast over. realization_coords = { variable.name(): variable.coord("realization") for variable in combined_cubes if variable.coords("realization") } if not realization_coords: # Case I: realization_coords is empty. Add single realization dim to all cubes. common_realization_coord = DimCoord([0], standard_name="realization", units=1) else: # Case II: realization_coords is not empty. # Note: In future, another option here could be to filter to common realization # values using filter_realizations() in utilities.cube_manipulation. variables_with_realization = list(realization_coords.keys()) sample_realization = realization_coords[ variables_with_realization[0]] for feature in variables_with_realization[1:]: if realization_coords[feature] != sample_realization: raise ValueError( "Mismatch between realization dimension coords.") common_realization_coord = sample_realization # Add realization coord to cubes where absent by broadcasting along this dimension aligned_cubes = CubeList() for cube in combined_cubes: if not cube.coords("realization"): expanded_cube = add_coordinate_to_cube( cube, new_coord=common_realization_coord) aligned_cubes.append(expanded_cube) else: aligned_cubes.append(cube) return aligned_cubes[:-1], aligned_cubes[-1]