31 - Radiation field¶
The radiation field describes how much shortwave radiation each leaf or ground element of the scene absorbs. It is the input to the energy balance, photosynthesis and fluorescence, and can also be used on its own to study light in a canopy, for example absorbed photosynthetically active radiation (APAR), the sunlit leaf fraction and vertical profiles.
LESS offers two ways to compute it:
RadiationFieldProcess |
ForwardRadiationFieldSensor |
|
|---|---|---|
| Method | Traces outwards from each scene element | Traces photons from the sun and sky into the scene |
| Result | Per triangle or statistical vegetation component | Per triangle or 3-D voxel |
| Use | Subsequent energy balance, photosynthesis, fluorescence | Vertical profiles, spatial distribution |
Absorption per element¶
import numpy as np
import less
scene = less.Scene()
scene.size = 6.0
scene.terrain = less.Terrain(property=less.Lambertian(reflectance=0.15))
scene.illumination = less.Illumination(
source=less.Sun(zenith=30, azimuth=150),
atmosphere=less.SimpleSpectralAtmosphere(turbidity=2.0))
plant = less.Object("plant")
plant.add_component("leaves", less.Mesh(less.asset_path("tutorial_tree.obj"), group="leaves"))
plant.set_property("leaves", less.Prospect(cab=45))
scene.add(plant, positions=[(3, 3, 0)])
radiation = scene.simulate(less.RadiationFieldProcess(n_samples=64))
print(radiation.absorbed_top.shape) # (elements, bands)
print(radiation.wavelengths)
| Result | Meaning |
|---|---|
absorbed_top, absorbed_bot |
Shortwave radiation absorbed by the front and back faces (per unit area, W m⁻²) |
absorbed_direct |
Direct sunlight absorbed when not obstructed |
sunlit_fraction |
Fraction of leaf area that sees the sun directly |
wavelengths, band_widths |
Band centres and widths (nm) |
leaf_area |
Area of each element |
In scenes made only of triangle meshes, each triangle is an element; in scenes with statistical vegetation, each placed statistical vegetation component is an element and the result is the leaf-area-weighted mean within the component.
Integrate over a wavelength range:
par_absorbed = radiation.integrate((400, 700)) # per element, W m⁻²
print(f"mean absorbed PAR: {par_absorbed.mean():.1f} W m⁻²")
print(radiation.summary())
Bands¶
By default 24 bands cover 415–2100 nm, 19 of them in the photosynthetically active range. spectral_profile sets other bands:
solar = less.SpectralProfile.solar_shortwave() # 400–2400 nm in 5 nm steps
par = less.SpectralProfile([less.SpectralBand(float(center), 10.0, "par")
for center in range(405, 700, 10)])
fine = scene.simulate(less.RadiationFieldProcess(spectral_profile=par, n_samples=64))
For pigment-related absorption, such as PAR absorbed by chlorophyll, narrower bands in the photosynthetically active range are preferable.
Sampling parameters¶
| Parameter | Meaning |
|---|---|
n_samples |
Samples per face of each triangle |
n_leaf_samples |
Leaf samples per statistical vegetation component |
max_depth |
Maximum number of reflections and transmissions traced |
Increase the number of samples and compare the results to make sure the absorption is stable.
Passing the radiation field to other processes¶
The energy balance, photosynthesis and fluorescence can all use a radiation field that has already been computed, which avoids computing it again:
plant.set_property("leaves", less.ThermalProperty(temperature="air", emissivity=0.98))
plant.set_property("leaves", less.BiophysicalProperty(leaf_width=0.05, stomata_side="bottom"))
scene.terrain.set_property(less.ThermalProperty(temperature="air", emissivity=0.95))
scene.terrain.set_property(less.BiophysicalProperty(two_sided=False, stomata_side="none",
ground_heat_fraction=0.3))
radiation = scene.simulate(less.RadiationFieldProcess(n_samples=64))
climate = less.Microclimate(air_temperature=25.0, humidity=60.0, wind_speed=2.0)
energy = scene.simulate(less.EnergyBalanceProcess(rad=radiation, quality="preview"),
microclimate=climate)
A radiation field belongs to the state of the scene it was computed for: after the illumination, properties or geometry change (including the thermal properties set just above), compute the radiation field again. Pass each radiation field to one process only.
Absorption in 3-D voxels¶
ForwardRadiationFieldSensor traces photons from the light sources and accumulates absorption on a horizontal grid in height layers, giving the vertical absorption profile of a canopy:
forest = less.Scene()
forest.size = 20.0
forest.terrain = less.Terrain(property=less.Lambertian(reflectance=0.15))
forest.illumination = less.Illumination(
source=less.Sun(zenith=30, azimuth=150), atmosphere=less.NoAtmosphere())
tree = less.Object("ash", mesh=less.asset_path("FREX.obj"))
tree.set_property("leaves", less.Prospect(cab=40))
tree.set_property("stem_branch", less.Lambertian(reflectance=0.1))
forest.add(tree, positions=less.place.random(forest, n=8, min_dist=5.0, seed=1))
field = forest.simulate(less.ForwardRadiationFieldSensor(
bands=[450, 550, 650], mode="grid", grid=(20, 20), layer_height=1.0,
photons_per_m2=4000, integration_range=(400, 700)))
print(field.absorption_grid.shape) # (layers, rows, columns, bands)
print(f"fraction of PAR absorbed by the canopy: {field.fpar:.3f}")
field.save("forest_radiation_field.npz")
mode="per_leaf" (default) gives the absorption of each triangle, and mode="grid" the voxel absorption as (layers, rows, columns, bands). layer_height is the thickness of each layer; layer_heights gives the layer boundaries directly.