Ejemplo n.º 1
0
def test_leafscale(method='MEDLYN_FARQUHAR', species='pine', Ebal=False):
    gamma = 1.0
    gfact = 1.0
    if species.upper() == 'PINE':
        photop = {
            #                'Vcmax': 55.0,
            #                'Jmax': 105.0,
            #                'Rd': 1.3,
            #                'tresp': {
            #                    'Vcmax': [78.0, 200.0, 650.0],
            #                    'Jmax': [56.0, 200.0, 647.0],
            #                    'Rd': [33.0]
            #                    },
            'Vcmax': 94.0,  # Tarvainen et al. 2018 Physiol. Plant.
            'Jmax': 143.0,
            'Rd': 1.3,
            'tresp': {
                'Vcmax': [78.3, 200.0, 650.1],
                'Jmax': [56.0, 200.0, 647.9],
                'Rd': [33.0]
            },
            'alpha': gamma * 0.2,
            'theta': 0.7,
            'La': 1600.0,
            'g1': gfact * 2.3,
            'g0': 1.0e-3,
            'kn': 0.6,
            'beta': 0.95,
            'drp': 0.7,
        }
        leafp = {'lt': 0.02, 'par_alb': 0.12, 'nir_alb': 0.55, 'emi': 0.98}
    if species.upper() == 'SPRUCE':
        photop = {
            #                'Vcmax': 60.0,
            #                'Jmax': 114.0,
            #                'Rd': 1.5,
            #                'tresp': {
            #                    'Vcmax': [53.2, 202.0, 640.3],  # Tarvainen et al. 2013 Oecologia
            #                    'Jmax': [38.4, 202.0, 655.8],
            #                    'Rd': [33.0]
            #                    },
            'Vcmax': 69.7,  # Tarvainen et al. 2013 Oecologia
            'Jmax': 130.2,
            'Rd': 1.3,
            'tresp': {
                'Vcmax': [53.2, 200.0, 640.0],
                'Jmax': [38.4, 200.0, 655.5],
                'Rd': [33.0]
            },
            'alpha': gamma * 0.2,
            'theta': 0.7,
            'La': 1600.0,
            'g1': gfact * 2.3,
            'g0': 1.0e-3,
            'kn': 0.6,
            'beta': 0.95,
            'drp': 0.7,
        }
        leafp = {'lt': 0.02, 'par_alb': 0.12, 'nir_alb': 0.55, 'emi': 0.98}
    if species.upper() == 'DECID':
        photop = {
            #                'Vcmax': 50.0,
            #                'Jmax': 95.0,
            #                'Rd': 1.3,
            #                'tresp': {
            #                    'Vcmax': [77.0, 200.0, 636.7],  # Medlyn et al 2002.
            #                    'Jmax': [42.8, 200.0, 637.0],
            #                    'Rd': [33.0]
            #                    },
            'Vcmax': 69.1,  # Medlyn et al 2002.
            'Jmax': 116.3,
            'Rd': 1.3,
            'tresp': {
                'Vcmax': [77.0, 200.0, 636.4],
                'Jmax': [42.8, 200.0, 636.6],
                'Rd': [33.0]
            },
            'alpha': gamma * 0.2,
            'theta': 0.7,
            'La': 600.0,
            'g1': gfact * 4.5,
            'g0': 1.0e-3,
            'kn': 0.6,
            'beta': 0.95,
            'drp': 0.7,
        }
        leafp = {'lt': 0.05, 'par_alb': 0.12, 'nir_alb': 0.55, 'emi': 0.98}
    if species.upper() == 'SHRUBS':
        photop = {
            'Vcmax': 50.0,
            'Jmax': 95.0,
            'Rd': 1.3,
            'alpha': gamma * 0.2,
            'theta': 0.7,
            'La': 600.0,
            'g1': gfact * 4.5,
            'g0': 1.0e-3,
            'kn': 0.3,
            'beta': 0.95,
            'drp': 0.7,
            'tresp': {
                'Vcmax': [77.0, 200.0, 636.7],
                'Jmax': [42.8, 200.0, 637.0],
                'Rd': [33.0]
            }
        }
        leafp = {'lt': 0.02, 'par_alb': 0.12, 'nir_alb': 0.55, 'emi': 0.98}
    # env. conditions
    N = 50
    P = 101300.0
    Qp = 1000. * np.ones(N)  # np.linspace(1.,1800.,50)#
    CO2 = 400. * np.ones(N)
    U = 1.0  # np.array([10.0, 1.0, 0.1, 0.01])
    T = np.linspace(1., 39., 50)  # 10. * np.ones(N) #
    esat, s = e_sat(T)
    H2O = (85.0 / 100.0) * esat / P
    SWabs = 0.5 * (1 - leafp['par_alb']) * Qp / PAR_TO_UMOL + 0.5 * (
        1 - leafp['nir_alb']) * Qp / PAR_TO_UMOL
    LWnet = -30.0 * np.ones(N)

    forcing = {
        'h2o': H2O,
        'co2': CO2,
        'air_temperature': T,
        'par_incident': Qp,
        'sw_absorbed': SWabs,
        'lw_net': LWnet,
        'wind_speed': U,
        'air_pressure': P
    }

    controls = {'photo_model': method, 'energy_balance': Ebal}

    x = leaf_interface(photop, leafp, forcing, controls)
    #    print(x)
    Y = T
    plt.figure(5)
    plt.subplot(421)
    plt.plot(Y, x['net_co2'], 'o')
    plt.title('net_co2')
    plt.subplot(422)
    plt.plot(Y, x['transpiration'], 'o')
    plt.title('transpiration')
    plt.subplot(423)
    plt.plot(Y, x['net_co2'] + x['dark_respiration'], 'o')
    plt.title('co2 uptake')
    plt.subplot(424)
    plt.plot(Y, x['dark_respiration'], 'o')
    plt.title('dark_respiration')
    plt.subplot(425)
    plt.plot(Y, x['stomatal_conductance'], 'o')
    plt.title('stomatal_conductance')
    plt.subplot(426)
    plt.plot(Y, x['boundary_conductance'], 'o')
    plt.title('boundary_conductance')
    plt.subplot(427)
    plt.plot(Y, x['leaf_internal_co2'], 'o')
    plt.title('leaf_internal_co2')
    plt.subplot(428)
    plt.plot(Y, x['leaf_surface_co2'], 'o')
    plt.title('leaf_surface_co2')
    plt.tight_layout()
Ejemplo n.º 2
0
def photo_c3_bwb(photop, Qp, T, RH, ca, gb_c, gb_v, P=101300.0):
    """
    Leaf gas-exchange by Farquhar-Ball-Woodrow-Berry model, as in standard Farquhar-
    model
    IN:
        photop - parameter dict with keys: Vcmax, Jmax, Rd, alpha, theta, La, tresp
           can be scalars or arrays.
           tresp - dictionary with keys: Vcmax, Jmax, Rd: temperature sensitivity
           parameters. OMIT key if no temperature adjustments for photoparameters.
        Qp - incident PAR at leaves (umolm-2s-1)
        Tleaf - leaf temperature (degC)
        rh - relative humidity at leaf temperature (-)
        ca - ambient CO2 (ppm)
        gb_c - boundary-layer conductance for co2 (mol m-2 s-1)
        gb_v - boundary-layer conductance for h2o (mol m-2 s-1)
        P - atm. pressure (Pa)
    OUT:
        An - net CO2 flux (umolm-2s-1)
        Rd - dark respiration (umolm-2s-1)
        fe - leaf transpiration rate (molm-2s-1)
        gs - stomatal conductance for CO2 (mol/m-2s-1)
        ci - leaf internal CO2 (ppm)
        cs - leaf surface CO2 (ppm)
    """
    Tk = T + DEG_TO_KELVIN

    MaxIter = 50

    # --- params ----
    Vcmax = photop['Vcmax']
    Jmax = photop['Jmax']
    Rd = photop['Rd']
    alpha = photop['alpha']
    theta = photop['theta']
    g1 = photop['g1']  # slope parameter
    g0 = photop['g0']
    beta = photop['beta']

    # --- CO2 compensation point -------
    Tau_c = 42.75 * np.exp(37830 * (Tk - TN) / (TN * GAS_CONSTANT * Tk))

    # ---- Kc & Ko (umol/mol), Rubisco activity for CO2 & O2 ------
    Kc = 404.9 * np.exp(79430.0 * (Tk - TN) / (TN * GAS_CONSTANT * Tk))
    Ko = 2.784e5 * np.exp(36380.0 * (Tk - TN) / (TN * GAS_CONSTANT * Tk))

    if 'tresp' in photop:  # adjust parameters for temperature
        tresp = photop['tresp']
        Vcmax_T = tresp['Vcmax']
        Jmax_T = tresp['Jmax']
        Rd_T = tresp['Rd']
        Vcmax, Jmax, Rd, Tau_c = photo_temperature_response(
            Vcmax, Jmax, Rd, Vcmax_T, Jmax_T, Rd_T, Tk)

    # --- model parameters k1_c, k2_c [umol/m2/s]
    Km = Kc * (1.0 + O2_IN_AIR / Ko)
    J = (Jmax + alpha * Qp -
         ((Jmax + alpha * Qp)**2.0 -
          (4 * theta * Jmax * alpha * Qp))**(0.5)) / (2 * theta)

    # --- iterative solution for cs and ci
    err = 9999.0
    cnt = 1
    cs = ca  # leaf surface CO2
    ci = 0.8 * ca  # internal CO2
    while err > 0.01 and cnt < MaxIter:
        # -- rubisco -limited rate
        Av = Vcmax * (ci - Tau_c) / (ci + Km)
        # -- RuBP -regeneration limited rate
        Aj = J / 4.0 * (ci - Tau_c) / (ci + 2.0 * Tau_c)

        #An = np.minimum(Av, Aj) - Rd  # single limiting rate
        # co-limitation
        x = Av + Aj
        y = Av * Aj
        An = (x - (x**2.0 - 4.0 * beta * y)**0.5) / (2.0 * beta) - Rd

        An1 = np.maximum(An, 0.0)
        # bwb -scheme
        gs_opt = g0 + g1 * An1 / ((cs - Tau_c)) * RH
        gs_opt = np.maximum(g0, gs_opt)  # gcut is the lower limit

        # CO2 supply
        cs = np.maximum(ca - An1 / gb_c, 0.5 * ca)  # through boundary layer
        ci0 = ci
        ci = np.maximum(cs - An1 / gs_opt, 0.1 * ca)  # through stomata

        err = max(abs(ci0 - ci))
        cnt += 1

    # when Rd > photo, assume stomata closed and ci == ca
    ix = np.where(An < 0)
    gs_opt[ix] = g0
    ci[ix] = ca[ix]
    cs[ix] = ca[ix]
    gs_v = H2O_CO2_RATIO * gs_opt

    geff = (gb_v * gs_v) / (gb_v + gs_v)  # molm-2s-1
    esat, _ = e_sat(T)
    VPD = (1.0 - RH) * esat / P  # mol mol-1
    fe = geff * VPD  # leaf transpiration rate

    return An, Rd, fe, gs_opt, ci, cs
Ejemplo n.º 3
0
def leaf_interface(photop,
                   leafp,
                   forcing,
                   controls,
                   df=1.0,
                   dict_output=True,
                   logger_info=''):
    r""" Entry-point to coupled leaf gas-exchange and energy balance functions.

    CALCULATES leaf photosynthesis (An), respiration (Rd), transpiration (E) and estimates of
    leaf temperature (Tl) and sensible heat fluxes (H) based onleaf energy balance equation coupled with
    leaf-level photosynthesis and stomatal control schemes.
    Energy balance is solved using Taylor's expansion (i.e isothermal net radiation -approximation) which
    eliminates need for iterations with radiation-sceme.

    Depending on choise of 'model', photosynthesis is calculated based on biochemical model of Farquhar et
    al. (1980) coupled with various stomatal control schemes (Medlyn, Ball-Woodrow-Berry, Hari, Katul-Vico et al.)
    In all these models, stomatal conductance (gs) is directly linked to An, either by optimal stomatal control principles or
    using semi-empirical models.

    Args:
        photop (dict): leaf gas-exchange parameters
            'Vcmax': maximum carboxylation velocity [umolm-2s-1]
            'Jmax': maximum rate of electron transport [umolm-2s-1]
            'Rd': dark respiration rate [umolm-2s-1]
            'alpha': quantum yield parameter [mol/mol]
            'theta': co-limitation parameter of Farquhar-model
            'La': stomatal parameter (Lambda, m, ...) depending on model
            'g1': stomatal slope parameter
            'g0': residual conductance for CO2 [molm-2s-1]
            'kn': ?? not used ??
            'beta': co-limitation parameter of Farquhar-model
            'drp': drought response parameters of Medlyn stomatal model and apparent Vcmax;
                 list: [Rew_crit_g1, slope_g1, Rew_crit_appVcmax, slope_appVcmax]
            'tresp' (dict): temperature sensitivity parameters
                'Vcmax' (list): [Ha, Hd, dS]; activation energy [kJmol-1], deactivation energy [kJmol-1], entropy factor [J mol-1]
                'Jmax' (list): [Ha, Hd, dS];
                'Rd' (list): [Ha]; activation energy [kJmol-1)]
        
        leafp (dict): leaf properties
            'lt': leaf lengthscale [m]
        
        forcing (dict):
            'h2o': water vapor mixing ratio (mol/mol)
            'co2': carbon dioxide mixing ratio (ppm)
            'air_temperature': ambient air temperature (degC)
            'par_incident': incident PAR at leaves (umolm-2s-1)
            'sw_absorbed': absorbed SW (PAR + NIR) at leaves (Wm-2)
            'lw_net': net isothermal long-wave radiation (Wm-2)
            'wind_speed': mean wind speed (m/s)
            'air_pressure': ambient pressure (Pa)
            'leaf_temperature': initial guess for leaf temperature (optional)
            'average_leaf_temperature': leaf temperature used for computing LWnet (optional)
            'radiative_conductance': radiative conductance used in computing LWnet (optional)
        controls (dict):
            'photo_model' (str): photosysthesis model
                CO_OPTI (Vico et al., 2014)
                MEDLYN (Medlyn et al., 2011 with co-limitation Farquhar)
                MEDLYN_FARQUHAR
                BWB (Ball et al., 1987 with co-limitation Farquhar)
                others?
            'energy_balance' (bool): True computes leaf temperature by solving energy balance
        dict_output (bool): True returns output as dict, False as separate arrays (optional)
        logger_info (str): optional

    OUTPUT:
        (dict):
            'net_co2': net CO2 flux (umol m-2 leaf s-1)
            'dark_respiration': CO2 respiration (umol m-2 leaf s-1)
            'transpiration': H2O flux (transpiration) (mol m-2 leaf s-1)
            'sensible_heat': sensible heat flux (W m-2 leaf)
            'fr': non-isothermal radiative flux (W m-2)
            'Tl': leaf temperature (degC)
            'stomatal_conductance': stomatal conductance for H2O (mol m-2 leaf s-1)
            'boundary_conductance': boundary layer conductance for H2O (mol m-2 leaf s-1)
            'leaf_internal_co2': leaf internal CO2 mixing ratio (mol/mol)
            'leaf_surface_co2': leaf surface CO2 mixing ratio (mol/mol)

    NOTE: Vectorized code can be used in multi-layer sense where inputs are vectors of equal length

    Samuli Launiainen LUKE 3/2011 - 5/2017
    Last edit 15.11.2019 / SL
    """

    # -- parameters -----
    lt = leafp['lt']

    T = np.array(forcing['air_temperature'], ndmin=1)
    H2O = np.array(forcing['h2o'], ndmin=1)
    Qp = forcing['par_incident']
    P = forcing['air_pressure']
    U = forcing['wind_speed']
    CO2 = forcing['co2']

    Ebal = controls['energy_balance']
    model = controls['photo_model']

    if Ebal:
        SWabs = np.array(forcing['sw_absorbed'], ndmin=1)
        LWnet = np.array(forcing['lw_net'], ndmin=1)
        Rabs = SWabs + LWnet
        # canopy nodes
        ic = np.where(abs(LWnet) > 0.0)

    if 'leaf_temperature' in forcing:
        Tl_ini = np.array(forcing['leaf_temperature'], ndmin=1).copy()

    else:
        Tl_ini = T.copy()

    Tl = Tl_ini.copy()
    Told = Tl.copy()

    if 'radiative_conductance' in forcing:
        gr = df * np.array(forcing['radiative_conductance'], ndmin=1)
    else:
        gr = np.zeros(len(T))

    if 'average_leaf_temperature' in forcing:
        Tl_ave = np.array(forcing['average_leaf_temperature'], ndmin=1)

    else:
        Tl_ave = Tl.copy()

    # vapor pressure
    esat, s = e_sat(Tl)
    s = s / P  # slope of esat, mol/mol / degC
    Dleaf = esat / P - H2O

    Lv = latent_heat(T) * MOLAR_MASS_H2O

    itermax = 20
    err = 999.0
    iter_no = 0

    while err > 0.01 and iter_no < itermax:
        iter_no += 1
        # boundary layer conductance
        gb_h, gb_c, gb_v = leaf_boundary_layer_conductance(
            U, lt, T, 0.5 * (Tl + Told) - T, P)

        Told = Tl.copy()

        if model.upper() == 'MEDLYN_FARQUHAR':
            An, Rd, fe, gs_opt, Ci, Cs = photo_c3_medlyn_farquhar(photop,
                                                                  Qp,
                                                                  Tl,
                                                                  Dleaf,
                                                                  CO2,
                                                                  gb_c,
                                                                  gb_v,
                                                                  P=P)

        if model.upper() == 'BWB':
            rh = (1 - Dleaf * P / esat)  # rh at leaf (-)
            An, Rd, fe, gs_opt, Ci, Cs = photo_c3_bwb(photop,
                                                      Qp,
                                                      Tl,
                                                      rh,
                                                      CO2,
                                                      gb_c,
                                                      gb_v,
                                                      P=P)

        # --- analytical co-limitation model Vico et al. 2013
        if model.upper() == 'CO_OPTI':
            An, Rd, fe, gs_opt, Ci, Cs = photo_c3_analytical(
                photop, Qp, Tl, Dleaf, CO2, gb_c, gb_v)

        gsv = H2O_CO2_RATIO * gs_opt
        # geff_v = (gb_v*gsv) / (gb_v + gsv)
        geff_v = np.where(
            Dleaf > 0.0, (gb_v * gsv) / (gb_v + gsv),
            df * gb_v)  # molm-2s-1, condensation only on dry leaf part
        gb_h = df * gb_h  # sensible heat exchange only through dry leaf part

        # solve leaf temperature from energy balance
        if Ebal:
            Tl[ic] = (Rabs[ic] + SPECIFIC_HEAT_AIR * gr[ic] * Tl_ave[ic] +
                      SPECIFIC_HEAT_AIR * gb_h[ic] * T[ic] -
                      Lv[ic] * geff_v[ic] * Dleaf[ic] +
                      Lv[ic] * s[ic] * geff_v[ic] * Told[ic]) / (
                          SPECIFIC_HEAT_AIR *
                          (gr[ic] + gb_h[ic]) + Lv[ic] * s[ic] * geff_v[ic])
            err = np.nanmax(abs(Tl - Told))

            if (err < 0.01 or iter_no
                    == itermax) and abs(np.mean(T) - np.mean(Tl)) > 20.0:
                logger.debug(
                    logger_info +
                    ' Unrealistic leaf temperature %.2f set to air temperature %.2f, %.2f, %.2f, %.2f, %.2f',
                    np.mean(Tl), np.mean(T), np.mean(LWnet), np.mean(Tl_ave),
                    np.mean(Tl_ini), np.mean(H2O))
                Tl = T.copy()
                Ebal = False  # recompute without solving leaf temperature
                err = 999.

            elif iter_no == itermax and err > 0.05:
                logger.debug(
                    logger_info +
                    ' Maximum number of iterations reached: Tl = %.2f (err = %.2f)',
                    np.mean(Tl), err)

            # vapor pressure
            esat, s = e_sat(Tl)
            s = s / P  # slope of esat, mol/mol / degC
            # s[esat / P < H2O] = EPS
            # Dleaf = np.maximum(EPS, esat / P - H2O)  # mol/mol
            Dleaf = esat / P - H2O
        else:
            err = 0.0

    # outputs
    H = SPECIFIC_HEAT_AIR * gb_h * (Tl - T)  # Wm-2
    Fr = SPECIFIC_HEAT_AIR * gr * (
        Tl - Tl_ave)  # flux due to radiative conductance (Wm-2)
    E = geff_v * np.maximum(
        0.0, Dleaf)  # condensation accounted for in wetleaf water balance
    LE = E * Lv  # condensation accounted for in wetleaf energy balance

    if dict_output:  # return dict

        x = {
            'net_co2': An,
            'dark_respiration': Rd,
            'transpiration': E,
            'sensible_heat': H,
            'latent_heat': LE,
            'fr': Fr,
            'leaf_temperature': Tl,
            'stomatal_conductance':
            np.minimum(gsv, 1.0),  # gsv gets high when VPD->0
            'boundary_conductance': gb_v,
            'leaf_internal_co2': Ci,
            'leaf_surface_co2': Cs
        }
        return x
    else:  # return 11 arrays
        return An, Rd, E, H, Fr, Tl, Ci, Cs, gsv, gs_opt, gb_v
Ejemplo n.º 4
0
def create_forcingfile(meteo_file,
                       output_file,
                       lat,
                       lon,
                       P_unit,
                       timezone=+2.0):
    """
    Create forcing file from meteo.
    Args:
        meteo_file (str): name of file with meteo (.csv not included)
        output_file (str): name of output file (.csv not included)
        lat (float): latitude
        lon (float): longitude
        P_unit (float): unit conversion needed to get to [Pa]
    """

    from canopy.radiation import solar_angles, compute_clouds_rad
    from canopy.micromet import e_sat

    fpar = 0.45

    forc_fp = direc + "forcing/" + meteo_file + ".csv"
    dat = pd.read_csv(forc_fp, sep=',', header='infer', encoding='ISO-8859-1')

    # set to dataframe index
    dat.index = pd.to_datetime({
        'year': dat['yyyy'],
        'month': dat['mo'],
        'day': dat['dd'],
        'hour': dat['hh'],
        'minute': dat['mm']
    })

    readme = ''
    cols = []

    # day of year
    dat['doy'] = dat.index.dayofyear
    cols.append('doy')
    readme += "\ndoy: Day of year [days]"

    # precipitaion unit from [mm/dt] to [m/s]
    dt = (dat.index[1] - dat.index[0]).total_seconds()
    dat['Prec'] = dat['Prec'] * 1e-3 / dt
    cols.append('Prec')
    readme += "\nPrec: Precipitation [m/s]"

    # atm. pressure unit from [XPa] to [Pa]
    dat['P'] = dat['P'] * P_unit
    cols.append('P')
    readme += "\nP: Ambient pressure [Pa]"

    # air temperature: instant and daily [degC]
    cols.append('Tair')
    readme += "\nTair: Air temperature [degC]"

    #    dat['Tdaily'] = dat['Tair'].rolling(int((24*3600)/dt), 1).mean()
    dat['Tdaily'] = dat['Tair'].resample('D').mean()
    dat['Tdaily'] = dat['Tdaily'].fillna(method='ffill')

    cols.append('Tdaily')
    readme += "\nTdaily: Daily air temperature [degC]"

    # wind speend and friction velocity
    cols.append('U')
    readme += "\nU: Wind speed [m/s]"
    cols.append('Ustar')
    readme += "\nUstar: Friction velocity [m/s]"

    # ambient H2O [mol/mol] from RH
    esat, _ = e_sat(dat['Tair'])
    dat['H2O'] = (dat['RH'] / 100.0) * esat / dat['P']
    cols.append('H2O')
    readme += "\nH2O: Ambient H2O [mol/mol]"

    # ambient CO2 [ppm]
    readme += "\nCO2: Ambient CO2 [ppm]"
    if 'CO2' not in dat:
        dat['CO2'] = 400.0
        readme += " - set constant!"
    cols.append('CO2')

    # zenith angle
    jday = dat.index.dayofyear + dat.index.hour / 24.0 + dat.index.minute / 1440.0
    # TEST (PERIOD START)
    jday = dat.index.dayofyear + dat.index.hour / 24.0 + dat.index.minute / 1440.0 + dt / 2.0 / 86400.0
    dat['Zen'], _, _, _, _, _ = solar_angles(lat, lon, jday, timezone=timezone)
    cols.append('Zen')
    readme += "\nZen: Zenith angle [rad], (lat = %.2f, lon = %.2f)" % (lat,
                                                                       lon)

    # radiation components

    if {'LWin', 'diffPar', 'dirPar', 'diffNir',
            'dirNir'}.issubset(dat.columns) == False:
        f_cloud, f_diff, emi_sky = compute_clouds_rad(
            dat['doy'].values, dat['Zen'].values, dat['Rg'].values,
            dat['H2O'].values * dat['P'].values, dat['Tair'].values)

    if 'LWin' not in dat or dat['LWin'].isnull().any():
        if 'LWin' not in dat:
            dat['LWin'] = np.nan
            print('Longwave radiation estimated')
        else:
            print('Longwave radiation partly estimated')
        # Downwelling longwve radiation
        # solar constant at top of atm.
        So = 1367
        # clear sky Global radiation at surface
        dat['Qclear'] = np.maximum(
            0.0,
            (So * (1.0 + 0.033 *
                   np.cos(2.0 * np.pi *
                          (np.minimum(dat['doy'].values, 365) - 10) / 365)) *
             np.cos(dat['Zen'].values)))
        tau_atm = tau_atm = dat['Rg'].rolling(
            4, 1).sum() / (dat['Qclear'].rolling(4, 1).sum() + EPS)
        # cloud cover fraction
        dat['f_cloud'] = 1.0 - (tau_atm - 0.2) / (0.7 - 0.2)
        dat['f_cloud'][dat['Qclear'] < 10] = np.nan

        dat['Qclear_12h'] = dat['Qclear'].resample('12H').sum()
        dat['Qclear_12h'] = dat['Qclear_12h'].fillna(method='ffill')
        dat['Rg_12h'] = dat['Rg'].resample('12H').sum()
        dat['Rg_12h'] = dat['Rg_12h'].fillna(method='ffill')

        tau_atm = dat['Rg_12h'] / (dat['Qclear_12h'] + EPS)
        dat['f_cloud_12h'] = 1.0 - (tau_atm - 0.2) / (0.7 - 0.2)

        dat['f_cloud'] = np.where(
            (dat.index.hour > 12) & (dat['f_cloud_12h'] < 0.2), 0.0,
            dat['f_cloud'])
        dat['f_cloud'] = dat['f_cloud'].fillna(method='ffill')
        dat['f_cloud'] = dat['f_cloud'].fillna(method='bfill')
        dat['f_cloud'][dat['f_cloud'] < 0.0] = 0.0
        dat['f_cloud'][dat['f_cloud'] > 1.0] = 1.0

        emi0 = 1.24 * (dat['H2O'].values * dat['P'].values / 100 /
                       (dat['Tair'].values + 273.15))**(1. / 7.)
        emi_sky = (1 - 0.84 * dat['f_cloud']) * emi0 + 0.84 * dat['f_cloud']

        # estimated long wave budget
        b = 5.6697e-8  # Stefan-Boltzman constant (W m-2 K-4)
        dat['LWin_estimated'] = emi_sky * b * (
            dat['Tair'] + 273.15)**4  # Wm-2 downwelling LW

        dat[['LWin', 'LWin_estimated']].plot(kind='line')

        dat['LWin'] = np.where(np.isfinite(dat['LWin']), dat['LWin'],
                               dat['LWin_estimated'])

    cols.append('LWin')
    readme += "\nLWin: Downwelling long wave radiation [W/m2]"

    # Short wave radiation; separate direct and diffuse PAR & NIR
    if {'diffPar', 'dirPar', 'diffNir',
            'dirNir'}.issubset(dat.columns) == False:
        print('Shortwave radiation components estimated')
        dat['diffPar'] = f_diff * fpar * dat['Rg']
        dat['dirPar'] = (1 - f_diff) * fpar * dat['Rg']
        dat['diffNir'] = f_diff * (1 - fpar) * dat['Rg']
        dat['dirNir'] = (1 - f_diff) * (1 - fpar) * dat['Rg']
    cols.extend(('diffPar', 'dirPar', 'diffNir', 'dirNir'))
    readme += "\ndiffPar: Diffuse PAR [W/m2] \ndirPar: Direct PAR [W/m2]"
    readme += "\ndiffNir: Diffuse NIR [W/m2] \ndirNir: Direct NIR [W/m2]"

    if {'Tsoil', 'Wliq'}.issubset(dat.columns):
        cols.extend(('Tsoil', 'Wliq'))
        dat['Wliq'] = dat['Wliq'] / 100.0
        readme += "\nTsoil: Soil surface layer temperature [degC]]"
        readme += "\nWliq: Soil surface layer moisture content [m3 m-3]"

    X = np.zeros(len(dat))
    DDsum = np.zeros(len(dat))
    for k in range(1, len(dat)):
        if dat['doy'][k] != dat['doy'][k - 1]:
            X[k] = X[k - 1] + 1.0 / 8.33 * (dat['Tdaily'][k - 1] - X[k - 1])
            if dat['doy'][k] == 1:  # reset in the beginning of the year
                DDsum[k] = 0.
            else:
                DDsum[k] = DDsum[k - 1] + max(0.0, dat['Tdaily'][k - 1] - 5.0)
        else:
            X[k] = X[k - 1]
            DDsum[k] = DDsum[k - 1]
    dat['X'] = X
    cols.append('X')
    readme += "\nX: phenomodel delayed temperature [degC]"
    dat['DDsum'] = DDsum
    cols.append('DDsum')
    readme += "\nDDsum: degreedays [days]"

    dat = dat[cols]
    dat[cols].plot(subplots=True, kind='line')

    print("NaN values in forcing data:")
    print(dat.isnull().any())

    save_df_to_csv(dat, output_file, readme=readme, fp=direc + "forcing/")
Ejemplo n.º 5
0
    def leaf_gas_exchange(self, forcing, controls, leaftype):
        r""" Solves leaf gas-exchange and energy balance (optionally). 
        Energy balance is solved using Taylor's expansion (i.e isothermal
        net radiation -approximation) which eliminates need for iterations with radiation-scheme.
        Args:
            forcing (dict):
                'h2o': water vapor mixing ratio (mol/mol)
                'co2': carbon dioxide mixing ratio (ppm)
                'air_temperature': ambient air temperature (degC)
                'par_incident': incident PAR at leaves (umolm-2s-1)
                'sw_absorbed': absorbed SW (PAR + NIR) at leaves (Wm-2)
                'lw_net': net isothermal long-wave radiation (Wm-2)
                'wind_speed': mean wind speed (m/s)
                'air_pressure': ambient pressure (Pa)
                'leaf_temperature': initial guess for leaf temperature (optional)
                'average_leaf_temperature': leaf temperature used for computing LWnet (optional)
                'radiative_conductance': radiative conductance used in computing LWnet (optional)
            controls (dict):
                'energy_balance' (bool): True computes leaf temperature by solving energy balance
                'logger_info' (str)
            leaftype (str): 'sunlit' / 'shaded'
        Returns:
            (dict):
                'net_co2': net CO2 flux (umol m-2 leaf s-1)
                'dark_respiration': CO2 respiration (umol m-2 leaf s-1)
                'transpiration': H2O flux (transpiration) (mol m-2 leaf s-1)
                'sensible_heat': sensible heat flux (W m-2 leaf)
                'fr': non-isothermal radiative flux (W m-2)
                'Tl': leaf temperature (degC)
                'stomatal_conductance': stomatal conductance for H2O (mol m-2 leaf s-1)
                'boundary_conductance': boundary layer conductance for H2O (mol m-2 leaf s-1)
                'leaf_internal_co2': leaf internal CO2 mixing ratio (mol/mol)
                'leaf_surface_co2': leaf surface CO2 mixing ratio (mol/mol)
    
        Samuli Launiainen & Kersti Haahti, Last edit 25.11.2019 / SL
        """

        Ebal = controls['energy_balance']
        logger_info = controls['logger_info'] + 'leaftype: ' + leaftype

        # -- unpack forcing
        T = np.array(forcing['air_temperature'], ndmin=1)
        H2O = np.array(forcing['h2o'], ndmin=1)
        P = forcing['air_pressure']
        U = forcing['wind_speed']
        CO2 = forcing['co2']

        # incident PAR at leaftype
        Qp = forcing['par'][leaftype]['incident'] * PAR_TO_UMOL  # umolm-2s-1

        # solve energy balance iteratively
        if Ebal:
            SWabs = forcing['par'][leaftype]['absorbed'] + forcing['nir'][
                leaftype]['absorbed']
            LWnet = forcing['lw']['net_leaf']
            Rabs = SWabs + LWnet

            gr = forcing['lw']['radiative_conductance']
            Tl_ave = forcing[
                'average_leaf_temperature']  # layer mean leaf temperature

            # initial guess for leaf temperature
            if leaftype is 'sunlit':
                Tl_ini = self.Tl_sl
            if leaftype is 'shaded':
                Tl_ini = self.Tl_sh
            # canopy nodes
            ic = np.where(abs(LWnet) > 0.0)

            Tl = Tl_ini.copy()
            Told = Tl.copy()

            # vapor pressure
            esat, s = e_sat(Tl)
            s = s / P  # slope of esat, mol/mol / degC
            Dleaf = esat / P - H2O

            Lv = latent_heat(T) * MOLAR_MASS_H2O

            itermax = 20
            err = 999.0
            iter_no = 0

            while err > 0.01 and iter_no < itermax:
                iter_no += 1
                Told = Tl.copy()
                # boundary layer conductance
                gb_h, gb_c, gb_v = leaf_boundary_layer_conductance(
                    U, self.leafp['lt'], T, 0.5 * (Tl + Told) - T, P)

                # solve leaf gas-exchange
                An, Rd, fe, gs_opt, Ci, Cs = photo_c3_medlyn_farquhar(
                    self.photop, Qp, Tl, Dleaf, CO2, gb_c, gb_v, P=P)

                gsv = H2O_CO2_RATIO * gs_opt
                geff_v = np.where(Dleaf > 0.0, (gb_v * gsv) / (gb_v + gsv),
                                  gb_v)  # molm-2s-1

                # solve leaf temperature from energy balance
                Tl[ic] = (Rabs[ic] + SPECIFIC_HEAT_AIR * gr[ic] * Tl_ave[ic] +
                          SPECIFIC_HEAT_AIR * gb_h[ic] * T[ic] -
                          Lv[ic] * geff_v[ic] * Dleaf[ic] +
                          Lv[ic] * s[ic] * geff_v[ic] * Told[ic]) / (
                              SPECIFIC_HEAT_AIR * (gr[ic] + gb_h[ic]) +
                              Lv[ic] * s[ic] * geff_v[ic])
                err = np.nanmax(abs(Tl - Told))

                if (err < 0.01 or iter_no
                        == itermax) and abs(np.mean(T) - np.mean(Tl)) > 20.0:
                    logger.debug(
                        logger_info +
                        ' Unrealistic leaf temperature %.2f set to air temperature %.2f, %.2f, %.2f, %.2f, %.2f',
                        np.mean(Tl), np.mean(T), np.mean(LWnet),
                        np.mean(Tl_ave), np.mean(Tl_ini), np.mean(H2O))
                    Tl = T.copy()
                    Ebal = False  # recompute without solving leaf temperature
                    err = 999.

                elif iter_no == itermax and err > 0.05:
                    logger.debug(
                        logger_info +
                        ' Maximum number of iterations reached: Tl = %.2f (err = %.2f)',
                        np.mean(Tl), err)

                # vapor pressure
                esat, s = e_sat(Tl)
                s = s / P  # slope of esat, mol/mol / degC
                Dleaf = esat / P - H2O

            H = SPECIFIC_HEAT_AIR * gb_h * (Tl - T)  # Wm-2
            Fr = SPECIFIC_HEAT_AIR * gr * (
                Tl - Tl_ave)  # flux due to radiative conductance (Wm-2)
            E = geff_v * np.maximum(
                0.0, Dleaf
            )  # mol m-2 s-1, condensation accounted for in wetleaf water balance
            LE = E * Lv  # W m-2

        else:  # or assume leaves are at air temperature

            Tl = T.copy()
            esat, s = e_sat(Tl)
            s = s / P  # slope of esat, mol/mol / degC
            Dleaf = esat / P - H2O

            Lv = latent_heat(T) * MOLAR_MASS_H2O
            # boundary-layer conductances mol m-2 s-1
            dT = 0.0
            gb_h, gb_c, gb_v = leaf_boundary_layer_conductance(
                U, self.leafp['lt'], T, dT, P)

            # solve leaf gas-exchange
            An, Rd, fe, gs_opt, Ci, Cs = photo_c3_medlyn_farquhar(self.photop,
                                                                  Qp,
                                                                  Tl,
                                                                  Dleaf,
                                                                  CO2,
                                                                  gb_c,
                                                                  gb_v,
                                                                  P=P)

            gsv = H2O_CO2_RATIO * gs_opt
            geff_v = np.where(Dleaf > 0.0, (gb_v * gsv) / (gb_v + gsv),
                              gb_v)  # molm-2s-1

            H = 0.0
            Fr = 0.0  # flux due to radiative conductance (Wm-2)
            E = geff_v * np.maximum(
                0.0, Dleaf
            )  # mol m-2 s-1, condensation accounted for in wetleaf water balance
            LE = E * Lv  # W m-2

        # prepare output dict
        x = {
            'net_co2': An,
            'dark_respiration': Rd,
            'transpiration': E,
            'sensible_heat': H,
            'latent_heat': LE,
            'fr': Fr,
            'leaf_temperature': Tl,
            'stomatal_conductance':
            np.minimum(gsv, 1.0),  # gsv gets high when VPD->0
            'boundary_conductance': gb_v,
            'leaf_internal_co2': Ci,
            'leaf_surface_co2': Cs
        }

        return x
Ejemplo n.º 6
0
def heat_balance(forcing, parameters, controls, properties, temperature):
    r""" Solves bare soil surface temperature

    Uses linearized energy balance equation from soil conditions from
    previous timestep

    Args:
        forcing (dict):
            'wind_speed': [m s-1]
            'air_temperature': [degC]
            'h2o': [mol mol\ :sup:`-1`\ ]
            'air_pressure': [Pa]
            'forestfloor_temperature': [degC]
            'soil_tempereture': [degC]
            'soil_water_potential': [m]
            'par': [W m-2]
            'nir': [W m-2]
            'lw_dn': [W m-2]
            'lw_up': [W m-2]
        parameters (dict):
            'soil_hydraulic_conductivity': [m s-1]
            'soil_thermal_conductivity': []
            'height': [m] height to the first canopy calculation node
            'soil_depth': [m] depth to the first soil calculation node
        controls (dict):
            'energy_balance': boolean
        properties (dict):
            'optical_properties':
                'emissivity'
                'albedo_PAR'
                'albedo_NIR'
    """

    U = forcing['wind_speed']
    T = forcing['air_temperature']
    P = forcing['air_pressure']
    T_ave = forcing['forestfloor_temperature']
    T_soil = forcing['soil_temperature']
    h_soil = forcing['soil_water_potential']

    z_soil = parameters['soil_depth']
    Kt = parameters['soil_thermal_conductivity']
    Kh = parameters['soil_hydraulic_conductivity']

    soil_emi = properties['optical_properties']['emissivity']
    # radiative conductance [mol m-2 s-1]
    gr = 4.0 * soil_emi * STEFAN_BOLTZMANN * T_ave**3 / SPECIFIC_HEAT_AIR

    if controls['energy_balance']:  # energy balance switch

        albedo_par = properties['optical_properties']['albedo_PAR']
        albedo_nir = properties['optical_properties']['albedo_NIR']

        # absorbed shortwave radiation
        SW_gr = (1 - albedo_par) * forcing['par'] + (
            1 - albedo_nir) * forcing['nir']

        # net longwave radiation
        LWn = forcing['lw_dn'] - forcing['lw_up']

        # initial guess for surface temperature
        surface_temperature = temperature
    else:

        SW_gr, LWn = 0.0, 0.0
        # set temperature to average of soil and air
        surface_temperature = forcing['air_temperature']
        # geometric mean of air_temperature and soil_temperature
#        surface_temperature = (
#            np.power(forcing['air_temperature'] * forcing['soil_temperature'], 0.5)
#        )

    dz_soil = -z_soil

    # change this either to baresoil temperature or baresoil old_temperature

    #    # boundary layer conductances for forcing['h2o'] and heat [mol m-2 s-1]
    #    gb_h, _, gb_v = soil_boundary_layer_conductance(
    #        u=forcing['wind_speed'],
    #        z=parameters['height'],
    #        zo=properties['roughness_length'],
    #        Ta=forcing['air_temperature'],
    #        dT=0.0,
    #        P=forcing['air_pressure']
    #    )  # OK to assume dt = 0.0?

    atm_conductance = surface_atm_conductance(
        wind_speed=forcing['wind_speed'],
        height=parameters['height'],
        friction_velocity=forcing['friction_velocity'],
        dT=0.0)
    gb_v = atm_conductance['h2o']
    gb_h = atm_conductance['heat']

    # Maximum LE
    # atm pressure head in equilibrium with atm. relative humidity
    es_a, _ = e_sat(T)
    RH = min(1.0, forcing['h2o'] * P /
             es_a)  # air relative humidity above ground [-]
    h_atm = GAS_CONSTANT * (DEG_TO_KELVIN + T) * np.log(RH) / (
        MOLAR_MASS_H2O * GRAVITY)  # [m]
    # maximum latent heat flux constrained by h_atm
    LEmax = max(0.0, -LATENT_HEAT * Kh * (h_atm - h_soil - z_soil) / dz_soil *
                WATER_DENSITY / MOLAR_MASS_H2O)  # [W/m2]

    # LE demand
    # vapor pressure deficit between leaf and air, and slope of vapor pressure curve at T
    es, s = e_sat(surface_temperature)
    Dsurf = es / P - forcing['h2o']  # [mol/mol] - allows condensation
    s = s / P  # [mol/mol/degC]
    LE = LATENT_HEAT * gb_v * Dsurf

    if LE > LEmax:
        LE = LEmax
        s = 0.0
    """ --- solve surface temperature --- """
    itermax = 20
    err = 999.0
    iterNo = 0
    while err > 0.01 and iterNo < itermax:
        iterNo += 1
        Told = surface_temperature
        if controls['energy_balance']:
            # solve surface temperature [degC]
            surface_temperature = (
                (SW_gr + LWn + SPECIFIC_HEAT_AIR * gr * T_ave +
                 SPECIFIC_HEAT_AIR * gb_h * T - LE +
                 LATENT_HEAT * s * gb_v * Told + Kt / dz_soil * T_soil) /
                (SPECIFIC_HEAT_AIR *
                 (gr + gb_h) + LATENT_HEAT * s * gb_v + Kt / dz_soil))

            err = abs(surface_temperature - Told)
            es, s = e_sat(surface_temperature)
            Dsurf = es / P - forcing['h2o']  # [mol/mol] - allows condensation
            s = s / P  # [mol/mol/degC]
            LE = LATENT_HEAT * gb_v * Dsurf

            if LE > LEmax:
                LE = LEmax
                s = 0.0
            if iterNo == itermax:
                logger.debug(
                    'Maximum number of iterations reached: T_baresoil = %.2f, err = %.2f',
                    surface_temperature, err)
        else:
            err = 0.0

    if (abs(surface_temperature - temperature) > 20
            or np.isnan(surface_temperature)
        ):  # into iteration loop? chech photo or interception
        logger.debug(
            'Unrealistic baresoil temperature %.2f set to previous value %.2f: %.5f,%.5f,%.5f,%.5f,%.5f,%.5f,%.5f,%.5f,%.5f',
            surface_temperature, temperature, U, T, forcing['h2o'], P, T_ave,
            T_soil, h_soil, SW_gr, LWn)
        surface_temperature = temperature
        es, s = e_sat(surface_temperature)
        Dsurf = es / P - forcing['h2o']  # [mol/mol] - allows condensation
        LE = LATENT_HEAT * gb_v * Dsurf
        if LE > LEmax:
            LE = LEmax
    """ --- energy and water fluxes --- """
    # sensible heat flux [W m-2]
    Hw = SPECIFIC_HEAT_AIR * gb_h * (surface_temperature - T)
    # non-isothermal radiative flux [W m-2]
    Frw = SPECIFIC_HEAT_AIR * gr * (surface_temperature - T_ave)
    # ground heat flux [W m-2]
    Gw = Kt / dz_soil * (surface_temperature - T_soil)
    # evaporation rate [mol m-2 s-1]
    Ep = LE / LATENT_HEAT  #gb_v * Dsurf

    # energy closure
    closure = SW_gr + LWn - Hw - LE - Gw

    fluxes = {
        'latent_heat': LE,
        'energy_closure': closure,
        'evaporation': Ep,
        'radiative_flux': Frw,
        'sensible_heat': Hw,
        'ground_heat': Gw
    }

    states = {'temperature': surface_temperature}

    return states, fluxes