Source code for windspharm.iris

"""Spherical harmonic vector wind computations (`iris` interface)."""
# Copyright (c) 2012-2018 Andrew Dawson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from __future__ import absolute_import

from iris.cube import Cube
from iris.util import reverse

from . import standard
from ._common import get_apiorder, inspect_gridtype, to3d


[docs]class VectorWind(object): """Vector wind computations (`iris` interface).""" def __init__(self, u, v, rsphere=6.3712e6, legfunc='stored'): """Initialize a VectorWind instance. **Arguments:** *u*, *v* Zonal and meridional components of the vector wind respectively. Both components should be `~iris.cube.Cube` instances. The components must have the same dimension coordinates and contain no missing values. **Optional argument:** *rsphere* The radius in metres of the sphere used in the spherical harmonic computations. Default is 6371200 m, the approximate mean spherical Earth radius. *legfunc* 'stored' (default) or 'computed'. If 'stored', associated legendre functions are precomputed and stored when the class instance is created. This uses O(nlat**3) memory, but speeds up the spectral transforms. If 'computed', associated legendre functions are computed on the fly when transforms are requested. This uses O(nlat**2) memory, but slows down the spectral transforms a bit. **Example:** Initialize a `VectorWind` instance with zonal and meridional components of the vector wind:: from windspharm.iris import VectorWind w = VectorWind(u, v) """ # Make sure inputs are Iris cubes. if type(u) is not Cube or type(v) is not Cube: raise TypeError('u and v must be iris cubes') # Get the coordinates of each component and make sure they are the # same. ucoords = u.dim_coords vcoords = v.dim_coords if ucoords != vcoords: raise ValueError('u and v must have the same dimensions') # Extract the latitude and longitude dimension coordinates. lat, lat_dim = _dim_coord_and_dim(u, 'latitude') lon, lon_dim = _dim_coord_and_dim(v, 'longitude') # Reverse the latitude dimension if necessary. if (lat.points[0] < lat.points[1]): # need to reverse latitude dimension u = reverse(u, lat_dim) v = reverse(v, lat_dim) lat, lat_dim = _dim_coord_and_dim(u, 'latitude') # Determine the grid type of the input. gridtype = inspect_gridtype(lat.points) # Determine the ordering list (input to transpose) which will put the # latitude and longitude dimensions at the front of the cube's # dimensions, and the ordering list which will reverse this process. apiorder, self._reorder = get_apiorder(u.ndim, lat_dim, lon_dim) # Re-order the inputs (in-place, so we take a copy first) so latiutude # and longitude are at the front. u = u.copy() v = v.copy() u.transpose(apiorder) v.transpose(apiorder) # Records the current shape and dimension coordinates of the inputs. self._ishape = u.shape self._coords = u.dim_coords # Reshape the inputs so they are compatible with pyspharm. u = to3d(u.data) v = to3d(v.data) # Create a base VectorWind instance to do the computations. self._api = standard.VectorWind(u, v, gridtype=gridtype, rsphere=rsphere, legfunc=legfunc) def _metadata(self, var, **attributes): """Re-shape outputs and add meta-data.""" var = var.reshape(self._ishape) var = Cube( var, dim_coords_and_dims=list(zip(self._coords, range(var.ndim)))) var.transpose(self._reorder) for attribute, value in attributes.items(): setattr(var, attribute, value) return var
[docs] def u(self): """Zonal component of vector wind. **Returns:** *u* The zonal component of the wind. **Example:** Get the zonal component of the vector wind:: u = w.u() """ u = self._api.u u = self._metadata(u, standard_name='eastward_wind', units='m s**-1', long_name='eastward component of wind') return u
[docs] def v(self): """Meridional component of vector wind. **Returns:** *v* The meridional component of the wind. **Example:** Get the meridional component of the vector wind:: v = w.v() """ v = self._api.v v = self._metadata(v, standard_name='northward_wind', units='m s**-1', long_name='northward component of wind') return v
[docs] def magnitude(self): """Wind speed (magnitude of vector wind). **Returns:** *speed* The wind speed. **Example:** Get the magnitude of the vector wind:: spd = w.magnitude() """ m = self._api.magnitude() m = self._metadata(m, standard_name='wind_speed', units='m s**-1', long_name='wind speed') return m
[docs] def vrtdiv(self, truncation=None): """Relative vorticity and horizontal divergence. **Optional argument:** *truncation* Truncation limit (triangular truncation) for the spherical harmonic computation. **Returns:** *vrt*, *div* The relative vorticity and divergence respectively. **See also:** `~VectorWind.vorticity`, `~VectorWind.divergence`. **Examples:** Compute the relative vorticity and divergence:: vrt, div = w.vrtdiv() Compute the relative vorticity and divergence and apply spectral truncation at triangular T13:: vrtT13, divT13 = w.vrtdiv(truncation=13) """ vrt, div = self._api.vrtdiv(truncation=truncation) vrt = self._metadata(vrt, units='s**-1', standard_name='atmosphere_relative_vorticity', long_name='relative vorticity') div = self._metadata(div, units='s**-1', standard_name='divergence_of_wind', long_name='horizontal divergence') return vrt, div
[docs] def vorticity(self, truncation=None): """Relative vorticity. **Optional argument:** *truncation* Truncation limit (triangular truncation) for the spherical harmonic computation. **Returns:** *vrt* The relative vorticity. **See also:** `~VectorWind.vrtdiv`, `~VectorWind.absolutevorticity`. **Examples:** Compute the relative vorticity:: vrt = w.vorticity() Compute the relative vorticity and apply spectral truncation at triangular T13:: vrtT13 = w.vorticity(truncation=13) """ vrt = self._api.vorticity(truncation=truncation) vrt = self._metadata(vrt, units='s**-1', standard_name='atmosphere_relative_vorticity', long_name='relative vorticity') return vrt
[docs] def divergence(self, truncation=None): """Horizontal divergence. **Optional argument:** *truncation* Truncation limit (triangular truncation) for the spherical harmonic computation. **Returns:** *div* The divergence. **See also:** `~VectorWind.vrtdiv`. **Examples:** Compute the divergence:: div = w.divergence() Compute the divergence and apply spectral truncation at triangular T13:: divT13 = w.divergence(truncation=13) """ div = self._api.divergence(truncation=truncation) div = self._metadata(div, units='s**-1', standard_name='divergence_of_wind', long_name='horizontal divergence') return div
[docs] def planetaryvorticity(self, omega=None): """Planetary vorticity (Coriolis parameter). **Optional argument:** *omega* Earth's angular velocity. The default value if not specified is 7.292x10**-5 s**-1. **Returns:** *pvorticity* The planetary vorticity. **See also:** `~VectorWind.absolutevorticity`. **Example:** Compute planetary vorticity using default values:: pvrt = w.planetaryvorticity() Override the default value for Earth's angular velocity:: pvrt = w.planetaryvorticity(omega=7.2921150) """ f = self._api.planetaryvorticity(omega=omega) f = self._metadata( f, units='s**-1', standard_name='coriolis_parameter', long_name='planetary vorticity (coriolis parameter)') return f
[docs] def absolutevorticity(self, omega=None, truncation=None): """Absolute vorticity (sum of relative and planetary vorticity). **Optional arguments:** *omega* Earth's angular velocity. The default value if not specified is 7.292x10**-5 s**-1. *truncation* Truncation limit (triangular truncation) for the spherical harmonic computation. **Returns:** *avorticity* The absolute (relative + planetary) vorticity. **See also:** `~VectorWind.vorticity`, `~VectorWind.planetaryvorticity`. **Examples:** Compute absolute vorticity:: avrt = w.absolutevorticity() Compute absolute vorticity and apply spectral truncation at triangular T13, also override the default value for Earth's angular velocity:: avrt = w.absolutevorticity(omega=7.2921150, truncation=13) """ avrt = self._api.absolutevorticity(omega=omega, truncation=truncation) avrt = self._metadata(avrt, units='s**-1', standard_name='atmosphere_absolute_vorticity', long_name='absolute vorticity') return avrt
[docs] def sfvp(self, truncation=None): """Streamfunction and velocity potential. **Optional argument:** *truncation* Truncation limit (triangular truncation) for the spherical harmonic computation. **Returns:** *sf*, *vp* The streamfunction and velocity potential respectively. **See also:** `~VectorWind.streamfunction`, `~VectorWind.velocitypotential`. **Examples:** Compute streamfunction and velocity potential:: sf, vp = w.sfvp() Compute streamfunction and velocity potential and apply spectral truncation at triangular T13:: sfT13, vpT13 = w.sfvp(truncation=13) """ sf, vp = self._api.sfvp(truncation=truncation) sf = self._metadata( sf, units='m**2 s**-1', standard_name='atmosphere_horizontal_streamfunction', long_name='streamfunction') vp = self._metadata( vp, units='m**2 s**-1', standard_name='atmosphere_horizontal_velocity_potential', long_name='velocity potential') return sf, vp
[docs] def streamfunction(self, truncation=None): """Streamfunction. **Optional argument:** *truncation* Truncation limit (triangular truncation) for the spherical harmonic computation. **Returns:** *sf* The streamfunction. **See also:** `~VectorWind.sfvp`. **Examples:** Compute streamfunction:: sf = w.streamfunction() Compute streamfunction and apply spectral truncation at triangular T13:: sfT13 = w.streamfunction(truncation=13) """ sf = self._api.streamfunction(truncation=truncation) sf = self._metadata( sf, units='m**2 s**-1', standard_name='atmosphere_horizontal_streamfunction', long_name='streamfunction') return sf
[docs] def velocitypotential(self, truncation=None): """Velocity potential. **Optional argument:** *truncation* Truncation limit (triangular truncation) for the spherical harmonic computation. **Returns:** *vp* The velocity potential. **See also:** `~VectorWind.sfvp`. **Examples:** Compute velocity potential:: vp = w.velocity potential() Compute velocity potential and apply spectral truncation at triangular T13:: vpT13 = w.velocity potential(truncation=13) """ vp = self._api.velocitypotential(truncation=truncation) vp = self._metadata( vp, units='m**2 s**-1', standard_name='atmosphere_horizontal_velocity_potential', long_name='velocity potential') return vp
[docs] def helmholtz(self, truncation=None): """Irrotational and non-divergent components of the vector wind. **Optional argument:** *truncation* Truncation limit (triangular truncation) for the spherical harmonic computation. **Returns:** *uchi*, *vchi*, *upsi*, *vpsi* Zonal and meridional components of irrotational and non-divergent wind components respectively. **See also:** `~VectorWind.irrotationalcomponent`, `~VectorWind.nondivergentcomponent`. **Examples:** Compute the irrotational and non-divergent components of the vector wind:: uchi, vchi, upsi, vpsi = w.helmholtz() Compute the irrotational and non-divergent components of the vector wind and apply spectral truncation at triangular T13:: uchiT13, vchiT13, upsiT13, vpsiT13 = w.helmholtz(truncation=13) """ uchi, vchi, upsi, vpsi = self._api.helmholtz(truncation=truncation) uchi = self._metadata(uchi, units='m s**-1', long_name='irrotational_eastward_wind') vchi = self._metadata(vchi, units='m s**-1', long_name='irrotational_northward_wind') upsi = self._metadata(upsi, units='m s**-1', long_name='non_divergent_eastward_wind') vpsi = self._metadata(vpsi, units='m s**-1', long_name='non_divergent_northward_wind') return uchi, vchi, upsi, vpsi
[docs] def irrotationalcomponent(self, truncation=None): """Irrotational (divergent) component of the vector wind. .. note:: If both the irrotational and non-divergent components are required then `~VectorWind.helmholtz` should be used instead. **Optional argument:** *truncation* Truncation limit (triangular truncation) for the spherical harmonic computation. **Returns:** *uchi*, *vchi* The zonal and meridional components of the irrotational wind respectively. **See also:** `~VectorWind.helmholtz`. **Examples:** Compute the irrotational component of the vector wind:: uchi, vchi = w.irrotationalcomponent() Compute the irrotational component of the vector wind and apply spectral truncation at triangular T13:: uchiT13, vchiT13 = w.irrotationalcomponent(truncation=13) """ uchi, vchi = self._api.irrotationalcomponent(truncation=truncation) uchi = self._metadata(uchi, units='m s**-1', long_name='irrotational_eastward_wind') vchi = self._metadata(vchi, units='m s**-1', long_name='irrotational_northward_wind') return uchi, vchi
[docs] def nondivergentcomponent(self, truncation=None): """Non-divergent (rotational) component of the vector wind. .. note:: If both the non-divergent and irrotational components are required then `~VectorWind.helmholtz` should be used instead. **Optional argument:** *truncation* Truncation limit (triangular truncation) for the spherical harmonic computation. **Returns:** *upsi*, *vpsi* The zonal and meridional components of the non-divergent wind respectively. **See also:** `~VectorWind.helmholtz`. **Examples:** Compute the non-divergent component of the vector wind:: upsi, vpsi = w.nondivergentcomponent() Compute the non-divergent component of the vector wind and apply spectral truncation at triangular T13:: upsiT13, vpsiT13 = w.nondivergentcomponent(truncation=13) """ upsi, vpsi = self._api.nondivergentcomponent(truncation=truncation) upsi = self._metadata(upsi, units='m s**-1', long_name='non_divergent_eastward_wind') vpsi = self._metadata(vpsi, units='m s**-1', long_name='non_divergent_northward_wind') return upsi, vpsi
[docs] def gradient(self, chi, truncation=None): """Computes the vector gradient of a scalar field on the sphere. **Argument:** *chi* A scalar field. It must be a `~iris.cube.Cube` with the same latitude and longitude dimensions as the vector wind components that initialized the `VectorWind` instance. **Optional argument:** *truncation* Truncation limit (triangular truncation) for the spherical harmonic computation. **Returns:** *uchi*, *vchi* The zonal and meridional components of the vector gradient respectively. **Examples:** Compute the vector gradient of absolute vorticity:: avrt = w.absolutevorticity() avrt_zonal, avrt_meridional = w.gradient(avrt) Compute the vector gradient of absolute vorticity and apply spectral truncation at triangular T13:: avrt = w.absolutevorticity() avrt_zonalT13, avrt_meridionalT13 = w.gradient(avrt, truncation=13) """ if type(chi) is not Cube: raise TypeError('scalar field must be an iris cube') name = chi.name() lat, lat_dim = _dim_coord_and_dim(chi, 'latitude') lon, lon_dim = _dim_coord_and_dim(chi, 'longitude') if (lat.points[0] < lat.points[1]): # need to reverse latitude dimension chi = reverse(chi, lat_dim) lat, lat_dim = _dim_coord_and_dim(chi, 'latitude') apiorder, reorder = get_apiorder(chi.ndim, lat_dim, lon_dim) chi = chi.copy() chi.transpose(apiorder) ishape = chi.shape coords = chi.dim_coords chi = to3d(chi.data) uchi, vchi = self._api.gradient(chi, truncation=truncation) uchi = uchi.reshape(ishape) vchi = vchi.reshape(ishape) uchi = Cube( uchi, dim_coords_and_dims=list(zip(coords, range(uchi.ndim)))) vchi = Cube( vchi, dim_coords_and_dims=list(zip(coords, range(vchi.ndim)))) uchi.transpose(reorder) vchi.transpose(reorder) uchi.long_name = 'zonal_gradient_of_{!s}'.format(name) vchi.long_name = 'meridional_gradient_of_{!s}'.format(name) return uchi, vchi
[docs] def truncate(self, field, truncation=None): """Apply spectral truncation to a scalar field. This is useful to represent other fields in a way consistent with the output of other `VectorWind` methods. **Argument:** *field* A scalar field. It must be a `~iris.cube.Cube` with the same latitude and longitude dimensions as the vector wind components that initialized the `VectorWind` instance. **Optional argument:** *truncation* Truncation limit (triangular truncation) for the spherical harmonic computation. If not specified it will default to *nlats - 1* where *nlats* is the number of latitudes. **Returns:** *truncated_field* The field with spectral truncation applied. **Examples:** Truncate a scalar field to the computational resolution of the `VectorWind` instance:: scalar_field_truncated = w.truncate(scalar_field) Truncate a scalar field to T21:: scalar_field_T21 = w.truncate(scalar_field, truncation=21) """ if type(field) is not Cube: raise TypeError('scalar field must be an iris cube') lat, lat_dim = _dim_coord_and_dim(field, 'latitude') lon, lon_dim = _dim_coord_and_dim(field, 'longitude') if (lat.points[0] < lat.points[1]): # need to reverse latitude dimension field = reverse(field, lat_dim) lat, lat_dim = _dim_coord_and_dim(field, 'latitude') apiorder, reorder = get_apiorder(field.ndim, lat_dim, lon_dim) field = field.copy() field.transpose(apiorder) ishape = field.shape fielddata = to3d(field.data) fieldtrunc = self._api.truncate(fielddata, truncation=truncation) field.data = fieldtrunc.reshape(ishape) field.transpose(reorder) return field
def _dim_coord_and_dim(cube, coord): """ Retrieve a given dimension coordinate from a `~iris.cube.Cube` and the dimension number it corresponds to. """ coords = [c for c in cube.dim_coords if coord in c.name()] if len(coords) > 1: raise ValueError('multiple {!s} coordinates not ' 'allowed: {!r}'.format(coord, cube)) try: c = coords[0] except IndexError: raise ValueError('cannot get {!s} coordinate ' 'from cube {!r}'.format(coord, cube)) c_dim = cube.coord_dims(c) if len(c_dim) != 1: raise ValueError('multiple dimensions with {!s} coordinate ' 'not allowed: {!r}'.format(coord, cube)) c_dim = c_dim[0] return c, c_dim