Source code for pcse.crop.n_dynamics

#!/usr/bin/env python
# Herman Berghuijs (herman.berghuijs@wur.nl) and Allard de Wit (allard.dewit@wur.nl), April 2024

from .. import exceptions as exc
from ..traitlets import Float, Int, Instance
from ..decorators import prepare_rates, prepare_states
from ..base import ParamTemplate, StatesTemplate, RatesTemplate, \
    SimulationObject
from ..util import AfgenTrait
from .nutrients import N_Demand_Uptake


[docs] class N_Crop_Dynamics(SimulationObject): """Implementation of overall N crop dynamics. NPK_Crop_Dynamics implements the overall logic of N book-keeping within the crop. **Simulation parameters** ============= ================================================= ======================= Name Description Unit ============= ================================================= ======================= NMAXLV_TB Maximum N concentration in leaves as kg N kg-1 dry biomass function of dvs NMAXRT_FR Maximum N concentration in roots as fraction - of maximum N concentration in leaves NMAXST_FR Maximum N concentration in stems as fraction - of maximum N concentration in leaves NRESIDLV Residual N fraction in leaves kg N kg-1 dry biomass NRESIDRT Residual N fraction in roots kg N kg-1 dry biomass NRESIDST Residual N fraction in stems kg N kg-1 dry biomass ============= ================================================= ======================= **State variables** ========== ================================================== ============ Name Description Unit ========== ================================================== ============ NamountLV Actual N amount in living leaves |kg N ha-1| NamountST Actual N amount in living stems |kg N ha-1| NamountSO Actual N amount in living storage organs |kg N ha-1| NamountRT Actual N amount in living roots |kg N ha-1| Nuptake_T total absorbed N amount |kg N ha-1| Nfix_T total biological fixated N amount |kg N ha-1| ========== ================================================== ============ **Rate variables** =========== ================================================= ================ Name Description Unit =========== ================================================= ================ RNamountLV Weight increase (N) in leaves |kg N ha-1 d-1| RNamountST Weight increase (N) in stems |kg N ha-1 d-1| RNamountRT Weight increase (N) in roots |kg N ha-1 d-1| RNamountSO Weight increase (N) in storage organs |kg N ha-1 d-1| RNdeathLV Rate of N loss in leaves |kg N ha-1 d-1| RNdeathST Rate of N loss in roots |kg N ha-1 d-1| RNdeathRT Rate of N loss in stems |kg N ha-1 d-1| RNloss N loss due to senescence |kg N ha-1 d-1| =========== ================================================= ================ **Signals send or handled** None **External dependencies** ======= =================================== ==================== ============== Name Description Provided by Unit ======= =================================== ==================== ============== DVS Crop development stage DVS_Phenology - WLV Dry weight of living leaves WOFOST_Leaf_Dynamics |kg ha-1| WRT Dry weight of living roots WOFOST_Root_Dynamics |kg ha-1| WST Dry weight of living stems WOFOST_Stem_Dynamics |kg ha-1| DRLV Death rate of leaves WOFOST_Leaf_Dynamics |kg ha-1 d-1| DRRT Death rate of roots WOFOST_Root_Dynamics |kg ha-1 d-1| DRST Death rate of stems WOFOST_Stem_Dynamics |kg ha-1 d-1| ======= =================================== ==================== ============== """ demand_uptake = Instance(SimulationObject) NamountLVI = Float(-99.) # initial soil N amount in leaves NamountSTI = Float(-99.) # initial soil N amount in stems NamountRTI = Float(-99.) # initial soil N amount in roots NamountSOI = Float(-99.) # initial soil N amount in storage organs class Parameters(ParamTemplate): NMAXLV_TB = AfgenTrait() NMAXST_FR = Float(-99.) NMAXRT_FR = Float(-99.) NRESIDLV = Float(-99.) # residual N fraction in leaves [kg N kg-1 dry biomass] NRESIDST = Float(-99.) # residual N fraction in stems [kg N kg-1 dry biomass] NRESIDRT = Float(-99.) # residual N fraction in roots [kg N kg-1 dry biomass] class StateVariables(StatesTemplate): NamountLV = Float(-99.) # N amount in leaves [kg N ha-1] NamountST = Float(-99.) # N amount in stems [kg N ] NamountSO = Float(-99.) # N amount in storage organs [kg N ] NamountRT = Float(-99.) # N amount in roots [kg N ] NuptakeTotal = Float(-99.) # total absorbed N amount [kg N ] NfixTotal = Float(-99.) # total biological fixated N amount [kg N ] NlossesTotal = Float(-99.) class RateVariables(RatesTemplate): RNamountLV = Float(-99.) # Net rates of N in different plant organs RNamountST = Float(-99.) RNamountRT = Float(-99.) RNdeathLV = Float(-99.) # N loss rate leaves [kg ha-1 d-1] RNdeathST = Float(-99.) # N loss rate stems [kg ha-1 d-1] RNdeathRT = Float(-99.) # N loss rate roots [kg ha-1 d-1] RNloss = Float(-99.) def initialize(self, day, kiosk, parvalues): """ :param kiosk: variable kiosk of this PCSE instance :param parvalues: dictionary with parameters as key/value pairs """ self.params = self.Parameters(parvalues) self.rates = self.RateVariables(kiosk) self.kiosk = kiosk # Initialize components of the npk_crop_dynamics self.demand_uptake = N_Demand_Uptake(day, kiosk, parvalues) # INITIAL STATES params = self.params k = kiosk # Initial amounts self.NamountLVI = NamountLV = k.WLV * params.NMAXLV_TB(k.DVS) self.NamountSTI = NamountST = k.WST * params.NMAXLV_TB(k.DVS) * params.NMAXST_FR self.NamountRTI = NamountRT = k.WRT * params.NMAXLV_TB(k.DVS) * params.NMAXRT_FR self.NamountSOI = NamountSO = 0. self.states = self.StateVariables(kiosk, publish=["NamountLV", "NamountST", "NamountRT", "NamountSO"], NamountLV=NamountLV, NamountST=NamountST, NamountRT=NamountRT, NamountSO=NamountSO, NuptakeTotal=0, NfixTotal=0., NlossesTotal=0) @prepare_rates def calc_rates(self, day, drv): rates = self.rates params = self.params states = self.states k = self.kiosk self.demand_uptake.calc_rates(day, drv) # Compute loss of NPK due to death of plant material if k.WLV > 0.: rates.RNdeathLV = (states.NamountLV / k.WLV) * k.DRLV else: rates.RNdeathLV = 0. if k.WST > 0.: rates.RNdeathST = (states.NamountST / k.WST) * k.DRST else: rates.RNdeathST = 0. if k.WRT > 0.: rates.RNdeathRT = (states.NamountRT / k.WRT) * k.DRRT else: rates.RNdeathRT= 0. # N rates in leaves, stems, root and storage organs computed as # uptake - translocation - death. # except for storage organs which only take up as a result of translocation. rates.RNamountLV = k.RNuptakeLV - k.RNtranslocationLV - rates.RNdeathLV rates.RNamountST = k.RNuptakeST - k.RNtranslocationST - rates.RNdeathST rates.RNamountRT = k.RNuptakeRT - k.RNtranslocationRT - rates.RNdeathRT rates.RNamountSO = k.RNuptakeSO + k.RNtranslocation rates.RNloss = rates.RNdeathLV + rates.RNdeathST + rates.RNdeathRT self._check_N_balance(day) @prepare_states def integrate(self, day, delt=1.0): rates = self.rates states = self.states k = self.kiosk # N amount in leaves, stems, root and storage organs states.NamountLV += rates.RNamountLV states.NamountST += rates.RNamountST states.NamountRT += rates.RNamountRT states.NamountSO += rates.RNamountSO self.demand_uptake.integrate(day, delt) # total NPK uptake from soil states.NuptakeTotal += k.RNuptake states.NfixTotal += k.RNfixation states.NlossesTotal += rates.RNloss def _check_N_balance(self, day): s = self.states checksum = abs(s.NuptakeTotal + s.NfixTotal + (self.NamountLVI + self.NamountSTI + self.NamountRTI + self.NamountSOI) - (s.NamountLV + s.NamountST + s.NamountRT + s.NamountSO + s.NlossesTotal)) if abs(checksum) >= 1.0: msg = "N flows not balanced on day %s\n" % day msg += "Checksum: %f, Nuptake_T: %f, Nfix_T: %f\n" % (checksum, s.NuptakeTotal, s.NfixTotal) msg += "NamountLVI: %f, NamountSTI: %f, NamountRTI: %f, NamountSOI: %f\n" % \ (self.NamountLVI, self.NamountSTI, self.NamountRTI, self.NamountSOI) msg += "NamountLV: %f, NamountST: %f, NamountRT: %f, NamountSO: %f\n" % \ (s.NamountLV, s.NamountST, s.NamountRT, s.NamountSO) msg += "NLOSST: %f\n" % s.NlossesTotal raise exc.NutrientBalanceError(msg)