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.

References

SIMPLACE Irradiation.java.

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 coefficient \(K\):

\[ \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 per SIMPLACE Irradiation.java, then calculates PAR interception using 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 per
    SIMPLACE Irradiation.java, then calculates PAR interception using
    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``).
            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]``.
                Will be converted to J m⁻² d⁻¹ for PENMAN calculation.
            params: Crop parameters; uses ``params.k`` (extinction
                coefficient).

        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 calculation
        dtr_j = dtr * 1e6

        # Daily total irradiation (SIMPLACE DailyTotalIrradiation logic)
        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 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

        # Beer-Lambert interception by canopy
        frac = 1.0 - torch.exp(-params.k * 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).

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]. Will be converted to J m⁻² d⁻¹ for PENMAN calculation.

required
params CropParameters

Crop parameters; uses params.k (extinction coefficient).

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``).
        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]``.
            Will be converted to J m⁻² d⁻¹ for PENMAN calculation.
        params: Crop parameters; uses ``params.k`` (extinction
            coefficient).

    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 calculation
    dtr_j = dtr * 1e6

    # Daily total irradiation (SIMPLACE DailyTotalIrradiation logic)
    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 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

    # Beer-Lambert interception by canopy
    frac = 1.0 - torch.exp(-params.k * state.lai)
    parint = par * frac

    return {
        "avrad": avrad,
        "atmtr": atmtr,
        "par": par,
        "parint": parint,
        "frac_intercepted": frac,
    }