irradiation¶
Daily solar irradiation and PAR interception by the canopy.
Combines solar geometry with measured radiation to obtain the daily
total irradiation AVRAD, then applies Beer–Lambert extinction to
give the fraction of PAR captured by the canopy.
Equations¶
Solar constant adjusted for Earth–Sun distance:
Daily integral of \(\sin\beta\) (solar elevation) over the daylight period:
Extraterrestrial radiation, with the daily total capped at 80 % of the extraterrestrial value:
PAR is taken as 50 % of global radiation and intercepted following
Beer–Lambert extinction with a DVS-dependent extinction coefficient
\(K\) for diffuse PAR (KDIFTB), scaled by cScaleFactorKDIF:
Irradiation (Module)
¶
Daily total irradiation and PAR interception by canopy.
Computes AVRAD (daily total irradiation) from solar geometry,
then calculates PAR interception via the Beer–Lambert extinction
law.
Source code in torchcrop/processes/irradiation.py
class Irradiation(nn.Module):
"""Daily total irradiation and PAR interception by canopy.
Computes ``AVRAD`` (daily total irradiation) from solar geometry,
then calculates PAR interception via the Beer–Lambert extinction
law.
"""
def forward(
self,
state: ModelState,
doy: torch.Tensor,
dayl: torch.Tensor,
sinld: torch.Tensor,
cosld: torch.Tensor,
dtr: torch.Tensor,
params: CropParameters,
) -> dict[str, torch.Tensor]:
"""Compute daily irradiation and canopy PAR interception.
Args:
state: Current model state (uses ``state.lai``,
``state.dvs``).
doy: Day of year [1-365], shape ``[B]``.
dayl: Daylength [hours], shape ``[B]``.
sinld: sin(declination) [dimensionless], shape ``[B]``.
cosld: cos(declination) [dimensionless], shape ``[B]``.
dtr: Daily total radiation [MJ m⁻² d⁻¹], shape ``[B]``.
Converted to J m⁻² d⁻¹ for the PENMAN calculation.
params: Crop parameters; uses ``params.kdiftb`` (DVS-indexed
diffuse-PAR extinction table) and
``params.scale_factor_kdif`` (sensitivity scale on its
y-values).
Returns:
Dict of ``[B]`` tensors:
* ``avrad`` [J m⁻² d⁻¹] — Daily total irradiation
(computed from solar geometry; converted from input
MJ m⁻² d⁻¹).
* ``atmtr`` [-] — Atmospheric transmission fraction.
* ``par`` [J m⁻² d⁻¹] — Photosynthetically active
radiation (``0.5 · avrad``).
* ``parint`` [J m⁻² d⁻¹] — PAR intercepted by canopy.
* ``frac_intercepted`` [-] — Beer–Lambert interception
fraction.
"""
# Convert DTR from MJ m⁻² d⁻¹ to J m⁻² d⁻¹ for PENMAN.
dtr_j = dtr * 1e6
# Daily total irradiation.
aob = torch.clamp(sinld / cosld, min=-1.0, max=1.0)
dsinb = 3600.0 * (
dayl * sinld
+ 24.0 * cosld * torch.sqrt(torch.clamp(1.0 - aob * aob, min=0.0)) / math.pi
)
# Solar constant [W m⁻²] as a function of day of year.
sc = 1370.0 * (1.0 + 0.033 * torch.cos(2.0 * math.pi * doy / 365.0))
# Extraterrestrial radiation [J m⁻² d⁻¹].
angot = torch.clamp(sc * dsinb, min=0.0001)
# Daily total irradiation: minimum of 80 % extraterrestrial
# and measured.
avrad = torch.min(0.80 * angot, dtr_j)
# Atmospheric transmission.
atmtr = avrad / angot
# PAR (50 % of global radiation).
par = 0.5 * avrad
# DVS-dependent diffuse-PAR extinction coefficient
# K = cScaleFactorKDIF · KDIFTB(DVS)
kdif = params.scale_factor_kdif * interpolate(params.kdiftb, state.dvs)
# Beer–Lambert interception by canopy.
frac = 1.0 - torch.exp(-kdif * state.lai)
parint = par * frac
return {
"avrad": avrad,
"atmtr": atmtr,
"par": par,
"parint": parint,
"frac_intercepted": frac,
}
forward(self, state, doy, dayl, sinld, cosld, dtr, params)
¶
Compute daily irradiation and canopy PAR interception.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state |
ModelState |
Current model state (uses |
required |
doy |
torch.Tensor |
Day of year [1-365], shape |
required |
dayl |
torch.Tensor |
Daylength [hours], shape |
required |
sinld |
torch.Tensor |
sin(declination) [dimensionless], shape |
required |
cosld |
torch.Tensor |
cos(declination) [dimensionless], shape |
required |
dtr |
torch.Tensor |
Daily total radiation [MJ m⁻² d⁻¹], shape |
required |
params |
CropParameters |
Crop parameters; uses |
required |
Returns:
| Type | Description |
|---|---|
Dict of ``[B]`` tensors |
|
Source code in torchcrop/processes/irradiation.py
def forward(
self,
state: ModelState,
doy: torch.Tensor,
dayl: torch.Tensor,
sinld: torch.Tensor,
cosld: torch.Tensor,
dtr: torch.Tensor,
params: CropParameters,
) -> dict[str, torch.Tensor]:
"""Compute daily irradiation and canopy PAR interception.
Args:
state: Current model state (uses ``state.lai``,
``state.dvs``).
doy: Day of year [1-365], shape ``[B]``.
dayl: Daylength [hours], shape ``[B]``.
sinld: sin(declination) [dimensionless], shape ``[B]``.
cosld: cos(declination) [dimensionless], shape ``[B]``.
dtr: Daily total radiation [MJ m⁻² d⁻¹], shape ``[B]``.
Converted to J m⁻² d⁻¹ for the PENMAN calculation.
params: Crop parameters; uses ``params.kdiftb`` (DVS-indexed
diffuse-PAR extinction table) and
``params.scale_factor_kdif`` (sensitivity scale on its
y-values).
Returns:
Dict of ``[B]`` tensors:
* ``avrad`` [J m⁻² d⁻¹] — Daily total irradiation
(computed from solar geometry; converted from input
MJ m⁻² d⁻¹).
* ``atmtr`` [-] — Atmospheric transmission fraction.
* ``par`` [J m⁻² d⁻¹] — Photosynthetically active
radiation (``0.5 · avrad``).
* ``parint`` [J m⁻² d⁻¹] — PAR intercepted by canopy.
* ``frac_intercepted`` [-] — Beer–Lambert interception
fraction.
"""
# Convert DTR from MJ m⁻² d⁻¹ to J m⁻² d⁻¹ for PENMAN.
dtr_j = dtr * 1e6
# Daily total irradiation.
aob = torch.clamp(sinld / cosld, min=-1.0, max=1.0)
dsinb = 3600.0 * (
dayl * sinld
+ 24.0 * cosld * torch.sqrt(torch.clamp(1.0 - aob * aob, min=0.0)) / math.pi
)
# Solar constant [W m⁻²] as a function of day of year.
sc = 1370.0 * (1.0 + 0.033 * torch.cos(2.0 * math.pi * doy / 365.0))
# Extraterrestrial radiation [J m⁻² d⁻¹].
angot = torch.clamp(sc * dsinb, min=0.0001)
# Daily total irradiation: minimum of 80 % extraterrestrial
# and measured.
avrad = torch.min(0.80 * angot, dtr_j)
# Atmospheric transmission.
atmtr = avrad / angot
# PAR (50 % of global radiation).
par = 0.5 * avrad
# DVS-dependent diffuse-PAR extinction coefficient
# K = cScaleFactorKDIF · KDIFTB(DVS)
kdif = params.scale_factor_kdif * interpolate(params.kdiftb, state.dvs)
# Beer–Lambert interception by canopy.
frac = 1.0 - torch.exp(-kdif * state.lai)
parint = par * frac
return {
"avrad": avrad,
"atmtr": atmtr,
"par": par,
"parint": parint,
"frac_intercepted": frac,
}