示例#1
0
def module_tree(v, g):
    """Return the list of vid of module tree

    :param v: selected vid
    :type v: int
    :param g: MTG
    :type g: MTG
    :return: List of module tree
    :rtype: list
    """

    #     _complete = g.property('complete')
    #     if not complete:
    #         complete_module(g)
    #         _complete = g.property('complete')

    for cid in g.Sons(v, EdgeType='+'):
        cpx = g.complex(cid)
        visibles = g.property('visible')
        if DEBUG:
            if cpx in visibles and (is_axis_root(g, cpx)):
                return [
                    m for m in traversal.pre_order2(g, cpx) if m in visibles
                ]
        else:
            bt = branching_type(cid, g)
            if bt == 6:
                return [
                    m for m in traversal.pre_order2(g, cpx) if m in visibles
                ]
示例#2
0
def module_tree(v, g):
    #     _complete = g.property('complete')
    #     if not complete:
    #         complete_module(g)
    #         _complete = g.property('complete')

    for cid in g.Sons(v, EdgeType='+'):
        cpx = g.complex(cid)
        visibles = g.property('visible')
        if DEBUG:
            if cpx in visibles and (is_axis_root(g, cpx)):
                return [
                    m for m in traversal.pre_order2(g, cpx) if m in visibles
                ]
        else:
            bt = branching_type(cid, g)
            if bt == 6:
                return [
                    m for m in traversal.pre_order2(g, cpx) if m in visibles
                ]
示例#3
0
def test_dynamic_mtg():
    """ Add to the each metamers a thermal time (e.g. 10).
    Length = length * (thermal_time - start_thermaltime)/(end-start)
    if the thermal time is lower that the start thermal time, 
    do not proceed more.
    """

    g = adel_mtg()

    v = g.component_roots_at_scale_iter(g.root, scale=3).next()
    tt = 0
    dtt = 10.
    for metamer in pre_order2(g, v):
        nm = g.node(metamer)
        for node in nm.components():
            node.start_tt = tt
            node.end_tt = tt + dtt
        tt += dtt

    return g, tt
示例#4
0
def test_dynamic_mtg():
    """ Add to the each metamers a thermal time (e.g. 10).
    Length = length * (thermal_time - start_thermaltime)/(end-start)
    if the thermal time is lower that the start thermal time, 
    do not proceed more.
    """

    g = adel_mtg()

    v = g.component_roots_at_scale(g.root, scale=3).next()
    tt = 0
    dtt = 10.
    for metamer in pre_order2(g, v):
        nm = g.node(metamer)
        for node in nm.components():
            node.start_tt = tt
            node.end_tt = tt+dtt
        tt += dtt

    return g, tt
示例#5
0
def solve_interactions(g, meteo, psi_soil, t_soil, t_sky_eff, vid_collar,
                       vid_base, length_conv, time_conv, rhyzo_total_volume,
                       params, form_factors, simplified_form_factors):
    """Computes gas-exchange, energy and hydraulic structure of plant's shoot jointly.

    Args:
        g: MTG object
        meteo (DataFrame): forcing meteorological variables
        psi_soil (float): [MPa] soil (root zone) water potential
        t_soil (float): [degreeC] soil surface temperature
        t_sky_eff (float): [degreeC] effective sky temperature
        vid_collar (int): id of the collar node of the mtg
        vid_base (int): id of the basal node of the mtg
        length_conv (float): [-] conversion factor from the `unit_scene_length` to 1 m
        time_conv (float): [-] conversion factor from meteo data time step to seconds
        rhyzo_total_volume (float): [m3] volume of the soil occupied with roots
        params (params): [-] :class:`hydroshoot.params.Params()` object

    """
    unit_scene_length = params.simulation.unit_scene_length

    hydraulic_structure = params.simulation.hydraulic_structure
    negligible_shoot_resistance = params.simulation.negligible_shoot_resistance
    soil_water_deficit = params.simulation.soil_water_deficit
    energy_budget = params.simulation.energy_budget

    par_photo = params.exchange.par_photo
    par_photo_n = params.exchange.par_photo_N
    par_gs = params.exchange.par_gs
    rbt = params.exchange.rbt

    mass_conv = params.hydraulic.MassConv
    xylem_k_max = params.hydraulic.Kx_dict
    xylem_k_cavitation = params.hydraulic.par_K_vul
    psi_min = params.hydraulic.psi_min

    solo = params.energy.solo

    irradiance_type2 = params.irradiance.E_type2

    leaf_lbl_prefix = params.mtg_api.leaf_lbl_prefix
    collar_label = params.mtg_api.collar_label

    soil_class = params.soil.soil_class
    dist_roots, rad_roots = params.soil.roots

    temp_step = params.numerical_resolution.t_step
    psi_step = params.numerical_resolution.psi_step
    max_iter = params.numerical_resolution.max_iter
    psi_error_threshold = params.numerical_resolution.psi_error_threshold
    temp_error_threshold = params.numerical_resolution.t_error_crit

    modelx, psi_critx, slopex = [
        xylem_k_cavitation[ikey]
        for ikey in ('model', 'fifty_cent', 'sig_slope')
    ]

    if hydraulic_structure:
        assert (
            par_gs['model'] != 'vpd'
        ), "Stomatal conductance model should be linked to the hydraulic strucutre"
    else:
        par_gs['model'] = 'vpd'
        negligible_shoot_resistance = True

        print "par_gs: 'model' is forced to 'vpd'"
        print "negligible_shoot_resistance is forced to True."

    # Initialize all xylem potential values to soil water potential
    for vtx_id in traversal.pre_order2(g, vid_base):
        g.node(vtx_id).psi_head = psi_soil

    # Temperature loop +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    t_error_trace = []
    it_step = temp_step

    # Initialize leaf  temperature to air temperature
    g.properties()['Tlc'] = energy.leaf_temperature_as_air_temperature(
        g, meteo, leaf_lbl_prefix)

    for it in range(max_iter):
        t_prev = deepcopy(g.property('Tlc'))

        # Hydraulic loop +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        if hydraulic_structure:
            psi_error_trace = []
            ipsi_step = psi_step
            for ipsi in range(max_iter):
                psi_prev = deepcopy(g.property('psi_head'))

                # Compute gas-exchange fluxes. Leaf T and Psi are from prev calc loop
                exchange.gas_exchange_rates(g, par_photo, par_photo_n, par_gs,
                                            meteo, irradiance_type2,
                                            leaf_lbl_prefix, rbt)

                # Compute sap flow and hydraulic properties
                hydraulic.hydraulic_prop(g,
                                         mass_conv=mass_conv,
                                         length_conv=length_conv,
                                         a=xylem_k_max['a'],
                                         b=xylem_k_max['b'],
                                         min_kmax=xylem_k_max['min_kmax'])

                # Update soil water status
                psi_collar = hydraulic.soil_water_potential(
                    psi_soil,
                    g.node(vid_collar).Flux * time_conv, soil_class,
                    rhyzo_total_volume, psi_min)

                if soil_water_deficit:
                    psi_collar = max(-1.3, psi_collar)
                else:
                    psi_collar = max(-0.7, psi_collar)

                    for vid in g.Ancestors(vid_collar):
                        g.node(vid).psi_head = psi_collar

                # Compute xylem water potential
                n_iter_psi = hydraulic.xylem_water_potential(
                    g,
                    psi_soil=psi_collar,
                    model=modelx,
                    psi_min=psi_min,
                    psi_error_crit=psi_error_threshold,
                    max_iter=max_iter,
                    length_conv=length_conv,
                    fifty_cent=psi_critx,
                    sig_slope=slopex,
                    dist_roots=dist_roots,
                    rad_roots=rad_roots,
                    negligible_shoot_resistance=negligible_shoot_resistance,
                    start_vid=vid_collar,
                    stop_vid=None,
                    psi_step=psi_step)

                psi_new = g.property('psi_head')

                # Evaluate xylem conversion criterion
                psi_error_dict = {}
                for vtx_id in g.property('psi_head').keys():
                    psi_error_dict[vtx_id] = abs(psi_prev[vtx_id] -
                                                 psi_new[vtx_id])

                psi_error = max(psi_error_dict.values())
                psi_error_trace.append(psi_error)

                print 'psi_error = ', round(
                    psi_error, 3
                ), ':: Nb_iter = %d' % n_iter_psi, 'ipsi_step = %f' % ipsi_step

                # Manage temperature step to ensure convergence
                if psi_error < psi_error_threshold:
                    break
                else:
                    try:
                        if psi_error_trace[-1] >= psi_error_trace[
                                -2] - psi_error_threshold:
                            ipsi_step = max(0.05, ipsi_step / 2.)
                    except IndexError:
                        pass

                    psi_new_dict = {}
                    for vtx_id in psi_new.keys():
                        psix = psi_prev[vtx_id] + ipsi_step * (
                            psi_new[vtx_id] - psi_prev[vtx_id])
                        psi_new_dict[vtx_id] = psix

                    g.properties()['psi_head'] = psi_new_dict

        else:
            # Compute gas-exchange fluxes. Leaf T and Psi are from prev calc loop
            exchange.gas_exchange_rates(g, par_photo, par_photo_n, par_gs,
                                        meteo, irradiance_type2,
                                        leaf_lbl_prefix, rbt)

            # Compute sap flow and hydraulic properties
            hydraulic.hydraulic_prop(g,
                                     mass_conv=mass_conv,
                                     length_conv=length_conv,
                                     a=xylem_k_max['a'],
                                     b=xylem_k_max['b'],
                                     min_kmax=xylem_k_max['min_kmax'])

        # End Hydraulic loop +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        # Compute leaf temperature
        if energy_budget:
            leaves_length = energy.get_leaves_length(
                g,
                leaf_lbl_prefix=leaf_lbl_prefix,
                unit_scene_length=unit_scene_length)
            leaf_wind_speed = energy.leaf_wind_as_air_wind(
                g, meteo, leaf_lbl_prefix)
            gbH = energy.heat_boundary_layer_conductance(
                leaves_length, leaf_wind_speed)
            t_init = g.property('Tlc')
            ev = g.property('E')
            ei = g.property('Ei')
            g.properties()['Tlc'], t_iter = energy.leaf_temperature(
                g,
                meteo,
                t_soil,
                t_sky_eff,
                t_init=t_init,
                form_factors=form_factors,
                gbh=gbH,
                ev=ev,
                ei=ei,
                solo=solo,
                ff_type=simplified_form_factors,
                leaf_lbl_prefix=leaf_lbl_prefix,
                max_iter=max_iter,
                t_error_crit=temp_error_threshold,
                t_step=temp_step)

            # t_iter_list.append(t_iter)
            t_new = deepcopy(g.property('Tlc'))

            # Evaluation of leaf temperature conversion creterion
            error_dict = {
                vtx: abs(t_prev[vtx] - t_new[vtx])
                for vtx in g.property('Tlc').keys()
            }

            t_error = round(max(error_dict.values()), 3)
            print 't_error = ', t_error, 'counter =', it, 't_iter = ', t_iter, 'it_step = ', it_step
            t_error_trace.append(t_error)

            # Manage temperature step to ensure convergence
            if t_error < temp_error_threshold:
                break
            else:
                assert (it <= max_iter
                        ), 'The energy budget solution did not converge.'

                try:
                    if t_error_trace[
                            -1] >= t_error_trace[-2] - temp_error_threshold:
                        it_step = max(0.001, it_step / 2.)
                except IndexError:
                    pass

                t_new_dict = {}
                for vtx_id in t_new.keys():
                    tx = t_prev[vtx_id] + it_step * (t_new[vtx_id] -
                                                     t_prev[vtx_id])
                    t_new_dict[vtx_id] = tx

                g.properties()['Tlc'] = t_new_dict
示例#6
0
def run(g, wd, scene=None, write_result=True, **kwargs):
    """
    Calculates leaf gas and energy exchange in addition to the hydraulic structure of an individual plant.

    :Parameters:
    - **g**: a multiscale tree graph object
    - **wd**: string, working directory
    - **scene**: PlantGl scene
    - **kwargs** can include:
        - **psi_soil**: [MPa] predawn soil water potential
        - **gdd_since_budbreak**: [°Cd] growing degree-day since bubreak
        - **sun2scene**: PlantGl scene, when prodivided, a sun object (sphere) is added to it
        - **soil_size**: [cm] length of squared mesh size
    """
    print('++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++')
    print('+ Project: ', wd)
    print('++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++')
    time_on = datetime.now()

    # Read user parameters
    params_path = wd + 'params.json'
    params = Params(params_path)

    output_index = params.simulation.output_index

    # ==============================================================================
    # Initialisation
    # ==============================================================================
    #   Climate data
    meteo_path = wd + params.simulation.meteo
    meteo_tab = read_csv(meteo_path, sep=';', decimal='.', header=0)
    meteo_tab.time = DatetimeIndex(meteo_tab.time)
    meteo_tab = meteo_tab.set_index(meteo_tab.time)

    #   Adding missing data
    if 'Ca' not in meteo_tab.columns:
        meteo_tab['Ca'] = [400.] * len(meteo_tab)  # ppm [CO2]
    if 'Pa' not in meteo_tab.columns:
        meteo_tab['Pa'] = [101.3] * len(meteo_tab)  # atmospheric pressure

    #   Determination of the simulation period
    sdate = datetime.strptime(params.simulation.sdate, "%Y-%m-%d %H:%M:%S")
    edate = datetime.strptime(params.simulation.edate, "%Y-%m-%d %H:%M:%S")
    datet = date_range(sdate, edate, freq='H')
    meteo = meteo_tab.loc[datet, :]
    time_conv = {'D': 86.4e3, 'H': 3600., 'T': 60., 'S': 1.}[datet.freqstr]

    # Reading available pre-dawn soil water potential data
    if 'psi_soil' in kwargs:
        psi_pd = DataFrame([kwargs['psi_soil']] * len(meteo.time),
                           index=meteo.time, columns=['psi'])
    else:
        assert (isfile(wd + 'psi_soil.input')), "The 'psi_soil.input' file is missing."
        psi_pd = read_csv(wd + 'psi_soil.input', sep=';', decimal='.').set_index('time')
        psi_pd.index = [datetime.strptime(s, "%Y-%m-%d") for s in psi_pd.index]

    # Unit length conversion (from scene unit to the standard [m]) unit)
    unit_scene_length = params.simulation.unit_scene_length
    length_conv = {'mm': 1.e-3, 'cm': 1.e-2, 'm': 1.}[unit_scene_length]

    # Determination of cumulative degree-days parameter
    t_base = params.phenology.t_base
    budbreak_date = datetime.strptime(params.phenology.emdate, "%Y-%m-%d %H:%M:%S")

    if 'gdd_since_budbreak' in kwargs:
        gdd_since_budbreak = kwargs['gdd_since_budbreak']
    elif min(meteo_tab.index) <= budbreak_date:
        tdays = date_range(budbreak_date, sdate, freq='D')
        tmeteo = meteo_tab.loc[tdays, :].Tac.to_frame()
        tmeteo = tmeteo.set_index(DatetimeIndex(tmeteo.index).normalize())
        df_min = tmeteo.groupby(tmeteo.index).aggregate(np.min).Tac
        df_max = tmeteo.groupby(tmeteo.index).aggregate(np.max).Tac
        # df_tt = merge(df_max, df_min, how='inner', left_index=True, right_index=True)
        # df_tt.columns = ('max', 'min')
        # df_tt['gdd'] = df_tt.apply(lambda x: 0.5 * (x['max'] + x['min']) - t_base)
        # gdd_since_budbreak = df_tt['gdd'].cumsum()[-1]
        df_tt = 0.5 * (df_min + df_max) - t_base
        gdd_since_budbreak = df_tt.cumsum()[-1]
    else:
        raise ValueError('Cumulative degree-days temperature is not provided.')

    print('GDD since budbreak = %d °Cd' % gdd_since_budbreak)

    # Determination of perennial structure arms (for grapevine)
    # arm_vid = {g.node(vid).label: g.node(vid).components()[0]._vid for vid in g.VtxList(Scale=2) if
    #            g.node(vid).label.startswith('arm')}

    # Soil reservoir dimensions (inter row, intra row, depth) [m]
    soil_dimensions = params.soil.soil_dimensions
    soil_total_volume = soil_dimensions[0] * soil_dimensions[1] * soil_dimensions[2]
    rhyzo_coeff = params.soil.rhyzo_coeff
    rhyzo_total_volume = rhyzo_coeff * np.pi * min(soil_dimensions[:2]) ** 2 / 4. * soil_dimensions[2]

    # Counter clockwise angle between the default X-axis direction (South) and
    # the real direction of X-axis.
    scene_rotation = params.irradiance.scene_rotation

    # Sky and cloud temperature [degreeC]
    t_sky = params.energy.t_sky
    t_cloud = params.energy.t_cloud

    # Topological location
    latitude = params.simulation.latitude
    longitude = params.simulation.longitude
    elevation = params.simulation.elevation
    geo_location = (latitude, longitude, elevation)

    # Pattern
    ymax, xmax = map(lambda dim: dim / length_conv, soil_dimensions[:2])
    pattern = ((-xmax / 2.0, -ymax / 2.0), (xmax / 2.0, ymax / 2.0))

    # Label prefix of the collar internode
    vtx_label = params.mtg_api.collar_label

    # Label prefix of the leaves
    leaf_lbl_prefix = params.mtg_api.leaf_lbl_prefix

    # Label prefices of stem elements
    stem_lbl_prefix = params.mtg_api.stem_lbl_prefix

    E_type = params.irradiance.E_type
    tzone = params.simulation.tzone
    turtle_sectors = params.irradiance.turtle_sectors
    icosphere_level = params.irradiance.icosphere_level
    turtle_format = params.irradiance.turtle_format

    energy_budget = params.simulation.energy_budget
    print('Energy_budget: %s' % energy_budget)

    # Optical properties
    opt_prop = params.irradiance.opt_prop

    print('Hydraulic structure: %s' % params.simulation.hydraulic_structure)

    psi_min = params.hydraulic.psi_min

    # Parameters of leaf Nitrogen content-related models
    Na_dict = params.exchange.Na_dict

    # Computation of the form factor matrix
    form_factors = None
    if energy_budget:
        print('Computing form factors...')
        form_factors = energy.form_factors_simplified(
            g, pattern=pattern, infinite=True, leaf_lbl_prefix=leaf_lbl_prefix, turtle_sectors=turtle_sectors,
            icosphere_level=icosphere_level, unit_scene_length=unit_scene_length)

    # Soil class
    soil_class = params.soil.soil_class
    print('Soil class: %s' % soil_class)

    # Rhyzosphere concentric radii determination
    rhyzo_radii = params.soil.rhyzo_radii
    rhyzo_number = len(rhyzo_radii)

    # Add rhyzosphere elements to mtg
    rhyzo_solution = params.soil.rhyzo_solution
    print('rhyzo_solution: %s' % rhyzo_solution)

    if rhyzo_solution:
        if not any(item.startswith('rhyzo') for item in g.property('label').values()):
            vid_collar = architecture.mtg_base(g, vtx_label=vtx_label)
            vid_base = architecture.add_soil_components(g, rhyzo_number, rhyzo_radii,
                                                        soil_dimensions, soil_class, vtx_label)
        else:
            vid_collar = g.node(g.root).vid_collar
            vid_base = g.node(g.root).vid_base

            radius_prev = 0.

            for ivid, vid in enumerate(g.Ancestors(vid_collar)[1:]):
                radius = rhyzo_radii[ivid]
                g.node(vid).Length = radius - radius_prev
                g.node(vid).depth = soil_dimensions[2] / length_conv  # [m]
                g.node(vid).TopDiameter = radius * 2.
                g.node(vid).BotDiameter = radius * 2.
                g.node(vid).soil_class = soil_class
                radius_prev = radius

    else:
        # Identifying and attaching the base node of a single MTG
        vid_collar = architecture.mtg_base(g, vtx_label=vtx_label)
        vid_base = vid_collar

    g.node(g.root).vid_base = vid_base
    g.node(g.root).vid_collar = vid_collar

    # Initializing sapflow to 0
    for vtx_id in traversal.pre_order2(g, vid_base):
        g.node(vtx_id).Flux = 0.

    # Addition of a soil element
    if 'Soil' not in g.properties()['label'].values():
        if 'soil_size' in kwargs:
            if kwargs['soil_size'] > 0.:
                architecture.add_soil(g, kwargs['soil_size'])
        else:
            architecture.add_soil(g, 500.)

    # Suppression of undesired geometry for light and energy calculations
    geom_prop = g.properties()['geometry']
    vidkeys = []
    for vid in g.properties()['geometry']:
        n = g.node(vid)
        if not n.label.startswith(('L', 'other', 'soil')):
            vidkeys.append(vid)
    [geom_prop.pop(x) for x in vidkeys]
    g.properties()['geometry'] = geom_prop

    # Attaching optical properties to MTG elements
    g = irradiance.optical_prop(g, leaf_lbl_prefix=leaf_lbl_prefix,
                                stem_lbl_prefix=stem_lbl_prefix, wave_band='SW',
                                opt_prop=opt_prop)

    # Estimation of Nitroen surface-based content according to Prieto et al. (2012)
    # Estimation of intercepted irradiance over past 10 days:
    if not 'Na' in g.property_names():
        print('Computing Nitrogen profile...')
        assert (sdate - min(
            meteo_tab.index)).days >= 10, 'Meteorological data do not cover 10 days prior to simulation date.'

        ppfd10_date = sdate + timedelta(days=-10)
        ppfd10t = date_range(ppfd10_date, sdate, freq='H')
        ppfd10_meteo = meteo_tab.loc[ppfd10t, :]
        caribu_source, RdRsH_ratio = irradiance.irradiance_distribution(ppfd10_meteo, geo_location, E_type,
                                                                        tzone, turtle_sectors, turtle_format,
                                                                        None, scene_rotation, None)

        # Compute irradiance interception and absorbtion
        g, caribu_scene = irradiance.hsCaribu(mtg=g,
                                              unit_scene_length=unit_scene_length,
                                              source=caribu_source, direct=False,
                                              infinite=True, nz=50, ds=0.5,
                                              pattern=pattern)

        g.properties()['Ei10'] = {vid: g.node(vid).Ei * time_conv / 10. / 1.e6 for vid in g.property('Ei').keys()}

        # Estimation of leaf surface-based nitrogen content:
        for vid in g.VtxList(Scale=3):
            if g.node(vid).label.startswith(leaf_lbl_prefix):
                g.node(vid).Na = exchange.leaf_Na(gdd_since_budbreak, g.node(vid).Ei10,
                                                  Na_dict['aN'],
                                                  Na_dict['bN'],
                                                  Na_dict['aM'],
                                                  Na_dict['bM'])

    # Define path to folder
    output_path = wd + 'output' + output_index + '/'

    # Save geometry in an external file
    # HSArc.mtg_save_geometry(scene, output_path)

    # ==============================================================================
    # Simulations
    # ==============================================================================

    sapflow = []
    # sapEast = []
    # sapWest = []
    an_ls = []
    rg_ls = []
    psi_stem = {}
    Tlc_dict = {}
    Ei_dict = {}
    an_dict = {}
    gs_dict = {}

    # The time loop +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    for date in meteo.time:
        print("=" * 72)
        print('Date', date, '\n')

        # Select of meteo data
        imeteo = meteo[meteo.time == date]

        # Add a date index to g
        g.date = datetime.strftime(date, "%Y%m%d%H%M%S")

        # Read soil water potntial at midnight
        if 'psi_soil' in kwargs:
            psi_soil = kwargs['psi_soil']
        else:
            if date.hour == 0:
                try:
                    psi_soil_init = psi_pd.loc[date, :][0]
                    psi_soil = psi_soil_init
                except KeyError:
                    pass
            # Estimate soil water potntial evolution due to transpiration
            else:
                psi_soil = hydraulic.soil_water_potential(psi_soil,
                                                          g.node(vid_collar).Flux * time_conv,
                                                          soil_class, soil_total_volume, psi_min)

        if 'sun2scene' not in kwargs or not kwargs['sun2scene']:
            sun2scene = None
        elif kwargs['sun2scene']:
            sun2scene = display.visu(g, def_elmnt_color_dict=True, scene=Scene())

        # Compute irradiance distribution over the scene
        caribu_source, RdRsH_ratio = irradiance.irradiance_distribution(imeteo, geo_location, E_type, tzone,
                                                                        turtle_sectors, turtle_format, sun2scene,
                                                                        scene_rotation, None)

        # Compute irradiance interception and absorbtion
        g, caribu_scene = irradiance.hsCaribu(mtg=g,
                                              unit_scene_length=unit_scene_length,
                                              source=caribu_source, direct=False,
                                              infinite=True, nz=50, ds=0.5,
                                              pattern=pattern)

        # g.properties()['Ei'] = {vid: 1.2 * g.node(vid).Ei for vid in g.property('Ei').keys()}

        # Trace intercepted irradiance on each time step
        rg_ls.append(sum([g.node(vid).Ei / (0.48 * 4.6) * surface(g.node(vid).geometry) * (length_conv ** 2) \
                          for vid in g.property('geometry') if g.node(vid).label.startswith('L')]))

        # Hack forcing of soil temperture (model of soil temperature under development)
        t_soil = energy.forced_soil_temperature(imeteo)

        # Climatic data for energy balance module
        # TODO: Change the t_sky_eff formula (cf. Gliah et al., 2011, Heat and Mass Transfer, DOI: 10.1007/s00231-011-0780-1)
        t_sky_eff = RdRsH_ratio * t_cloud + (1 - RdRsH_ratio) * t_sky

        solver.solve_interactions(g, imeteo, psi_soil, t_soil, t_sky_eff,
                                  vid_collar, vid_base, length_conv, time_conv,
                                  rhyzo_total_volume, params, form_factors)

        # Write mtg to an external file
        if scene is not None:
            architecture.mtg_save(g, scene, output_path)

        # Plot stuff..
        sapflow.append(g.node(vid_collar).Flux)
        # sapEast.append(g.node(arm_vid['arm1']).Flux)
        # sapWest.append(g.node(arm_vid['arm2']).Flux)

        an_ls.append(g.node(vid_collar).FluxC)

        psi_stem[date] = deepcopy(g.property('psi_head'))
        Tlc_dict[date] = deepcopy(g.property('Tlc'))
        Ei_dict[date] = deepcopy(g.property('Eabs'))
        an_dict[date] = deepcopy(g.property('An'))
        gs_dict[date] = deepcopy(g.property('gs'))

        print('---------------------------')
        print('psi_soil', round(psi_soil, 4))
        print('psi_collar', round(g.node(3).psi_head, 4))
        print('psi_leaf', round(np.median([g.node(vid).psi_head for vid in g.property('gs').keys()]), 4))
        print('')
        # print('Rdiff/Rglob ', RdRsH_ratio)
        # print('t_sky_eff ', t_sky_eff)
        print('gs', np.median(list(g.property('gs').values())))
        print('flux H2O', round(g.node(vid_collar).Flux * 1000. * time_conv, 4))
        print('flux C2O', round(g.node(vid_collar).FluxC, 4))
        print('Tleaf ', round(np.median([g.node(vid).Tlc for vid in g.property('gs').keys()]), 2),
              'Tair ', round(imeteo.Tac[0], 4))
        print('')
        print("=" * 72)

    # End time loop +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    # Write output
    # Plant total transpiration
    sapflow = [flow * time_conv * 1000. for flow in sapflow]

    # sapEast, sapWest = [np.array(flow) * time_conv * 1000. for i, flow in enumerate((sapEast, sapWest))]

    # Median leaf temperature
    t_ls = [np.median(list(Tlc_dict[date].values())) for date in meteo.time]

    # Intercepted global radiation
    rg_ls = np.array(rg_ls) / (soil_dimensions[0] * soil_dimensions[1])

    results_dict = {
        'Rg': rg_ls,
        'An': an_ls,
        'E': sapflow,
        # 'sapEast': sapEast,
        # 'sapWest': sapWest,
        'Tleaf': t_ls
    }

    # Results DataFrame
    results_df = DataFrame(results_dict, index=meteo.time)

    # Write
    if write_result:
        results_df.to_csv(output_path + 'time_series.output',
                          sep=';', decimal='.')

    time_off = datetime.now()

    print("")
    print("beg time", time_on)
    print("end time", time_off)
    print("--- Total runtime: %d minute(s) ---" % int((time_off - time_on).seconds / 60.))

    return results_df
示例#7
0
def transient_xylem_water_potential(g, model='tuzet', length_conv=1.e-2, psi_soil=-0.6, psi_min=-3., fifty_cent=-0.51,
                                    sig_slope=1., dist_roots=0.013, rad_roots=.0001, negligible_shoot_resistance=False,
                                    start_vid=None, stop_vid=None):
    """Computes a transient hydraulic structure of a plant shoot based on constant values of the hydraulic segments'
    conductivities. The hydraulic segments are assumed isotropic having only axial conductivities.

    Args:
        g (openalea.mtg.MTG): a multiscale tree graph object
        model (str): one of 'misson', 'tuzet' or 'linear', see :func:`cavitation_factor` for details
        length_conv (float): conversion coefficient from the length unit of the mtg to that of [1 m]
        psi_soil (float): [MPa] soil water potential
        psi_min (float): [MPa] minimum allowable water potential in the hydraulic segments
        fifty_cent (float): [MPa] water potential at which the conductivity of the hydraulic segment drops to 50%
            of its maximum value, see :func:`cavitation_factor` for details
        sig_slope (float): a shape parameter controlling the slope of the S-curve (used only for 'misson' [-] or
            'tuzet' [MPa-1] models), see :func:`cavitation_factor` for details
        dist_roots (float): [m] mean distance between the neighbouring roots
        rad_roots (float): [m] mean root radius
        negligible_shoot_resistance (bool): to consider (True) or not to consider (False) shoot resistance to xylem
            flow
        start_vid (int): vertex id from which the iteration starts (if `None` it is then taken to the basal element)
        stop_vid (int): vertex id at which the iteration breaks (if `None` iteration will include all elements up until
            the leaves)

    Notes:
        The flux through the stem must be given in [kg s-1]
        The hydraulic conductivity of the stem must be given in [kg m s-1 MPa-1]
    """

    vid_base = g.node(g.root).vid_base

    if start_vid is None:
        start_vid = vid_base

    for vtx_id in traversal.pre_order2(g, start_vid):
        if vtx_id == stop_vid:
            break
        else:
            n = g.node(vtx_id)
            p = n.parent()

            if n.label.startswith('LI'):
                n.psi_head = p.psi_head
            else:
                flux = n.properties()['Flux']
                length = n.properties()['Length'] * length_conv
                z_head = n.properties()['TopPosition'][2] * length_conv
                z_base = n.properties()['BotPosition'][2] * length_conv

                psi_base = psi_soil if vtx_id == vid_base else p.psi_head

                try:
                    psi_head = n.psi_head
                    assert n.psi_head, "Water potential has a `None` value"
                except AttributeError:
                    psi_head = psi_base

                psi = 0.5 * (psi_head + psi_base)

                if n.label.startswith('rhyzo'):
                    cyl_diameter = n.TopDiameter * length_conv
                    depth = n.depth * length_conv
                    flux *= 8640. / (pi * cyl_diameter * depth)  # [cm d-1]

                    if n.label.startswith('rhyzo0'):
                        k_soil = k_soil_soil(psi, n.soil_class)  # [cm d-1]
                        g_act = k_soil_root(k_soil, dist_roots, rad_roots)  # [cm d-1 m-1]
                        psi_head = max(psi_min, psi_base - (flux / g_act) * rho * g_p * 1.e-6)
                        k_act = None
                    else:
                        k_act = k_soil_soil(psi, n.soil_class)  # [cm d-1]
                        psi_head = max(psi_min, psi_base - (length * flux / k_act) * rho * g_p * 1.e-6)

                else:
                    k_max = n.properties()['Kmax']

                    if not negligible_shoot_resistance:
                        k_act = k_max * cavitation_factor(psi, model, fifty_cent, sig_slope)
                        psi_head = max(psi_min,
                                       psi_base - length * flux / k_act - (rho * g_p * (z_head - z_base)) * 1.e-6)
                    else:
                        k_act = None
                        psi_head = max(psi_min, psi_base - (rho * g_p * (z_head - z_base)) * 1.e-6)

                n.psi_head = psi_head
                n.KL = k_act

    return
示例#8
0
def transient_xylem_water_potential(g, model='tuzet', vtx_label='inT',
                                    LengthConv=1.e-2,psi_soil=-0.6,psi_min=-3.,
                                    fifty_cent=-0.51, sig_slope=1.,
                                    dist_roots=0.013, rad_roots=.0001,
                                    negligible_shoot_resistance = False,
                                    start_vid = None, stop_vid = None):
    """
    Returns a transient hydraulic structure of a plant shoot based on constant values of stem conductivities.
    Stems are assumed isotropic, linear, element.
    
    :Attention:
    1. the flux through the stem must have been given in [kg s-1]
    2. the hydraulic condutctivity of the stem must have been given in [kg m s-1 MPa-1]

    :Parameters:
    - **g**: an MTG object
    - **model**: either 'misson' (logistic function with polynomial formula) or 'tuzet' (logistic function with exponential formula)
    - **vtx_label**: string, the label prefix of the basal vertex at highest scale
    - **LengthConv**: conversion coefficient from the length unit used for building the mtg to 1 m
    - **psi_soil**: soil water potential [MPa]
    - **psi_min**: minimum simulated xylem water potential [MPa]
    - **fifty_cent**: water potential at which the conductivity of the stem is reduced by 50% reltive to its maximum value
    - **sig_slope**: a shape parameter controling the slope of the S-curve
    - **dist_roots**: float, the average distance between roots [m]
    - **rad_roots**: float, the average radius of individual roots [m]
    - **negligible_shoot_resistance**: logical, defalut `False`, whether shoot resistance should be considered (False) or not (True)
    - **start_vid**: `None` or integer, vertex id from which the iteration starts (if `None` it is then taken to the basal element)
    - **stop_vid**: `None` or integer, vertex id at which the iteration breaks (def `None` iteration will include all elements up until the leaves)
    """

    vid_base = g.node(g.root).vid_base
#    vid_collar = g.node(g.root).vid_collar

    if start_vid is None:
        start_vid = vid_base

    for vtx_id in traversal.pre_order2(g, start_vid):
        if vtx_id == stop_vid:
            break
        else:
            n = g.node(vtx_id)
            p = n.parent()
    
            if n.label.startswith('LI'):
                n.psi_head = p.psi_head
            else:
                F = n.properties()['Flux']
                L = n.properties()['Length']*LengthConv
                Z_head = n.properties()['TopPosition'][2]*LengthConv
                Z_base = n.properties()['BotPosition'][2]*LengthConv

    
                psi_base = psi_soil if vtx_id == vid_base else p.psi_head
    
                try:
                    psi_head = n.psi_head
                    assert (n.psi_head != None), "Water potential has a `None` value"
                except:
                    psi_head = psi_base
    
                psi = 0.5 * (psi_head + psi_base)

                if n.label.startswith('rhyzo'):
#                    # Hack
#                    k_sat = g.node(vid_collar).Kmax
#                    KL = k_sat * k_vulnerability(psi, 'misson', -.2, 5)
#                    psi_head = max(psi_min, psi_base - L*F/KL)
                    cyl_diameter = n.TopDiameter * LengthConv
                    depth = n.depth * LengthConv
                    F *= 8640./(pi *cyl_diameter* depth) # [cm d-1]

                    if n.label.startswith('rhyzo0'):
                        k_soil = k_soil_soil(psi, n.soil_class) # [cm d-1]
                        gL = k_soil_root(k_soil, dist_roots, rad_roots) #[cm d-1 m-1]
                        psi_head = max(psi_min, psi_base - (F/gL) * rho*g_p*1.e-6)
                    else:
                        KL = k_soil_soil(psi, n.soil_class) # [cm d-1]
                        psi_head = max(psi_min, psi_base - (L*F/KL) * rho*g_p*1.e-6)

                else:
                    Kmax = n.properties()['Kmax']

                    if not negligible_shoot_resistance:
                        KL = Kmax *k_vulnerability(psi, model, fifty_cent,sig_slope)
                        psi_head = max(psi_min, psi_base - L*F/KL - (rho*g_p*(Z_head-Z_base))*1.e-6)
                    else:
                        KL = None
                        psi_head = max(psi_min, psi_base - (rho*g_p*(Z_head-Z_base))*1.e-6)
#                def _psi_headX(_psi_head):
#                    psi = 0.5*(_psi_head+psi_base)
#                    _KL = Kmax*k_vulnerability(psi, model, fifty_cent,sig_slope)
#                    eq_error = _psi_head - (psi_base - L*F/_KL - (rho*g_p*(Z_head-Z_base))*1.e-6)
#                    return eq_error
#    
#                psi_head = optimize.newton_krylov(_psi_headX,psi_base).tolist()
    
    
                n.psi_head = psi_head
                n.KL = KL

    return