def test_ok(self): """Test conformant data (no error is thrown and inputs are not changed)""" result = self.cube + self.ok_mask inputs = [self.cube.copy(), self.ok_mask.copy()] # The following statement renders each cube into an XML string # describing all aspects of the cube (including a checksum of the # data) to verify that nothing has been changed anywhere on the # cube. expected_checksums = [ CubeList([c]).xml(checksum=True) for c in inputs + [result] ] enforce_dtype("add", inputs, result) result_checksums = [ CubeList([c]).xml(checksum=True) for c in inputs + [result] ] for a, b in zip(expected_checksums, result_checksums): self.assertStringEqual(a, b)
def _fill_timezones(self, input_cube: Cube) -> None: """ Populates the output cube data with data from input_cube. This is done by multiplying the inverse of the timezone_cube.data with the input_cube.data and summing along the time axis. Because timezone_cube.data is a mask of 1 and 0, inverting it gives 1 where we WANT data and 0 where we don't. Summing these up produces the result. The same logic can be used for times. Modifies self.output_cube and self.time_points. Assumes that input_cube and self.timezone_cube have been arranged so that time or UTC_offset are the inner-most coord (dim=-1). Args: input_cube: Cube of data to extract timezone-offsets from. Must contain a time coord spanning all the timezones. Raises: TypeError: If combining the timezone_cube and input_cube results in float64 data. (Hint: timezone_cube should be int8 and input cube should be float32) """ # Get the output_data result = input_cube.data * (1 - self.timezone_cube.data) self.output_data = result.sum(axis=-1) # Check resulting dtype enforce_dtype("multiply", [input_cube, self.timezone_cube], result) # Sort out the time points input_time_points = input_cube.coord("time").points # Add scalar coords to allow broadcast to spatial coords. times = input_time_points.reshape((1, 1, len(input_time_points))) * ( 1 - self.timezone_cube.data ) self.time_points = times.sum(axis=-1) # Sort out the time bounds (if present) bounds_offsets = self._get_time_bounds_offset(input_cube) if bounds_offsets is not None: # Add scalar coords to allow broadcast to spatial coords. self.time_bounds = bounds_offsets.reshape( (1, 1, 2) ) + self.time_points.reshape(list(self.time_points.shape) + [1])
def test_fail(self): """Test non-conformant data (error is thrown and inputs are not changed)""" result = self.cube + self.bad_mask inputs = [self.cube.copy(), self.bad_mask.copy()] # The following statement renders each cube into an XML string # describing all aspects of the cube (including a checksum of the # data) to verify that nothing has been changed anywhere on the # cube. expected_checksums = [ CubeList([c]).xml(checksum=True) for c in inputs + [result] ] msg = ( r"Operation add on types .* results in " r"float64 data which cannot be safely coerced to float32 \(Hint: " r"combining int8 and float32 works\)") with self.assertRaisesRegex(TypeError, msg): enforce_dtype("add", inputs, result) result_checksums = [ CubeList([c]).xml(checksum=True) for c in inputs + [result] ] for a, b in zip(expected_checksums, result_checksums): self.assertStringEqual(a, b)
def _combine_cube_data(self, cube_list: Union[List[Cube], CubeList]) -> Cube: """ Perform cumulative operation to combine cube data Args: cube_list Returns: Combined cube Raises: TypeError: if the operation results in an escalated datatype """ result = cube_list[0].copy() for cube in cube_list[1:]: result.data = self.operator(result.data, cube.data) if self.normalise: result.data = result.data / len(cube_list) enforce_dtype(str(self.operator), cube_list, result) return result