Ejemplo n.º 1
0
def cal_cp(qa_kgkg, ta_K, pres_hPa):
    from atmosp import calculate as ac

    temp_C = ta_K - 273.15

    rh_pct = ac("RH", qv=qa_kgkg, T=ta_K, p=pres_hPa * 100)

    # Garratt equation a20(1992)
    cpd = 1005.0 + ((temp_C + 23.16)**2) / 3364.0

    # Beer(1990) for water vapour
    cpm = (1859 + 0.13 * rh_pct + (19.3 + 0.569 * rh_pct) * (temp_C / 100.0) +
           (10.0 + 0.5 * rh_pct) * (temp_C / 100.0)**2)

    # air density
    rho = ac("rho", qv=qa_kgkg, T=ta_K, p=pres_hPa * 100)

    # water vapour mixing ratio
    rv = ac("rv", qv=qa_kgkg, T=ta_K, p=pres_hPa * 100)

    # dry air density
    rho_d = rv / (1 + rv) * rho

    # water vapour density
    rho_v = rho - rho_d

    # heat capacity of air
    cp = cpd * (rho_d / (rho_d + rho_v)) + cpm * (rho_v / (rho_d + rho_v))

    return cp
Ejemplo n.º 2
0
Archivo: _atm.py Proyecto: LewisB7/SuPy
def cal_dq(rh_pct, ta_c, pres_hPa):
    from atmosp import calculate as ac

    ta_k = ta_c + 273.16
    pa = pres_hPa * 100
    dq = ac("qvs", T=ta_k, p=pa) - ac("qv", T=ta_k, p=pa, RH=rh_pct)

    return dq
Ejemplo n.º 3
0
Archivo: _gs.py Proyecto: LewisB7/SuPy
def cal_rs_iPM(qh, qe, ta, rh, pa, ra):
    """Calculate surface resistance based on observations, notably turbulent fluxes.

    Parameters
    ----------
    qh : numeric
        sensible heat flux [W m-2]
    qe : numeric
        latent heat flux [W m-2]
    ta : numeric
        air temperature [degC]
    rh : numeric
        relative humidity [%]
    pa : numeric
        air pressure [Pa]
    ra : numeric
        aerodynamic resistance [m s-1]

    Returns
    -------
    numeric
        Surface resistance based on observations [s m-1]
    """
    from atmosp import calculate as ac

    # psychrometric constant [Pa K-1] as a function of air pressure
    ser_gamma = 0.665e-3 * pa

    # air density [kg m-3]
    val_rho = 1.27

    # heat capacity of air [J kg-1 K-1]
    val_cp = 1005

    # convert temp from C to K
    ta_K = ta + 273.15

    # slope of es(Ta) curve at Ta
    ser_des_dTa = cal_des_dta(ta_K, pa, dta=1.0)
    #
    arr_e = ac("e", p=pa, T=ta_K, RH=rh)
    arr_es = ac("es", p=pa, T=ta_K)
    arr_vpd = arr_es - arr_e
    #
    ser_rs_1 = (ser_des_dTa / ser_gamma) * (qh / qe - 1) * ra
    ser_rs_2 = val_rho * val_cp * arr_vpd / (ser_gamma * qe)
    ser_rs = ser_rs_1 + ser_rs_2

    try:
        # try to pack as Series
        ser_rs = pd.Series(ser_rs, index=ta_K.index)
    except AttributeError as ex:
        print(ex, "cannot pack into pd.Series")
        pass

    return ser_rs
Ejemplo n.º 4
0
def cal_vpd(Temp_C, RH_pct, Press_hPa):

    ta = Temp_C + 273.16
    pa = Press_hPa * 100
    rh = RH_pct

    e = ac('e', p=pa, T=ta, RH=rh)
    es = ac('es', p=pa, T=ta)
    vpd = es - e
    des_vpd = pd.Series(vpd, index=ta.index)
    return des_vpd
Ejemplo n.º 5
0
def cal_rs_obs(qh, qe, ta, rh, pa):
    """Calculate surface resistance based on observations, notably turbulent fluxes.

    Parameters
    ----------
    qh : numeric
        sensible heat flux [W m-2]
    qe : numeric
        latent heat flux [W m-2]
    ta : numeric
        air temperature [K]
    rh : numeric
        relative humidity [%]
    pa : numeric
        air pressure [Pa]

    Returns
    -------
    numeric
        Surface resistance based on observations [s m-1]
    """

    # surface resistance at water surface [s m-1]
    rav = 50

    # psychrometric constant [Pa K-1] as a function of air pressure
    ser_gamma = 0.665e-3 * pa

    # air density [kg m-3]
    val_rho = 1.27

    # heat capacity of air [J kg-1 K-1]
    val_cp = 1005

    # slope of es(Ta) curve at Ta
    ser_des_dTa = cal_des_dta(ta, pa, dta=1.0)
    #
    arr_e = ac('e', p=pa, T=ta, RH=rh)
    arr_es = ac('es', p=pa, T=ta)
    arr_vpd = arr_es-arr_e
    #
    ser_rs_1 = (ser_des_dTa / ser_gamma) * (qh / qe - 1) * rav
    ser_rs_2 = (val_rho * val_cp * arr_vpd / (ser_gamma * qe))
    ser_rs = ser_rs_1 + ser_rs_2

    try:
        # try to pack as Series
        ser_rs = pd.Series(ser_rs, index=ta.index)
    except AttributeError as ex:
        print(ex, 'cannot pack into pd.Series')
        pass

    return ser_rs
Ejemplo n.º 6
0
def cal_des_dta(Temp_C, Press_hPa, dta=1.0):

    ta = Temp_C + 273.16
    pa = Press_hPa * 100

    des = ac('es', p=pa, T=ta + dta / 2) - ac('es', p=pa, T=ta - dta / 2)
    des_dta = des / dta
    try:
        # try to pack as Series
        des_dta = pd.Series(des_dta, index=ta.index)
    except AttributeError as ex:
        print(ex, 'cannot pack into pd.Series')
        pass
    return des_dta
Ejemplo n.º 7
0
def cal_lat_vap(qa_kgkg, theta_K, pres_hPa):
    from atmosp import calculate as ac
    # wel-bulb temperature
    tw = ac("Tw",
            qv=qa_kgkg,
            p=pres_hPa,
            theta=theta_K,
            remove_assumptions=("constant Lv"))
    # latent heat [J kg-1]
    Lv = 2.501e6 - 2370.0 * (tw - 273.15)
    return Lv
Ejemplo n.º 8
0
def cal_des_dta(ta, pa, dta=1.0):
    """Calculate slope of es(Ta), i.e., saturation evaporation pressure `es` as function of air temperature `ta [K]`
    Parameters
    ----------
    ta : numeric
        Air temperature [K]
    pa : numeric
        Air pressure [Pa]
    dta : float, optional
        change in ta for calculating that in es, by default 1.0 K
    """

    des = ac('es', p=pa, T=ta + dta / 2) - ac('es', p=pa, T=ta - dta / 2)
    des_dta = des / dta
    try:
        # try to pack as Series
        des_dta = pd.Series(des_dta, index=ta.index)
    except AttributeError as ex:
        print(ex, 'cannot pack into pd.Series')
        pass
    return des_dta
Ejemplo n.º 9
0
def diag_era5_simple(z0m, ustar, pres_z0, uv10, t2, q2, z):
    from atmosp import calculate as ac
    import xarray as xr

    # constants
    # environmental lapse rate [K m^-1]
    env_lapse = 6.5 / 1000.0
    # gravity [m s^-2]
    grav = 9.80616
    # Gas constant for dry air [J K^-1 kg^-1]
    rd = 287.04

    # correct temperature using lapse rate
    t_z = t2 - (z - 2) * env_lapse

    # barometric equation with varying temperature:
    # (https://en.wikipedia.org/wiki/Barometric_formula)
    # p_z = pres_z0 * np.exp((grav * (0 - z)) / (rd * t2))
    p_z = pres_z0 * (t2 / (t2 + env_lapse * (z - 2)))**(grav /
                                                        (rd * env_lapse))

    # correct humidity assuming invariable relative humidity
    RH_z = ac("RH", qv=q2, p=pres_z0, T=t2) + 0 * t_z
    q_z = ac("qv", RH=RH_z, p=p_z, T=t_z) + 0 * t_z

    # correct wind speed using log law; assuming neutral condition (without stability correction)
    uv_z = uv10 * (np.log((z + z0m) / z0m) / np.log((10 + z0m) / z0m))

    # generate dataset
    ds_diag = xr.merge([
        uv_z.rename("uv_z"),
        t_z.rename("t_z"),
        q_z.rename("q_z"),
        RH_z.rename("RH_z"),
        p_z.rename("p_z"),
    ])

    return ds_diag
Ejemplo n.º 10
0
def diag_era5(za, uv_za, t_za, q_za, pres_za, qh, qe, z0m, ustar, pres_z0,
              uv10, t2, q2, z):
    # reference:
    # Section 3.10.2 and 3.10.3 in
    # IFS Documentation CY41R2: Part IV: Physical Processes
    # https://www.ecmwf.int/en/elibrary/16648-part-iv-physical-processes

    from atmosp import calculate as ac
    import xarray as xr
    from ._atm import cal_lat_vap, cal_cp, cal_psi_mom, cal_psi_heat

    # von Karman constant
    kappa = 0.4

    # gravity acceleration
    g = 9.8

    # note the roughness correction: see EC technical report
    z0m = np.where(z0m < 0.03, z0m, 0.03)

    # air density
    avdens = ac("rho", qv=q2, p=pres_z0, theta=t2)

    # vapour pressure
    # lv_j_kg = cal_lat_vap(q2, t2, pres_z0)

    # heat capacity
    avcp = cal_cp(q2, t2, pres_z0)

    # temperature/humidity scales
    tstar = -qh / (avcp * avdens) / ustar
    # qstar = -qe / (lv_j_kg * avdens) / ustar

    l_mod = ustar**2 / (g / t2 * kappa * tstar)
    zoL = np.where(
        np.abs((z + z0m) / l_mod) < 5,
        (z + z0m) / l_mod,
        np.sign((z + z0m) / l_mod) * 5,
    )

    # `stab_psi_mom`, `stab_psi_heat`
    # stability correction for momentum
    psim_z = cal_psi_mom(zoL)
    psim_z0 = cal_psi_mom(z0m / l_mod)
    psim_10 = cal_psi_mom((10 + z0m) / l_mod)

    # wind speed
    uv_z = uv10 * ((np.log((z + z0m) / z0m) - psim_z + psim_z0) / (np.log(
        (10 + z0m) / z0m) - psim_10 + psim_z0))

    # stability correction for heat
    psih_z = cal_psi_heat(zoL)
    psih_2 = cal_psi_heat(2 / l_mod)
    psih_z0 = cal_psi_heat(z0m / l_mod)
    psih_za = cal_psi_heat(za / l_mod)

    # atmospheric pressure: assuming same air density at `za`
    # using iteration to get `p_z`
    p_z = pres_z0 + (pres_za - pres_z0) * z / za

    # specific humidity
    q_z = q2 + (q_za - q2) * ((np.log(z / z0m) - psih_z + psih_z0) /
                              (np.log(za / z0m) - psih_za + psih_z0))

    # dry static energy: eq 3.5 in EC tech report;
    # also AMS ref: http://glossary.ametsoc.org/wiki/Dry_static_energy
    # 2 m agl:
    cp2 = cal_cp(q2, t2, pres_z0 / 100)
    s2 = g * 2 + cp2 * t2
    # za:
    cp_za = cal_cp(q_za, t_za, pres_za / 100)
    s_za = g * za + cp_za * t_za

    s_z = s2 + (s_za - s2) * ((np.log(z / 2) - psih_z + psih_2) /
                              (np.log(za / 2) - psih_za + psih_2))

    # calculate temperature at z
    tx_z = t_za
    dif = 10
    while dif > 0.1:
        cp_z = cal_cp(q_z, tx_z, p_z / 100)
        t_z = (s_z - g * z) / cp_z
        dif = np.mean(np.abs(t_z - tx_z))
        tx_z = t_z
    # get final `t_z` and store in the conformity to `t_za`
    t_z = tx_z + t_za * 0

    # relative humidity; cap at 105% if above
    RH_z = ac("RH", qv=q_z, p=p_z, T=t_z) + 0 * q_z
    RH_z = RH_z.where(RH_z < 105, 105)

    # generate dataset
    ds_diag = xr.merge([
        uv_z.rename("uv_z"),
        t_z.rename("t_z"),
        q_z.rename("q_z"),
        RH_z.rename("RH_z"),
        p_z.rename("p_z"),
    ])
    return ds_diag
Ejemplo n.º 11
0
def gen_ds_diag_era5(list_fn_sfc,
                     list_fn_ml,
                     hgt_agl_diag=100,
                     simple_mode=True):
    import xarray as xr
    from atmosp import calculate as ac

    # load data from from `sfc` files
    ds_sfc = xr.open_mfdataset(list_fn_sfc,
                               concat_dim="time",
                               combine="by_coords").load()
    # close dangling handlers
    ds_sfc.close()

    # load data from from `ml` files
    ds_ml = xr.open_mfdataset(list_fn_ml,
                              concat_dim="time",
                              combine="by_coords").load()
    # close dangling handlers
    ds_ml.close()

    # surface level atmospheric pressure
    pres_z0 = ds_sfc.sp

    # hgt_agl_diag: height where to calculate diagnostics
    # hgt_agl_diag = 100

    # determine a lowest level higher than surface at all times
    level_sel = get_level_diag(ds_sfc, ds_ml, hgt_agl_diag)

    # retrieve variables from the identified lowest level
    ds_ll = ds_ml.sel(level=level_sel)

    # altitude
    alt_z0 = geopotential2geometric(ds_sfc.z, ds_sfc.latitude)
    alt_za = geopotential2geometric(ds_ll.z, ds_ll.latitude)

    # atmospheric pressure [Pa]
    pres_za = pres_z0 * 0 + ds_ll.level * 100

    # u-wind [m s-1]
    u_za = ds_ll.u
    # u-wind [m s-1]
    v_za = ds_ll.v
    # wind speed [m s-1]
    uv_za = np.sqrt(u_za**2 + v_za**2)

    # temperature [K]
    t_za = ds_ll.t

    # specific humidity [kg kg-1]
    q_za = ds_ll.q

    # ------------------------
    # retrieve surface data

    # wind speed
    u10 = ds_sfc.u10
    v10 = ds_sfc.v10
    uv10 = np.sqrt(u10**2 + v10**2)

    # sensible/latent heat flux [W m-2]
    # conversion from cumulative value to hourly average
    qh = -ds_sfc.sshf / 3600
    qe = -ds_sfc.slhf / 3600

    # surface roughness [m]
    z0m = ds_sfc.fsr

    # friction velocity [m s-1]
    ustar = ds_sfc.zust

    # air temperature
    t2 = ds_sfc.t2m

    # dew point
    d2 = ds_sfc.d2m

    # specific humidity
    q2 = ac("qv", Td=d2, T=t2, p=pres_z0)

    # diagnose wind, temperature and humidity at 100 m agl or `hgt_agl_max` (see below)
    # conform dimensionality using an existing variable
    za = alt_za - alt_z0
    z = za * 0 + hgt_agl_diag
    da_alt_z = (alt_z0 + z).rename("alt_z")
    ds_alt_z = da_alt_z.to_dataset()

    # get dataset of diagnostics
    if simple_mode:
        ds_diag = diag_era5_simple(z0m, ustar, pres_z0, uv10, t2, q2, z)
    else:
        ds_diag = diag_era5(za, uv_za, t_za, q_za, pres_za, qh, qe, z0m, ustar,
                            pres_z0, uv10, t2, q2, z)

    # merge altitude
    ds_diag = ds_diag.merge(ds_alt_z).drop_vars("level")

    return ds_diag
Ejemplo n.º 12
0
def cal_gs_mod(kd,
               ta_k,
               rh,
               pa,
               smd,
               lai,
               g_cst,
               g_max=30.,
               lai_max=6.,
               s1=5.56):
    """Model surface conductance/resistance using phenology and atmospheric forcing conditions.
    Parameters
    ----------
    kd : numeric
        Incoming solar radiation [W m-2]
    ta_k : numeric
        Air temperature [K]
    rh : numeric
        Relative humidity [%]
    pa : numeric
        Air pressure
    smd : numeric
        Soil moisture deficit [mm]
    lai : numeric
        Leaf area index [m2 m-2]
    g_cst : size-6 array
        Parameters to determine surface conductance/resistance:
        g1 (LAI related), g2 (solar radiation related),
        g3 (humidity related), g4 (humidity related),
        g5 (air temperature related),
        g6 (soil moisture related)
    g_max : numeric, optional
        Maximum surface conductance [mm s-1], by default 30
    lai_max : numeric, optional
        Maximum LAI [m2 m-2], by default 6
    s1 : numeric, optional
        Wilting point (WP=s1/g6, indicated as deficit [mm]) related parameter, by default 5.56
    Returns
    -------
    numeric
        Modelled surface conductance [mm s-1]
    """

    # broadcast g1 – g6
    # print('g_cst', g_cst)
    g1, g2, g3, g4, g5, g6 = g_cst
    # print(g1, g2, g3, g4, g5, g6)
    # lai related
    g_lai = cal_g_lai(lai, g1, lai_max)
    # print('g_lai', g_lai)

    # kdown related
    g_kd = cal_g_kd(kd, g2)
    # print('g_kd', g_kd)
    # dq related
    # ta_k = ta_c+273.15
    dq = ac('qvs', T=ta_k, p=pa) - ac('qv', T=ta_k, p=pa, RH=rh)
    g_dq = cal_g_dq(dq, g3, g4)
    # print('g_dq', g_dq)
    # ta related
    ta_c = ta_k - 273.15
    g_ta = cal_g_ta(ta_c, g5)
    # print('g_ta', g_ta)
    # smd related
    g_smd = cal_g_smd(smd, g6, s1)
    # print('g_smd', g_smd)
    # combine all corrections
    gs_c = g_lai * g_kd * g_dq * g_ta * g_smd
    gs = g_max * gs_c

    return gs, g_lai, g_kd, g_dq, g_ta, g_smd, g_max
Ejemplo n.º 13
0
def format_df_forcing(df_forcing_raw):
    from atmosp import calculate as ac

    df_forcing_grid = df_forcing_raw.copy().round(3)

    # convert energy fluxes: [J m-2] to [W m-2]
    df_forcing_grid.loc[:, ["ssrd", "strd", "sshf", "slhf"]] /= 3600

    # reverse the sign of qh and qe
    df_forcing_grid.loc[:, ["sshf", "slhf"]] *= -1

    # convert rainfall: from [m] to [mm]
    df_forcing_grid.loc[:, "tp"] *= 1000

    # get dry bulb temperature and relative humidity
    df_forcing_grid = df_forcing_grid.assign(Tair=df_forcing_grid.t_z - 273.15)
    df_forcing_grid = df_forcing_grid.assign(RH=ac(
        "RH",
        qv=df_forcing_grid.q_z,
        T=df_forcing_grid.t_z,
        p=df_forcing_grid.p_z,
    ))

    # convert atmospheric pressure: [Pa] to [kPa]
    df_forcing_grid.loc[:, "p_z"] /= 1000

    # renaming for consistency with SUEWS
    df_forcing_grid = df_forcing_grid.rename(
        {
            "ssrd": "kdown",
            "strd": "ldown",
            "sshf": "qh",
            "slhf": "qe",
            "tp": "rain",
            "uv_z": "U",
            "p_z": "pres",
        },
        axis=1,
    )

    col_suews = [
        "iy",
        "id",
        "it",
        "imin",
        "qn",
        "qh",
        "qe",
        "qs",
        "qf",
        "U",
        "RH",
        "Tair",
        "pres",
        "rain",
        "kdown",
        "snow",
        "ldown",
        "fcld",
        "Wuh",
        "xsmd",
        "lai",
        "kdiff",
        "kdir",
        "wdir",
        "alt_z",
    ]

    df_forcing_grid = df_forcing_grid.reindex(col_suews, axis=1)

    df_forcing_grid = df_forcing_grid.assign(
        iy=df_forcing_grid.index.year,
        id=df_forcing_grid.index.dayofyear,
        it=df_forcing_grid.index.hour,
        imin=df_forcing_grid.index.minute,
    )

    # corrections
    df_forcing_grid.loc[:, "RH"] = df_forcing_grid.loc[:, "RH"].where(
        df_forcing_grid.loc[:, "RH"].between(0.001, 105), 105)
    df_forcing_grid.loc[:, "kdown"] = df_forcing_grid.loc[:, "kdown"].where(
        df_forcing_grid.loc[:, "kdown"] > 0, 0)

    # trim decimals
    df_forcing_grid.iloc[:, 4:] = df_forcing_grid.iloc[:, 4:].round(2)

    df_forcing_grid = df_forcing_grid.replace(np.nan, -999).asfreq("1h")

    return df_forcing_grid
Ejemplo n.º 14
0
def cal_rh(qa_kgkg, theta_K, pres_hPa):
    from atmosp import calculate as ac
    RH = ac("RH", av=qa_kgkg, p=pres_hPa * 100, theta=theta_K)
    return RH
Ejemplo n.º 15
0
def cal_qa(rh_pct, theta_K, pres_hPa):
    from atmosp import calculate as ac
    qa = ac("qv", RH=rh_pct, p=pres_hPa * 100, theta=theta_K)
    return qa
Ejemplo n.º 16
0
Archivo: _gs.py Proyecto: LewisB7/SuPy
def cal_rs_FG(qh, qe, ta, rh, pa, ra):
    """Calculate surface resistance based on observations, notably turbulent fluxes.

    Parameters
    ----------
    qh : numeric
        sensible heat flux [W m-2]
    qe : numeric
        latent heat flux [W m-2]
    ta : numeric
        air temperature [degC]
    rh : numeric
        relative humidity [%]
    pa : numeric
        air pressure [Pa]
    ra : numeric
        aerodynamic resistance [m s-1]

    Returns
    -------
    numeric
        Surface resistance based on observations [s m-1]
    """
    from atmosp import calculate as ac
    from ._atm import cal_dens_vap, cal_lat_vap, cal_qa

    # air density [kg m-3]
    val_rho = 1.27

    # heat capacity of air [J kg-1 K-1]
    val_cp = 1005

    # convert temp from C to K
    ta_K = ta + 273.15

    # canopy bulk surface temperature
    tc_K = qh / (val_rho * val_cp) * ra + ta_K

    # actual atmospheric vapour pressure [Pa]
    ser_qa = ac("qv", p=pa, T=ta_K, RH=rh) + rh * 0

    # saturated atmospheric vapour pressure at canopy surface [Pa]
    ser_qs_c = ac("qvs", p=pa, T=tc_K) + rh * 0

    # # vapour pressure deficit [Pa]
    # arr_vpd = arr_es_c - arr_ea

    # specific humidity [kg kg-1]
    # ser_qa = cal_qa(rh, ta_K, pa / 100)

    # latent heat of vapour [J kg-1]
    ser_lv = cal_lat_vap(ser_qa, ta_K, pa / 100) + pa * 0

    # vapour density [kg m-3]
    rho_v = ac("rho", RH=rh, T=ta_K, p=pa) + pa * 0

    ser_et = qe / ser_lv
    ser_rs = rho_v * (ser_qs_c - ser_qa) / ser_et - ra
    print(
        # ser_qa.median(),
        # ta_K.median(),
        # pa.median(),
        # tc_K.median(),
        # rho_v.median(),
        # (ser_qs_c - ser_qa).median(),
        # ser_et.median(),
        # ra.median(),
        # ser_lv.median(),
    )

    try:
        # try to pack as Series
        ser_rs = pd.Series(ser_rs, index=ta_K.index)
    except AttributeError as ex:
        print(ex, "cannot pack into pd.Series")
        pass

    return ser_rs