def differentiate(cube, coord_to_differentiate): r""" Calculate the differential of a given cube with respect to the coord_to_differentiate. Args: * coord_to_differentiate: Either a Coord instance or the unique name of a coordinate which exists in the cube. If a Coord instance is provided, it does not necessarily have to exist on the cube. Example usage:: u_wind_acceleration = differentiate(u_wind_cube, 'forecast_time') The algorithm used is equivalent to: .. math:: d_i = \frac{v_{i+1}-v_i}{c_{i+1}-c_i} Where ``d`` is the differential, ``v`` is the data value, ``c`` is the coordinate value and ``i`` is the index in the differential direction. Hence, in a normal situation if a cube has a shape (x: n; y: m) differentiating with respect to x will result in a cube of shape (x: n-1; y: m) and differentiating with respect to y will result in (x: n; y: m-1). If the coordinate to differentiate is :attr:`circular <iris.coords.DimCoord.circular>` then the resultant shape will be the same as the input cube. .. note:: Difference method used is the same as :func:`cube_delta` and therefore has the same limitations. .. note:: Spherical differentiation does not occur in this routine. """ # Get the delta cube in the required differential direction. Don't add this to the resultant # cube's history as we will do that ourself. # This operation results in a copy of the original cube. delta_cube = cube_delta(cube, coord_to_differentiate, update_history=False) if isinstance(coord_to_differentiate, basestring): coord = cube.coord(coord_to_differentiate) else: coord = coord_to_differentiate delta_coord = _construct_delta_coord(coord) delta_dim = cube.coord_dims(coord)[0] # calculate delta_cube / delta_coord to give the differential. Don't update the history, as we will # do this ourself. delta_cube = iris.analysis.maths.divide(delta_cube, delta_coord, delta_dim, update_history=False) # Update the history of the new cube delta_cube.add_history('differential of %s wrt to %s' % (cube.name(), coord.name())) # Update the standard name delta_cube.rename( ('derivative_of_%s_wrt_%s' % (cube.name(), coord.name()))) return delta_cube
def differentiate(cube, coord_to_differentiate): r""" Calculate the differential of a given cube with respect to the coord_to_differentiate. Args: * coord_to_differentiate: Either a Coord instance or the unique name of a coordinate which exists in the cube. If a Coord instance is provided, it does not necessarily have to exist on the cube. Example usage:: u_wind_acceleration = differentiate(u_wind_cube, 'forecast_time') The algorithm used is equivalent to: .. math:: d_i = \frac{v_{i+1}-v_i}{c_{i+1}-c_i} Where ``d`` is the differential, ``v`` is the data value, ``c`` is the coordinate value and ``i`` is the index in the differential direction. Hence, in a normal situation if a cube has a shape (x: n; y: m) differentiating with respect to x will result in a cube of shape (x: n-1; y: m) and differentiating with respect to y will result in (x: n; y: m-1). If the coordinate to differentiate is :attr:`circular <iris.coords.DimCoord.circular>` then the resultant shape will be the same as the input cube. .. note:: Difference method used is the same as :func:`cube_delta` and therefore has the same limitations. .. note:: Spherical differentiation does not occur in this routine. """ # Get the delta cube in the required differential direction. Don't add this to the resultant # cube's history as we will do that ourself. # This operation results in a copy of the original cube. delta_cube = cube_delta(cube, coord_to_differentiate, update_history=False) if isinstance(coord_to_differentiate, basestring): coord = cube.coord(coord_to_differentiate) else: coord = coord_to_differentiate delta_coord = _construct_delta_coord(coord) delta_dim = cube.coord_dims(coord)[0] # calculate delta_cube / delta_coord to give the differential. Don't update the history, as we will # do this ourself. delta_cube = iris.analysis.maths.divide(delta_cube, delta_coord, delta_dim, update_history=False) # Update the history of the new cube delta_cube.add_history('differential of %s wrt to %s' % (cube.name(), coord.name()) ) # Update the standard name delta_cube.rename(('derivative_of_%s_wrt_%s' % (cube.name(), coord.name())) ) return delta_cube
def exp(cube, update_history=True, in_place=False): """ Calculate the exponential (exp(x)) of the cube. Args: * cube: An instance of :class:`iris.cube.Cube`. .. note:: Taking an exponential will return a cube with dimensionless units. Kwargs: * update_history: Whether to add an entry into the resulting cube's "history" attribute. * in_place: Whether to create a new Cube, or alter the given "cube". Returns: An instance of :class:`iris.cube.Cube`. """ return _math_op_common(cube, np.exp, iris.unit.Unit('1'), history='exp(%s)' % (cube.name()), update_history=update_history, in_place=in_place)
def category_map(*args): """Create a category map from multiple binary cubes Args: *args: Any number of :py:class:`iris.cube.Cube`'s with a 0/1 mapping Returns: iris.cube.Cube: A single cube with data 0-N, where N is the number of cubes input. The data then represents the cube number which is True at that location. A `names` attribute, which is a dictionary mapping of numbers to cube names, is also added to the cube. """ names = {} # Copy the shape and coordinates from one of the cubes mapping = args[0].copy() mapping.data[:] = 0 # For each cube set a number for it's mapping for n, cube in enumerate(args): names[n] = cube.name() mapping.data += n * cube.data # Save the key mapping numbers to names mapping.attributes['names'] = names return mapping
def _get_horizontal_coord(cube, axis): """ Gets the horizontal coordinate on the supplied cube along the specified axis. Args: * cube: An instance of :class:`iris.cube.Cube`. * axis: Locate coordinates on `cube` along this axis. Returns: The horizontal coordinate on the specified axis of the supplied cube. """ coords = cube.coords(axis=axis, dim_coords=False) if len(coords) != 1: raise ValueError( "Cube {!r} must contain a single 1D {} " "coordinate.".format(cube.name()), axis, ) return coords[0]
def spatial_vectors_with_phenom_name(i_cube, j_cube, k_cube=None): """ Given 2 or 3 spatially dependent cubes, return a list of the spatial coordinate names with appropriate phenomenon name. This routine is designed to identify the vector quantites which each of the cubes provided represent and return a list of their 3d spatial dimension names and associated phenomenon. For example, given a cube of "u wind" and "v wind" the return value would be (['u', 'v', 'w'], 'wind'):: >>> spatial_vectors_with_phenom_name(u_wind_cube, v_wind_cube) #doctest: +SKIP (['u', 'v', 'w'], 'wind') """ directional_names = ( ("u", "v", "w"), ("x", "y", "z"), ("i", "j", "k"), ("eastward", "northward", "upward"), ("easterly", "northerly", "vertical"), ("easterly", "northerly", "radial"), ) # Create a list of the standard_names of our incoming cubes (excluding the k_cube if it is None) cube_standard_names = [cube.name() for cube in (i_cube, j_cube, k_cube) if cube is not None] # Define a regular expr which represents (direction, phenomenon) from the standard name of a cube # e.g from "w wind" -> ("w", "wind") vector_qty = re.compile(r"([^\W_]+)[\W_]+(.*)") # Make a dictionary of {direction: phenomenon quantity} cube_directions, cube_phenomena = zip( *[re.match(vector_qty, std_name).groups() for std_name in cube_standard_names] ) # Check that there is only one distinct phenomenon if len(set(cube_phenomena)) != 1: raise ValueError( "Vector phenomenon name not consistent between vector cubes. Got " "cube phenomena: %s; from standard names: %s." % (", ".join(cube_phenomena), ", ".join(cube_standard_names)) ) # Get the appropriate direction list from the cube_directions we have got from the standard name direction = None for possible_direction in directional_names: # if this possible direction (minus the k_cube if it is none) matches direction from the given cubes use it. if possible_direction[0 : len(cube_directions)] == cube_directions: direction = possible_direction # If we didn't get a match, raise an Exception if direction is None: direction_string = "; ".join((", ".join(possible_direction) for possible_direction in directional_names)) raise ValueError( "%s are not recognised vector cube_directions. Possible cube_directions are: %s." % (cube_directions, direction_string) ) return (direction, cube_phenomena[0])
def spatial_vectors_with_phenom_name(i_cube, j_cube, k_cube=None): """ Given 2 or 3 spatially dependent cubes, return a list of the spatial coordinate names with appropriate phenomenon name. This routine is designed to identify the vector quantites which each of the cubes provided represent and return a list of their 3d spatial dimension names and associated phenomenon. For example, given a cube of "u wind" and "v wind" the return value would be (['u', 'v', 'w'], 'wind'):: >>> spatial_vectors_with_phenom_name(u_wind_cube, v_wind_cube) #doctest: +SKIP (['u', 'v', 'w'], 'wind') """ directional_names = (('u', 'v', 'w'), ('x', 'y', 'z'), ('i', 'j', 'k'), ('eastward', 'northward', 'upward'), ('easterly', 'northerly', 'vertical'), ('easterly', 'northerly', 'radial')) # Create a list of the standard_names of our incoming cubes (excluding the k_cube if it is None) cube_standard_names = [ cube.name() for cube in (i_cube, j_cube, k_cube) if cube is not None ] # Define a regular expr which represents (direction, phenomenon) from the standard name of a cube # e.g from "w wind" -> ("w", "wind") vector_qty = re.compile(r'([^\W_]+)[\W_]+(.*)') # Make a dictionary of {direction: phenomenon quantity} cube_directions, cube_phenomena = zip(*[ re.match(vector_qty, std_name).groups() for std_name in cube_standard_names ]) # Check that there is only one distinct phenomenon if len(set(cube_phenomena)) != 1: raise ValueError('Vector phenomenon name not consistent between vector cubes. Got ' 'cube phenomena: %s; from standard names: %s.' % \ (', '.join(cube_phenomena), ', '.join(cube_standard_names)) ) # Get the appropriate direction list from the cube_directions we have got from the standard name direction = None for possible_direction in directional_names: # if this possible direction (minus the k_cube if it is none) matches direction from the given cubes use it. if possible_direction[0:len(cube_directions)] == cube_directions: direction = possible_direction # If we didn't get a match, raise an Exception if direction is None: direction_string = '; '.join( (', '.join(possible_direction) for possible_direction in directional_names)) raise ValueError('%s are not recognised vector cube_directions. Possible cube_directions are: %s.' % \ (cube_directions, direction_string) ) return (direction, cube_phenomena[0])
def _get_xy_dim_coords(cube): """ Return the x and y dimension coordinates from a cube. This function raises a ValueError if the cube does not contain one and only one set of x and y dimension coordinates. It also raises a ValueError if the identified x and y coordinates do not have coordinate systems that are equal. Args: * cube: An instance of :class:`iris.cube.Cube`. Returns: A tuple containing the cube's x and y dimension coordinates. """ x_coords = cube.coords(axis='x', dim_coords=True) if len(x_coords) != 1: raise ValueError('Cube {!r} must contain a single 1D x ' 'coordinate.'.format(cube.name())) x_coord = x_coords[0] y_coords = cube.coords(axis='y', dim_coords=True) if len(y_coords) != 1: raise ValueError('Cube {!r} must contain a single 1D y ' 'coordinate.'.format(cube.name())) y_coord = y_coords[0] if x_coord.coord_system != y_coord.coord_system: raise ValueError("The cube's x ({!r}) and y ({!r}) " "coordinates must have the same coordinate " "system.".format(x_coord.name(), y_coord.name())) return x_coord, y_coord
def _cube_netcdf_variable_name(cube): """ Returns a CF-netCDF variable name for the given cube. Args: * cube (class:`iris.cube.Cube`): An instance of a cube for which a CF-netCDF variable name is required. Returns: A CF-netCDF variable name as a string. """ if cube.var_name is not None: cf_name = cube.var_name else: # Convert to lower case and replace whitespace by underscores. cf_name = '_'.join(cube.name().lower().split()) return cf_name
def _get_cube_variable_name(self, cube): """ Returns a CF-netCDF variable name for the given cube. Args: * cube (class:`iris.cube.Cube`): An instance of a cube for which a CF-netCDF variable name is required. Returns: A CF-netCDF variable name as a string. """ if cube.var_name is not None: cf_name = cube.var_name else: # Convert to lower case and replace whitespace by underscores. cf_name = '_'.join(cube.name().lower().split()) return cf_name
def remap_3d(cube, target, vert_coord=None): """Remap one cube on to the target mapping Args: cube (iris.cube.Cube): The cube to be re-mapped target (iris.cube.Cube): The cube to re-map to vert_coord (str, optional): The name of the coordinate for the vertical re-mapping to be done on. Default is None and will use the DimCoord for the z-axis Returns: iris.cube.Cube: """ # Regrid in the horizontal cube = cube.regrid(target, iris.analysis.Linear()) # Interpolate in the vertical if vert_coord is None: z = target.coord(axis='z', dim_coords=True) else: z = target.coord(vert_coord) cube = cube.interpolate([(z.name(), z.points)], iris.analysis.Linear()) # Match coordinate information newcube = target.copy(data=cube.data) newcube.rename(cube.name()) newcube.units = cube.units # Put back correct time information for coord in newcube.aux_coords: if iris.util.guess_coord_axis(coord) == 'T': newcube.remove_coord(coord) for coord in cube.aux_coords: if iris.util.guess_coord_axis(coord) == 'T': newcube.add_aux_coord(coord) return newcube
def _get_horizontal_coord(cube, axis): """ Gets the horizontal coordinate on the supplied cube along the specified axis. Args: * cube: An instance of :class:`iris.cube.Cube`. * axis: Locate coordinates on `cube` along this axis. Returns: The horizontal coordinate on the specified axis of the supplied cube. """ coords = cube.coords(axis=axis, dim_coords=False) if len(coords) != 1: raise ValueError('Cube {!r} must contain a single 1D {} ' 'coordinate.'.format(cube.name()), axis) return coords[0]
def spatial_vectors_with_phenom_name(i_cube, j_cube, k_cube=None): """ Given 2 or 3 spatially dependent cubes, return a list of the spatial coordinate names with appropriate phenomenon name. This routine is designed to identify the vector quantites which each of the cubes provided represent and return a list of their 3d spatial dimension names and associated phenomenon. For example, given a cube of "u wind" and "v wind" the return value would be (['u', 'v', 'w'], 'wind'):: >>> spatial_vectors_with_phenom_name(u_wind_cube, v_wind_cube) \ #doctest: +SKIP (['u', 'v', 'w'], 'wind') """ directional_names = ( ("u", "v", "w"), ("x", "y", "z"), ("i", "j", "k"), ("eastward", "northward", "upward"), ("easterly", "northerly", "vertical"), ("easterly", "northerly", "radial"), ) # Create a list of the standard_names of our incoming cubes # (excluding the k_cube if it is None). cube_standard_names = [ cube.name() for cube in (i_cube, j_cube, k_cube) if cube is not None ] # Define a regular expr which represents (direction, phenomenon) # from the standard name of a cube. # e.g from "w wind" -> ("w", "wind") vector_qty = re.compile(r"([^\W_]+)[\W_]+(.*)") # Make a dictionary of {direction: phenomenon quantity} cube_directions, cube_phenomena = zip( *[ re.match(vector_qty, std_name).groups() for std_name in cube_standard_names ] ) # Check that there is only one distinct phenomenon if len(set(cube_phenomena)) != 1: raise ValueError( "Vector phenomenon name not consistent between " "vector cubes. Got cube phenomena: {}; from " "standard names: {}.".format( ", ".join(cube_phenomena), ", ".join(cube_standard_names) ) ) # Get the appropriate direction list from the cube_directions we # have got from the standard name. direction = None for possible_direction in directional_names: # If this possible direction (minus the k_cube if it is none) # matches direction from the given cubes use it. if possible_direction[0 : len(cube_directions)] == cube_directions: direction = possible_direction # If we didn't get a match, raise an Exception if direction is None: direction_string = "; ".join( ", ".join(possible_direction) for possible_direction in directional_names ) raise ValueError( "{} are not recognised vector cube_directions. " "Possible cube_directions are: {}.".format( cube_directions, direction_string ) ) return (direction, cube_phenomena[0])
def _create_cf_data_variable(dataset, cube, dimension_names): """ Create CF-netCDF data variable for the cube and any associated grid mapping. Args: * dataset (:class:`netCDF4.Dataset`): The CF-netCDF data file being created. * cube (:class:`iris.cube.Cube`): The associated cube being saved to CF-netCDF file. * dimension_names: List of string names for each dimension of the cube. Returns: The newly created CF-netCDF data variable. """ cf_name = cube.name() # Determine whether there is a cube MDI value. fill_value = None if isinstance(cube.data, np.ma.core.MaskedArray): fill_value = cube.data.fill_value # Create the cube CF-netCDF data variable with data payload. cf_var = dataset.createVariable(cf_name, cube.data.dtype, dimension_names, fill_value=fill_value) cf_var[:] = cube.data if cube.standard_name: cf_var.standard_name = cube.standard_name if cube.long_name: cf_var.long_name = cube.long_name if cube.units != 'unknown': cf_var.units = str(cube.units) # Add any other cube attributes as CF-netCDF data variable attributes. for attr_name in sorted(cube.attributes): value = cube.attributes[attr_name] if attr_name == 'STASH': # Adopting provisional Metadata Conventions for representing MO Scientific Data encoded in NetCDF Format. attr_name = 'ukmo__um_stash_source' value = str(value) if attr_name == "ukmo__process_flags": value = " ".join([x.replace(" ", "_") for x in value]) if attr_name.lower() != 'conventions': setattr(cf_var, attr_name, value) # Declare the CF conventions versions. dataset.Conventions = _CF_CONVENTIONS_VERSION # Create the CF-netCDF data variable cell method attribute. cell_methods = _create_cf_cell_methods(cube, dimension_names) if cell_methods: cf_var.cell_methods = cell_methods # Create the CF-netCDF grid mapping. _create_cf_grid_mapping(dataset, cube, cf_var) return cf_var
def _get_xy_coords(cube): """ Return the x and y coordinates from a cube. This function will preferentially return a pair of dimension coordinates (if there are more than one potential x or y dimension coordinates a ValueError will be raised). If the cube does not have a pair of x and y dimension coordinates it will return 1D auxiliary coordinates (including scalars). If there is not one and only one set of x and y auxiliary coordinates a ValueError will be raised. Having identified the x and y coordinates, the function checks that they have equal coordinate systems and that they do not occupy the same dimension on the cube. Args: * cube: An instance of :class:`iris.cube.Cube`. Returns: A tuple containing the cube's x and y coordinates. """ # Look for a suitable dimension coords first. x_coords = cube.coords(axis='x', dim_coords=True) if not x_coords: # If there is no x coord in dim_coords look for scalars or # monotonic coords in aux_coords. x_coords = [ coord for coord in cube.coords(axis='x', dim_coords=False) if coord.ndim == 1 and coord.is_monotonic() ] if len(x_coords) != 1: raise ValueError('Cube {!r} must contain a single 1D x ' 'coordinate.'.format(cube.name())) x_coord = x_coords[0] # Look for a suitable dimension coords first. y_coords = cube.coords(axis='y', dim_coords=True) if not y_coords: # If there is no y coord in dim_coords look for scalars or # monotonic coords in aux_coords. y_coords = [ coord for coord in cube.coords(axis='y', dim_coords=False) if coord.ndim == 1 and coord.is_monotonic() ] if len(y_coords) != 1: raise ValueError('Cube {!r} must contain a single 1D y ' 'coordinate.'.format(cube.name())) y_coord = y_coords[0] if x_coord.coord_system != y_coord.coord_system: raise ValueError("The cube's x ({!r}) and y ({!r}) " "coordinates must have the same coordinate " "system.".format(x_coord.name(), y_coord.name())) # The x and y coordinates must describe different dimensions # or be scalar coords. x_dims = cube.coord_dims(x_coord) x_dim = None if x_dims: x_dim = x_dims[0] y_dims = cube.coord_dims(y_coord) y_dim = None if y_dims: y_dim = y_dims[0] if x_dim is not None and y_dim == x_dim: raise ValueError("The cube's x and y coords must not describe the " "same data dimension.") return x_coord, y_coord
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 to_level(cube, order=0, **kwargs): """ Interpolates to the vertical co-ordinate level Args: cube (iris.cube.Cube): order (int): Order of interpolation. Currently only supports linear (1). **kwargs: Provides the coordinate value pair to be interpolated to. Must be specified as a list. e.g. to interpolate a cube onto a vertical surface of 1000m >>> to_level(cube, altitude=[1000]) Returns: iris.cube.Cube: A cube interpolated onto the new vertical co-ordinate. Has the same properties as the input cube but with new vertical co-ordinates """ if len(kwargs) > 1: raise Exception('Can only specify a single vertical co-ordinate') # Extract the specified output co-ordinate information coord_name = list(kwargs)[0] coord_in = cube.coord(coord_name) coord_out = kwargs[coord_name] # Broadcast array to cube shape dims = np.ndim(coord_out) if dims == 1: ny, nx = cube.shape[1:] coord_out_3d = coord_out * np.ones([nx, ny, len(coord_out)]) coord_out_3d = coord_out_3d.transpose() elif dims == 3: coord_out_3d = coord_out else: raise Exception('Coordinate must be 3d or a list of levels') # Select the interpolation flag based on the coordinate if 'pressure' in coord_name: # Air pressure is interpolated logarithmically interp_flag = 1 else: # Otherwise interpolation is linear interp_flag = 0 # Interpolate data newdata, mask = finterpolate.to_level( cube.data, coord_in.points, coord_out_3d, interp_flag, order) newdata = np.ma.masked_where(mask, newdata) # Create a new cube with the new number of vertical levels newcube = iris.cube.Cube( newdata, long_name=cube.name(), units=cube.units, attributes=cube.attributes, dim_coords_and_dims=[(cube.coord(axis='y', dim_coords=True), 1), (cube.coord(axis='x', dim_coords=True), 2)]) # Add the new co-ordinate to the output cube newcoord = iris.coords.AuxCoord( coord_out, long_name=coord_name, units=coord_in.units) newcube.add_aux_coord(newcoord, range(newcoord.ndim)) # Promote single dimensional coordinates to dimensional coordinates try: iris.util.promote_aux_coord_to_dim_coord(newcube, coord_name) except ValueError: dummy_coord = iris.coords.DimCoord(range(len(coord_out)), long_name='level_number') newcube.add_dim_coord(dummy_coord, 0) # Add single value coordinates back to the newcube add_scalar_coords(cube, newcube) return newcube
def spatial_vectors_with_phenom_name(i_cube, j_cube, k_cube=None): """ Given 2 or 3 spatially dependent cubes, return a list of the spatial coordinate names with appropriate phenomenon name. This routine is designed to identify the vector quantites which each of the cubes provided represent and return a list of their 3d spatial dimension names and associated phenomenon. For example, given a cube of "u wind" and "v wind" the return value would be (['u', 'v', 'w'], 'wind'):: >>> spatial_vectors_with_phenom_name(u_wind_cube, v_wind_cube) \ #doctest: +SKIP (['u', 'v', 'w'], 'wind') """ directional_names = (('u', 'v', 'w'), ('x', 'y', 'z'), ('i', 'j', 'k'), ('eastward', 'northward', 'upward'), ('easterly', 'northerly', 'vertical'), ('easterly', 'northerly', 'radial')) # Create a list of the standard_names of our incoming cubes # (excluding the k_cube if it is None). cube_standard_names = [cube.name() for cube in (i_cube, j_cube, k_cube) if cube is not None] # Define a regular expr which represents (direction, phenomenon) # from the standard name of a cube. # e.g from "w wind" -> ("w", "wind") vector_qty = re.compile(r'([^\W_]+)[\W_]+(.*)') # Make a dictionary of {direction: phenomenon quantity} cube_directions, cube_phenomena = zip( *[re.match(vector_qty, std_name).groups() for std_name in cube_standard_names]) # Check that there is only one distinct phenomenon if len(set(cube_phenomena)) != 1: raise ValueError('Vector phenomenon name not consistent between ' 'vector cubes. Got cube phenomena: {}; from ' 'standard names: {}.'.format( ', '.join(cube_phenomena), ', '.join(cube_standard_names))) # Get the appropriate direction list from the cube_directions we # have got from the standard name. direction = None for possible_direction in directional_names: # If this possible direction (minus the k_cube if it is none) # matches direction from the given cubes use it. if possible_direction[0:len(cube_directions)] == cube_directions: direction = possible_direction # If we didn't get a match, raise an Exception if direction is None: direction_string = '; '.join(', '.join(possible_direction) for possible_direction in directional_names) raise ValueError('{} are not recognised vector cube_directions. ' 'Possible cube_directions are: {}.'.format( cube_directions, direction_string)) return (direction, cube_phenomena[0])
def _get_xy_coords(cube): """ Return the x and y coordinates from a cube. This function will preferentially return a pair of dimension coordinates (if there are more than one potential x or y dimension coordinates a ValueError will be raised). If the cube does not have a pair of x and y dimension coordinates it will return 1D auxiliary coordinates (including scalars). If there is not one and only one set of x and y auxiliary coordinates a ValueError will be raised. Having identified the x and y coordinates, the function checks that they have equal coordinate systems and that they do not occupy the same dimension on the cube. Args: * cube: An instance of :class:`iris.cube.Cube`. Returns: A tuple containing the cube's x and y coordinates. """ # Look for a suitable dimension coords first. x_coords = cube.coords(axis='x', dim_coords=True) if not x_coords: # If there is no x coord in dim_coords look for scalars or # monotonic coords in aux_coords. x_coords = [coord for coord in cube.coords(axis='x', dim_coords=False) if coord.ndim == 1 and coord.is_monotonic()] if len(x_coords) != 1: raise ValueError('Cube {!r} must contain a single 1D x ' 'coordinate.'.format(cube.name())) x_coord = x_coords[0] # Look for a suitable dimension coords first. y_coords = cube.coords(axis='y', dim_coords=True) if not y_coords: # If there is no y coord in dim_coords look for scalars or # monotonic coords in aux_coords. y_coords = [coord for coord in cube.coords(axis='y', dim_coords=False) if coord.ndim == 1 and coord.is_monotonic()] if len(y_coords) != 1: raise ValueError('Cube {!r} must contain a single 1D y ' 'coordinate.'.format(cube.name())) y_coord = y_coords[0] if x_coord.coord_system != y_coord.coord_system: raise ValueError("The cube's x ({!r}) and y ({!r}) " "coordinates must have the same coordinate " "system.".format(x_coord.name(), y_coord.name())) # The x and y coordinates must describe different dimensions # or be scalar coords. x_dims = cube.coord_dims(x_coord) x_dim = None if x_dims: x_dim = x_dims[0] y_dims = cube.coord_dims(y_coord) y_dim = None if y_dims: y_dim = y_dims[0] if x_dim is not None and y_dim == x_dim: raise ValueError("The cube's x and y coords must not describe the " "same data dimension.") return x_coord, y_coord
def differentiate(cube, coord_to_differentiate): r""" Calculate the differential of a given cube with respect to the coord_to_differentiate. Args: * coord_to_differentiate: Either a Coord instance or the unique name of a coordinate which exists in the cube. If a Coord instance is provided, it does not necessarily have to exist on the cube. Example usage:: u_wind_acceleration = differentiate(u_wind_cube, 'forecast_time') The algorithm used is equivalent to: .. math:: d_i = \frac{v_{i+1}-v_i}{c_{i+1}-c_i} Where ``d`` is the differential, ``v`` is the data value, ``c`` is the coordinate value and ``i`` is the index in the differential direction. Hence, in a normal situation if a cube has a shape (x: n; y: m) differentiating with respect to x will result in a cube of shape (x: n-1; y: m) and differentiating with respect to y will result in (x: n; y: m-1). If the coordinate to differentiate is :attr:`circular <iris.coords.DimCoord.circular>` then the resultant shape will be the same as the input cube. In the returned cube the `coord_to_differentiate` object is redefined such that the output coordinate values are set to the averages of the original coordinate values (i.e. the mid-points). Similarly, the output lower bounds values are set to the averages of the original lower bounds values and the output upper bounds values are set to the averages of the original upper bounds values. In more formal terms: * `C[i] = (c[i] + c[i+1]) / 2` * `B[i, 0] = (b[i, 0] + b[i+1, 0]) / 2` * `B[i, 1] = (b[i, 1] + b[i+1, 1]) / 2` where `c` and `b` represent the input coordinate values and bounds, and `C` and `B` the output coordinate values and bounds. .. note:: Difference method used is the same as :func:`cube_delta` and therefore has the same limitations. .. note:: Spherical differentiation does not occur in this routine. """ # Get the delta cube in the required differential direction. # This operation results in a copy of the original cube. delta_cube = cube_delta(cube, coord_to_differentiate) if isinstance(coord_to_differentiate, str): coord = cube.coord(coord_to_differentiate) else: coord = coord_to_differentiate delta_coord = _construct_delta_coord(coord) delta_dim = cube.coord_dims(coord.name())[0] # calculate delta_cube / delta_coord to give the differential. delta_cube = iris.analysis.maths.divide(delta_cube, delta_coord, delta_dim) # Update the standard name delta_cube.rename('derivative_of_{}_wrt_{}'.format(cube.name(), coord.name())) return delta_cube
def differentiate(cube, coord_to_differentiate): r""" Calculate the differential of a given cube with respect to the coord_to_differentiate. Args: * coord_to_differentiate: Either a Coord instance or the unique name of a coordinate which exists in the cube. If a Coord instance is provided, it does not necessarily have to exist on the cube. Example usage:: u_wind_acceleration = differentiate(u_wind_cube, 'forecast_time') The algorithm used is equivalent to: .. math:: d_i = \frac{v_{i+1}-v_i}{c_{i+1}-c_i} Where ``d`` is the differential, ``v`` is the data value, ``c`` is the coordinate value and ``i`` is the index in the differential direction. Hence, in a normal situation if a cube has a shape (x: n; y: m) differentiating with respect to x will result in a cube of shape (x: n-1; y: m) and differentiating with respect to y will result in (x: n; y: m-1). If the coordinate to differentiate is :attr:`circular <iris.coords.DimCoord.circular>` then the resultant shape will be the same as the input cube. In the returned cube the `coord_to_differentiate` object is redefined such that the output coordinate values are set to the averages of the original coordinate values (i.e. the mid-points). Similarly, the output lower bounds values are set to the averages of the original lower bounds values and the output upper bounds values are set to the averages of the original upper bounds values. In more formal terms: * `C[i] = (c[i] + c[i+1]) / 2` * `B[i, 0] = (b[i, 0] + b[i+1, 0]) / 2` * `B[i, 1] = (b[i, 1] + b[i+1, 1]) / 2` where `c` and `b` represent the input coordinate values and bounds, and `C` and `B` the output coordinate values and bounds. .. note:: Difference method used is the same as :func:`cube_delta` and therefore has the same limitations. .. note:: Spherical differentiation does not occur in this routine. """ # Get the delta cube in the required differential direction. # This operation results in a copy of the original cube. delta_cube = cube_delta(cube, coord_to_differentiate) if isinstance(coord_to_differentiate, basestring): coord = cube.coord(coord_to_differentiate) else: coord = coord_to_differentiate delta_coord = _construct_delta_coord(coord) delta_dim = cube.coord_dims(coord.name())[0] # calculate delta_cube / delta_coord to give the differential. delta_cube = iris.analysis.maths.divide(delta_cube, delta_coord, delta_dim) # Update the standard name delta_cube.rename('derivative_of_{}_wrt_{}'.format(cube.name(), coord.name())) return delta_cube