class AtmosFlow: """ Atmospheric Flow Used to calculate meteorological parameters from the given cubes. Derived quantities are stored as cached properties to save computational time. Calculating quantites that involve on horizontal derivatives are only true if used in cartesian coordinates, e.g. on the output from a LAM model with constant grid spacing. Use `prepare_cube_on_model_levels` to prepare cubes for `AtmosFlow` on a cartesian grid. Attributes ---------- cubes: iris.cube.CubeList list of cubes representing meteorological parameters main_cubes: iris.cube.CubeList list of non-scalar cubes wind_cmpnt: iris.cube.CubeList list of u,v,w-wind components {x,y,z}coord: iris.coord.Coord Coordinates in the respective dimensions pres: iris.cube.Cube Pressure created from a coordinate, if possible lats: iris.cube.Cube latitudes fcor: iris.cube.Cube Coriolis parameter (taken at 45N by default) d{x,y}: iris.cube.Cube Grid spacing (if cartesian=True) """ def __init__(self, cartesian=True, **kw_vars): """ Parameters ---------- cartesian: bool (default True) Cartesian coord system flag **kw_vars: dict of iris cubes meteorological parameters Examples -------- Initialise an `AtmosFlow` object with 3 wind components >>> AF = AtmosFlow(u=u_cart, v=v_cart, w=w_cart) and calculate relative vorticity: >>> rv = AF.rel_vort """ self.__dict__.update(kw_vars) self.cartesian = cartesian self.cubes = CubeList(filter(iscube, self.__dict__.values())) self.main_cubes = CubeList(filter(iscube_and_not_scalar, self.__dict__.values())) self.wind_cmpnt = CubeList(filter(None, [getattr(self, 'u', None), getattr(self, 'v', None), getattr(self, 'w', None)])) thecube = self.main_cubes[0] check_coords(self.main_cubes) # Get the dim_coord, or None if none exist, for the xyz dimensions self.xcoord = thecube.coord(axis='X', dim_coords=True) self.ycoord = thecube.coord(axis='Y', dim_coords=True) self.zcoord = thecube.coord(axis='Z') if self.zcoord.units.is_convertible('Pa'): # Check if the vertical coordinate is pressure self.zmode = 'pcoord' for cube in self.main_cubes: if self.zcoord in cube.dim_coords: clean_pressure_coord(cube) self.pres = coords.pres_coord_to_cube(thecube) self.cubes.append(self.pres) if not hasattr(self, 'lats'): try: _, lats = grid.unrotate_lonlat_grids(thecube) except (ValueError, AttributeError): lats = np.array([45.]) self.lats = Cube(lats, units='degrees', standard_name='latitude') self.fcor = mcalc.coriolis_parameter(self.lats) self.fcor.convert_units('s-1') if self.cartesian: for ax, rot_name in zip(('x', 'y'), ('grid_longitude', 'grid_latitude')): for cube in self.cubes: if rot_name in [i.name() for i in cube.coords(axis=ax)]: cube.remove_coord(rot_name) try: _dx = thecube.attributes['um_res'].to_flt('m') except KeyError: _dx = 1. self.dx = Cube(_dx, units='m') self.dy = Cube(_dx, units='m') # Non-spherical coords? # self.horiz_cs = thecube.coord(axis='x', dim_coords=True).coord_system self.horiz_cs = thecube.coord_system() self._spherical_coords = isinstance(self.horiz_cs, (iris.coord_systems.GeogCS, iris.coord_systems.RotatedGeogCS)) # todo: interface for spherical coordinates switch? # assert not self._spherical_coords,\ # 'Only non-spherical coordinates are allowed ...' if self.cartesian and self._spherical_coords: warnings.warn('Cubes are in spherical coordinates!' '\n Use `replace_lonlat_dimcoord_with_cart function`' ' to change coordinates!') def __repr__(self): msg = "arke `Atmospheric Flow` containing of:\n" msg += "\n".join(tuple(i.name() for i in self.cubes)) return msg def d_dx(self, name=None, alias=None): r""" Derivative of a cube along the x-axis .. math:: \frac{\partial }{\partial x} """ if name is None and isinstance(alias, str): v = getattr(self, alias) else: v = self.cubes.extract_strict(name) return cube_deriv(v, self.xcoord) def d_dy(self, name=None, alias=None): r""" Derivative of a cube along the y-axis .. math:: \frac{\partial }{\partial y} """ if name is None and isinstance(alias, str): v = getattr(self, alias) else: v = self.cubes.extract_strict(name) return cube_deriv(v, self.ycoord) def hgradmag(self, name=None, alias=None): r""" Magnitude of the horizontal gradient of a cube .. math:: \sqrt[(\frac{\partial }{\partial y})^2 +(\frac{\partial }{\partial y})^2] """ dvdx2 = self.d_dx(name=name, alias=alias) ** 2 dvdy2 = self.d_dy(name=name, alias=alias) ** 2 return (dvdx2 + dvdy2) ** 0.5 @cached_property def wspd(self): r""" Calculate wind speed (magnitude) .. math:: \sqrt{u^2 + v^2 + w^2} """ res = 0 for cmpnt in self.wind_cmpnt: res += cmpnt**2 res = res**0.5 res.rename('wind_speed') return res @cached_property def tke(self): r""" Calculate total kinetic energy .. math:: 0.5(u^2 + v^2 + w^2) """ res = 0 for cmpnt in self.wind_cmpnt: res += cmpnt**2 res = 0.5 * res # * self.density res.convert_units('m2 s-2') res.rename('total_kinetic_energy') return res @cached_property def du_dx(self): r""" Derivative of u-wind along the x-axis .. math:: u_x = \frac{\partial u}{\partial x} """ return cube_deriv(self.u, self.xcoord) @cached_property def du_dy(self): r""" Derivative of u-wind along the y-axis .. math:: u_y = \frac{\partial u}{\partial y} """ return cube_deriv(self.u, self.ycoord) @cached_property def du_dz(self): r""" Derivative of u-wind along the z-axis .. math:: u_z = \frac{\partial u}{\partial z} """ return cube_deriv(self.u, self.zcoord) @cached_property def dv_dx(self): r""" Derivative of v-wind along the x-axis .. math:: v_x = \frac{\partial v}{\partial x} """ return cube_deriv(self.v, self.xcoord) @cached_property def dv_dy(self): r""" Derivative of v-wind along the y-axis .. math:: v_y = \frac{\partial v}{\partial y} """ return cube_deriv(self.v, self.ycoord) @cached_property def dv_dz(self): r""" Derivative of v-wind along the z-axis .. math:: v_z = \frac{\partial v}{\partial z} """ return cube_deriv(self.v, self.zcoord) @cached_property def dw_dx(self): r""" Derivative of w-wind along the x-axis .. math:: w_x = \frac{\partial w}{\partial x} """ return cube_deriv(self.w, self.xcoord) @cached_property def dw_dy(self): r""" Derivative of w-wind along the y-axis .. math:: w_y = \frac{\partial w}{\partial y} """ return cube_deriv(self.w, self.ycoord) @cached_property def dw_dz(self): r""" Derivative of w-wind along the z-axis .. math:: w_z = \frac{\partial w}{\partial z} """ return cube_deriv(self.w, self.zcoord) @cached_property def rel_vort(self): r""" Calculate the vertical component of the vorticity vector .. math:: \zeta = v_x - u_y """ res = self.dv_dx - self.du_dy res.rename('atmosphere_relative_vorticity') res.convert_units('s-1') return res @cached_property def div_h(self): r""" Calculate the horizontal divergence .. math:: D_h = u_x + v_y """ res = self.du_dx + self.dv_dy res.rename('divergence_of_wind') res.convert_units('s-1') return res @cached_property def rel_vort_hadv(self): r""" Calculate the horizontal advection of relative vorticity .. math:: \vec v\cdot \nabla \zeta """ res = (self.u*cube_deriv(self.rel_vort, self.xcoord) + self.v*cube_deriv(self.rel_vort, self.ycoord)) res.rename('horizontal_advection_of_atmosphere_relative_vorticity') res.convert_units('s-2') return res @cached_property def rel_vort_vadv(self): r""" Calculate the vertical advection of relative vorticity .. math:: w\frac{\partial \zeta}{\partial z} """ res = self.w*cube_deriv(self.rel_vort, self.zcoord) res.rename('vertical_advection_of_atmosphere_relative_vorticity') res.convert_units('s-2') return res @cached_property def rel_vort_stretch(self): r""" Stretching term .. math:: \nabla\cdot\vec v (\zeta+f) """ res = self.div_h * (self.rel_vort + self.fcor.data) res.rename('stretching_term_of_atmosphere_relative_vorticity_budget') res.convert_units('s-2') return res @cached_property def rel_vort_tilt(self): r""" Tilting (twisting) term .. math:: \vec k \cdot \nabla w\times\frac{\partial\vec v}{\partial z} = \frac{\partial w}{\partial x}*\frac{\partial v}{\partial z} - \frac{\partial w}{\partial y}*\frac{\partial u}{\partial z} """ res = self.dw_dx * self.dv_dz - self.dw_dy * self.du_dz res.rename('tilting_term_of_atmosphere_relative_vorticity_budget') res.convert_units('s-2') return res @cached_property def dfm_stretch(self): r""" Stretching deformation .. math:: Def = u_x - v_y """ res = self.du_dx - self.dv_dy res.rename('stretching_deformation_2d') res.convert_units('s-1') return res @cached_property def dfm_shear(self): r""" Shearing deformation .. math:: Def' = u_y + v_x """ res = self.du_dy + self.dv_dx res.rename('shearing_deformation_2d') res.convert_units('s-1') return res @cached_property def kvn(self): r""" Kinematic vorticity number .. math:: W_k=\frac{||\Omega||}{||S||}= \frac{\sqrt{\zeta^2}}{\sqrt{D_h^2 + Def^2 + Def'^2}} where .. math:: \zeta=v_x - u_y D_h = u_x + v_y Def = u_x - v_y Def' = u_y + v_x Reference: http://dx.doi.org/10.3402/tellusa.v68.29464 """ numerator = self.rel_vort denominator = (self.div_h**2 + self.dfm_stretch**2 + self.dfm_shear**2)**0.5 res = numerator/denominator res.rename('kinematic_vorticity_number_2d') return res @cached_property def density(self): r""" Air density .. math:: \rho = \frac{p}{R_d T} """ p = self.cubes.extract_strict('air_pressure') rd = AuxCoord(mconst.Rd.data, units=mconst.Rd.units) try: temp = self.cubes.extract_strict('air_temperature') except iris.exceptions.ConstraintMismatchError: temp = self.temp res = p / (temp * rd) res.rename('air_density') res.convert_units('kg m-3') self.cubes.append(res) return res @cached_property def theta(self): r""" Air potential temperature If temperature is given: .. math:: \theta = T (p_0/p)^{R_d/c_p}} """ try: th = self.cubes.extract_strict('air_potential_temperature') except iris.exceptions.ConstraintMismatchError: p = self.cubes.extract_strict('air_pressure') temp = self.cubes.extract_strict('air_temperature') th = mcalc.potential_temperature(p, temp) th.rename('air_potential_temperature') th.convert_units('K') self.cubes.append(th) return th @cached_property def temp(self): r""" Air temperature If potential temperature is given: .. math:: T = \theta (p/p_0)^{R_d/c_p}} """ try: t = self.cubes.extract_strict('air_temperature') except iris.exceptions.ConstraintMismatchError: p0 = AuxCoord(mconst.P0.data, units=mconst.P0.units) kappa = mconst.kappa.data p = self.cubes.extract_strict('air_pressure') t = self.theta * (p / p0) ** kappa t.rename('air_temperature') t.convert_units('K') self.cubes.append(t) return t @cached_property def mixr(self): r""" Water vapour mixing ratio """ try: mixr = self.cubes.extract_strict('mixing_ratio') except iris.exceptions.ConstraintMismatchError: spechum = self.cubes.extract_strict('specific_humidity') mixr = mcalc.specific_humidity_to_mixing_ratio(spechum) self.cubes.append(mixr) return mixr @cached_property def thetae(self): r""" Equivalent potential temperature .. math:: \theta_e = \theta e^\frac{L_v r_s}{C_{pd} T} """ try: th = self.cubes.extract_strict('equivalent_potential_temperature') except iris.exceptions.ConstraintMismatchError: p = self.cubes.extract_strict('air_pressure') temp = self.cubes.extract_strict('air_temperature') spechum = self.cubes.extract_strict('specific_humidity') mixr = mcalc.specific_humidity_to_mixing_ratio(spechum) e = mcalc.vapor_pressure(p, mixr) dew = mcalc.dewpoint(e) dew.convert_units('K') th = mcalc.equivalent_potential_temperature(p, temp, dew) th.rename('equivalent_potential_temperature') th.convert_units('K') self.cubes.append(th) return th @cached_property def relh(self): r""" Relative humdity """ try: rh = self.cubes.extract_strict('relative_humidity') except iris.exceptions.ConstraintMismatchError: p = self.cubes.extract_strict('air_pressure') temp = self.cubes.extract_strict('air_temperature') spechum = self.cubes.extract_strict('specific_humidity') rh = mcalc.specific_to_relative_humidity(p, temp, spechum) rh.rename('relative_humidity') rh.convert_units('1') self.cubes.append(rh) return rh @cached_property def specific_volume(self): r""" Air Specific Volume .. math:: \alpha = \rho^{-1} """ res = self.density ** (-1) res.rename('air_specific_volume') self.main_cubes.append(res) return res @cached_property def dp_dx(self): r""" Derivative of pressure along the x-axis .. math:: p_x = \frac{\partial p}{\partial x} """ return cube_deriv(self.pres, self.xcoord) @cached_property def dp_dy(self): r""" Derivative of pressure along the y-axis .. math:: p_y = \frac{\partial p}{\partial y} """ return cube_deriv(self.pres, self.ycoord) @cached_property def dsv_dx(self): r""" Derivative of specific volume along the x-axis .. math:: \alpha_x = \frac{\partial\alpha}{\partial x} """ return cube_deriv(self.specific_volume, self.xcoord) @cached_property def dsv_dy(self): r""" Derivative of specific volume along the y-axis .. math:: \alpha_y = \frac{\partial\alpha}{\partial y} """ return cube_deriv(self.specific_volume, self.ycoord) @cached_property def baroclinic_term(self): r""" Baroclinic term of relative vorticity budget .. math:: \frac{\partial p}{\partial x}\frac{\partial\alpha}{\partial y} - \frac{\partial p}{\partial y}\frac{\partial\alpha}{\partial x} """ res = (self.dp_dx * self.dsv_dy - self.dp_dy * self.dsv_dx) res.rename('baroclinic_term_of_atmosphere_relative_vorticity_budget') res.convert_units('s-1') return res @cached_property def brunt_vaisala_squared(self): r""" Brunt–Väisälä frequency squared (pressure coordinates) .. math:: N^2 = -\frac{g^2 \rho}{\theta}\frac{\partial \theta}{\partial p} """ dthdp = cube_deriv(self.theta, self.zcoord) g2 = -1 * mconst.g**2 g2 = AuxCoord(g2.data, units=g2.units) res = self.density / self.theta * dthdp * g2 res.rename('square_of_brunt_vaisala_frequency_in_air') res.convert_units('s-2') return res @cached_property def eady_growth_rate(self): r""" Eady growth rate (pressure coordinates) .. math:: EGR = 0.31 * \frac{f}{N}\frac{\partial V}{\partial z} = 0.31 * \frac{-\rho g f}{N}\frac{\partial V}{\partial p} """ factor = -1 * mconst.egr_factor * self.fcor * mconst.g dvdp = cube_deriv(self.wspd, self.zcoord) dvdp.data = abs(dvdp.data) res = (self.density * factor.data * AuxCoord(1, units=factor.units) * dvdp / self.brunt_vaisala_squared**0.5) res.rename('eady_growth_rate') res.convert_units('s-1') return res @cached_property def kinematic_frontogenesis(self): r""" 2D Kinematic frontogenesis from MetPy package .. math:: F=\frac{1}{2}\left|\nabla \theta\right|[D cos(2\beta)-\delta] """ res = mcalc.frontogenesis(self.theta, self.u, self.v, self.dx, self.dy, dim_order='yx') res.rename('kinematic_frontogenesis') res.convert_units('K m-1 s-1') return res