# Version 1.0 released by David Romps on September 12, 2017. # Version 1.1 vectorized lcl.R, released on May 24, 2021. # # When using this code, please cite: # # @article{16lcl, # Title = {Exact expression for the lifting condensation level}, # Author = {David M. Romps}, # Journal = {Journal of the Atmospheric Sciences}, # Year = {2017}, # Month = dec, # Number = {12}, # Pages = {3891--3900}, # Volume = {74} # } # # This lcl function returns the height of the lifting condensation level # (LCL) in meters. The inputs are: # - p in Pascals # - T in Kelvins # - Exactly one of rh, rhl, and rhs (dimensionless, from 0 to 1): # * The value of rh is interpreted to be the relative humidity with # respect to liquid water if T >= 273.15 K and with respect to ice if # T < 273.15 K. # * The value of rhl is interpreted to be the relative humidity with # respect to liquid water # * The value of rhs is interpreted to be the relative humidity with # respect to ice # - return_ldl is an optional logical flag. If true, the lifting deposition # level (LDL) is returned instead of the LCL. # - return_min_lcl_ldl is an optional logical flag. If true, the minimum of the # LCL and LDL is returned. def lcl(p,T,rh=None,rhl=None,rhs=None,return_ldl=False,return_min_lcl_ldl=False): import math import scipy.special # Parameters Ttrip = 273.16 # K ptrip = 611.65 # Pa E0v = 2.3740e6 # J/kg E0s = 0.3337e6 # J/kg ggr = 9.81 # m/s^2 rgasa = 287.04 # J/kg/K rgasv = 461 # J/kg/K cva = 719 # J/kg/K cvv = 1418 # J/kg/K cvl = 4119 # J/kg/K cvs = 1861 # J/kg/K cpa = cva + rgasa cpv = cvv + rgasv # The saturation vapor pressure over liquid water def pvstarl(T): return ptrip * (T/Ttrip)**((cpv-cvl)/rgasv) * \ math.exp( (E0v - (cvv-cvl)*Ttrip) / rgasv * (1/Ttrip - 1/T) ) # The saturation vapor pressure over solid ice def pvstars(T): return ptrip * (T/Ttrip)**((cpv-cvs)/rgasv) * \ math.exp( (E0v + E0s - (cvv-cvs)*Ttrip) / rgasv * (1/Ttrip - 1/T) ) # Calculate pv from rh, rhl, or rhs rh_counter = 0 if rh is not None: rh_counter = rh_counter + 1 if rhl is not None: rh_counter = rh_counter + 1 if rhs is not None: rh_counter = rh_counter + 1 if rh_counter != 1: print(rh_counter) exit('Error in lcl: Exactly one of rh, rhl, and rhs must be specified') if rh is not None: # The variable rh is assumed to be # with respect to liquid if T > Ttrip and # with respect to solid if T < Ttrip if T > Ttrip: pv = rh * pvstarl(T) else: pv = rh * pvstars(T) rhl = pv / pvstarl(T) rhs = pv / pvstars(T) elif rhl is not None: pv = rhl * pvstarl(T) rhs = pv / pvstars(T) if T > Ttrip: rh = rhl else: rh = rhs elif rhs is not None: pv = rhs * pvstars(T) rhl = pv / pvstarl(T) if T > Ttrip: rh = rhl else: rh = rhs if pv > p: return NA # Calculate lcl_liquid and lcl_solid qv = rgasa*pv / (rgasv*p + (rgasa-rgasv)*pv) rgasm = (1-qv)*rgasa + qv*rgasv cpm = (1-qv)*cpa + qv*cpv if rh == 0: return cpm*T/ggr aL = -(cpv-cvl)/rgasv + cpm/rgasm bL = -(E0v-(cvv-cvl)*Ttrip)/(rgasv*T) cL = pv/pvstarl(T)*math.exp(-(E0v-(cvv-cvl)*Ttrip)/(rgasv*T)) aS = -(cpv-cvs)/rgasv + cpm/rgasm bS = -(E0v+E0s-(cvv-cvs)*Ttrip)/(rgasv*T) cS = pv/pvstars(T)*math.exp(-(E0v+E0s-(cvv-cvs)*Ttrip)/(rgasv*T)) lcl = cpm*T/ggr*( 1 - \ bL/(aL*scipy.special.lambertw(bL/aL*cL**(1/aL),-1).real) ) ldl = cpm*T/ggr*( 1 - \ bS/(aS*scipy.special.lambertw(bS/aS*cS**(1/aS),-1).real) ) # Return either lcl or ldl if return_ldl and return_min_lcl_ldl: exit('return_ldl and return_min_lcl_ldl cannot both be true') elif return_ldl: return ldl elif return_min_lcl_ldl: return min(lcl,ldl) else: return lcl