Source code for src.pbl_model

import logging
import numpy as np


[docs] def vertical_profiles( n, meas_height, wind, ustar=0.2, mol=1e9, prsc=1.0, closure="MOST", z0=-1e9, z0_min=0.001, z0_max=2.0, tke=None, ): """ Computes vertical profiles of horizontal wind components and eddy diffusivity in the planetary boundary layer (PBL) based on Monin-Obukhov Similarity Theory (MOST) or other closure models. Parameters: n (int): Number of vertical grid points. meas_height (float): Measurement height above the ground. wind (tuple of floats): Zonal (u) and meridional (v) wind components at the measurement height. ustar (float or numpy.ndarray): Friction velocity [m/s]. mol (float or numpy.ndarray, optional): Monin-Obukhov length. Default is 1e9 (neutral conditions). prsc (float, optional): Prandtl or Schmidt number. Default is 1.0. closure (str, optional): Closure model to use. Options are "MOST", "CONSTANT", or "OAAHOC". Default is "MOST". z0 (float or numpy.ndarray, optional): Roughness length. Default is -1e9 (auto-calculated). z0_min (float, optional): Minimum allowable roughness length. Default is 0.001. z0_max (float, optional): Maximum allowable roughness length. Default is 2.0. tke (float or numpy.ndarray, optional): Turbulent kinetic energy [m²/s²] for the "OAAHOC" closure. If not provided, it will default to 1.0. Returns: tuple: - z (numpy.ndarray): 1D array of vertical grid points. - profiles (tuple of numpy.ndarray): 1D arrays of horizontal wind components (u, v) and eddy diffusivity (K) at each vertical grid point. Raises: ValueError: If invalid closure type is provided. Notes: - The "OAAHOC" closure uses a one-and-a-half order closure model based on Schumann-Lilly. - The "MOST" closure uses Monin-Obukhov Similarity Theory. References: - Kormann, R., & Meixner, F. X. (2001). An analytical footprint model for non-neutral stratification. Boundary-Layer Meteorology, 99(2), 207–224. https://doi.org/10.1023/A:1018991015119 - Schumann, U. (1991). Subgrid length-scales for large-eddy simulation of stratified turbulence. Theoretical and Computational Fluid Dynamics, 2(5), 279–290. https://doi.org/10.1007/BF00271468 """ zm, (um, vm) = meas_height, wind # make function dimension-agnostic # here, we create (Nts x Nz arrays) um = np.array(um) vm = np.array(vm) zm = np.array(zm) ustar = np.array(ustar) um = um[..., np.newaxis] vm = vm[..., np.newaxis] zm = zm[..., np.newaxis] ustar = ustar[..., np.newaxis] kap = 0.4 # Karman constant if closure == "CONSTANT": Km = kap * ustar * zm / prsc z0 = np.zeros_like(um) z = np.array([np.linspace(z00, zm, n) for z00 in z0]).squeeze() u = um * np.ones(n) v = vm * np.ones(n) K = Km * np.ones(n) elif closure == "MOST": # absolute wind at zm absum = np.sqrt(um**2 + vm**2) if z0 < 0.0: # roughness length z0 = zm * np.exp(-kap * absum / ustar + psi(zm / mol)) # sanity checks z0[...] = np.clip(z0, z0_min, z0_max) else: z0 = np.array(z0)[..., np.newaxis] ustar = absum * kap / (np.log(zm / z0) + psi(zm / mol)) # equidistant vertical grid # find a way to vectorize this properly z = np.array([np.linspace(z00, zm, n) for z00 in z0]).squeeze() absu = ustar / kap * (np.log(z / z0) + psi(z / mol)) u = um / absum * absu v = vm / absum * absu K = kap * ustar * z / phi(z / mol) / prsc # One-and-a-half order closure # according to Schumann-Lilly closure (Schumann, 1991) elif closure == "OAAHOC": cl = 0.845 cm = 0.0856 ch = 0.204 # compute tke from mol by tke balance equation if tke is None: logging.warning("No tke provided. Setting TKE to 1.0.") tke = 1.0 tke = np.array(tke)[..., np.newaxis] # absolute wind at zm absum = np.sqrt(um**2 + vm**2) # roughness length z0 = zm * np.exp(-cm * cl * absum * np.sqrt(tke) / ustar**2) # equidistant vertical grid z = np.array([np.linspace(z00, zm, n) for z00 in z0]).squeeze() absu = ustar**2 / cm / cl / np.sqrt(tke) * np.log(z / z0) u = um / absum * absu v = vm / absum * absu K = ch * cl * z * np.sqrt(tke) else: raise ValueError( f"Invalid closure type: {closure}. " "Supported closures are 'MOST', 'CONSTANT', and 'OAAHOC'." ) if len(z) <= 1: logging.info("Stats from vertical_profiles") logging.info("z0 = %.3f m", z[0]) logging.info("ustar = %.3f m s-1", ustar) logging.info( "umax = %.3f m s-1, vmax = %.3f m s-1, Kmax = %.3f m2 s-1", max(u), max(v), max(K), ) logging.info( "umin = %.3f m s-1, vmin = %.3f m s-1, Kmin = %.3f m2 s-1", min(u), min(v), min(K), ) return z.squeeze(), (u.squeeze(), v.squeeze(), K.squeeze())
[docs] def psi(x): """ Stability correction function for Monin-Obukhov Similarity Theory (MOST). Parameters: x (float or numpy.ndarray): Dimensionless stability parameter (z / mol). Returns: numpy.ndarray: Stability correction factor for momentum. Notes: - For stable conditions (x > 0), a linear relationship is used. - For unstable conditions (x < 0), the Businger-Dyer formulation is applied. """ xi = np.where(x > 0.0, np.nan, np.power(1.0 - 16.0 * x, 0.25, dtype=complex).real) return np.where( x > 0.0, 5.0 * x, -2.0 * np.log(0.5 * (1.0 + xi)) - np.log(0.5 * (1.0 + xi**2)) + 2.0 * np.arctan(xi) - 0.5 * np.pi, )
[docs] def phi(x): """ Stability correction function for eddy diffusivity in Monin-Obukhov Similarity Theory (MOST). Parameters: x (float or numpy.ndarray): Dimensionless stability parameter (z / mol). Returns: numpy.ndarray: Stability correction factor for eddy diffusivity. Notes: - For stable conditions (x > 0), a linear relationship is used. - For unstable conditions (x < 0), the Businger-Dyer formulation is applied. """ return np.where( x > 0.0, 1.0 + 5.0 * x, np.power(1.0 - 16.0 * x, -0.5, dtype=complex).real )