Skip to article frontmatterSkip to article content

Calculate the SM-T coupling using icon_d3hp003

Calculate the SM-T coupling using icon_d3hp003

Author: Yifan Cheng

Reference:

Coupling_metrics_V30_SM-T.pdf

Miralles et al. (2012)

import datetime
current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
print(f'Last updated at {current_time}')
Last updated at 2025-05-16 09:43
from pathlib import Path 
import xarray as xr
import cartopy.crs as ccrs
import uxarray as ux
import numpy as np
import cartopy.feature as cf
import matplotlib.pyplot as plt
import pandas as pd
import intake

cat_url = "https://digital-earths-global-hackathon.github.io/catalog/catalog.yaml"
cat = intake.open_catalog(cat_url).NCAR

# check on global models
model_run1 = cat['icon_d3hp003']
model_run2 = cat['ifs_tco3999-ng5_deepoff']
model_run3 = cat['nicam_gl11']
model_run4 = cat['mpas_dyamond3']
model_run5 = cat['mpas_dyamond2']
model_run6 = cat['scream_ne120'] #3hourly average
ds_fine1 = model_run1(zoom=7,time='P1D').to_dask()
/glade/work/yifanc17/miniconda3/envs/hackathon_env/lib/python3.12/site-packages/intake_xarray/base.py:21: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
  'dims': dict(self._ds.dims),
uxds_fine1 = ux.UxDataset.from_healpix(ds_fine1)
var_longname_dict1 = {
    var: ds_fine1[var].attrs.get("long_name", "N/A")
    for var in ds_fine1.data_vars
}
var_longname_dict1
{'clivi': 'cloud ice path', 'clt': 'total cloud cover', 'clwvi': 'cloud condensed water path', 'egpvi': 'Atmosphere Geopotential Energy Content', 'einvi': 'Atmosphere Moist Internal Energy Content', 'ekhvi': 'Atmosphere Horizontal Kinetic Energy Content', 'ekvvi': 'Atmosphere Vertical Kinetic Energy Content', 'hflsd': 'latent heat flux', 'hfssd': 'sensible heat flux', 'hur': 'relative humidity', 'hus': 'Specific humidity', 'huss': 'specific humidity in 2m', 'mrso': 'Water content of soil layers', 'o3vi': 'ozone path', 'orog': 'surface altitude', 'pr': 'precipitation flux', 'prs': 'large-scale precipitation flux (snow)', 'prw': 'water vapor path', 'ps': 'surface pressure', 'psl': 'mean sea level pressure', 'qall': 'mass fraction of all hydrometeors in air', 'rlds': 'surface downwelling longwave radiation', 'rldscs': 'surface downwelling clear-sky longwave radiation', 'rlus': 'surface upwelling longwave radiation', 'rlut': 'toa outgoing longwave radiation', 'rlutcs': 'toa outgoing clear-sky longwave radiation', 'rsds': 'surface downwelling shortwave radiation', 'rsdscs': 'surface downwelling clear-sky shortwave radiation', 'rsdt': 'toa incident shortwave radiation', 'rsus': 'surface upwelling shortwave radiation', 'rsuscs': 'surface upwelling clear-sky shortwave radiation', 'rsut': 'toa outgoing shortwave radiation', 'rsutcs': 'toa outgoing clear-sky shortwave radiation', 'rva': 'Vorticity', 'sftgif': 'cell area fraction occupied by land ice', 'sftlf': 'cell area fraction occupied by land including lakes', 'siconc': 'fraction of ocean covered by sea ice', 'sncvfa': 'Snow area fraction', 'swe': 'Snow amount', 'ta': 'Temperature', 'tas': 'temperature in 2m', 'tauu': 'u-momentum flux at the surface', 'tauv': 'v-momentum flux at the surface', 'tend_egpdynvi': 'Tendency of Atmosphere Geopotential Energy Content due to Dynamics', 'tend_eincldvi': 'Tendency of Atmosphere Moist Internal Energy Content due to Cloud Microphysics', 'tend_eindynvi': 'Tendency of Atmosphere Moist Internal Energy Content due to Dynamics', 'tend_einradvi': 'Tendency of Atmosphere Moist Internal Energy Content due to Radiation', 'tend_eintmxvi': 'Tendency of Atmosphere Moist Internal Energy Content due to Turbulent Mixing', 'tend_ekhdynvi': 'Tendency of Atmosphere Horizontal Kinetic Energy Content due to Dynamics', 'tend_ekhtmxvi': 'Tendency of Atmosphere Horizontal Kinetic Energy Content due to Turbulent Mixing', 'tend_ekvdynvi': 'Tendency of Atmosphere Vertical Kinetic Energy Content due to Dynamics', 'ts': 'surface temperature', 'ua': 'Zonal wind', 'uas': 'zonal wind in 10m', 'va': 'Meridional wind', 'vas': 'meridional wind in 10m', 'wa': 'vertical velocity in m/s', 'zg': 'geometric height at full level center'}
# 'pres_sfc': 'surface pressure'
#  'rlds': 'surface downwelling longwave radiation',
#  'rlus': 'surface upwelling longwave radiation'
# 'rsds': 'surface downwelling shortwave radiation',
#  'rsus': 'surface upwelling shortwave radiation',
# 'hfls': 'latent heat flux',
#  'hfss': 'sensible heat flux',
# 'tas': 'temperature in 2m',
land_mask=ux.UxDataArray((uxds_fine1.sftlf==1).astype(int),uxgrid=uxds_fine1.uxgrid)
land_mask.plot.points(
    cmap="inferno",
    rasterize=True,
    dynamic=False,
    features=["coastline", "borders"]
)
Loading...
uxds_fine1=uxds_fine1.sel(time=uxds_fine1.time.dt.year==2020)
%%time
sel_vswc=(uxds_fine1['mrso'])
sel_le=(uxds_fine1['hflsd']) #W/m2
sel_h=(uxds_fine1['hfssd']) #W/m2
sel_rnet=((uxds_fine1['rlds']-uxds_fine1['rlus']+uxds_fine1['rsds']-uxds_fine1['rsus'])) #W/m2
sel_t2m=(uxds_fine1['tas']-273.15) #C
sel_sp=(uxds_fine1['ps']/1000) #kPa
CPU times: user 10.2 s, sys: 2.08 s, total: 12.3 s
Wall time: 15.7 s
%%time
sel_le_DJF=sel_le.sel(time=(uxds_fine1.time.dt.month.isin([1,2,12]))&(uxds_fine1.time.dt.year == 2020)).where(land_mask)
sel_h_DJF=sel_h.sel(time=(uxds_fine1.time.dt.month.isin([1,2,12]))&(uxds_fine1.time.dt.year == 2020)).where(land_mask)
sel_rnet_DJF=sel_rnet.sel(time=(uxds_fine1.time.dt.month.isin([1,2,12]))&(uxds_fine1.time.dt.year == 2020)).where(land_mask)
sel_t2m_DJF=sel_t2m.sel(time=(uxds_fine1.time.dt.month.isin([1,2,12]))&(uxds_fine1.time.dt.year == 2020)).where(land_mask)
sel_sp_DJF=sel_sp.sel(time=(uxds_fine1.time.dt.month.isin([1,2,12]))&(uxds_fine1.time.dt.year == 2020)).where(land_mask)
CPU times: user 1min 4s, sys: 28 s, total: 1min 32s
Wall time: 2min 56s
%%time
sel_le_JJA=sel_le.sel(time=(uxds_fine1.time.dt.month.isin([6,7,8])&(uxds_fine1.time.dt.year.isin([2020])))).where(land_mask)
sel_h_JJA=sel_h.sel(time=(uxds_fine1.time.dt.month.isin([6,7,8])&(uxds_fine1.time.dt.year.isin([2020])))).where(land_mask)
sel_rnet_JJA=sel_rnet.sel(time=(uxds_fine1.time.dt.month.isin([6,7,8])&(uxds_fine1.time.dt.year.isin([2020])))).where(land_mask)
sel_t2m_JJA=sel_t2m.sel(time=(uxds_fine1.time.dt.month.isin([6,7,8])&(uxds_fine1.time.dt.year.isin([2020])))).where(land_mask)
sel_sp_JJA=sel_sp.sel(time=(uxds_fine1.time.dt.month.isin([6,7,8])&(uxds_fine1.time.dt.year.isin([2020])))).where(land_mask)
CPU times: user 1min 4s, sys: 32.2 s, total: 1min 36s
Wall time: 3min 31s
def saturation_vapor_pressure(T):
    return 6.112*np.exp((17.67*T)/(T+243.5))*0.1  # hPa -> kPa

def delta_svp(T):
    es=saturation_vapor_pressure(T)
    return (4302.645*es)/((T+243.5)**2)  # in kPa/°C

def alpha_piecewise(vswc): # values from literature based on Priestley–Taylor equation
    return xr.where(
        vswc < 0.1, 0.8,
        xr.where(vswc < 0.2, 1.0, 1.26)
    )

def calc_gamma(P_kPa):
    cp=1.013e3      # J/kg/°C
    epsilon=0.622
    lambda_v=2.45e6 # Latent heat of vaporization J/kg
    return (cp*P_kPa)/(epsilon*lambda_v)  # in kPa/°C

def priestley_taylor_ET(vswc, Rnet, T, P_kPa): # assume Rnet=LE+H
    alpha=alpha_piecewise(vswc)
    delta=delta_svp(T)
    gamma=calc_gamma(P_kPa)
    coeff=alpha*(delta/(delta+gamma))
    return coeff*Rnet  # W/m2

def safe_corr(x, y, dim='time', min_count=30):
    valid = x.notnull() & y.notnull()
    count = valid.sum(dim=dim)
    r = xr.corr(x, y, dim=dim)
    return r.where(count >= min_count)
    
def calculate_coupling_index(vswc, Rnet, LE, T, P):
    H=Rnet-LE

    LE_p=priestley_taylor_ET(vswc, Rnet, T, P)
    H_p=Rnet-LE_p
    
    H_xr=H.to_dataset(name='H')
    Hp_xr=H_p.to_dataset(name='Hp')
    T_xr=T.to_dataset(name='T2M')

    H_corr = safe_corr(H, T, dim='time', min_count=30)
    Hp_corr = safe_corr(H_p, T, dim='time', min_count=30)

    PI=H_corr-Hp_corr
    PI.name='coupling_index'

    return PI
%%time
PI_DJF=calculate_coupling_index(sel_rnet_DJF,sel_le_DJF,sel_t2m_DJF,sel_sp_DJF).compute()
/glade/work/yifanc17/miniconda3/envs/hackathon_env/lib/python3.12/site-packages/numpy/lib/_nanfunctions_impl.py:2019: RuntimeWarning: Degrees of freedom <= 0 for slice.
  var = nanvar(a, axis=axis, dtype=dtype, out=out, ddof=ddof,
/glade/work/yifanc17/miniconda3/envs/hackathon_env/lib/python3.12/site-packages/numpy/lib/_nanfunctions_impl.py:2019: RuntimeWarning: Degrees of freedom <= 0 for slice.
  var = nanvar(a, axis=axis, dtype=dtype, out=out, ddof=ddof,
/glade/work/yifanc17/miniconda3/envs/hackathon_env/lib/python3.12/site-packages/numpy/lib/_nanfunctions_impl.py:2019: RuntimeWarning: Degrees of freedom <= 0 for slice.
  var = nanvar(a, axis=axis, dtype=dtype, out=out, ddof=ddof,
/glade/work/yifanc17/miniconda3/envs/hackathon_env/lib/python3.12/site-packages/numpy/lib/_nanfunctions_impl.py:2019: RuntimeWarning: Degrees of freedom <= 0 for slice.
  var = nanvar(a, axis=axis, dtype=dtype, out=out, ddof=ddof,
CPU times: user 1min 30s, sys: 1min 11s, total: 2min 41s
Wall time: 4min 52s
PI_JJA=calculate_coupling_index(sel_rnet_JJA,sel_le_JJA,sel_t2m_JJA,sel_sp_JJA).compute()
/glade/work/yifanc17/miniconda3/envs/hackathon_env/lib/python3.12/site-packages/numpy/lib/_nanfunctions_impl.py:2019: RuntimeWarning: Degrees of freedom <= 0 for slice.
  var = nanvar(a, axis=axis, dtype=dtype, out=out, ddof=ddof,
/glade/work/yifanc17/miniconda3/envs/hackathon_env/lib/python3.12/site-packages/numpy/lib/_nanfunctions_impl.py:2019: RuntimeWarning: Degrees of freedom <= 0 for slice.
  var = nanvar(a, axis=axis, dtype=dtype, out=out, ddof=ddof,
/glade/work/yifanc17/miniconda3/envs/hackathon_env/lib/python3.12/site-packages/numpy/lib/_nanfunctions_impl.py:2019: RuntimeWarning: Degrees of freedom <= 0 for slice.
  var = nanvar(a, axis=axis, dtype=dtype, out=out, ddof=ddof,
/glade/work/yifanc17/miniconda3/envs/hackathon_env/lib/python3.12/site-packages/numpy/lib/_nanfunctions_impl.py:2019: RuntimeWarning: Degrees of freedom <= 0 for slice.
  var = nanvar(a, axis=axis, dtype=dtype, out=out, ddof=ddof,
PI_DJF.plot()
Loading...
PI_JJA.plot()
Loading...
References
  1. Miralles, D. G., van den Berg, M. J., Teuling, A. J., & de Jeu, R. A. M. (2012). Soil moisture‐temperature coupling: A multiscale observational analysis. Geophysical Research Letters, 39(21). 10.1029/2012gl053703