def add_area_topvine(g, conversion_factor=1000., label='lf'): """ Compute the area of the leaves in topvine in cm2 and add it as a MTG property. Parameters ---------- g: MTG MTG representing the canopy conversion_factor: float Conversion factor to pass from the default size unit of the MTG to cm2 label: str Label of the part of the MTG concerned by the calculation """ from openalea.plantgl import all as pgl geometries = g.property('geometry') area = g.property('area') g_area = g.property('green_area') if len(area)==0: g.add_property('area') area = g.property('area') if len(g_area)==0: g.add_property('green_area') g_area = g.property('green_area') new_vids = [n for n in g if g.label(n).startswith(label) if n not in area] # Add area information to each leaf area.update({vid:pgl.surface(geometries[vid][0])*conversion_factor for vid in new_vids}) # At start green area equals total area g_area.update({vid:area[vid] for vid in new_vids})
def add_area_topvine(g, conversion_factor=1000., label='lf'): """ Compute the area of the leaves in topvine in cm2 and add it as a MTG property. Parameters ---------- g: MTG MTG representing the canopy conversion_factor: float Conversion factor to pass from the default size unit of the MTG to cm2 label: str Label of the part of the MTG concerned by the calculation """ from openalea.plantgl import all as pgl geometries = g.property('geometry') area = g.property('area') g_area = g.property('green_area') if len(area) == 0: g.add_property('area') area = g.property('area') if len(g_area) == 0: g.add_property('green_area') g_area = g.property('green_area') new_vids = [n for n in g if g.label(n).startswith(label) if n not in area] # Add area information to each leaf area.update({ vid: pgl.surface(geometries[vid][0]) * conversion_factor for vid in new_vids }) # At start green area equals total area g_area.update({vid: area[vid] for vid in new_vids})
def scene_irradiance(scene, directions, north = 0, horizontal = False, scene_unit = 'm', screenwidth = 600): """ Compute the irradiance received by all the shapes of a given scene. :Parameters: - `scene` : scene for which the irradiance has to be computed - `directions` : list of tuple composed of the an azimuth, an elevation and an irradiance (in J.s-1.m-2) - `north` : the angle between the north direction of the azimuths (in degrees) - `horizontal` : specify if the irradiance use an horizontal convention (True) or a normal direction (False) - `scene_unit` : specify the units in which the scene is built. Convert then all the result in m. :returns: the area of the shapes of the scene in m2 and the irradiance in J.s-1.m-2 :returntype: pandas.DataFrame """ units = {'mm': 0.001, 'cm': 0.01, 'dm': 0.1, 'm': 1, 'dam': 10, 'hm': 100, 'km': 1000} conv_unit = units[scene_unit] conv_unit2 = conv_unit**2 res = directionalInterception(scene = scene, directions = directions, north = north, horizontal = horizontal, screenwidth=screenwidth) res = { sid : conv_unit2 * value for sid, value in res.iteritems() } surfaces = dict([(sid, conv_unit2*sum([pgl.surface(sh.geometry) for sh in shapes])) for sid, shapes in scene.todict().iteritems()]) irradiance = { sid : value / surfaces[sid] for sid, value in res.iteritems() } import pandas return pandas.DataFrame( {'area' : surfaces, 'irradiance' : irradiance} )
def totalSurface(scene): """Returns the total surface of the shape in a scene :param Scene scene: a PGL scene with shapes :return: the total surface area .. todo:: what are the units ? """ sum = 0 for i in scene: sum += pgl.surface(i.geometry) return sum
def surfPerTriangle(sc): """return an array of center, surface cople for every triangle in the scene""" res = [] t = pgl.Tesselator() for sh in sc: sh.geometry.apply(t) d = t.triangulation for i in range(d.indexListSize()): center = (d.pointAt(i,0)+d.pointAt(i,1)+d.pointAt(i,2))/3 surf = pgl.surface (d.pointAt(i,0),d.pointAt(i,1),d.pointAt(i,2)) res += [(center,surf)] return res
def surfPerLeaf(sc): """return an array of center, surface cople for every leaf in the scene""" res = [] t = pgl.Tesselator() for sh in sc: sh.geometry.apply(t) d = t.triangulation res2=[] lcenter = pgl.Vector3( 0,0,0 ) for i in range(d.indexListSize()): lcenter += (d.pointAt(i,0)+d.pointAt(i,1)+d.pointAt(i,2))/3 surf = pgl.surface (d.pointAt(i,0),d.pointAt(i,1),d.pointAt(i,2)) #where in openalea.plantgl ?? res2 += [surf] lcenter /= len( res2 ) lsurf=sum( res2 ) res+=[ ( lcenter, lsurf ) ] return res
def surfaceByShape(sc):#aka surfaces surfbysh = {} for i in sc: surfbysh[i.id] = pgl.surface(i.geometry) #where in openalea.plantgl ?? return surfbysh
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
""" Gather different strategies for modeling dispersal of fungus propagules.
def area(vid): # areas[vid] = pgl.surface(geometries[vid][0])*1000 areas[vid] = pgl.surface(geometries[vid][0])