14 - Photosynthesis with SIF Simulation¶
This chapter combines dynamic leaf temperature, Farquhar photosynthesis, and
sun-induced chlorophyll fluorescence (SIF). A scene may contain mesh leaves,
TurbidBoundary vegetation, or both. The returned products provide results for
the corresponding mesh primitives, statistical vegetation components, and
temperature-enabled terrain surfaces.
Required properties¶
A leaf component included in dynamic photosynthesis and SIF calculations needs:
| Attribute domain | Common attributes | Purpose |
|---|---|---|
| optical | Fluspect |
Leaf reflection, transmission, absorption and fluorescence spectra |
| thermal | ThermalProperty |
Initial temperature and long-wave emissivity |
| biophysical | BiophysicalProperty |
Leaf width, stomatal surface, heat and water vapor exchange |
| physiological | Farquhar |
An, gs and dynamic Phi_f |
plant.set_property(
"leaves",
less.Fluspect(
N=1.5, cab=45, car=10, cw=0.012, cm=0.006,
fqe=0.012,
),
)
plant.set_property(
"leaves",
less.ThermalProperty(
temperature="air",
emissivity=0.98,
),
)
plant.set_property(
"leaves",
less.BiophysicalProperty(
leaf_width=0.05,
stomata_side="bottom",
),
)
plant.set_property(
"leaves",
less.Farquhar(Vcmax25=60.0),
)
Fluspect.fqe controls static SIF. Dynamic SIF uses Phi_f calculated from
the photosynthetic state.
Choosing the FvCB formulation¶
less.Farquhar() now uses formulation="scope" and
absorption_component="chlorophyll" by default, so both arguments may be
omitted. The default follows the SCOPE C3 equations.
The historical LESS3 equations remain available explicitly:
For formulation="jmax" only, an omitted Jmax25 resolves to
2 * Vcmax25. The Scope formulation does not use Jmax and therefore rejects
Jmax25. Remove Jmax25 to use Scope, or select formulation="jmax" when a
Jmax capacity is intended. Selecting Jmax does not change the absorbed-light
component: chlorophyll absorption remains the default in both formulations.
Choosing the absorbed-light component¶
Farquhar.absorption_component selects which absorber drives photosynthesis.
It defaults to "chlorophyll". Prospect and Fluspect provide their
wavelength-dependent biochemical absorption partitions automatically, so the
usual leaf models need no additional partition data. Select
absorption_component="total" explicitly when total leaf absorption should
drive the Farquhar calculation:
A MeasuredSpectrum does not infer pigment absorption from a chlorophyll
content value alone. Supply either fractions measured on the same wavelength
grid or a named partition model:
measured = less.MeasuredSpectrum(
wavelengths=[400.0, 550.0, 700.0],
reflectance=[0.08, 0.12, 0.18],
transmittance=[0.04, 0.08, 0.12],
absorption_partition=less.ProspectPartition(cab=45.0),
)
For pigment-sensitive APAR comparisons, use approximately 1 nm spectral sampling across PAR. Coarser bands can bias the wavelength-dependent chlorophyll fraction. This recommendation concerns spectral absorption; it does not correct differences caused by the chosen physiology formulation.
Environment units and result metadata¶
Microclimate.pressure is in Pa, Microclimate.oxygen is in mmol mol-1, and
Microclimate.ca is CO2 in umol mol-1. LESS derives actual vapor pressure in
Pa from air temperature and humidity. During a Scope leaf-temperature solve,
that actual vapor pressure stays fixed while leaf relative humidity is
recomputed at the solved leaf temperature.
Photosynthesis products keep formulation, absorption_component, pressure,
oxygen, actual vapor pressure, CO2, and the scientific environment fingerprint
aligned with each output row. APAR remains the selected-component value and
APAR_total remains total leaf-absorbed PAR.
When reading legacy serialized Farquhar records that do not contain the two
selectors, LESS migrates them to formulation="jmax" plus
absorption_component="total", preserving their historical meaning. Newly
serialized records store both fields explicitly.
Recommended workflow¶
microclimate = less.Microclimate(
air_temperature=25.0, # degrees Celsius
humidity=60.0, # relative humidity, percent
wind_speed=2.0,
ca=400.0,
)
state = scene.solve(
microclimate=microclimate,
modules=["energy_balance", "photosynthesis", "sif"],
)
photo = state.photosynthesis
print("An:", photo.An)
print("gs:", photo.gs)
print("Phi_f:", photo.Phi_f)
print("leaf temperature:", photo.T_leaf)
thermal = scene.simulate(
less.ThermalImager(
less.Orthographic(image_size=512),
bands=[10600.0],
quality=128,
)
)
sif = scene.simulate(
less.SIFImager(
less.Orthographic(image_size=512),
bands=[687.0, 740.0, 760.0],
quality=256,
)
)
thermal.save("dynamic_temperature.tif")
sif.save("dynamic_sif.tif")
This call calculates energy balance, An, gs, and Phi_f together.
ThermalImager and SIFImager use the scene's current solved state by default,
so you do not need to pass intermediate arrays manually.
Sunlit and shaded leaves¶
Direct light is not uniform across the canopy. LESS records:
sunlit_fraction: fraction of leaf area directly illuminated by the sun;APAR: APAR for the selectedabsorption_component;APAR_total: total leaf-absorbed PAR before component partitioning;APAR_sunlit,APAR_shaded: conditional selected-component APAR;APAR_total_sunlit,APAR_total_shaded: conditional total APAR;absorption_component: the resolved component for each output element;T_sunlit,T_shaded: conditional sunlit and shaded leaf temperatures;An_sunlit,An_shaded: corresponding net photosynthetic rate.
Diffuse skylight and multiple scattering contribute to both groups. Only the
sunlit group receives the unobstructed direct-sun term. The final component or
primitive result is weighted by sunlit_fraction:
Both APAR pairs obey this weighting independently. For example,
Because photosynthesis is nonlinear, LESS solves the sunlit and shaded states separately before averaging their results. Averaging APAR before evaluating the Farquhar model would not give the same result.
Result space hierarchy¶
- A mesh-only scene returns
"primitive", with one result for each triangle; - Static photosynthesis in a scene containing
TurbidBoundaryreturns"component"top-level arrays for the Turbid components. In a static Mesh+Turbid scene,photo.scene_elementsadditionally preserves the aligned Mesh and Turbid row identities; - A joint dynamic thermo-physiology product returns
"mixed"when its public arrays span more than one scene-element kind; - Leaf samples used to integrate a statistical vegetation medium do not become permanent voxels or scene geometry;
- Divide a crown into multiple components when finer spatial summaries are required.
Static SIF without photosynthesis or temperature solving¶
To simulate fluorescence from a prescribed Fluspect.fqe, calculate static SIF
directly:
source = scene.simulate(
less.SIFProcess(
mode="static",
quality="high",
)
)
image = scene.simulate(
less.SIFImager(
less.Orthographic(image_size=512),
bands=[687.0, 740.0, 760.0],
quality=256,
sif_result=source,
)
)
Static SIF does not run the Farquhar model and does not require a dynamic
photosynthesis or temperature calculation. It still requires Fluspect, which
provides leaf reflectance, transmittance, absorption, fluorescence excitation,
and emission spectra.
Sampling quality¶
Quality presets are available for dynamic physiological processes:
state = scene.solve(
microclimate=microclimate,
modules=["energy_balance", "photosynthesis", "sif"],
photosynthesis_kwargs={"quality": "high"},
sif_kwargs={"quality": "high"},
seed=42,
)
Available values are "preview", "standard", and "high". For scientific
studies, repeat the calculation at higher sampling quality and confirm that
APAR, leaf temperature, An, gs, and Phi_f are stable. Set seed to make
repeated calculations reproducible.
Choosing a backend¶
The same public API is available with each supported backend:
scene = less.Scene(backend="optix")
scene = less.Scene(backend="vulkan")
scene = less.Scene(backend="embree")
OptiX uses an NVIDIA GPU, Vulkan uses a GPU with Ray Query support, and Embree
uses the CPU. Switching backends does not change how solve, ThermalImager,
or SIFImager is called.
Check closure and convergence¶
eb = state.energy_balance.result
print("energy converged:", eb.converged)
print("photo converged:", state.photosynthesis.converged)
print("iterations:", state.photosynthesis.iterations)
print("max residual:", abs(eb.residual[eb.active_mask]).max())
If illumination, microclimate, properties, or geometry change, run
scene.solve(...) again. An outdated state cannot be used for a new thermal or
SIF image.
Next step¶
Scenes containing mesh and Turbid vegetation¶
scene.solve(...) can include mesh leaves, a TurbidBoundary canopy, and
terrain in one scene. scene_elements identifies the scene element represented
by each row. The result includes leaf area, sunlit fraction, sunlit and shaded
leaf temperatures, APAR, net photosynthetic rate, and stomatal conductance.
After changing scene properties, illumination, or microclimate, run
scene.solve(...) again before creating a new image.
Related API¶
less.PhotosynthesisProcess,less.SIFProcessless.Farquhar,less.Fluspect,less.BiophysicalPropertyless.Microclimate,less.EnergyBalanceProcessless.SIFImager,less.ThermalImagerless.PhotosynthesisProcessProduct,less.SIFProcessProduct