Skip to content

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:

\[ SC = 1370 \cdot (1 + 0.033 \cos(2\pi \, \text{DOY}/365)) \]

Daily integral of \(\sin\beta\) (solar elevation) over the daylight period:

\[ A_0 = \text{LIMIT}(-1, 1, \text{SINLD}/\text{COSLD}) \]
\[ \text{DSINB} = 3600 \left(\text{DAYL} \cdot \text{SINLD} + \frac{24}{\pi} \, \text{COSLD} \sqrt{1 - A_0^2}\right) \]

Extraterrestrial radiation, with the daily total capped at 80 % of the extraterrestrial value:

\[ \text{ANGOT} = \max(10^{-4},\; SC \cdot \text{DSINB}) \]
\[ \text{AVRAD} = \min(0.80 \cdot \text{ANGOT},\; \text{DTR}) \]

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:

\[ K = \text{cScaleFactorKDIF} \cdot \text{AFGEN}(\text{KDIFTB}, \text{DVS}) \]
\[ \text{PAR} = 0.5 \cdot \text{AVRAD}, \qquad \text{PARINT} = \text{PAR} \cdot \bigl(1 - e^{-K \, \text{LAI}}\bigr) \]

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 state.lai, state.dvs).

required
doy torch.Tensor

Day of year [1-365], shape [B].

required
dayl torch.Tensor

Daylength [hours], shape [B].

required
sinld torch.Tensor

sin(declination) [dimensionless], shape [B].

required
cosld torch.Tensor

cos(declination) [dimensionless], shape [B].

required
dtr torch.Tensor

Daily total radiation [MJ m⁻² d⁻¹], shape [B]. Converted to J m⁻² d⁻¹ for the PENMAN calculation.

required
params CropParameters

Crop parameters; uses params.kdiftb (DVS-indexed diffuse-PAR extinction table) and params.scale_factor_kdif (sensitivity scale on its y-values).

required

Returns:

Type Description
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.
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,
    }