Skip to content

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.

Next