def test_contrived_non_spherical_curl1(self): # testing : # F(x, y, z) = (y, 0, 0) # curl( F(x, y, z) ) = (0, 0, -1) cube = build_cube(np.empty((25, 50)), spherical=False) x_pts, x_ones, y_pts, y_ones, z_pts, z_ones = self.get_coord_pts(cube) u = cube.copy(data=x_ones * y_pts) u.rename("u_wind") v = cube.copy(data=u.data * 0) v.rename("v_wind") r = iris.analysis.calculus.curl(u, v) # Curl returns None when there is no components of Curl self.assertEqual(r[0], None) self.assertEqual(r[1], None) cube = r[2] self.assertCML( cube, ('analysis', 'calculus', 'grad_contrived_non_spherical1.cml'), checksum=False) self.assertTrue(np.all(np.abs(cube.data - (-1.0)) < 1.0e-7))
def test_contrived_spherical_curl1(self): # testing: # F(lon, lat, r) = (- r sin(lon), -r cos(lon) sin(lat), 0) # curl( F(x, y, z) ) = (0, 0, 0) cube = build_cube(np.empty((30, 60)), spherical=True) radius = iris.analysis.cartography.DEFAULT_SPHERICAL_EARTH_RADIUS x = cube.coord('longitude') y = cube.coord('latitude') cos_x_pts = np.cos(np.radians(x.points)).reshape(1, x.shape[0]) sin_x_pts = np.sin(np.radians(x.points)).reshape(1, x.shape[0]) cos_y_pts = np.cos(np.radians(y.points)).reshape(y.shape[0], 1) sin_y_pts = np.sin(np.radians(y.points)).reshape(y.shape[0], 1) y_ones = np.ones((cube.shape[0], 1)) u = cube.copy(data=-sin_x_pts * y_ones * radius) v = cube.copy(data=-cos_x_pts * sin_y_pts * radius) u.rename('u_wind') v.rename('v_wind') r = iris.analysis.calculus.curl(u, v)[2] result = r.copy(data=r.data * 0) # Note: This numerical comparison was created when the radius was 1000 times smaller np.testing.assert_array_almost_equal(result.data[5:-5], r.data[5:-5] / 1000.0, decimal=1) self.assertCML(r, ('analysis', 'calculus', 'grad_contrived1.cml'), checksum=False)
def test_contrived_spherical_curl1(self): # testing: # F(lon, lat, r) = (- r sin(lon), -r cos(lon) sin(lat), 0) # curl( F(x, y, z) ) = (0, 0, 0) cube = build_cube(np.empty((30, 60)), spherical=True) radius = iris.analysis.cartography.DEFAULT_SPHERICAL_EARTH_RADIUS x = cube.coord('longitude') y = cube.coord('latitude') cos_x_pts = np.cos(np.radians(x.points)).reshape(1, x.shape[0]) sin_x_pts = np.sin(np.radians(x.points)).reshape(1, x.shape[0]) cos_y_pts = np.cos(np.radians(y.points)).reshape(y.shape[0], 1) sin_y_pts = np.sin(np.radians(y.points)).reshape(y.shape[0], 1) y_ones = np.ones((cube.shape[0], 1)) u = cube.copy(data=-sin_x_pts * y_ones * radius) v = cube.copy(data=-cos_x_pts * sin_y_pts * radius) u.rename('u_wind') v.rename('v_wind') r = iris.analysis.calculus.curl(u, v)[2] result = r.copy(data=r.data * 0) # Note: This numerical comparison was created when the radius was 1000 times smaller np.testing.assert_array_almost_equal(result.data[5:-5], r.data[5:-5]/1000.0, decimal=1) self.assertCML(r, ('analysis', 'calculus', 'grad_contrived1.cml'), checksum=False)
def _math_op_common(cube, operation_function, new_unit, new_dtype=None, in_place=False): _assert_is_cube(cube) if in_place: new_cube = cube if cube.has_lazy_data(): new_cube.data = operation_function(cube.lazy_data()) else: try: operation_function(cube.data, out=cube.data) except TypeError: # Non ufunc function operation_function(cube.data) else: new_cube = cube.copy(data=operation_function(cube.core_data())) # If the result of the operation is scalar and masked, we need to fix up # the dtype if new_dtype is not None \ and not new_cube.has_lazy_data() \ and new_cube.data.shape == () \ and ma.is_masked(new_cube.data): new_cube.data = ma.masked_array(0, 1, dtype=new_dtype) iris.analysis.clear_phenomenon_identity(new_cube) new_cube.units = new_unit return new_cube
def cube_delta(cube, coord): """ Given a cube calculate the difference between each value in the given coord's direction. Args: * coord either a Coord instance or the unique name of a coordinate in the cube. If a Coord instance is provided, it does not necessarily have to exist in the cube. Example usage:: change_in_temperature_wrt_pressure = cube_delta(temperature_cube, 'pressure') .. note:: Missing data support not yet implemented. """ # handle the case where a user passes a coordinate name if isinstance(coord, basestring): coord = cube.coord(coord) if coord.ndim != 1: raise iris.exceptions.CoordinateMultiDimError(coord) # Try and get a coord dim delta_dims = cube.coord_dims(coord) if (coord.shape[0] == 1 and not getattr(coord, 'circular', False)) or not delta_dims: raise ValueError( 'Cannot calculate delta over "%s" as it has length of 1.' % coord.name()) delta_dim = delta_dims[0] # Calculate the actual delta, taking into account whether the given coordinate is circular delta_cube_data = delta(cube.data, delta_dim, circular=getattr(coord, 'circular', False)) # If the coord/dim is circular there is no change in cube shape if getattr(coord, 'circular', False): delta_cube = cube.copy(data=delta_cube_data) else: # Subset the cube to the appropriate new shape by knocking off the last row of the delta dimension subset_slice = [slice(None, None)] * cube.ndim subset_slice[delta_dim] = slice(None, -1) delta_cube = cube[tuple(subset_slice)] delta_cube.data = delta_cube_data # Replace the delta_dim coords with midpoints (no shape change if circular). for cube_coord in cube.coords(dimensions=delta_dim): delta_cube.replace_coord( _construct_midpoint_coord(cube_coord, circular=getattr(coord, 'circular', False))) delta_cube.rename('change_in_%s_wrt_%s' % (delta_cube.name(), coord.name())) return delta_cube
def test_contrived_spherical_curl2(self): # testing: # F(lon, lat, r) = (r sin(lat) cos(lon), -r sin(lon), 0) # curl( F(x, y, z) ) = (0, 0, -2 cos(lon) cos(lat) ) cube = build_cube(np.empty((70, 150)), spherical=True) radius = iris.analysis.cartography.DEFAULT_SPHERICAL_EARTH_RADIUS x = cube.coord('longitude') y = cube.coord('latitude') cos_x_pts = np.cos(np.radians(x.points)).reshape(1, x.shape[0]) sin_x_pts = np.sin(np.radians(x.points)).reshape(1, x.shape[0]) cos_y_pts = np.cos(np.radians(y.points)).reshape(y.shape[0], 1) sin_y_pts = np.sin(np.radians(y.points)).reshape(y.shape[0], 1) y_ones = np.ones((cube.shape[0], 1)) u = cube.copy(data=sin_y_pts * cos_x_pts * radius) v = cube.copy(data=-sin_x_pts * y_ones * radius) u.rename('u_wind') v.rename('v_wind') lon_coord = x.copy() lon_coord.convert_units('radians') lat_coord = y.copy() lat_coord.convert_units('radians') cos_lat_coord = iris.coords.AuxCoord.from_coord(lat_coord) cos_lat_coord.points = np.cos(lat_coord.points) cos_lat_coord.units = '1' cos_lat_coord.rename('cos({})'.format(lat_coord.name())) r = iris.analysis.calculus.curl(u, v)[2] x = r.coord('longitude') y = r.coord('latitude') cos_x_pts = np.cos(np.radians(x.points)).reshape(1, x.shape[0]) cos_y_pts = np.cos(np.radians(y.points)).reshape(y.shape[0], 1) # Expected r-component value: -2 cos(lon) cos(lat) result = r.copy(data=-2 * cos_x_pts * cos_y_pts) # Note: This numerical comparison was created when the radius was 1000 times smaller np.testing.assert_array_almost_equal(result.data[30:-30, :], r.data[30:-30, :] / 1000.0, decimal=1) self.assertCML(r, ('analysis', 'calculus', 'grad_contrived2.cml'), checksum=False)
def cube_delta(cube, coord): """ Given a cube calculate the difference between each value in the given coord's direction. Args: * coord either a Coord instance or the unique name of a coordinate in the cube. If a Coord instance is provided, it does not necessarily have to exist in the cube. Example usage:: change_in_temperature_wrt_pressure = \ cube_delta(temperature_cube, 'pressure') .. note:: Missing data support not yet implemented. """ # handle the case where a user passes a coordinate name if isinstance(coord, six.string_types): coord = cube.coord(coord) if coord.ndim != 1: raise iris.exceptions.CoordinateMultiDimError(coord) # Try and get a coord dim delta_dims = cube.coord_dims(coord.name()) if (coord.shape[0] == 1 and not getattr(coord, "circular", False)) or not delta_dims: raise ValueError("Cannot calculate delta over {!r} as it has " "length of 1.".format(coord.name())) delta_dim = delta_dims[0] # Calculate the actual delta, taking into account whether the given # coordinate is circular. delta_cube_data = delta(cube.data, delta_dim, circular=getattr(coord, "circular", False)) # If the coord/dim is circular there is no change in cube shape if getattr(coord, "circular", False): delta_cube = cube.copy(data=delta_cube_data) else: # Subset the cube to the appropriate new shape by knocking off # the last row of the delta dimension. subset_slice = [slice(None, None)] * cube.ndim subset_slice[delta_dim] = slice(None, -1) delta_cube = cube[tuple(subset_slice)] delta_cube.data = delta_cube_data # Replace the delta_dim coords with midpoints # (no shape change if circular). for cube_coord in cube.coords(dimensions=delta_dim): delta_cube.replace_coord(_construct_midpoint_coord(cube_coord, circular=getattr(coord, "circular", False))) delta_cube.rename("change_in_{}_wrt_{}".format(delta_cube.name(), coord.name())) return delta_cube
def test_contrived_spherical_curl2(self): # testing: # F(lon, lat, r) = (r sin(lat) cos(lon), -r sin(lon), 0) # curl( F(x, y, z) ) = (0, 0, -2 cos(lon) cos(lat) ) cube = build_cube(np.empty((70, 150)), spherical=True) radius = iris.analysis.cartography.DEFAULT_SPHERICAL_EARTH_RADIUS x = cube.coord('longitude') y = cube.coord('latitude') cos_x_pts = np.cos(np.radians(x.points)).reshape(1, x.shape[0]) sin_x_pts = np.sin(np.radians(x.points)).reshape(1, x.shape[0]) cos_y_pts = np.cos(np.radians(y.points)).reshape(y.shape[0], 1) sin_y_pts = np.sin(np.radians(y.points)).reshape(y.shape[0], 1) y_ones = np.ones((cube.shape[0], 1)) u = cube.copy(data=sin_y_pts * cos_x_pts * radius) v = cube.copy(data=-sin_x_pts * y_ones * radius) u.rename('u_wind') v.rename('v_wind') lon_coord = x.copy() lon_coord.convert_units('radians') lat_coord = y.copy() lat_coord.convert_units('radians') cos_lat_coord = iris.coords.AuxCoord.from_coord(lat_coord) cos_lat_coord.points = np.cos(lat_coord.points) cos_lat_coord.units = '1' cos_lat_coord.rename('cos({})'.format(lat_coord.name())) r = iris.analysis.calculus.curl(u, v)[2] x = r.coord('longitude') y = r.coord('latitude') cos_x_pts = np.cos(np.radians(x.points)).reshape(1, x.shape[0]) cos_y_pts = np.cos(np.radians(y.points)).reshape(y.shape[0], 1) # Expected r-component value: -2 cos(lon) cos(lat) result = r.copy(data=-2*cos_x_pts*cos_y_pts) # Note: This numerical comparison was created when the radius was 1000 times smaller np.testing.assert_array_almost_equal(result.data[30:-30, :], r.data[30:-30, :]/1000.0, decimal=1) self.assertCML(r, ('analysis', 'calculus', 'grad_contrived2.cml'), checksum=False)
def _math_op_common(cube, operation_function, new_unit, in_place=False): _assert_is_cube(cube) if in_place: new_cube = cube operation_function(new_cube.data, out=new_cube.data) else: new_cube = cube.copy(data=operation_function(cube.data)) iris.analysis.clear_phenomenon_identity(new_cube) new_cube.units = new_unit return new_cube
def test_contrived_non_spherical_curl2(self): # testing : # F(x, y, z) = (z^3, x+2, y^2) # curl( F(x, y, z) ) = (2y, 3z^2, 1) cube = build_cube(np.empty((10, 25, 50)), spherical=False) x_pts, x_ones, y_pts, y_ones, z_pts, z_ones = self.get_coord_pts(cube) u = cube.copy(data=pow(z_pts, 3) * x_ones * y_ones) v = cube.copy(data=z_ones * (x_pts + 2.0) * y_ones) w = cube.copy(data=z_ones * x_ones * pow(y_pts, 2.0)) u.rename("u_wind") v.rename("v_wind") w.rename("w_wind") r = iris.analysis.calculus.curl(u, v, w) # TODO #235 When regridding is not nearest neighbour: the commented out code could be made to work # r[0].data should now be tending towards result.data as the resolution of the grid gets higher. # result = r[0].copy(data=True) # x_pts, x_ones, y_pts, y_ones, z_pts, z_ones = self.get_coord_pts(result) # result.data = y_pts * 2. * x_ones * z_ones # print(repr(r[0].data[0:1, 0:5, 0:25:5])) # print(repr(result.data[0:1, 0:5, 0:25:5])) # np.testing.assert_array_almost_equal(result.data, r[0].data, decimal=2) # # result = r[1].copy(data=True) # x_pts, x_ones, y_pts, y_ones, z_pts, z_ones = self.get_coord_pts(result) # result.data = pow(z_pts, 2) * x_ones * y_ones # np.testing.assert_array_almost_equal(result.data, r[1].data, decimal=6) result = r[2].copy() result.data = result.data * 0 + 1 np.testing.assert_array_almost_equal(result.data, r[2].data, decimal=4) self.assertCML( r, ("analysis", "calculus", "curl_contrived_cartesian2.cml"), checksum=False, )
def test_contrived_non_spherical_curl2(self): # testing : # F(x, y, z) = (z^3, x+2, y^2) # curl( F(x, y, z) ) = (2y, 3z^2, 1) cube = build_cube(np.empty((10, 25, 50)), spherical=False) x_pts, x_ones, y_pts, y_ones, z_pts, z_ones = self.get_coord_pts(cube) u = cube.copy(data=pow(z_pts, 3) * x_ones * y_ones) v = cube.copy(data=z_ones * (x_pts + 2.) * y_ones) w = cube.copy(data=z_ones * x_ones * pow(y_pts, 2.)) u.rename('u_wind') v.rename('v_wind') w.rename('w_wind') r = iris.analysis.calculus.curl(u, v, w) # TODO #235 When regridding is not nearest neighbour: the commented out code could be made to work # r[0].data should now be tending towards result.data as the resolution of the grid gets higher. # result = r[0].copy(data=True) # x_pts, x_ones, y_pts, y_ones, z_pts, z_ones = self.get_coord_pts(result) # result.data = y_pts * 2. * x_ones * z_ones # print repr(r[0].data[0:1, 0:5, 0:25:5]) # print repr(result.data[0:1, 0:5, 0:25:5]) # np.testing.assert_array_almost_equal(result.data, r[0].data, decimal=2) # # result = r[1].copy(data=True) # x_pts, x_ones, y_pts, y_ones, z_pts, z_ones = self.get_coord_pts(result) # result.data = pow(z_pts, 2) * x_ones * y_ones # np.testing.assert_array_almost_equal(result.data, r[1].data, decimal=6) result = r[2].copy() result.data = result.data * 0 + 1 np.testing.assert_array_almost_equal(result.data, r[2].data, decimal=4) normalise_order(r[1]) self.assertCML(r, ('analysis', 'calculus', 'curl_contrived_cartesian2.cml'), checksum=False)
def _math_op_common(cube, math_op, new_unit, in_place): data = math_op(cube.data) if in_place: copy_cube = cube copy_cube.data = data else: copy_cube = cube.copy(data) # Update the metadata iris.analysis.clear_phenomenon_identity(copy_cube) copy_cube.units = new_unit return copy_cube
def test_contrived_differential2(self): # testing : # w = y^2 # dw_dy = 2*y cube = build_cube(np.empty((10, 30, 60)), spherical=False) x_pts, x_ones, y_pts, y_ones, z_pts, z_ones = self.get_coord_pts(cube) w = cube.copy(data=z_ones * x_ones * pow(y_pts, 2.)) r = iris.analysis.calculus.differentiate(w, 'projection_y_coordinate') x_pts, x_ones, y_pts, y_ones, z_pts, z_ones = self.get_coord_pts(r) result = r.copy(data=y_pts * 2. * x_ones * z_ones) np.testing.assert_array_almost_equal(result.data, r.data, decimal=6)
def test_contrived_differential2(self): # testing : # w = y^2 # dw_dy = 2*y cube = build_cube(np.empty((10, 30, 60)), spherical=False) x_pts, x_ones, y_pts, y_ones, z_pts, z_ones = self.get_coord_pts(cube) w = cube.copy(data=z_ones * x_ones * pow(y_pts, 2.)) r = iris.analysis.calculus.differentiate(w, 'projection_y_coordinate') x_pts, x_ones, y_pts, y_ones, z_pts, z_ones = self.get_coord_pts(r) result = r.copy(data = y_pts * 2. * x_ones * z_ones) np.testing.assert_array_almost_equal(result.data, r.data, decimal=6)
def _compute_anomalies(cube, reference, period, seasons): cube_coord = _get_period_coord(cube, period, seasons) ref_coord = _get_period_coord(reference, period, seasons) data = cube.core_data() cube_time = cube.coord('time') ref = {} for ref_slice in reference.slices_over(ref_coord): ref[ref_slice.coord(ref_coord).points[0]] = ref_slice.core_data() cube_coord_dim = cube.coord_dims(cube_coord)[0] slicer = [slice(None)] * len(data.shape) new_data = [] for i in range(cube_time.shape[0]): slicer[cube_coord_dim] = i new_data.append(data[tuple(slicer)] - ref[cube_coord.points[i]]) data = da.stack(new_data, axis=cube_coord_dim) cube = cube.copy(data) cube.remove_coord(cube_coord) return cube
def _math_op_common( cube, operation_function, new_unit, new_dtype=None, in_place=False, skeleton_cube=False, ): _assert_is_cube(cube) if in_place and not skeleton_cube: if cube.has_lazy_data(): cube.data = operation_function(cube.lazy_data()) else: try: operation_function(cube.data, out=cube.data) except TypeError: # Non-ufunc function operation_function(cube.data) new_cube = cube else: data = operation_function(cube.core_data()) if skeleton_cube: # Simply wrap the resultant data in a cube, as no # cube metadata is required by the caller. new_cube = iris.cube.Cube(data) else: new_cube = cube.copy(data) # If the result of the operation is scalar and masked, we need to fix-up the dtype. if (new_dtype is not None and not new_cube.has_lazy_data() and new_cube.data.shape == () and ma.is_masked(new_cube.data)): new_cube.data = ma.masked_array(0, 1, dtype=new_dtype) _sanitise_metadata(new_cube, new_unit) return new_cube
def _multiply_divide_common(operation_function, operation_symbol, operation_noun, cube, other, dim=None, update_history=True): """ Function which shares common code between multiplication and division of cubes. operation_function - function which does the operation (e.g. numpy.divide) operation_symbol - the textual symbol of the operation (e.g. '/') operation_noun - the noun of the operation (e.g. 'division') operation_past_tense - the past tesnse of the operation (e.g. 'divided') .. seealso:: For information on the dim keyword argument see :func:`multiply`. """ if not isinstance(cube, iris.cube.Cube): raise TypeError('The "cube" argument must be an instance of iris.Cube.') if isinstance(other, (int, float)): other = np.array(other) other_unit = None history = None if isinstance(other, np.ndarray): _assert_compatible(cube, other) copy_cube = cube.copy(data=operation_function(cube.data, other)) if update_history: if other.ndim == 0: history = '%s %s %s' % (cube.name(), operation_symbol, other) else: history = '%s %s array' % (cube.name(), operation_symbol) other_unit = '1' elif isinstance(other, iris.coords.Coord): # Deal with cube multiplication/division by coordinate # What dimension are we processing? data_dimension = None if dim is not None: # Ensure the given dim matches the coord if other in cube.coords() and cube.coord_dims(other) != [dim]: raise ValueError("dim provided does not match dim found for coord") data_dimension = dim else: # Try and get a coord dim if other.shape != (1,): try: coord_dims = cube.coord_dims(other) data_dimension = coord_dims[0] if coord_dims else None except iris.exceptions.CoordinateNotFoundError: raise ValueError("Could not determine dimension for mul/div. Use mul(coord, dim=dim)") if other.ndim != 1: raise iris.exceptions.CoordinateMultiDimError(other) if other.has_bounds(): warnings.warn('%s by a bounded coordinate not well defined, ignoring bounds.' % operation_noun) points = other.points # If the axis is defined then shape the provided points so that we can do the # division (this is needed as there is no "axis" keyword to numpy's divide/multiply) if data_dimension is not None: points_shape = [1] * cube.data.ndim points_shape[data_dimension] = -1 points = points.reshape(points_shape) copy_cube = cube.copy(data=operation_function(cube.data, points)) if update_history: history = '%s %s %s' % (cube.name(), operation_symbol, other.name()) other_unit = other.units elif isinstance(other, iris.cube.Cube): # Deal with cube multiplication/division by cube copy_cube = cube.copy(data=operation_function(cube.data, other.data)) if update_history: history = '%s %s %s' % (cube.name() or 'unknown', operation_symbol, other.name() or 'unknown') other_unit = other.units else: return NotImplemented # Update the units if operation_function == np.multiply: copy_cube.units = cube.units * other_unit elif operation_function == np.divide: copy_cube.units = cube.units / other_unit iris.analysis.clear_phenomenon_identity(copy_cube) if history is not None: copy_cube.add_history(history) return copy_cube
def _add_subtract_common(operation_function, operation_symbol, operation_noun, operation_past_tense, cube, other, dim=None, ignore=True, update_history=True, in_place=False): """ Function which shares common code between addition and subtraction of cubes. operation_function - function which does the operation (e.g. numpy.subtract) operation_symbol - the textual symbol of the operation (e.g. '-') operation_noun - the noun of the operation (e.g. 'subtraction') operation_past_tense - the past tense of the operation (e.g. 'subtracted') """ if not isinstance(cube, iris.cube.Cube): raise TypeError('The "cube" argument must be an instance of iris.Cube.') if isinstance(other, (int, float)): # Promote scalar to a coordinate and associate unit type with cube unit type other = np.array(other) # Check that the units of the cube and the other item are the same, or if the other does not have a unit, skip this test if cube.units != getattr(other, 'units', cube.units) : raise iris.exceptions.NotYetImplementedError('Differing units (%s & %s) %s not implemented' % \ (cube.units, other.units, operation_noun)) history = None if isinstance(other, np.ndarray): _assert_compatible(cube, other) if in_place: new_cube = cube operation_function(new_cube.data, other, new_cube.data) else: new_cube = cube.copy(data=operation_function(cube.data, other)) if update_history: if other.ndim == 0: history = '%s %s %s' % (cube.name(), operation_symbol, other) else: history = '%s %s array' % (cube.name(), operation_symbol) elif isinstance(other, iris.coords.Coord): # Deal with cube addition/subtraction by coordinate # What dimension are we processing? data_dimension = None if dim is not None: # Ensure the given dim matches the coord if other in cube.coords() and cube.coord_dims(other) != [dim]: raise ValueError("dim provided does not match dim found for coord") data_dimension = dim else: # Try and get a coord dim if other.shape != (1,): try: coord_dims = cube.coord_dims(other) data_dimension = coord_dims[0] if coord_dims else None except iris.exceptions.CoordinateNotFoundError: raise ValueError("Could not determine dimension for add/sub. Use add(coord, dim=dim)") if other.ndim != 1: raise iris.exceptions.CoordinateMultiDimError(other) if other.has_bounds(): warnings.warn('%s by a bounded coordinate not well defined, ignoring bounds.' % operation_noun) points = other.points if data_dimension is not None: points_shape = [1] * cube.data.ndim points_shape[data_dimension] = -1 points = points.reshape(points_shape) if in_place: new_cube = cube operation_function(new_cube.data, points, new_cube.data) else: new_cube = cube.copy(data=operation_function(cube.data, points)) if update_history: history = '%s %s %s (coordinate)' % (cube.name(), operation_symbol, other.name()) elif isinstance(other, iris.cube.Cube): # Deal with cube addition/subtraction by cube # get a coordinate comparison of this cube and the cube to do the operation with coord_comp = iris.analysis.coord_comparison(cube, other) if coord_comp['transposable']: raise ValueError('Cubes cannot be %s, differing axes. ' 'cube.transpose() may be required to re-order the axes.' % operation_past_tense) # provide a deprecation warning if the ignore keyword has been set if ignore is not True: warnings.warn('The "ignore" keyword has been deprecated in add/subtract. This functionality is now automatic. ' 'The provided value to "ignore" has been ignored, and has been automatically calculated.') bad_coord_grps = (coord_comp['ungroupable_and_dimensioned'] + coord_comp['resamplable']) if bad_coord_grps: raise ValueError('This operation cannot be performed as there are differing coordinates (%s) remaining ' 'which cannot be ignored.' % ', '.join({coord_grp.name() for coord_grp in bad_coord_grps})) if in_place: new_cube = cube operation_function(new_cube.data, other.data, new_cube.data) else: new_cube = cube.copy(data=operation_function(cube.data, other.data)) # If a coordinate is to be ignored - remove it ignore = filter(None, [coord_grp[0] for coord_grp in coord_comp['ignorable']]) if not ignore: ignore_string = '' else: ignore_string = ' (ignoring %s)' % ', '.join([coord.name() for coord in ignore]) for coord in ignore: new_cube.remove_coord(coord) if update_history: history = '%s %s %s%s' % (cube.name() or 'unknown', operation_symbol, other.name() or 'unknown', ignore_string) else: return NotImplemented iris.analysis.clear_phenomenon_identity(new_cube) if history is not None: new_cube.add_history(history) return new_cube
def _add_subtract_common(operation_function, operation_symbol, operation_noun, operation_past_tense, cube, other, dim=None, ignore=True, in_place=False): """ Function which shares common code between addition and subtraction of cubes. operation_function - function which does the operation (e.g. numpy.subtract) operation_symbol - the textual symbol of the operation (e.g. '-') operation_noun - the noun of the operation (e.g. 'subtraction') operation_past_tense - the past tense of the operation (e.g. 'subtracted') """ if not isinstance(cube, iris.cube.Cube): raise TypeError( 'The "cube" argument must be an instance of iris.Cube.') if isinstance(other, (int, float)): # Promote scalar to a coordinate and associate unit type with cube unit type other = np.array(other) # Check that the units of the cube and the other item are the same, or if the other does not have a unit, skip this test if cube.units != getattr(other, 'units', cube.units): raise iris.exceptions.NotYetImplementedError('Differing units (%s & %s) %s not implemented' % \ (cube.units, other.units, operation_noun)) if isinstance(other, np.ndarray): _assert_compatible(cube, other) if in_place: new_cube = cube operation_function(new_cube.data, other, new_cube.data) else: new_cube = cube.copy(data=operation_function(cube.data, other)) elif isinstance(other, iris.coords.Coord): # Deal with cube addition/subtraction by coordinate # What dimension are we processing? data_dimension = None if dim is not None: # Ensure the given dim matches the coord if other in cube.coords() and cube.coord_dims(other) != [dim]: raise ValueError( "dim provided does not match dim found for coord") data_dimension = dim else: # Try and get a coord dim if other.shape != (1, ): try: coord_dims = cube.coord_dims(other) data_dimension = coord_dims[0] if coord_dims else None except iris.exceptions.CoordinateNotFoundError: raise ValueError( "Could not determine dimension for add/sub. Use add(coord, dim=dim)" ) if other.ndim != 1: raise iris.exceptions.CoordinateMultiDimError(other) if other.has_bounds(): warnings.warn( '%s by a bounded coordinate not well defined, ignoring bounds.' % operation_noun) points = other.points if data_dimension is not None: points_shape = [1] * cube.ndim points_shape[data_dimension] = -1 points = points.reshape(points_shape) if in_place: new_cube = cube operation_function(new_cube.data, points, new_cube.data) else: new_cube = cube.copy(data=operation_function(cube.data, points)) elif isinstance(other, iris.cube.Cube): # Deal with cube addition/subtraction by cube # get a coordinate comparison of this cube and the cube to do the operation with coord_comp = iris.analysis.coord_comparison(cube, other) if coord_comp['transposable']: # User does not need to transpose their cubes if numpy # array broadcasting will make the dimensions match broadcast_padding = cube.ndim - other.ndim coord_dims_equal = True for coord_group in coord_comp['transposable']: cube_coord, other_coord = coord_group.coords cube_coord_dims = cube.coord_dims(coord=cube_coord) other_coord_dims = other.coord_dims(coord=other_coord) other_coord_dims_broadcasted = tuple( [dim + broadcast_padding for dim in other_coord_dims]) if cube_coord_dims != other_coord_dims_broadcasted: coord_dims_equal = False if not coord_dims_equal: raise ValueError('Cubes cannot be %s, differing axes. ' 'cube.transpose() may be required to ' 're-order the axes.' % operation_past_tense) # provide a deprecation warning if the ignore keyword has been set if ignore is not True: warnings.warn( 'The "ignore" keyword has been deprecated in add/subtract. This functionality is now automatic. ' 'The provided value to "ignore" has been ignored, and has been automatically calculated.' ) bad_coord_grps = (coord_comp['ungroupable_and_dimensioned'] + coord_comp['resamplable']) if bad_coord_grps: raise ValueError( 'This operation cannot be performed as there are differing coordinates (%s) remaining ' 'which cannot be ignored.' % ', '.join({coord_grp.name() for coord_grp in bad_coord_grps})) if in_place: new_cube = cube operation_function(new_cube.data, other.data, new_cube.data) else: new_cube = cube.copy( data=operation_function(cube.data, other.data)) # If a coordinate is to be ignored - remove it ignore = filter( None, [coord_grp[0] for coord_grp in coord_comp['ignorable']]) if not ignore: ignore_string = '' else: ignore_string = ' (ignoring %s)' % ', '.join( [coord.name() for coord in ignore]) for coord in ignore: new_cube.remove_coord(coord) else: return NotImplemented iris.analysis.clear_phenomenon_identity(new_cube) return new_cube
def _multiply_divide_common(operation_function, operation_symbol, operation_noun, cube, other, dim=None, in_place=False): """ Function which shares common code between multiplication and division of cubes. operation_function - function which does the operation (e.g. numpy.divide) operation_symbol - the textual symbol of the operation (e.g. '/') operation_noun - the noun of the operation (e.g. 'division') operation_past_tense - the past tense of the operation (e.g. 'divided') .. seealso:: For information on the dim keyword argument see :func:`multiply`. """ if not isinstance(cube, iris.cube.Cube): raise TypeError( 'The "cube" argument must be an instance of iris.Cube.') if isinstance(other, (int, float)): other = np.array(other) other_unit = None if isinstance(other, np.ndarray): _assert_compatible(cube, other) if in_place: new_cube = cube new_cube.data = operation_function(cube.data, other) else: new_cube = cube.copy(data=operation_function(cube.data, other)) other_unit = '1' elif isinstance(other, iris.coords.Coord): # Deal with cube multiplication/division by coordinate # What dimension are we processing? data_dimension = None if dim is not None: # Ensure the given dim matches the coord if other in cube.coords() and cube.coord_dims(other) != [dim]: raise ValueError( "dim provided does not match dim found for coord") data_dimension = dim else: # Try and get a coord dim if other.shape != (1, ): try: coord_dims = cube.coord_dims(other) data_dimension = coord_dims[0] if coord_dims else None except iris.exceptions.CoordinateNotFoundError: raise ValueError( "Could not determine dimension for mul/div. Use mul(coord, dim=dim)" ) if other.ndim != 1: raise iris.exceptions.CoordinateMultiDimError(other) if other.has_bounds(): warnings.warn( '%s by a bounded coordinate not well defined, ignoring bounds.' % operation_noun) points = other.points # If the axis is defined then shape the provided points so that we can do the # division (this is needed as there is no "axis" keyword to numpy's divide/multiply) if data_dimension is not None: points_shape = [1] * cube.ndim points_shape[data_dimension] = -1 points = points.reshape(points_shape) if in_place: new_cube = cube new_cube.data = operation_function(cube.data, points) else: new_cube = cube.copy(data=operation_function(cube.data, points)) other_unit = other.units elif isinstance(other, iris.cube.Cube): # Deal with cube multiplication/division by cube if in_place: new_cube = cube new_cube.data = operation_function(cube.data, other.data) else: new_cube = cube.copy( data=operation_function(cube.data, other.data)) other_unit = other.units else: return NotImplemented # Update the units if operation_function == np.multiply: new_cube.units = cube.units * other_unit elif operation_function == np.divide: new_cube.units = cube.units / other_unit iris.analysis.clear_phenomenon_identity(new_cube) return new_cube