Skip to content

root_dynamics

Root depth growth, root biomass and root senescence.

Root depth advances at the maximum daily increase RRI whenever the crop has emerged and the soil is not severely water-stressed. Growth is capped at the maximum rooting depth RDM and gated by an INSW-style water-stress step (zero growth when TRANRF < 0.01).

Equations

Root depth growth:

RR = min(RRI · INSW(TRANRF − 0.01, 0, 1), RDM − RD)

Root senescence:

DRRT = WRT · RDRRT when DVS ≥ DVSDR, else 0

where RDRRT = RDRRTB(DVS) · scale_factor_rdr_roots is the DVS- indexed relative root death rate.

Net living-root biomass change is RWRT = GRT · FRT − DRRT; the GRT · FRT term arrives pre-computed as g_root from Partitioning.

RootDynamics (Module)

Root depth, root biomass and dead-root rates.

Source code in torchcrop/processes/root_dynamics.py
class RootDynamics(nn.Module):
    """Root depth, root biomass and dead-root rates."""

    def forward(
        self,
        state: ModelState,
        g_root: torch.Tensor,
        tranrf: torch.Tensor,
        params: CropParameters,
        emerg: torch.Tensor | None = None,
    ) -> dict[str, torch.Tensor]:
        """Compute root depth and root-biomass rates for one day.

        Args:
            state: Current state; uses ``state.rootd``, ``state.dvs``,
                ``state.wrt`` and (for the default emergence mask)
                ``state.tsump``.
            g_root: Gross root biomass growth from partitioning
                [g DM m⁻² d⁻¹], shape ``[B]`` — the ``GRT · FRT``
                term.
            tranrf: Water-stress factor in ``[0, 1]``, shape ``[B]``.
            params: Crop parameters; uses ``rri`` (maximum daily root
                depth increase), ``rdmcr`` (crop-specific maximum
                rooting depth), ``rdrrtb`` (relative root death rate vs
                DVS), ``scale_factor_rdr_roots`` and ``dvsdr`` (DVS
                threshold above which root death starts).
            emerg: Optional emergence mask in ``{0, 1}`` (broadcast
                to ``[B]``). When ``0``, the root front does not
                advance (pre-emergence) and dead-root accumulation is
                also suppressed. Defaults to
                ``state.tsump >= params.tsumem``.

        Returns:
            Dict of ``[B]`` tensors grouped as follows.

            Rate variables (consumed by the engine for state update):

            * ``rootd_rate`` [m d⁻¹] — Daily increment of rooting
                depth, ``min(RRI · INSW(TRANRF − 0.01, 0, 1),
                RDM − RD)``, gated to zero pre-emergence.
            * ``wrt_rate`` [g DM m⁻² d⁻¹] — Net daily change in
                living root biomass (``= g_root − drrt``).
            * ``wrtd_rate`` [g DM m⁻² d⁻¹] — Daily senesced root
                mass transferred to the dead-root pool (``= drrt``).

            Diagnostics:

            * ``drrt`` [g DM m⁻² d⁻¹] — Root death rate
                ``DRRT = WRT · RDRRT · 𝟙[DVS ≥ DVSDR]``.
            * ``rdrrt`` [d⁻¹] — Effective DVS-indexed relative
                root death rate after the scale factor.
        """
        rootd = state.rootd
        dvs = state.dvs
        wrt = state.wrt

        dtype = rootd.dtype
        zero = torch.zeros_like(rootd)
        one = torch.ones_like(rootd)

        if emerg is None:
            emerg = (state.tsump >= params.tsumem).to(dtype)
        else:
            emerg = emerg.to(dtype)

        # ----- Root depth growth -----
        # Hard step at TRANRF = 0.01: full root-front velocity above
        # the threshold, zero below — not a linear scaling.
        insw_water = torch.where(tranrf - 0.01 < 0.0, zero, one)
        rr_pot = params.rri * insw_water

        # Avoid overshoot beyond the crop-specific maximum rooting
        # depth (``rdmcr``).
        headroom = torch.clamp(params.rdmcr - rootd, min=0.0)
        rootd_rate = torch.minimum(rr_pot, headroom)

        # Root growth only after emergence.
        rootd_rate = rootd_rate * emerg

        # ----- Root senescence (DRRT) -----
        # DRRT = WRT * RDRRT when DVS >= DVSDR, else 0.
        rdrrt = interpolate(params.rdrrtb, dvs) * params.scale_factor_rdr_roots
        death_mask = (dvs >= params.dvsdr).to(dtype)
        drrt = wrt * rdrrt * death_mask * emerg

        # ----- Net living-root biomass change -----
        # RWRT = GRT * FRT - DRRT; g_root already carries the
        # GRT * FRT contribution from `Partitioning`.
        wrt_rate = g_root - drrt

        return {
            "rootd_rate": rootd_rate,
            "wrt_rate": wrt_rate,
            "wrtd_rate": drrt,
            "drrt": drrt,
            "rdrrt": rdrrt,
        }

forward(self, state, g_root, tranrf, params, emerg=None)

Compute root depth and root-biomass rates for one day.

Parameters:

Name Type Description Default
state ModelState

Current state; uses state.rootd, state.dvs, state.wrt and (for the default emergence mask) state.tsump.

required
g_root torch.Tensor

Gross root biomass growth from partitioning [g DM m⁻² d⁻¹], shape [B] — the GRT · FRT term.

required
tranrf torch.Tensor

Water-stress factor in [0, 1], shape [B].

required
params CropParameters

Crop parameters; uses rri (maximum daily root depth increase), rdmcr (crop-specific maximum rooting depth), rdrrtb (relative root death rate vs DVS), scale_factor_rdr_roots and dvsdr (DVS threshold above which root death starts).

required
emerg torch.Tensor | None

Optional emergence mask in {0, 1} (broadcast to [B]). When 0, the root front does not advance (pre-emergence) and dead-root accumulation is also suppressed. Defaults to state.tsump >= params.tsumem.

None

Returns:

Type Description
Dict of ``[B]`` tensors grouped as follows. Rate variables (consumed by the engine for state update)
  • rootd_rate [m d⁻¹] — Daily increment of rooting depth, min(RRI · INSW(TRANRF − 0.01, 0, 1), RDM − RD), gated to zero pre-emergence.
  • wrt_rate [g DM m⁻² d⁻¹] — Net daily change in living root biomass (= g_root − drrt).
  • wrtd_rate [g DM m⁻² d⁻¹] — Daily senesced root mass transferred to the dead-root pool (= drrt).

Diagnostics:

  • drrt [g DM m⁻² d⁻¹] — Root death rate DRRT = WRT · RDRRT · 𝟙[DVS ≥ DVSDR].
  • rdrrt [d⁻¹] — Effective DVS-indexed relative root death rate after the scale factor.
Source code in torchcrop/processes/root_dynamics.py
def forward(
    self,
    state: ModelState,
    g_root: torch.Tensor,
    tranrf: torch.Tensor,
    params: CropParameters,
    emerg: torch.Tensor | None = None,
) -> dict[str, torch.Tensor]:
    """Compute root depth and root-biomass rates for one day.

    Args:
        state: Current state; uses ``state.rootd``, ``state.dvs``,
            ``state.wrt`` and (for the default emergence mask)
            ``state.tsump``.
        g_root: Gross root biomass growth from partitioning
            [g DM m⁻² d⁻¹], shape ``[B]`` — the ``GRT · FRT``
            term.
        tranrf: Water-stress factor in ``[0, 1]``, shape ``[B]``.
        params: Crop parameters; uses ``rri`` (maximum daily root
            depth increase), ``rdmcr`` (crop-specific maximum
            rooting depth), ``rdrrtb`` (relative root death rate vs
            DVS), ``scale_factor_rdr_roots`` and ``dvsdr`` (DVS
            threshold above which root death starts).
        emerg: Optional emergence mask in ``{0, 1}`` (broadcast
            to ``[B]``). When ``0``, the root front does not
            advance (pre-emergence) and dead-root accumulation is
            also suppressed. Defaults to
            ``state.tsump >= params.tsumem``.

    Returns:
        Dict of ``[B]`` tensors grouped as follows.

        Rate variables (consumed by the engine for state update):

        * ``rootd_rate`` [m d⁻¹] — Daily increment of rooting
            depth, ``min(RRI · INSW(TRANRF − 0.01, 0, 1),
            RDM − RD)``, gated to zero pre-emergence.
        * ``wrt_rate`` [g DM m⁻² d⁻¹] — Net daily change in
            living root biomass (``= g_root − drrt``).
        * ``wrtd_rate`` [g DM m⁻² d⁻¹] — Daily senesced root
            mass transferred to the dead-root pool (``= drrt``).

        Diagnostics:

        * ``drrt`` [g DM m⁻² d⁻¹] — Root death rate
            ``DRRT = WRT · RDRRT · 𝟙[DVS ≥ DVSDR]``.
        * ``rdrrt`` [d⁻¹] — Effective DVS-indexed relative
            root death rate after the scale factor.
    """
    rootd = state.rootd
    dvs = state.dvs
    wrt = state.wrt

    dtype = rootd.dtype
    zero = torch.zeros_like(rootd)
    one = torch.ones_like(rootd)

    if emerg is None:
        emerg = (state.tsump >= params.tsumem).to(dtype)
    else:
        emerg = emerg.to(dtype)

    # ----- Root depth growth -----
    # Hard step at TRANRF = 0.01: full root-front velocity above
    # the threshold, zero below — not a linear scaling.
    insw_water = torch.where(tranrf - 0.01 < 0.0, zero, one)
    rr_pot = params.rri * insw_water

    # Avoid overshoot beyond the crop-specific maximum rooting
    # depth (``rdmcr``).
    headroom = torch.clamp(params.rdmcr - rootd, min=0.0)
    rootd_rate = torch.minimum(rr_pot, headroom)

    # Root growth only after emergence.
    rootd_rate = rootd_rate * emerg

    # ----- Root senescence (DRRT) -----
    # DRRT = WRT * RDRRT when DVS >= DVSDR, else 0.
    rdrrt = interpolate(params.rdrrtb, dvs) * params.scale_factor_rdr_roots
    death_mask = (dvs >= params.dvsdr).to(dtype)
    drrt = wrt * rdrrt * death_mask * emerg

    # ----- Net living-root biomass change -----
    # RWRT = GRT * FRT - DRRT; g_root already carries the
    # GRT * FRT contribution from `Partitioning`.
    wrt_rate = g_root - drrt

    return {
        "rootd_rate": rootd_rate,
        "wrt_rate": wrt_rate,
        "wrtd_rate": drrt,
        "drrt": drrt,
        "rdrrt": rdrrt,
    }