Ejemplo n.º 1
0
    def __init__(self, control, params, state, fluxes, met_data):
        """
        Parameters
        ----------
        control : integers, object
            model control flags
        params: floats, object
            model parameters
        state: floats, object
            model state
        fluxes : floats, object
            model fluxes
        met_data : floats, dictionary
            meteorological forcing data

        """
        self.params = params
        self.fluxes = fluxes
        self.control = control
        self.state = state
        self.met_data = met_data
        self.bw = Bewdy(self.control, self.params, self.state, self.fluxes,
                        self.met_data)
        self.wb = WaterBalance(self.control, self.params, self.state,
                               self.fluxes, self.met_data)

        if self.control.ps_pathway == "C3":
            self.mt = MateC3(self.control, self.params, self.state,
                             self.fluxes, self.met_data)
        else:
            self.mt = MateC4(self.control, self.params, self.state,
                             self.fluxes, self.met_data)

        self.sm = SoilMoisture(self.control, self.params, self.state,
                               self.fluxes)
        self.sm.initialise_parameters()

        self.rm = RootingDepthModel(d0x=self.params.d0x,
                                    r0=self.params.r0,
                                    top_soil_depth=self.params.topsoil_depth *
                                    const.MM_TO_M)

        # Window size = root lifespan in days...
        self.window_size = (int(1.0 /
                                (self.params.rdecay * const.NDAYS_IN_YR) *
                                const.NDAYS_IN_YR))
        #self.window_size = 365

        # If we don't have any information about the N&water limitation, i.e.
        # as would be the case with spin-up, assume that there is no limitation
        # to begin with.
        if self.state.prev_sma is None:
            self.state.prev_sma = 1.0

        self.sma = MovingAverageFilter(self.window_size, self.state.prev_sma)
Ejemplo n.º 2
0
    def __init__(self, control, params, state, fluxes, met_data):
        """
        Parameters
        ----------
        control : integers, object
            model control flags
        params: floats, object
            model parameters
        state: floats, object
            model state
        fluxes : floats, object
            model fluxes
        met_data : floats, dictionary
            meteorological forcing data

        """
        self.params = params
        self.fluxes = fluxes
        self.control = control
        self.state = state
        self.met_data = met_data
        self.bw = Bewdy(self.control, self.params, self.state, self.fluxes,
                        self.met_data)
        self.wb = WaterBalance(self.control, self.params, self.state,
                               self.fluxes, self.met_data)
        
        self.mt = Mate(self.control, self.params, self.state, self.fluxes,
                       self.met_data)
        
        self.sm = SoilMoisture(self.control, self.params, self.state, 
                               self.fluxes)
        
        self.rm = RootingDepthModel(d0x=self.params.d0x, r0=self.params.r0, 
                                    top_soil_depth=self.params.top_soil_depth)
Ejemplo n.º 3
0
    def __init__(self, control, params, state, fluxes, met_data):
        """
        Parameters
        ----------
        control : integers, object
            model control flags
        params: floats, object
            model parameters
        state: floats, object
            model state
        fluxes : floats, object
            model fluxes
        met_data : floats, dictionary
            meteorological forcing data

        """
        self.params = params
        self.fluxes = fluxes
        self.control = control
        self.state = state
        self.met_data = met_data
        self.bw = Bewdy(self.control, self.params, self.state, self.fluxes,
                        self.met_data)
        self.wb = WaterBalance(self.control, self.params, self.state,
                               self.fluxes, self.met_data)
        
        
        if self.control.ps_pathway == "C3":
            self.mt = MateC3(self.control, self.params, self.state, self.fluxes,
                             self.met_data)
        else:
            self.mt = MateC4(self.control, self.params, self.state, self.fluxes,
                             self.met_data)
                             
        self.sm = SoilMoisture(self.control, self.params, self.state, 
                               self.fluxes)
        self.sm.initialise_parameters()
        
        self.rm = RootingDepthModel(d0x=self.params.d0x, r0=self.params.r0, 
                                    top_soil_depth=self.params.topsoil_depth*const.MM_TO_M)
        
        # Window size = root lifespan in days...
        self.window_size = (int(1.0 / (self.params.rdecay * const.NDAYS_IN_YR)* 
                            const.NDAYS_IN_YR))
        #self.window_size = 365
        
        # If we don't have any information about the N&water limitation, i.e.
        # as would be the case with spin-up, assume that there is no limitation
        # to begin with.
        if self.state.prev_sma is None:
            self.state.prev_sma = 1.0 
        if self.state.grw_seas_stress is None:
            self.state.grw_seas_stress = 1.0
        
        self.sma = SimpleMovingAverage(self.window_size, self.state.prev_sma)
Ejemplo n.º 4
0
class PlantGrowth(object):
    """ G'DAY plant growth module.

    Calls photosynthesis model, water balance and evolve plant state.
    Pools recieve C through allocation of accumulated photosynthate and N
    from both soil uptake and retranslocation within the plant.

    Key feedback through soil N mineralisation and plant N uptake

    * Note met_forcing is an object with radiation, temp, precip data, etc.
    """
    def __init__(self, control, params, state, fluxes, met_data):
        """
        Parameters
        ----------
        control : integers, object
            model control flags
        params: floats, object
            model parameters
        state: floats, object
            model state
        fluxes : floats, object
            model fluxes
        met_data : floats, dictionary
            meteorological forcing data

        """
        self.params = params
        self.fluxes = fluxes
        self.control = control
        self.state = state
        self.met_data = met_data
        self.bw = Bewdy(self.control, self.params, self.state, self.fluxes,
                        self.met_data)
        self.wb = WaterBalance(self.control, self.params, self.state,
                               self.fluxes, self.met_data)
        
        
        if self.control.ps_pathway == "C3":
            self.mt = MateC3(self.control, self.params, self.state, self.fluxes,
                             self.met_data)
        else:
            self.mt = MateC4(self.control, self.params, self.state, self.fluxes,
                             self.met_data)
                             
        self.sm = SoilMoisture(self.control, self.params, self.state, 
                               self.fluxes)
        self.sm.initialise_parameters()
        
        self.rm = RootingDepthModel(d0x=self.params.d0x, r0=self.params.r0, 
                                    top_soil_depth=self.params.topsoil_depth*const.MM_TO_M)
        
        # Window size = root lifespan in days...
        self.window_size = (int(1.0 / (self.params.rdecay * const.NDAYS_IN_YR)* 
                            const.NDAYS_IN_YR))
        #self.window_size = 365
        
        # If we don't have any information about the N&water limitation, i.e.
        # as would be the case with spin-up, assume that there is no limitation
        # to begin with.
        if self.state.prev_sma is None:
            self.state.prev_sma = 1.0 
        if self.state.grw_seas_stress is None:
            self.state.grw_seas_stress = 1.0
        
        self.sma = SimpleMovingAverage(self.window_size, self.state.prev_sma)
        
        
    def calc_day_growth(self, project_day, fdecay, rdecay, daylen, doy, 
                        days_in_yr, yr_index):
        """Evolve plant state, photosynthesis, distribute N and C"

        Parameters:
        -----------
        project_day : integer
            simulation day
        fdecay : float
            foliage decay rate
        rdecay : float
            fine root decay rate
        """
        # if grazing took place need to reset "stress" running mean calculation
        # for grasses
        if self.control.grazing == 2 and self.params.disturbance_doy == doy: 
            self.sma.reset_stream()
        
        # calculate NPP
        self.carbon_production(project_day, daylen)

        # calculate water balance. We also need to store the previous days
        # soil water store
        previous_topsoil_store = self.state.pawater_topsoil
        previous_rootzone_store = self.state.pawater_root
        self.wb.calculate_water_balance(project_day, daylen)
        
        # leaf N:C as a fraction of Ncmaxyoung, i.e. the max N:C ratio of
        # foliage in young stand
        nitfac = min(1.0, self.state.shootnc / self.params.ncmaxfyoung)
        
        # figure out the C allocation fractions 
        if not self.control.deciduous_model:
            # daily allocation...
            self.calc_carbon_allocation_fracs(nitfac)
        else:
            # Allocation is annually for deciduous "tree" model, but we need to 
            # keep a check on stresses during the growing season and the LAI
            # figure out limitations during leaf growth period. This also
            # applies for deciduous grasses, need to do the growth stress
            # calc for grasses here too.
            if self.state.leaf_out_days[doy] > 0.0:
                self.calculate_growth_stress_limitation()
                
                # Need to save max lai for pipe model because at the end of the
                # year LAI=0.0
                if self.state.lai > self.state.max_lai:
                    self.state.max_lai = self.state.lai
                    
        # Distribute new C and N through the system
        self.carbon_allocation(nitfac, doy, days_in_yr)
        
        (ncbnew, nccnew, ncwimm, ncwnew) = self.calculate_ncwood_ratios(nitfac)
        recalc_wb = self.nitrogen_allocation(ncbnew, nccnew, ncwimm, ncwnew, fdecay, 
                                             rdecay, doy, days_in_yr, 
                                             project_day)
        
        # If we didn't have enough N available to satisfy wood demand, NPP
        # is down-regulated and thus so is GPP. We also need to recalculate the
        # water balance given the lower GPP.
        if recalc_wb:
            self.state.pawater_topsoil = previous_topsoil_store 
            self.state.pawater_root = previous_rootzone_store
            self.wb.calculate_water_balance(project_day, daylen)
            
        self.update_plant_state(fdecay, rdecay, project_day, doy)
        #if self.control.deciduous_model:
        
        self.precision_control()
        
        
        
    def calculate_ncwood_ratios(self, nitfac):
        """ Estimate the N:C ratio in the branch and stem. Option to vary
        the N:C ratio of the stem following Jeffreys (1999) or keep it a fixed
        fraction

        Parameters:
        -----------
        nitfac : float
            leaf N:C as a fraction of the max N:C ratio of foliage in young
            stand

        Returns:
        --------
        ncbnew : float
            N:C ratio of branch
        ncwimm : float
            N:C ratio of immobile stem
        ncwnew : float
            N:C ratio of mobile stem

        References:
        ----------
        * Jeffreys, M. P. (1999) Dynamics of stemwood nitrogen in Pinus radiata
          with modelled implications for forest productivity under elevated
          atmospheric carbon dioxide. PhD.
        """
        # n:c ratio of new branch wood
        ncbnew = (self.params.ncbnew + nitfac *
                 (self.params.ncbnew - self.params.ncbnewz))
        
        # n:c ratio of new coarse root 
        nccnew = (self.params.nccnew + nitfac *
                 (self.params.nccnew - self.params.nccnewz))
        
        # fixed N:C in the stemwood
        if self.control.fixed_stem_nc == 1:
            # n:c ratio of stemwood - immobile pool and new ring
            ncwimm = (self.params.ncwimm + nitfac *
                     (self.params.ncwimm - self.params.ncwimmz))
            
            # New stem ring N:C at critical leaf N:C (mobile)
            ncwnew = (self.params.ncwnew + nitfac *
                     (self.params.ncwnew - self.params.ncwnewz))
            
        # vary stem N:C based on reln with foliage, see Jeffreys. Jeffreys 1999
        # showed that N:C ratio of new wood increases with foliar N:C ratio,
        # modelled here based on evidence as a linear function.
        else:
            ncwimm = max(0.0, (0.0282 * self.state.shootnc + 0.000234) * 
                         self.params.fhw)

            # New stem ring N:C at critical leaf N:C (mobile)
            ncwnew = max(0.0, 0.162 * self.state.shootnc - 0.00143)
        
        return (ncbnew, nccnew, ncwimm, ncwnew)

    def carbon_production(self, project_day, daylen):
        """ Calculate GPP, NPP and plant respiration

        Parameters:
        -----------
        project_day : integer
            simulation day
        daylen : float
            daytime length (hrs)

        References:
        -----------
        * Jackson, J. E. and Palmer, J. W. (1981) Annals of Botany, 47, 561-565.
        """

        if self.state.lai > 0.0:
            # average leaf nitrogen content (g N m-2 leaf)
            leafn = (self.state.shootnc * self.params.cfracts /
                     self.params.sla * const.KG_AS_G)
            
            # total nitrogen content of the canopy
            self.state.ncontent = leafn * self.state.lai
        else:
            self.state.ncontent = 0.0
         
        # When canopy is not closed, canopy light interception is reduced
        cf = min(1.0, self.state.lai / self.params.lai_cover)
        
        # fIPAR - the fraction of intercepted PAR = IPAR/PAR incident at the 
        # top of the canopy, accounting for partial closure based on Jackson
        # and Palmer (1981), derived from beer's law
        if self.state.lai > 0.0:
            self.state.fipar = ((1.0 - exp(-self.params.kext * 
                                           self.state.lai / cf)) * cf)
        else:
            self.state.fipar = 0.0
        
        # Canopy extinction coefficient if the canopy is open
        #if cf < 1.0:
        #    kext = -log(1.0 - self.state.fipar) / LAI
        
        if self.control.water_stress:
            # Calculate the soil moisture availability factors [0,1] in the 
            # topsoil and the entire root zone
            (self.state.wtfac_topsoil, 
             self.state.wtfac_root) = self.sm.calculate_soil_water_fac()
        else:
            # really this should only be a debugging option!
            self.state.wtfac_tsoil = 1.0
            self.state.wtfac_root = 1.0
       
        # Estimate photosynthesis 
        if self.control.assim_model == "BEWDY":
            self.bw.calculate_photosynthesis(frac_gcover, project_day, daylen)
        elif self.control.assim_model == "MATE":
            self.mt.calculate_photosynthesis(project_day, daylen)
        else:
            raise AttributeError('Unknown assimilation model')
    
    def calc_carbon_allocation_fracs(self, nitfac):
        """Carbon allocation fractions to move photosynthate through the plant.

        Parameters:
        -----------
        nitfac : float
            leaf N:C as a fraction of 'Ncmaxfyoung' (max 1.0)

        Returns:
        --------
        alleaf : float
            allocation fraction for shoot
        alroot : float
            allocation fraction for fine roots
        albranch : float
            allocation fraction for branches
        alstem : float
            allocation fraction for stem
       
        References:
        -----------
        Corbeels, M. et al (2005) Ecological Modelling, 187, 449-474.
        McMurtrie, R. E. et al (2000) Plant and Soil, 224, 135-152.
        
        """
        if self.control.alloc_model == "FIXED":
        
            self.fluxes.alleaf = (self.params.c_alloc_fmax + nitfac *
                                 (self.params.c_alloc_fmax - 
                                 self.params.c_alloc_fmin))
            
            self.fluxes.alroot = (self.params.c_alloc_rmax + nitfac *
                                 (self.params.c_alloc_rmax - 
                                 self.params.c_alloc_rmin))

            self.fluxes.albranch = (self.params.c_alloc_bmax + nitfac *
                                   (self.params.c_alloc_bmax - 
                                   self.params.c_alloc_bmin))
        
            # allocate remainder to stem
            self.fluxes.alstem = (1.0 - self.fluxes.alleaf - 
                                  self.fluxes.alroot - 
                                  self.fluxes.albranch)
            
            self.fluxes.alcroot = self.params.c_alloc_cmax * self.fluxes.alstem
            self.fluxes.alstem -= self.fluxes.alcroot
            
        elif self.control.alloc_model == "GRASSES":
            
            # if combining grasses with the deciduous model this calculation
            # is done only during the leaf out period. See above.
            if not self.control.deciduous_model:
                self.calculate_growth_stress_limitation()
            
            # figure out root allocation given available water & nutrients
            # hyperbola shape to allocation
            min_root_alloc = 0.4
            self.fluxes.alroot = (self.params.c_alloc_rmax * 
                                  min_root_alloc / 
                                 (min_root_alloc + 
                                 (self.params.c_alloc_rmax - 
                                  min_root_alloc) * 
                                  self.state.prev_sma))
            
            self.fluxes.alstem = 0.0
            self.fluxes.albranch = 0.0
            self.fluxes.alcroot = 0.0
            self.fluxes.alleaf = (1.0 - self.fluxes.alroot)
            
        elif self.control.alloc_model == "ALLOMETRIC":
            
            if not self.control.deciduous_model:
                self.calculate_growth_stress_limitation()
            else:
                # reset the buffer at the end of the growing season
                self.sma.reset_stream()
                
            # figure out root allocation given available water & nutrients
            # hyperbola shape to allocation
            min_root_alloc = 0.1
            self.fluxes.alroot = (self.params.c_alloc_rmax * 
                                  min_root_alloc / 
                                 (min_root_alloc + 
                                 (self.params.c_alloc_rmax - 
                                  min_root_alloc) * 
                                  self.state.prev_sma))
            
            #self.fluxes.alroot = (self.params.c_alloc_rmin + 
            #                    (self.params.c_alloc_rmax - 
            #                     self.params.c_alloc_rmin) * 
            #                     self.state.prev_sma)
            
            
            # Calculate tree height: allometric reln using the power function 
            # (Causton, 1985)
            self.state.canht = (self.params.heighto * 
                                self.state.stem**self.params.htpower)

            # LAI to stem sapwood cross-sectional area (As m-2 m-2) 
            # (dimensionless)
            # Assume it varies between LS0 and LS1 as a linear function of tree
            # height (m) 
            arg1 = self.state.sapwood * const.TONNES_AS_KG * const.M2_AS_HA
            arg2 = self.state.canht * self.params.density * self.params.cfracts
            sap_cross_sec_area = arg1 / arg2
            
            if not self.control.deciduous_model:
                leaf2sap = self.state.lai / sap_cross_sec_area
            else:
                leaf2sap = self.state.max_lai / sap_cross_sec_area

            # Allocation to leaves dependant on height. Modification of pipe 
            # theory, leaf-to-sapwood ratio is not constant above a certain 
            # height, due to hydraulic constraints (Magnani et al 2000; Deckmyn
            # et al. 2006).
            
            if float_le(self.state.canht, self.params.height0):
                leaf2sa_target = self.params.leafsap0
            elif float_ge(self.state.canht, self.params.height1):
                leaf2sa_target = self.params.leafsap1
            else:
                arg1 = self.params.leafsap0
                arg2 = self.params.leafsap1 - self.params.leafsap0
                arg3 = self.state.canht - self.params.height0
                arg4 = self.params.height1 - self.params.height0
                leaf2sa_target = arg1 + (arg2 * arg3 / arg4) 
                
            self.fluxes.alleaf = self.alloc_goal_seek(leaf2sap, leaf2sa_target, 
                                                      self.params.c_alloc_fmax, 
                                                      self.params.targ_sens) 
            
            
            # Allocation to branch dependent on relationship between the stem
            # and branch
            target_branch = (self.params.branch0 * 
                             self.state.stem**self.params.branch1)
            self.fluxes.albranch = self.alloc_goal_seek(self.state.branch, 
                                                       target_branch, 
                                                       self.params.c_alloc_bmax, 
                                                       self.params.targ_sens) 
            
            coarse_root_target = (self.params.croot0 * 
                                  self.state.stem**self.params.croot1)
            self.fluxes.alcroot = self.alloc_goal_seek(self.state.croot, 
                                                       coarse_root_target, 
                                                       self.params.c_alloc_cmax, 
                                                       self.params.targ_sens) 
            
            self.fluxes.alstem = (1.0 - self.fluxes.alroot - 
                                        self.fluxes.albranch - 
                                        self.fluxes.alleaf -
                                        self.fluxes.alcroot)
            
            
            # allocation to stem is the residual
            #self.fluxes.alstem = (1.0 - self.fluxes.alroot - 
            #                            self.fluxes.albranch - 
            #                            self.fluxes.alleaf)
            #self.fluxes.alcroot = 0.2 * self.fluxes.alstem
            #self.fluxes.alstem -= self.fluxes.alcroot
            
            # Because I have allowed the max fracs sum > 1, possibility
            # stem frac would be negative. Perhaps the above shouldn't be 
            # allowed...? But this will stop wood allocation in such a 
            # situation.
            #if self.fluxes.alstem < 0.0:
            #    extra = self.fluxes.alstem
            #    self.fluxes.alstem = 0.0
            #    self.fluxes.alleaf -= extra
            
            # minimum allocation to leaves - without it tree would die, as this
            # is done annually.
            if self.control.deciduous_model:
                if self.fluxes.alleaf < 0.1:
                    min_leaf_alloc = 0.1
                    self.fluxes.alstem -= min_leaf_alloc
                    self.fluxes.alleaf = min_leaf_alloc
            
        else:
            raise AttributeError('Unknown C allocation model')
        
        #print self.fluxes.alleaf, self.fluxes.alstem, self.fluxes.albranch, \
        #       self.fluxes.alroot, self.state.prev_sma, self.state.canht
        
        
        
        # Total allocation should be one, if not print warning:
        total_alloc = (self.fluxes.alroot + self.fluxes.alleaf + 
                       self.fluxes.albranch + self.fluxes.alstem + 
                       self.fluxes.alcroot)
        if float_gt(total_alloc, 1.0):
            raise RuntimeError, "Allocation fracs > 1" 
        
    def alloc_goal_seek(self, simulated, target, alloc_max, sensitivity):
        
        # Sensitivity parameter characterises how allocation fraction respond 
        # when the leaf:sapwood area ratio departs from the target value 
        # If sensitivity close to 0 then the simulated leaf:sapwood area ratio 
        # will closely track the target value 
        frac = 0.5 + 0.5 * (1.0 - simulated / target) / sensitivity
        
        return max(0.0, alloc_max * min(1.0, frac))    
    
    
    def calculate_growth_stress_limitation(self):
        """ Calculate level of stress due to nitrogen or water availability """
        # calculate the N limitation based on available canopy N
        # this logic appears counter intuitive, but it works out when
        # applied with the perhaps backwards logic below
        nf = self.state.shootnc
    
        # case - completely limited by N availability
        if nf < self.params.nf_min:
            nlim = 0.0
        elif nf < self.params.nf_crit:
       
            nlim = ((nf - self.params.nf_min) / 
                    (self.params.nf_crit - self.params.nf_min))
        # case - no N limitation
        else:
            nlim = 1.0
        
        # Limitation by nitrogen and water. Water constraint is implicit, 
        # in that, water stress results in an increase of root mass,
        # which are assumed to spread horizontally within the rooting zone.
        # So in effect, building additional root mass doesnt alleviate the
        # water limitation within the model. However, it does more 
        # accurately reflect an increase in root C production at a water
        # limited site. This implementation is also consistent with other
        # approaches, e.g. LPJ. In fact I dont see much evidence for models
        # that have a flexible bucket depth.
        current_limitation = min(nlim, self.state.wtfac_root)
        self.state.prev_sma = self.sma(current_limitation)
        
        
    def allocate_stored_c_and_n(self, init):
        """
        Allocate stored C&N. This is either down as the model is initialised 
        for the first time or at the end of each year. 
        """
        # JUST here for FACE stuff as first year of ele should have last years 
        # alloc fracs
        #if init == True:
        #    self.fluxes.alleaf = 0.26
        #    self.fluxes.alroot = 0.11
        #    self.fluxes.albranch = 0.06
        #    self.fluxes.alstem = 0.57
                
        # ========================
        # Carbon - fixed fractions
        # ========================
        self.state.c_to_alloc_shoot = self.fluxes.alleaf * self.state.cstore
        self.state.c_to_alloc_root = self.fluxes.alroot * self.state.cstore
        self.state.c_to_alloc_croot = self.fluxes.alcroot * self.state.cstore
        self.state.c_to_alloc_branch = self.fluxes.albranch * self.state.cstore
        self.state.c_to_alloc_stem = self.fluxes.alstem * self.state.cstore
        
        
        # =========================================================
        # Nitrogen - Fixed ratios N allocation to woody components.
        # =========================================================
        
        # N flux into new ring (immobile component -> structrual components)
        self.state.n_to_alloc_stemimm = (self.state.cstore * 
                                         self.fluxes.alstem * 
                                         self.params.ncwimm)
    
        # N flux into new ring (mobile component -> can be retrans for new
        # woody tissue)
        self.state.n_to_alloc_stemmob = (self.state.cstore * 
                                         self.fluxes.alstem * 
                                        (self.params.ncwnew - 
                                         self.params.ncwimm))

        self.state.n_to_alloc_branch = (self.state.cstore * 
                                        self.fluxes.albranch * 
                                        self.params.ncbnew)
        
        self.state.n_to_alloc_croot = (self.state.cstore * 
                                        self.fluxes.alcroot * 
                                        self.params.nccnew)
                                        
        # Calculate remaining N left to allocate to leaves and roots 
        ntot = max(0.0,(self.state.nstore - self.state.n_to_alloc_stemimm -
                        self.state.n_to_alloc_stemmob - 
                        self.state.n_to_alloc_branch))
        
       
        # allocate remaining N to flexible-ratio pools
        self.state.n_to_alloc_shoot = (ntot * self.fluxes.alleaf / 
                                      (self.fluxes.alleaf + 
                                       self.fluxes.alroot *
                                       self.params.ncrfac))
        self.state.n_to_alloc_root = ntot - self.state.n_to_alloc_shoot
               
        
    def nitrogen_allocation(self, ncbnew, nccnew, ncwimm, ncwnew, fdecay, rdecay, doy,
                            days_in_yr, project_day):
        """ Nitrogen distribution - allocate available N through system.
        N is first allocated to the woody component, surplus N is then allocated
        to the shoot and roots with flexible ratios.
        
        References:
        -----------
        McMurtrie, R. E. et al (2000) Plant and Soil, 224, 135-152.
        
        Parameters:
        -----------
        ncbnew : float
            N:C ratio of branch
        ncwimm : float
            N:C ratio of immobile stem
        ncwnew : float
            N:C ratio of mobile stem
        fdecay : float
            foliage decay rate
        rdecay : float
            fine root decay rate
        """
        # default is we don't need to recalculate the water balance, 
        # however if we cut back on NPP due to available N below then we do
        # need to do this
        recalc_wb = False
        
        # N retranslocated proportion from dying plant tissue and stored within
        # the plant
        self.fluxes.retrans = self.nitrogen_retrans(fdecay, rdecay, doy)
        self.fluxes.nuptake = self.calculate_nuptake(project_day)
        
        # Ross's Root Model.
        if self.control.model_optroot == True:    
            
            # convert t ha-1 day-1 to gN m-2 year-1
            nsupply = (self.calculate_nuptake() * const.TONNES_HA_2_G_M2 * 
                       const.DAYS_IN_YRS)
            
            # covnert t ha-1 to kg DM m-2
            rtot = (self.state.root * const.TONNES_HA_2_KG_M2 / 
                    self.params.cfracts)
            self.fluxes.nuptake_old = self.fluxes.nuptake
            
            (self.state.root_depth, 
             self.fluxes.nuptake,
             self.fluxes.rabove) = self.rm.main(rtot, nsupply, depth_guess=1.0)
            
            #umax = self.rm.calc_umax(self.fluxes.nuptake)
            #print umax
            
            # covert nuptake from gN m-2 year-1  to t ha-1 day-1
            self.fluxes.nuptake = (self.fluxes.nuptake * 
                                   const.G_M2_2_TONNES_HA * const.YRS_IN_DAYS)
            
            # covert from kg DM N m-2 to t ha-1
            self.fluxes.deadroots = (self.params.rdecay * self.fluxes.rabove * 
                                     self.params.cfracts * 
                                     const.KG_M2_2_TONNES_HA)
            
            self.fluxes.deadrootn = (self.state.rootnc * 
                                    (1.0 - self.params.rretrans) * 
                                     self.fluxes.deadroots)
            
           
        # Mineralised nitrogen lost from the system by volatilisation/leaching
        self.fluxes.nloss = self.params.rateloss * self.state.inorgn
    
        # total nitrogen to allocate 
        ntot = max(0.0, self.fluxes.nuptake + self.fluxes.retrans)
        
        if self.control.deciduous_model:
            # allocate N to pools with fixed N:C ratios
            
            # N flux into new ring (immobile component -> structrual components)
            self.fluxes.npstemimm = (self.fluxes.wnimrate * 
                                     self.state.growing_days[doy])
            
            # N flux into new ring (mobile component -> can be retrans for new
            # woody tissue)
            self.fluxes.npstemmob = (self.fluxes.wnmobrate * 
                                     self.state.growing_days[doy])
            
            self.fluxes.nproot = self.state.n_to_alloc_root / days_in_yr
            self.fluxes.npcroot = (self.fluxes.cnrate * 
                                   self.state.growing_days[doy])
            
            self.fluxes.npleaf = (self.fluxes.lnrate * 
                                  self.state.growing_days[doy])
            
            self.fluxes.npbranch = (self.fluxes.bnrate * 
                                    self.state.growing_days[doy])
        else:
            # allocate N to pools with fixed N:C ratios
            
            # N flux into new ring (immobile component -> structural components)
            self.fluxes.npstemimm = self.fluxes.npp * self.fluxes.alstem * ncwimm
    
            # N flux into new ring (mobile component -> can be retrans for new
            # woody tissue)
            self.fluxes.npstemmob = (self.fluxes.npp * self.fluxes.alstem * 
                                     (ncwnew - ncwimm))
            self.fluxes.npbranch = (self.fluxes.npp * self.fluxes.albranch * 
                                     ncbnew)
            
            self.fluxes.npcroot = (self.fluxes.npp * self.fluxes.alcroot * 
                                     nccnew)
            
            # If we have allocated more N than we have available 
            #  - cut back N prodn
            arg = (self.fluxes.npstemimm + self.fluxes.npstemmob +
                   self.fluxes.npbranch + self.fluxes.npcroot)
            
            if float_gt(arg, ntot) and self.control.fixleafnc == False:
                
                self.fluxes.npp *= (ntot / (self.fluxes.npstemimm +
                                    self.fluxes.npstemmob + 
                                    self.fluxes.npbranch ))
                
                # need to adjust growth values accordingly as well
                self.fluxes.cpleaf = self.fluxes.npp * self.fluxes.alleaf
                self.fluxes.cproot = self.fluxes.npp * self.fluxes.alroot
                self.fluxes.cpcroot = self.fluxes.npp * self.fluxes.alcroot
                self.fluxes.cpbranch = self.fluxes.npp * self.fluxes.albranch
                self.fluxes.cpstem = self.fluxes.npp * self.fluxes.alstem
                
                self.fluxes.npbranch = (self.fluxes.npp * self.fluxes.albranch * 
                                        ncbnew)
                self.fluxes.npstemimm = (self.fluxes.npp * self.fluxes.alstem * 
                                         ncwimm)
                self.fluxes.npstemmob = (self.fluxes.npp * self.fluxes.alstem * 
                                        (ncwnew - ncwimm))
                self.fluxes.npcroot = (self.fluxes.npp * self.fluxes.alcroot * 
                                        nccnew)
                
                # Also need to recalculate GPP and thus Ra and return a flag
                # so that we know to recalculate the water balance.
                self.fluxes.gpp = self.fluxes.npp / self.params.cue
                conv = const.G_AS_TONNES / const.M2_AS_HA
                self.fluxes.gpp_gCm2 = self.fluxes.gpp / conv
                self.fluxes.gpp_am_pm[0] = self.fluxes.gpp_gCm2 / 2.0
                self.fluxes.gpp_am_pm[1] = self.fluxes.gpp_gCm2 / 2.0
                
                # New respiration flux
                self.fluxes.auto_resp =  self.fluxes.gpp - self.fluxes.npp
                recalc_wb = True 
                
            ntot -= (self.fluxes.npbranch + self.fluxes.npstemimm +
                     self.fluxes.npstemmob + self.fluxes.npcroot)
            ntot = max(0.0, ntot)
            
            # allocate remaining N to flexible-ratio pools
            self.fluxes.npleaf = (ntot * self.fluxes.alleaf / 
                                 (self.fluxes.alleaf + self.fluxes.alroot *
                                 self.params.ncrfac))
            self.fluxes.nproot = ntot - self.fluxes.npleaf
            
        return recalc_wb 
        
    def nitrogen_retrans(self, fdecay, rdecay, doy):
        """ Nitrogen retranslocated from senesced plant matter.
        Constant rate of n translocated from mobile pool

        Parameters:
        -----------
        fdecay : float
            foliage decay rate
        rdecay : float
            fine root decay rate

        Returns:
        --------
        N retrans : float
            N retranslocated plant matter

        """
        if self.control.deciduous_model:
            leafretransn = (self.params.fretrans * self.fluxes.lnrate * 
                            self.state.remaining_days[doy])
        else:
            leafretransn = self.params.fretrans * fdecay * self.state.shootn
        
        rootretransn = self.params.rretrans * rdecay * self.state.rootn
        crootretransn = (self.params.cretrans * self.params.crdecay *
                         self.state.crootn)
        branchretransn = (self.params.bretrans * self.params.bdecay *
                          self.state.branchn)
        stemretransn = (self.params.wretrans * self.params.wdecay *
                        self.state.stemnmob + self.params.retransmob *
                        self.state.stemnmob)
        
        # store for NCEAS output
        self.fluxes.leafretransn = leafretransn
        
        return (leafretransn + rootretransn + crootretransn + branchretransn +
                stemretransn)
        
        
    
    def calculate_nuptake(self, project_day):
        """ N uptake depends on the rate at which soil mineral N is made 
        available to the plants.
        
        Returns:
        --------
        nuptake : float
            N uptake
            
        References:
        -----------
        * Dewar and McMurtrie, 1996, Tree Physiology, 16, 161-171.    
        * Raich et al. 1991, Ecological Applications, 1, 399-429.
            
        """
        if self.control.nuptake_model == 0:
            # Constant N uptake
            nuptake = self.params.nuptakez
        elif self.control.nuptake_model == 1:
            # evaluate nuptake : proportional to dynamic inorganic N pool
            nuptake = self.params.rateuptake * self.state.inorgn
        elif self.control.nuptake_model == 2:
            # N uptake is a saturating function on root biomass following
            # Dewar and McMurtrie, 1996.
            
            # supply rate of available mineral N
            U0 = self.params.rateuptake * self.state.inorgn
            Kr = self.params.kr
            nuptake = max(U0 * self.state.root / (self.state.root + Kr), 0.0)
        elif self.control.nuptake_model == 3:
            # N uptake is a function of available soil N, soil moisture 
            # following a Michaelis-Menten approach 
            # See Raich et al. 1991, pg. 423.
            
            vcn = 1.0 / 0.0215 # 46.4
            arg1 = (vcn * self.state.shootn) - self.state.shoot
            arg2 = (vcn * self.state.shootn) + self.state.shoot
            self.params.ac += self.params.adapt * arg1 / arg2
            self.params.ac = max(min(1.0, self.params.ac), 0.0)
            
            # soil moisture is assumed to influence nutrient diffusion rate
            # through the soil, ks [0,1]
            theta = self.state.pawater_root / self.params.wcapac_root 
            ks = 0.9 * theta**3.0 + 0.1
            
            arg1 = self.params.nmax * ks * self.state.inorgn 
            arg2 = self.params.knl + (ks * self.state.inorgn)
            arg3 = exp(0.0693 * self.met_data['tair'][project_day])
            arg4 = 1.0 - self.params.ac
            nuptake = (arg1 / arg2) * arg3 * arg4
            
            #print self.params.nmax, self.params.knl, ks, exp(0.0693 * tavg) 
            
        else:
            raise AttributeError('Unknown N uptake option')
        
        # Stop N uptake if C:N falls below 10
        #if self.state.plantnc > 0.1:
        #    nuptake = 0.0
        
        return nuptake
    
    def carbon_allocation(self, nitfac, doy, days_in_yr):
        """ C distribution - allocate available C through system

        Parameters:
        -----------
        nitfac : float
            leaf N:C as a fraction of 'Ncmaxfyoung' (max 1.0)
        """
        if self.control.deciduous_model:
            days_left = self.state.growing_days[doy]
            self.fluxes.cpleaf = self.fluxes.lrate * days_left
            self.fluxes.cpbranch = self.fluxes.brate * days_left
            self.fluxes.cpstem = self.fluxes.wrate * days_left
            self.fluxes.cproot = self.state.c_to_alloc_root * 1.0 / days_in_yr
            self.fluxes.cpcroot = self.fluxes.crate * days_left
        else:
            self.fluxes.cpleaf = self.fluxes.npp * self.fluxes.alleaf
            self.fluxes.cproot = self.fluxes.npp * self.fluxes.alroot
            self.fluxes.cpcroot = self.fluxes.npp * self.fluxes.alcroot
            self.fluxes.cpbranch = self.fluxes.npp * self.fluxes.albranch
            self.fluxes.cpstem = self.fluxes.npp * self.fluxes.alstem
            
        # evaluate SLA of new foliage accounting for variation in SLA 
        # with tree and leaf age (Sands and Landsberg, 2002). Assume 
        # SLA of new foliage is linearly related to leaf N:C ratio 
        # via nitfac. Based on date from two E.globulus stands in SW Aus, see
        # Corbeels et al (2005) Ecological Modelling, 187, 449-474.
        # (m2 onesided/kg DW)
        self.params.sla = (self.params.slazero + nitfac *
                          (self.params.slamax - self.params.slazero))
        
        if self.control.deciduous_model:
            if float_eq(self.state.shoot, 0.0):
                self.state.lai = 0.0
            elif self.state.leaf_out_days[doy] > 0.0:               
                
                self.state.lai += (self.fluxes.cpleaf * 
                                  (self.params.sla * const.M2_AS_HA / 
                                  (const.KG_AS_TONNES * self.params.cfracts)) -
                                  (self.fluxes.deadleaves + 
                                   self.fluxes.ceaten) *
                                   self.state.lai / self.state.shoot)
            else:
                self.state.lai = 0.0
        else:
            # update leaf area [m2 m-2]
            if float_eq(self.state.shoot, 0.0):
                self.state.lai = 0.0
            else:
                self.state.lai += (self.fluxes.cpleaf * 
                                  (self.params.sla * const.M2_AS_HA / 
                                  (const.KG_AS_TONNES * self.params.cfracts)) -
                                  (self.fluxes.deadleaves +  
                                   self.fluxes.ceaten) *
                                   self.state.lai / self.state.shoot)
   
    def precision_control(self, tolerance=1E-08):
        """ Detect very low values in state variables and force to zero to 
        avoid rounding and overflow errors """       
        
        # C & N state variables 
        if self.state.shoot < tolerance:
            self.fluxes.deadleaves += self.state.shoot
            self.fluxes.deadleafn += self.state.shootn
            self.state.shoot = 0.0 
            self.state.shootn = 0.0 
            
        if self.state.branch < tolerance:
            self.fluxes.deadbranch += self.state.branch
            self.fluxes.deadbranchn += self.state.branchn
            self.state.branch = 0.0
            self.state.branchn = 0.0

        if self.state.root < tolerance:
            self.fluxes.deadrootn += self.state.rootn
            self.fluxes.deadroots += self.state.root
            self.state.root = 0.0
            self.state.rootn = 0.0
        
        if self.state.croot < tolerance:
            self.fluxes.deadcrootn += self.state.crootn
            self.fluxes.deadcroots += self.state.croot
            self.state.croot = 0.0
            self.state.crootn = 0.0
        
        # Not setting these to zero as this just leads to errors with desert
        # regrowth...instead seeding them to a small value with a CN~25.
        
        if self.state.stem < tolerance:     
            self.fluxes.deadstems += self.state.stem
            self.fluxes.deadstemn += self.state.stemn
            self.state.stem = 0.001
            self.state.stemn = 0.00004
            self.state.stemnimm = 0.00004
            self.state.stemnmob = 0.0
        
        # need separate one as this will become very small if there is no
        # mobile stem N
        if self.state.stemnmob < tolerance: 
            self.fluxes.deadstemn += self.state.stemnmob
            self.state.stemnmob = 0.0  
            
        if self.state.stemnimm < tolerance: 
            self.fluxes.deadstemn += self.state.stemnimm
            self.state.stemnimm = 0.00004  
        
    def update_plant_state(self, fdecay, rdecay, project_day, doy):
        """ Daily change in C content

        Parameters:
        -----------
        fdecay : float
            foliage decay rate
        rdecay : float
            fine root decay rate

        """
        # 
        # Carbon pools
        #
        self.state.shoot += (self.fluxes.cpleaf - self.fluxes.deadleaves -
                             self.fluxes.ceaten)
        self.state.root += self.fluxes.cproot - self.fluxes.deadroots
        self.state.croot += self.fluxes.cpcroot - self.fluxes.deadcroots
        self.state.branch += self.fluxes.cpbranch - self.fluxes.deadbranch
        self.state.stem += self.fluxes.cpstem - self.fluxes.deadstems
        
        # annoying but can't see an easier way with the code as it is.
        # If we are modelling grases, i.e. no stem them without this
        # the sapwood will end up being reduced to a silly number as 
        # deadsapwood will keep being removed from the pool, even though there
        # is no wood. 
        if self.state.stem <= 0.01:
            self.state.sapwood = 0.01
        else:
            self.state.sapwood += self.fluxes.cpstem - self.fluxes.deadsapwood
        
        # 
        # Nitrogen pools
        #
        if self.control.deciduous_model:       
            self.state.shootn += (self.fluxes.npleaf - 
                                 (self.fluxes.lnrate * 
                                  self.state.remaining_days[doy]) - 
                                  self.fluxes.neaten)                        
        else:
            self.state.shootn += (self.fluxes.npleaf - 
                                  fdecay * self.state.shootn - 
                                  self.fluxes.neaten)
                                
        self.state.branchn += (self.fluxes.npbranch - self.params.bdecay *
                               self.state.branchn)
        self.state.rootn += self.fluxes.nproot - rdecay * self.state.rootn
        self.state.crootn += self.fluxes.npcroot - self.params.crdecay * self.state.crootn
        
        self.state.stemnimm += (self.fluxes.npstemimm - self.params.wdecay *
                                self.state.stemnimm)
        self.state.stemnmob += (self.fluxes.npstemmob - self.params.wdecay *
                                self.state.stemnmob -
                                self.params.retransmob * self.state.stemnmob)        
        self.state.stemn = self.state.stemnimm + self.state.stemnmob

        if self.control.deciduous_model:
            self.calculate_cn_store()
        
        #============================
        # Enforce maximum N:C ratios.
        # ===========================    
        # This doesn't make sense for the deciduous model because of the ramp
        # function. The way the deciduous logic works we now before we start
        # how much N we have to allocate so it is impossible (well) to allocate in 
        # excess. Therefore this is only relevant for evergreen model.
        if not self.control.deciduous_model:
            
            # If foliage or root N/C exceeds its max, then N uptake is cut back
            
            # maximum leaf n:c ratio is function of stand age
            #  - switch off age effect by setting ncmaxfyoung = ncmaxfold
            age_effect = ((self.state.age - self.params.ageyoung) / 
                          (self.params.ageold - self.params.ageyoung))

            ncmaxf = (self.params.ncmaxfyoung - 
                     (self.params.ncmaxfyoung - self.params.ncmaxfold) * 
                      age_effect)
            
            if float_lt(ncmaxf, self.params.ncmaxfold):
                ncmaxf = self.params.ncmaxfold

            if float_gt(ncmaxf, self.params.ncmaxfyoung):
                ncmaxf = self.params.ncmaxfyoung
            
            extras = 0.0
            if self.state.lai > 0.0:

                if float_gt(self.state.shootn, (self.state.shoot * ncmaxf)):
                    extras = self.state.shootn - self.state.shoot * ncmaxf
                    
                    # Ensure N uptake cannot be reduced below zero.
                    if float_gt(extras, self.fluxes.nuptake):
                        extras = self.fluxes.nuptake

                    self.state.shootn -= extras
                    self.fluxes.nuptake -= extras
                    
            # if root N:C ratio exceeds its max, then nitrogen uptake is cut 
            # back. n.b. new ring n/c max is already set because it is related 
            # to leaf n:c
            ncmaxr = ncmaxf * self.params.ncrfac  # max root n:c
            extrar = 0.0
            if float_gt(self.state.rootn, (self.state.root * ncmaxr)):
       
                extrar = self.state.rootn - self.state.root * ncmaxr

                # Ensure N uptake cannot be reduced below zero.
                if float_gt((extras + extrar), self.fluxes.nuptake):
                    extrar = self.fluxes.nuptake - extras

                self.state.rootn -= extrar
                self.fluxes.nuptake -= extrar 
                
    def calculate_cn_store(self):        
        """ Deciduous trees store carbohydrate during the winter which they then
        use in the following year to build new leaves (buds & budburst are 
        implied) 
        """
        # Total C & N storage to allocate annually.
        self.state.cstore += self.fluxes.npp
        self.state.nstore += self.fluxes.nuptake + self.fluxes.retrans 
        self.state.anpp += self.fluxes.npp
Ejemplo n.º 5
0
class PlantGrowth(object):
    """ G'DAY plant growth module.

    Calls photosynthesis model, water balance and evolve plant state.
    Pools recieve C through allocation of accumulated photosynthate and N
    from both soil uptake and retranslocation within the plant.

    Key feedback through soil N mineralisation and plant N uptake

    * Note met_forcing is an object with radiation, temp, precip data, etc.
    """
    def __init__(self, control, params, state, fluxes, met_data):
        """
        Parameters
        ----------
        control : integers, object
            model control flags
        params: floats, object
            model parameters
        state: floats, object
            model state
        fluxes : floats, object
            model fluxes
        met_data : floats, dictionary
            meteorological forcing data

        """
        self.params = params
        self.fluxes = fluxes
        self.control = control
        self.state = state
        self.met_data = met_data
        self.bw = Bewdy(self.control, self.params, self.state, self.fluxes,
                        self.met_data)
        self.wb = WaterBalance(self.control, self.params, self.state,
                               self.fluxes, self.met_data)
        
        self.mt = Mate(self.control, self.params, self.state, self.fluxes,
                       self.met_data)
        
        self.sm = SoilMoisture(self.control, self.params, self.state, 
                               self.fluxes)
        
        self.rm = RootingDepthModel(d0x=self.params.d0x, r0=self.params.r0, 
                                    top_soil_depth=self.params.top_soil_depth)
   
    def calc_day_growth(self, project_day, fdecay, rdecay, daylen, doy, 
                        days_in_yr, yr_index):
        """Evolve plant state, photosynthesis, distribute N and C"

        Parameters:
        -----------
        project_day : integer
            simulation day
        fdecay : float
            foliage decay rate
        rdecay : float
            fine root decay rate
        """
        # calculate NPP
        self.carbon_production(project_day, daylen)

        # calculate water balance
        self.wb.calculate_water_balance(project_day, daylen)
        
        # leaf N:C as a fraction of Ncmaxyoung, i.e. the max N:C ratio of
        # foliage in young stand
        nitfac = min(1.0, self.state.shootnc / self.params.ncmaxfyoung)
        
        # figure out the C allocation fractions 
        self.calc_carbon_allocation_fracs(nitfac, yr_index)
        
        # Distribute new C and N through the system
        self.carbon_allocation(nitfac, doy, days_in_yr)
        
        (ncbnew, ncwimm, ncwnew) = self.calculate_ncwood_ratios(nitfac)
        self.nitrogen_allocation(ncbnew, ncwimm, ncwnew, fdecay, rdecay, doy,
                                 days_in_yr)
        
        self.update_plant_state(fdecay, rdecay, project_day, doy)
        if self.control.deciduous_model:
            self.precision_control()
        
    def calculate_ncwood_ratios(self, nitfac):
        """ Estimate the N:C ratio in the branch and stem. Option to vary
        the N:C ratio of the stem following Jeffreys (1999) or keep it a fixed
        fraction

        Parameters:
        -----------
        nitfac : float
            leaf N:C as a fraction of the max N:C ratio of foliage in young
            stand

        Returns:
        --------
        ncbnew : float
            N:C ratio of branch
        ncwimm : float
            N:C ratio of immobile stem
        ncwnew : float
            N:C ratio of mobile stem

        References:
        ----------
        * Jeffreys, M. P. (1999) Dynamics of stemwood nitrogen in Pinus radiata
          with modelled implications for forest productivity under elevated
          atmospheric carbon dioxide. PhD.
        """
        # n:c ratio of new branch wood
        ncbnew = (self.params.ncbnew + nitfac *
                 (self.params.ncbnew - self.params.ncbnewz))
        self.state.branchnc = ncbnew
        
        # fixed N:C in the stemwood
        if self.control.fixed_stem_nc == 1:
            # n:c ratio of stemwood - immobile pool and new ring
            ncwimm = (self.params.ncwimm + nitfac *
                     (self.params.ncwimm - self.params.ncwimmz))
            
            # New stem ring N:C at critical leaf N:C (mobile)
            ncwnew = (self.params.ncwnew + nitfac *
                     (self.params.ncwnew - self.params.ncwnewz))
            
        # vary stem N:C based on reln with foliage, see Jeffreys. Jeffreys 1999
        # showed that N:C ratio of new wood increases with foliar N:C ratio,
        # modelled here based on evidence as a linear function.
        else:
            ncwimm = max(0.0, (0.0282 * self.state.shootnc + 0.000234) * 
                         self.params.fhw)

            # New stem ring N:C at critical leaf N:C (mobile)
            ncwnew = max(0.0, 0.162 * self.state.shootnc - 0.00143)
        
        return (ncbnew, ncwimm, ncwnew)

    def carbon_production(self, project_day, daylen):
        """ Calculate GPP, NPP and plant respiration

        Parameters:
        -----------
        project_day : integer
            simulation day
        daylen : float
            daytime length (hrs)

        References:
        -----------
        * Jackson, J. E. and Palmer, J. W. (1981) Annals of Botany, 47, 561-565.
        """

        if self.state.lai > 0.0:
            # average leaf nitrogen content (g N m-2 leaf)
            leafn = (self.state.shootnc * self.params.cfracts /
                     self.state.sla * const.KG_AS_G)
            
            # total nitrogen content of the canopy
            self.state.ncontent = leafn * self.state.lai
        else:
            self.state.ncontent = 0.0
         
        # fractional ground cover.
        if float_lt(self.state.lai, self.params.lai_cover):
            frac_gcover = self.state.lai / self.params.lai_cover
        else:
            frac_gcover = 1.0

        # Radiance intercepted by the canopy, accounting for partial closure
        # Jackson and Palmer (1981), derived from beer's law
        if self.state.lai > 0.0:
            self.state.light_interception = ((1.0 - exp(-self.params.kext *
                                             self.state.lai / frac_gcover)) *
                                             frac_gcover)
        else:
            self.state.light_interception = 0.0
        
        if self.control.water_stress:
            # Calculate the soil moisture availability factors [0,1] in the 
            # topsoil and the entire root zone
            (self.state.wtfac_tsoil, 
             self.state.wtfac_root) = self.sm.calculate_soil_water_fac()
        else:
            # really this should only be a debugging option!
            self.state.wtfac_tsoil = 1.0
            self.state.wtfac_root = 1.0
            
        # Estimate photosynthesis 
        if self.control.assim_model == "BEWDY":
            self.bw.calculate_photosynthesis(frac_gcover, project_day, daylen)
        elif self.control.assim_model == "MATE":
            self.mt.calculate_photosynthesis(project_day, daylen)
        else:
            raise AttributeError('Unknown assimilation model')
    
    def calc_carbon_allocation_fracs(self, nitfac, yr_index):
        """Carbon allocation fractions to move photosynthate through the plant.

        Parameters:
        -----------
        nitfac : float
            leaf N:C as a fraction of 'Ncmaxfyoung' (max 1.0)

        Returns:
        --------
        alleaf : float
            allocation fraction for shoot
        alroot : float
            allocation fraction for fine roots
        albranch : float
            allocation fraction for branches
        alstem : float
            allocation fraction for stem
        alroot_exudate : float
            allocation fraction for root exudate 
       
        References:
        -----------
        McMurtrie, R. E. et al (2000) Plant and Soil, 224, 135-152.
        """
        
        #self.state.alleaf = (self.params.callocf + nitfac *
        #                    (self.params.callocf - self.params.callocfz))
        
        if type(self.params.callocr) == type(list()):
            aw = self.params.callocr[yr_index]
            awz = self.params.callocrz[yr_index]
        else:
            aw = self.params.callocr
            awz = self.params.callocrz
    
        
        self.state.alroot = aw + nitfac * (aw - awz)
        
        if type(self.params.callocf) == type(list()):
            af = self.params.callocf[yr_index]
            afz = self.params.callocrz[yr_index]
        else:
            af = self.params.callocf
            afz = self.params.callocfz
    
        
        self.state.alleaf = af + nitfac * (af - afz)
        
        #self.state.alroot = (self.params.callocr + nitfac *
        #                    (self.params.callocr - self.params.callocrz))

        self.state.albranch = (self.params.callocb + nitfac *
                              (self.params.callocb - self.params.callocbz))
        
        # Remove some of the allocation to wood and instead allocate it to
        # root exudation. Following McMurtrie et al. 2000
        self.state.alroot_exudate = self.params.callocrx
        
        # allocate remainder to stem
        self.state.alstem = (1.0 - self.state.alleaf - self.state.alroot - 
                             self.state.albranch - self.state.alroot_exudate)
        
    def allocate_stored_c_and_n(self, init):
        """
        Allocate stored C&N. This is either down as the model is initialised 
        for the first time or at the end of each year. 
        """
        """# JUST here for phase stuff as first year of ele should have last years alloc fracs
        if init == True:
            self.state.alleaf = 0.26
            self.state.alroot = 0.11
            self.state.albranch = 0.06
            self.state.alstem = 0.57
        """
        # ========================
        # Carbon - fixed fractions
        # ========================
        self.state.c_to_alloc_shoot = self.state.alleaf * self.state.cstore
        self.state.c_to_alloc_root = self.state.alroot * self.state.cstore
        self.state.c_to_alloc_branch = self.state.albranch * self.state.cstore
        self.state.c_to_alloc_stem = self.state.alstem * self.state.cstore
        #self.state.c_to_alloc_rootexudate = (self.state.alroot_exudate *
        #                                     self.state.cstore)
        
        #print self.state.alleaf , self.state.alroot, self.state.albranch, self.state.alstem,
        
        
        # =========
        # Nitrogen
        # =========
        
        # Fixed ratios N allocation to woody components.
        
        # N flux into new ring (immobile component -> structrual components)
        self.state.n_to_alloc_stemimm = (self.state.cstore * self.state.alstem * 
                                         self.params.ncwimm)
    
        # N flux into new ring (mobile component -> can be retrans for new
        # woody tissue)
        self.state.n_to_alloc_stemmob = (self.state.cstore * self.state.alstem * 
                                        (self.params.ncwnew - 
                                         self.params.ncwimm))
        
        self.state.n_to_alloc_branch = (self.state.cstore * 
                                        self.state.albranch * 
                                        self.params.ncbnew)
        
        # Calculate remaining N left to allocate to leaves and roots 
        ntot = (self.state.nstore - self.state.n_to_alloc_stemimm -
                self.state.n_to_alloc_stemmob - self.state.n_to_alloc_branch)
        
        # allocate remaining N to flexible-ratio pools
        self.state.n_to_alloc_shoot = (ntot * self.state.alleaf / 
                                      (self.state.alleaf + 
                                       self.state.alroot *
                                       self.params.ncrfac))
        self.state.n_to_alloc_root = ntot - self.state.n_to_alloc_shoot
        
        
    def nitrogen_allocation(self, ncbnew, ncwimm, ncwnew, fdecay, rdecay, doy,
                            days_in_yr):
        """ Nitrogen distribution - allocate available N through system.
        N is first allocated to the woody component, surplus N is then allocated
        to the shoot and roots with flexible ratios.
        
        References:
        -----------
        McMurtrie, R. E. et al (2000) Plant and Soil, 224, 135-152.
        
        Parameters:
        -----------
        ncbnew : float
            N:C ratio of branch
        ncwimm : float
            N:C ratio of immobile stem
        ncwnew : float
            N:C ratio of mobile stem
        fdecay : float
            foliage decay rate
        rdecay : float
            fine root decay rate
        """
        # N retranslocated proportion from dying plant tissue and stored within
        # the plant
        self.fluxes.retrans = self.nitrogen_retrans(fdecay, rdecay, doy)
        self.fluxes.nuptake = self.calculate_nuptake()
        
        # Ross's Root Model.
        # NOT WORKING YET
        
        if self.control.model_optroot == True:    
            
            # convert t ha-1 day-1 to gN m-2 year-1
            nsupply = self.calculate_nuptake() * const.TONNES_HA_2_G_M2 * 365.25
            
            # covnert t ha-1 to kg m-2
            rtot = self.state.root * const.TONNES_HA_2_KG_M2
            
            (self.state.root_depth, 
             self.fluxes.nuptake,
             self.fluxes.rabove) = self.rm.main(rtot, nsupply, depth_guess=1.0)
            
            
            # covert nuptake from gN m-2 year-1  to t ha-1 day-1
            self.fluxes.nuptake = self.fluxes.nuptake * const.G_M2_2_TONNES_HA / 365.25
            
            # covert from kg N m-2 to t ha-1
            self.fluxes.deadroots = (self.params.rdecay * self.fluxes.rabove * 
                                     const.KG_M2_2_TONNES_HA)
            
            self.fluxes.deadrootn = (self.state.rootnc * 
                                    (1.0 - self.params.rretrans) * 
                                    self.fluxes.deadroots)
            
            
            #print self.fluxes.gpp*100, self.state.lai, self.state.root*100, \
            #      self.fluxes.nuptake *100.
            #print self.fluxes.gpp*100, self.state.lai, self.state.root*100, \
            #      root_depth, self.fluxes.nuptake *100.
        
        
        #print self.fluxes.nuptake* 365.25, self.fluxes.deadroots
        # N lost from system is proportional to the soil inorganic N pool, 
        # where the rate constant empirically defines gaseous and leaching 
        # losses, see McMurtrie et al. 2001.
        self.fluxes.nloss = self.params.rateloss * self.state.inorgn
    
        # total nitrogen to allocate 
        ntot = self.fluxes.nuptake + self.fluxes.retrans
        
        # N flux into root exudation, see McMurtrie et al. 2000
        self.fluxes.nrootexudate = (self.fluxes.npp * self.state.alroot_exudate * 
                                    self.params.vxfix)
        
        if self.control.deciduous_model:
            # allocate N to pools with fixed N:C ratios
            
            # N flux into new ring (immobile component -> structrual components)
            self.fluxes.npstemimm = (self.fluxes.wnimrate * 
                                     self.state.growing_days[doy])
            
            # N flux into new ring (mobile component -> can be retrans for new
            # woody tissue)
            self.fluxes.npstemmob = (self.fluxes.wnmobrate * 
                                     self.state.growing_days[doy])
            
            self.fluxes.nproot = self.state.n_to_alloc_root / days_in_yr
            
            self.fluxes.npleaf = (self.fluxes.lnrate * 
                                  self.state.growing_days[doy])
            
            self.fluxes.npbranch = (self.fluxes.bnrate * 
                                    self.state.growing_days[doy])
        else:
            # allocate N to pools with fixed N:C ratios
            
            # N flux into new ring (immobile component -> structural components)
            self.fluxes.npstemimm = self.fluxes.npp * self.state.alstem * ncwimm
    
            # N flux into new ring (mobile component -> can be retrans for new
            # woody tissue)
            self.fluxes.npstemmob = (self.fluxes.npp * self.state.alstem * 
                                     (ncwnew - ncwimm))
            self.fluxes.npbranch = (self.fluxes.npp * self.state.albranch * 
                                     ncbnew)
            
            # If we have allocated more N than we have available 
            #  - cut back N prodn
            arg = (self.fluxes.npstemimm + self.fluxes.npstemmob +
                   self.fluxes.npbranch )
    
            if float_gt(arg, ntot) and self.control.fixleafnc == False:
                self.fluxes.npp *= (ntot / (self.fluxes.npstemimm +
                                    self.fluxes.npstemmob + 
                                    self.fluxes.npbranch ))
                self.fluxes.npbranch = (self.fluxes.npp * self.state.albranch * 
                                        ncbnew)
                self.fluxes.npstemimm = (self.fluxes.npp * self.state.alstem * 
                                         ncwimm)
                self.fluxes.npstemmob = (self.fluxes.npp * self.state.alstem * 
                                        (ncwnew - ncwimm))
                
                
            ntot -= (self.fluxes.npbranch + self.fluxes.npstemimm +
                        self.fluxes.npstemmob)
            
            # allocate remaining N to flexible-ratio pools
            self.fluxes.npleaf = (ntot * self.state.alleaf / 
                                 (self.state.alleaf + self.state.alroot *
                                 self.params.ncrfac))
            self.fluxes.nproot = ntot - self.fluxes.npleaf
        
        
    def nitrogen_retrans(self, fdecay, rdecay, doy):
        """ Nitrogen retranslocated from senesced plant matter.
        Constant rate of n translocated from mobile pool

        Parameters:
        -----------
        fdecay : float
            foliage decay rate
        rdecay : float
            fine root decay rate

        Returns:
        --------
        N retrans : float
            N retranslocated plant matter

        """
        if self.control.deciduous_model:
            leafretransn = (self.params.fretrans * self.fluxes.lnrate * 
                            self.state.remaining_days[doy])
        else:
            leafretransn = self.params.fretrans * fdecay * self.state.shootn
        
        arg1 = (leafretransn +
                self.params.rretrans * rdecay * self.state.rootn +
                self.params.bretrans * self.params.bdecay *
                self.state.branchn)
        arg2 = (self.params.wretrans * self.params.wdecay *
                self.state.stemnmob + self.params.retransmob *
                self.state.stemnmob)
        
        return arg1 + arg2
    
    def calculate_nuptake(self):
        """ N uptake from the soil, note as it stands root biomass does not
        affect N uptake.
        
        Returns:
        --------
        nuptake : float
            N uptake
            
        References:
        -----------
        * Dewar and McMurtrie, 1996, Tree Physiology, 16, 161-171.    
            
        """
        if self.control.nuptake_model == 0:
            # Constant N uptake
            nuptake = self.params.nuptakez
        elif self.control.nuptake_model == 1:
            # evaluate nuptake : proportional to dynamic inorganic N pool
            nuptake = self.params.rateuptake * self.state.inorgn
        elif self.control.nuptake_model == 2:
            # Assume N uptake depends on the rate at which soil mineral
            # N is made available (self.params.Uo) and the value or root C
            # at which 50% of the available N is taken up (Dewar and McM).
            arg = (self.params.uo * self.state.inorgn *
                    (self.state.root / (self.state.root + self.params.kr)))
            nuptake = max(arg, 0.0)
        else:
            raise AttributeError('Unknown N uptake assumption')
        
        return nuptake
    
    def carbon_allocation(self, nitfac, doy, days_in_yr):
        """ C distribution - allocate available C through system

        Parameters:
        -----------
        nitfac : float
            leaf N:C as a fraction of 'Ncmaxfyoung' (max 1.0)
        
        References:
        -----------
        
        * Hale, M. G. et al. (1981) Factors affecting root exudation and 
          significance for the rhizosphere ecoystems. Biological and chemical 
          interactions in the rhizosphere. Stockholm. Sweden: Ecological 
          Research Committee of NFR. pg. 43--71.
        * Lambers, J. T. and Poot, P. (2003) Structure and Functioning of 
          Cluster Roots and Plant Responses to Phosphate Deficiency.
        * Martin, J. K. and Puckjeridge, D. W. (1982) Carbon flow through the 
          rhizosphere of wheat crops in South Australia. The cyclcing of carbon,
          nitrogen, sulpher and phosphorous in terrestrial and aquatic
          ecosystems. Canberra: Australian Academy of Science. pg 77--82.
        * McMurtrie, R. E. et al (2000) Plant and Soil, 224, 135-152.
        
        Also see:
        * Rovira, A. D. (1969) Plant Root Exudates. Botanical Review, 35, 
          pg 35--57.
        """
        if self.control.deciduous_model:
            days_left = self.state.growing_days[doy]
            self.fluxes.cpleaf = self.fluxes.lrate * days_left
            self.fluxes.cpbranch = self.fluxes.brate * days_left
            self.fluxes.cpstem = self.fluxes.wrate * days_left
            self.fluxes.cproot = self.state.c_to_alloc_root * 1.0 / days_in_yr
        else:
            self.fluxes.cpleaf = self.fluxes.npp * self.state.alleaf
            self.fluxes.cproot = self.fluxes.npp * self.state.alroot
            self.fluxes.cpbranch = self.fluxes.npp * self.state.albranch
            self.fluxes.cpstem = self.fluxes.npp * self.state.alstem
        
        # C flux into root exudation, see McMurtrie et al. 2000. There is no 
        # reference given for the 0.15 in McM, however 14c work by Hale et al and
        # Martin and Puckeridge suggest values range between 10-20% of NPP. So
        # presumably this is where this values of 0.15 (i.e. the average) comes
        # from
        self.fluxes.cprootexudate = self.fluxes.npp * self.state.alroot_exudate
        #self.fluxes.cprootexudate = 0.0
        
        # evaluate SLA of new foliage accounting for variation in SLA 
        # with tree and leaf age (Sands and Landsberg, 2002). Assume 
        # SLA of new foliage is linearly related to leaf N:C ratio 
        # via nitfac
        # (m2 onesided/kg DW)
        self.state.sla = (self.params.slazero + nitfac *
                         (self.params.slamax - self.params.slazero))
        
        if self.control.deciduous_model:
            if float_eq(self.state.shoot, 0.0):
                self.state.lai = 0.0
            elif self.state.leaf_out_days[doy] > 0.0:               
                self.state.lai += (self.fluxes.cpleaf * 
                                  (self.state.sla * const.M2_AS_HA / 
                                  (const.KG_AS_TONNES * self.params.cfracts)) -
                                  (self.fluxes.deadleaves + 
                                   self.fluxes.ceaten) *
                                   self.state.lai / self.state.shoot)
            else:
                self.state.lai = 0.0
        else:
            # update leaf area [m2 m-2]
            self.state.lai += (self.fluxes.cpleaf * 
                              (self.state.sla * const.M2_AS_HA / 
                              (const.KG_AS_TONNES * self.params.cfracts)) -
                              (self.fluxes.deadleaves + 
                               self.fluxes.ceaten) *
                               self.state.lai / self.state.shoot)
   
    def precision_control(self, tolerance=1E-08):
        """ Detect very low values in state variables and force to zero to 
        avoid rounding and overflow errors """       
        
        # C & N state variables 
        if self.state.shoot < tolerance:
            self.fluxes.deadleaves += self.state.shoot
            self.fluxes.deadleafn += self.state.shootn
            self.fluxes.deadbranch += self.state.branch
            self.fluxes.deadbranchn += self.state.branchn
            self.state.shoot = 0.0 
            self.state.shootn = 0.0 
            self.state.branch = 0.0
            self.state.branchn = 0.0
        
        if self.state.root < tolerance:
            self.fluxes.deadrootn += self.state.rootn
            self.fluxes.deadroots += self.state.root
            self.state.root = 0.0
            self.state.rootn = 0.0
    
        if self.state.stem < tolerance:     
            self.fluxes.deadstemn += self.state.stem
            self.state.stem = 0.0
            self.state.stemnimm = 0.0
            self.state.stemnmob = 0.0
            
        # should add check for soil pools - excess goes where?
        
    def update_plant_state(self, fdecay, rdecay, project_day, doy):
        """ Daily change in C content

        Parameters:
        -----------
        fdecay : float
            foliage decay rate
        rdecay : float
            fine root decay rate

        """
        # 
        # Carbon pools
        #
        self.state.shoot += (self.fluxes.cpleaf - self.fluxes.deadleaves -
                             self.fluxes.ceaten)
        self.state.root += self.fluxes.cproot - self.fluxes.deadroots
        self.state.branch += self.fluxes.cpbranch - self.fluxes.deadbranch
        self.state.stem += self.fluxes.cpstem - self.fluxes.deadstems
        
        # 
        # Nitrogen pools
        #
        if self.control.deciduous_model:       
            self.state.shootn += (self.fluxes.npleaf - 
                                 (self.fluxes.lnrate * 
                                  self.state.remaining_days[doy]) - 
                                  self.fluxes.neaten)                        
        else:
            self.state.shootn += (self.fluxes.npleaf - 
                                  fdecay * self.state.shootn - 
                                  self.fluxes.neaten)
                                
        self.state.branchn += (self.fluxes.npbranch - self.params.bdecay *
                               self.state.branchn)
        self.state.rootn += self.fluxes.nproot - rdecay * self.state.rootn
        
        self.state.stemnimm += (self.fluxes.npstemimm - self.params.wdecay *
                                self.state.stemnimm)
        self.state.stemnmob += (self.fluxes.npstemmob - self.params.wdecay *
                                self.state.stemnmob -
                                self.params.retransmob * self.state.stemnmob)
        self.state.stemn = self.state.stemnimm + self.state.stemnmob
        #self.state.exu_pool += self.fluxes.cprootexudate
        #self.fluxes.microbial_resp = self.calc_microbial_resp(project_day)
        #self.state.exu_pool -= self.fluxes.microbial_resp        
        if self.control.deciduous_model:
            self.calculate_cn_store()
        
        #============================
        # Enforce maximum N:C ratios.
        # ===========================    
        # This doesn't make sense for the deciduous model because of the ramp
        # function. The way the deciduous logic works we now before we start
        # how much N we have to allocate so it is impossible to allocate in 
        # excess. Therefore this is only relevant for evergreen model.
        if not self.control.deciduous_model:
            
            # If foliage or root N/C exceeds its max, then N uptake is cut back
            
            # maximum leaf n:c ratio is function of stand age
            #  - switch off age effect by setting ncmaxfyoung = ncmaxfold
            age_effect = ((self.state.age - self.params.ageyoung) / 
                          (self.params.ageold - self.params.ageyoung))

            ncmaxf = (self.params.ncmaxfyoung - 
                     (self.params.ncmaxfyoung - self.params.ncmaxfold) * 
                      age_effect)
    
            if float_lt(ncmaxf, self.params.ncmaxfold):
                ncmaxf = self.params.ncmaxfold

            if float_gt(ncmaxf, self.params.ncmaxfyoung):
                ncmaxf = self.params.ncmaxfyoung
    
            extras = 0.0
            if self.state.lai > 0.0:

                if float_gt(self.state.shootn, (self.state.shoot * ncmaxf)):
                    extras = self.state.shootn - self.state.shoot * ncmaxf

                    # Ensure N uptake cannot be reduced below zero.
                    if float_gt(extras, self.fluxes.nuptake):
                        extras = self.fluxes.nuptake

                    self.state.shootn -= extras
                    self.fluxes.nuptake -= extras
                    
            # if root N:C ratio exceeds its max, then nitrogen uptake is cut 
            # back. n.b. new ring n/c max is already set because it is related 
            # to leaf n:c
            ncmaxr = ncmaxf * self.params.ncrfac  # max root n:c
            extrar = 0.0
            if float_gt(self.state.rootn, (self.state.root * ncmaxr)):
       
                extrar = self.state.rootn - self.state.root * ncmaxr

                # Ensure N uptake cannot be reduced below zero.
                if float_gt((extras + extrar), self.fluxes.nuptake):
                    extrar = self.fluxes.nuptake - extras

                self.state.rootn -= extrar
                self.fluxes.nuptake -= extrar 
                
    def calculate_cn_store(self):        
        
        # Total C & N storage to allocate annually.
        self.state.cstore += self.fluxes.npp
        self.state.nstore += self.fluxes.nuptake + self.fluxes.retrans 
        self.state.anpp += self.fluxes.npp
  
    def calc_microbial_resp(self, project_day):
        """ Based on LPJ-why
        
        References:
        * Wania (2009) Integrating peatlands and permafrost into a dynamic 
          global vegetation model: 1. Evaluation and sensitivity of physical 
          land surface processes. GBC, 23, GB3014.
        * Also part 2 and the geosci paper in 2010
        """
        tsoil = self.met_data['tsoil'][project_day]
        
        # Lloyd and Taylor, 1994
        if tsoil < -10.0:
            temp_resp = 0.0
        else:
            temp_resp = (exp(308.56 * ((1.0 / 56.02) - (1.0 / 
                        (tsoil + const.DEG_TO_KELVIN - 227.13)))))
        
        moist_resp = ((1.0 - exp(-1.0 * self.state.wtfac_tsoil)) /  
                        (1.0 - exp(-1.0)))
        
        # Pool turnover rate = every 2 weeks -> days. Check you have this right
        # and turn into a parameter if so
        k_exu10 = 0.0714128571 
        k_exu = k_exu10 * temp_resp * moist_resp
        
        return  self.state.exu_pool * (1.0 - exp(-k_exu))
Ejemplo n.º 6
0
class PlantGrowth(object):
    """ G'DAY plant growth module.

    Calls photosynthesis model, water balance and evolve plant state.
    Pools recieve C through allocation of accumulated photosynthate and N
    from both soil uptake and retranslocation within the plant.

    Key feedback through soil N mineralisation and plant N uptake

    * Note met_forcing is an object with radiation, temp, precip data, etc.
    """
    def __init__(self, control, params, state, fluxes, met_data):
        """
        Parameters
        ----------
        control : integers, object
            model control flags
        params: floats, object
            model parameters
        state: floats, object
            model state
        fluxes : floats, object
            model fluxes
        met_data : floats, dictionary
            meteorological forcing data

        """
        self.params = params
        self.fluxes = fluxes
        self.control = control
        self.state = state
        self.met_data = met_data
        self.bw = Bewdy(self.control, self.params, self.state, self.fluxes,
                        self.met_data)
        self.wb = WaterBalance(self.control, self.params, self.state,
                               self.fluxes, self.met_data)

        if self.control.ps_pathway == "C3":
            self.mt = MateC3(self.control, self.params, self.state,
                             self.fluxes, self.met_data)
        else:
            self.mt = MateC4(self.control, self.params, self.state,
                             self.fluxes, self.met_data)

        self.sm = SoilMoisture(self.control, self.params, self.state,
                               self.fluxes)
        self.sm.initialise_parameters()

        self.rm = RootingDepthModel(d0x=self.params.d0x,
                                    r0=self.params.r0,
                                    top_soil_depth=self.params.topsoil_depth *
                                    const.MM_TO_M)

        # Window size = root lifespan in days...
        self.window_size = (int(1.0 /
                                (self.params.rdecay * const.NDAYS_IN_YR) *
                                const.NDAYS_IN_YR))
        #self.window_size = 365

        # If we don't have any information about the N&water limitation, i.e.
        # as would be the case with spin-up, assume that there is no limitation
        # to begin with.
        if self.state.prev_sma is None:
            self.state.prev_sma = 1.0

        self.sma = MovingAverageFilter(self.window_size, self.state.prev_sma)

    def calc_day_growth(self, project_day, fdecay, rdecay, daylen, doy,
                        days_in_yr, yr_index):
        """Evolve plant state, photosynthesis, distribute N and C"

        Parameters:
        -----------
        project_day : integer
            simulation day
        fdecay : float
            foliage decay rate
        rdecay : float
            fine root decay rate
        """
        # calculate NPP
        self.carbon_production(project_day, daylen)

        # calculate water balance
        self.wb.calculate_water_balance(project_day, daylen)

        # leaf N:C as a fraction of Ncmaxyoung, i.e. the max N:C ratio of
        # foliage in young stand
        nitfac = min(1.0, self.state.shootnc / self.params.ncmaxfyoung)

        # figure out the C allocation fractions
        self.calc_carbon_allocation_fracs(nitfac, yr_index, project_day)

        # Distribute new C and N through the system
        self.carbon_allocation(nitfac, doy, days_in_yr)

        (ncbnew, ncwimm, ncwnew) = self.calculate_ncwood_ratios(nitfac)
        self.nitrogen_allocation(ncbnew, ncwimm, ncwnew, fdecay, rdecay, doy,
                                 days_in_yr, project_day)

        self.update_plant_state(fdecay, rdecay, project_day, doy)
        #if self.control.deciduous_model:

        self.precision_control()

    def calculate_ncwood_ratios(self, nitfac):
        """ Estimate the N:C ratio in the branch and stem. Option to vary
        the N:C ratio of the stem following Jeffreys (1999) or keep it a fixed
        fraction

        Parameters:
        -----------
        nitfac : float
            leaf N:C as a fraction of the max N:C ratio of foliage in young
            stand

        Returns:
        --------
        ncbnew : float
            N:C ratio of branch
        ncwimm : float
            N:C ratio of immobile stem
        ncwnew : float
            N:C ratio of mobile stem

        References:
        ----------
        * Jeffreys, M. P. (1999) Dynamics of stemwood nitrogen in Pinus radiata
          with modelled implications for forest productivity under elevated
          atmospheric carbon dioxide. PhD.
        """
        # n:c ratio of new branch wood
        ncbnew = (self.params.ncbnew + nitfac *
                  (self.params.ncbnew - self.params.ncbnewz))

        # fixed N:C in the stemwood
        if self.control.fixed_stem_nc == 1:
            # n:c ratio of stemwood - immobile pool and new ring
            ncwimm = (self.params.ncwimm + nitfac *
                      (self.params.ncwimm - self.params.ncwimmz))

            # New stem ring N:C at critical leaf N:C (mobile)
            ncwnew = (self.params.ncwnew + nitfac *
                      (self.params.ncwnew - self.params.ncwnewz))

        # vary stem N:C based on reln with foliage, see Jeffreys. Jeffreys 1999
        # showed that N:C ratio of new wood increases with foliar N:C ratio,
        # modelled here based on evidence as a linear function.
        else:
            ncwimm = max(0.0, (0.0282 * self.state.shootnc + 0.000234) *
                         self.params.fhw)

            # New stem ring N:C at critical leaf N:C (mobile)
            ncwnew = max(0.0, 0.162 * self.state.shootnc - 0.00143)

        return (ncbnew, ncwimm, ncwnew)

    def carbon_production(self, project_day, daylen):
        """ Calculate GPP, NPP and plant respiration

        Parameters:
        -----------
        project_day : integer
            simulation day
        daylen : float
            daytime length (hrs)

        References:
        -----------
        * Jackson, J. E. and Palmer, J. W. (1981) Annals of Botany, 47, 561-565.
        """

        if self.state.lai > 0.0:
            # average leaf nitrogen content (g N m-2 leaf)
            leafn = (self.state.shootnc * self.params.cfracts /
                     self.state.sla * const.KG_AS_G)

            # total nitrogen content of the canopy
            self.state.ncontent = leafn * self.state.lai
        else:
            self.state.ncontent = 0.0

        # When canopy is not closed, canopy light interception is reduced
        cf = min(1.0, self.state.lai / self.params.lai_cover)

        # fIPAR - the fraction of intercepted PAR = IPAR/PAR incident at the
        # top of the canopy, accounting for partial closure based on Jackson
        # and Palmer (1981), derived from beer's law
        if self.state.lai > 0.0:
            self.state.fipar = (
                (1.0 - exp(-self.params.kext * self.state.lai / cf)) * cf)
        else:
            self.state.fipar = 0.0

        # Canopy extinction coefficient if the canopy is open
        #if cf < 1.0:
        #    kext = -log(1.0 - self.state.fipar) / LAI

        if self.control.water_stress:
            # Calculate the soil moisture availability factors [0,1] in the
            # topsoil and the entire root zone
            (self.state.wtfac_topsoil,
             self.state.wtfac_root) = self.sm.calculate_soil_water_fac()
        else:
            # really this should only be a debugging option!
            self.state.wtfac_tsoil = 1.0
            self.state.wtfac_root = 1.0

        # Estimate photosynthesis
        if self.control.assim_model == "BEWDY":
            self.bw.calculate_photosynthesis(frac_gcover, project_day, daylen)
        elif self.control.assim_model == "MATE":
            self.mt.calculate_photosynthesis(project_day, daylen)
        else:
            raise AttributeError('Unknown assimilation model')

    def calc_carbon_allocation_fracs(self, nitfac, yr_index, project_day):
        """Carbon allocation fractions to move photosynthate through the plant.

        Parameters:
        -----------
        nitfac : float
            leaf N:C as a fraction of 'Ncmaxfyoung' (max 1.0)

        Returns:
        --------
        alleaf : float
            allocation fraction for shoot
        alroot : float
            allocation fraction for fine roots
        albranch : float
            allocation fraction for branches
        alstem : float
            allocation fraction for stem
       
        References:
        -----------
        Corbeels, M. et al (2005) Ecological Modelling, 187, 449-474.
        McMurtrie, R. E. et al (2000) Plant and Soil, 224, 135-152.
        
        """
        if self.control.alloc_model == "FIXED":

            self.state.alleaf = (
                self.params.c_alloc_fmax + nitfac *
                (self.params.c_alloc_fmax - self.params.c_alloc_fmin))

            self.state.alroot = (
                self.params.c_alloc_rmax + nitfac *
                (self.params.c_alloc_rmax - self.params.c_alloc_rmin))

            self.state.albranch = (
                self.params.c_alloc_bmax + nitfac *
                (self.params.c_alloc_bmax - self.params.c_alloc_bmin))

            # allocate remainder to stem
            self.state.alstem = (1.0 - self.state.alleaf - self.state.alroot -
                                 self.state.albranch)
            #print self.state.alleaf, self.state.alstem, self.state.albranch, self.state.alroot

        elif self.control.alloc_model == "GRASSES":

            # calculate the N limitation based on available canopy N
            # this logic appears counter intuitive, but it works out when
            # applied with the perhaps backwards logic below
            nf = self.state.shootnc

            # case - completely limited by N availability
            if nf < self.params.nf_min:
                nlim = 0.0
            elif nf < self.params.nf_crit:

                nlim = ((nf - self.params.nf_min) /
                        (self.params.nf_crit - self.params.nf_min))
            # case - no N limitation
            else:
                nlim = 1.0

            # no constraint on water uptake via root mass, so makes no sense
            #limitation = self.sma(min(nlim, self.state.wtfac_root))

            # to increase allocation if water stressed.
            # dependent on lifespan of the roots...
            limitation = self.sma(nlim)
            self.state.prev_sma = limitation

            # figure out root allocation given available water & nutrients
            # hyperbola shape to allocation
            self.state.alroot = (
                self.params.c_alloc_rmax * self.params.c_alloc_rmin /
                (self.params.c_alloc_rmin +
                 (self.params.c_alloc_rmax - self.params.c_alloc_rmin) *
                 limitation))

            self.state.alstem = 0.0
            self.state.albranch = 0.0
            self.state.alleaf = (1.0 - self.state.alroot)
            #print nlim, limitation, self.state.alleaf, self.state.alroot
        elif self.control.alloc_model == "ALLOMETRIC":

            # calculate the N limitation based on available canopy N
            # this logic appears counter intuitive, but it works out when
            # applied with the perhaps backwards logic below
            nf = self.state.shootnc

            # case - completely limited by N availability
            if nf < self.params.nf_min:
                nlim = 0.0
            elif nf < self.params.nf_crit:

                nlim = ((nf - self.params.nf_min) /
                        (self.params.nf_crit - self.params.nf_min))
            # case - no N limitation
            else:
                nlim = 1.0

            # no constraint on water uptake via root mass, so makes no sense
            #limitation = self.sma(min(nlim, self.state.wtfac_root))

            # to increase allocation if water stressed.
            # dependent on lifespan of the roots...
            limitation = self.sma(nlim)
            self.state.prev_sma = limitation

            # figure out root allocation given available water & nutrients
            # hyperbola shape to allocation
            self.state.alroot = (
                self.params.c_alloc_rmax * self.params.c_alloc_rmin /
                (self.params.c_alloc_rmin +
                 (self.params.c_alloc_rmax - self.params.c_alloc_rmin) *
                 limitation))

            #self.state.alroot = (self.params.c_alloc_rmin +
            #                    (self.params.c_alloc_rmax -
            #                     self.params.c_alloc_rmin) * limitation)

            # Calculate tree height: allometric reln using the power function
            # (Causton, 1985)
            self.state.canht = (self.params.heighto *
                                self.state.stem**self.params.htpower)

            # LAI to stem sapwood cross-sectional area (As m-2 m-2)
            # (dimensionless)
            # Assume it varies between LS0 and LS1 as a linear function of tree
            # height (m)
            sap_cross_sec_area = (
                ((self.state.sapwood * const.TONNES_AS_KG * const.M2_AS_HA) /
                 self.params.cfracts) / self.state.canht / self.params.density)

            leaf2sap = self.state.lai / sap_cross_sec_area

            # Allocation to leaves dependant on height. Modification of pipe
            # theory, leaf-to-sapwood ratio is not constant above a certain
            # height, due to hydraulic constraints (Magnani et al 2000; Deckmyn
            # et al. 2006).
            if self.params.leafsap0 < self.params.leafsap1:
                min_target = self.params.leafsap0
            else:
                min_target = self.params.leafsap1

            if self.params.leafsap0 > self.params.leafsap1:
                max_target = self.params.leafsap0
            else:
                max_target = self.params.leafsap1

            leaf2sa_target = (self.params.leafsap0 +
                              (self.params.leafsap1 - self.params.leafsap0) *
                              (self.state.canht - self.params.height0) /
                              (self.params.height1 - self.params.height0))
            leaf2sa_target = clip(leaf2sa_target,
                                  min=min_target,
                                  max=max_target)

            self.state.alleaf = self.alloc_goal_seek(leaf2sap, leaf2sa_target,
                                                     self.params.c_alloc_fmax,
                                                     self.params.targ_sens)

            # Allocation to branch dependent on relationship between the stem
            # and branch
            target_branch = (self.params.branch0 *
                             self.state.stem**self.params.branch1)
            self.state.albranch = self.alloc_goal_seek(
                self.state.branch, target_branch, self.params.c_alloc_bmax,
                self.params.targ_sens)

            #target_coarse_roots = 0.34 * self.state.stem**0.84
            #self.state.alcroot = self.alloc_goal_seek(self.state.croot,
            #                                           target_coarse_roots,
            #                                           self.params.c_alloc_crmax,
            #                                           self.params.targ_sens)

            # allocation to stem is the residual
            self.state.alstem = (1.0 - self.state.alroot -
                                 self.state.albranch - self.state.alleaf)

            #print self.state.alleaf, self.state.albranch, self.state.alstem, self.state.alroot
        else:
            raise AttributeError('Unknown C allocation model')

        # Total allocation should be one, if not print warning:
        total_alloc = (self.state.alroot + self.state.alleaf +
                       self.state.albranch + self.state.alstem)
        if float_gt(total_alloc, 1.0):
            raise RuntimeError, "Allocation fracs > 1"

        #print self.state.alleaf, self.state.alstem, self.state.albranch, self.state.alroot

    def alloc_goal_seek(self, simulated, target, alloc_max, sensitivity):
        arg = 0.5 + 0.5 * ((1.0 - simulated / target) / sensitivity)
        return max(0.0, alloc_max * min(1.0, arg))

    def allocate_stored_c_and_n(self, init):
        """
        Allocate stored C&N. This is either down as the model is initialised 
        for the first time or at the end of each year. 
        """
        # JUST here for FACE stuff as first year of ele should have last years alloc fracs
        #if init == True:
        #    self.state.alleaf = 0.26
        #    self.state.alroot = 0.11
        #    self.state.albranch = 0.06
        #    self.state.alstem = 0.57

        # ========================
        # Carbon - fixed fractions
        # ========================
        self.state.c_to_alloc_shoot = self.state.alleaf * self.state.cstore
        self.state.c_to_alloc_root = self.state.alroot * self.state.cstore
        self.state.c_to_alloc_branch = self.state.albranch * self.state.cstore
        self.state.c_to_alloc_stem = self.state.alstem * self.state.cstore

        # =========
        # Nitrogen
        # =========

        # Fixed ratios N allocation to woody components.

        # N flux into new ring (immobile component -> structrual components)
        self.state.n_to_alloc_stemimm = (self.state.cstore *
                                         self.state.alstem *
                                         self.params.ncwimm)

        # N flux into new ring (mobile component -> can be retrans for new
        # woody tissue)
        self.state.n_to_alloc_stemmob = (
            self.state.cstore * self.state.alstem *
            (self.params.ncwnew - self.params.ncwimm))

        self.state.n_to_alloc_branch = (self.state.cstore *
                                        self.state.albranch *
                                        self.params.ncbnew)

        # Calculate remaining N left to allocate to leaves and roots
        ntot = (self.state.nstore - self.state.n_to_alloc_stemimm -
                self.state.n_to_alloc_stemmob - self.state.n_to_alloc_branch)

        # allocate remaining N to flexible-ratio pools
        self.state.n_to_alloc_shoot = (
            ntot * self.state.alleaf /
            (self.state.alleaf + self.state.alroot * self.params.ncrfac))
        self.state.n_to_alloc_root = ntot - self.state.n_to_alloc_shoot

    def nitrogen_allocation(self, ncbnew, ncwimm, ncwnew, fdecay, rdecay, doy,
                            days_in_yr, project_day):
        """ Nitrogen distribution - allocate available N through system.
        N is first allocated to the woody component, surplus N is then allocated
        to the shoot and roots with flexible ratios.
        
        References:
        -----------
        McMurtrie, R. E. et al (2000) Plant and Soil, 224, 135-152.
        
        Parameters:
        -----------
        ncbnew : float
            N:C ratio of branch
        ncwimm : float
            N:C ratio of immobile stem
        ncwnew : float
            N:C ratio of mobile stem
        fdecay : float
            foliage decay rate
        rdecay : float
            fine root decay rate
        """
        # N retranslocated proportion from dying plant tissue and stored within
        # the plant
        self.fluxes.retrans = self.nitrogen_retrans(fdecay, rdecay, doy)
        self.fluxes.nuptake = self.calculate_nuptake(project_day)

        # Ross's Root Model.
        if self.control.model_optroot == True:

            # convert t ha-1 day-1 to gN m-2 year-1
            nsupply = (self.calculate_nuptake() * const.TONNES_HA_2_G_M2 *
                       const.DAYS_IN_YRS)

            # covnert t ha-1 to kg DM m-2
            rtot = (self.state.root * const.TONNES_HA_2_KG_M2 /
                    self.params.cfracts)
            self.fluxes.nuptake_old = self.fluxes.nuptake

            (self.state.root_depth, self.fluxes.nuptake,
             self.fluxes.rabove) = self.rm.main(rtot, nsupply, depth_guess=1.0)

            #umax = self.rm.calc_umax(self.fluxes.nuptake)
            #print umax

            # covert nuptake from gN m-2 year-1  to t ha-1 day-1
            self.fluxes.nuptake = (self.fluxes.nuptake *
                                   const.G_M2_2_TONNES_HA * const.YRS_IN_DAYS)

            # covert from kg DM N m-2 to t ha-1
            self.fluxes.deadroots = (self.params.rdecay * self.fluxes.rabove *
                                     self.params.cfracts *
                                     const.KG_M2_2_TONNES_HA)

            self.fluxes.deadrootn = (self.state.rootnc *
                                     (1.0 - self.params.rretrans) *
                                     self.fluxes.deadroots)

        # Mineralised nitrogen lost from the system by volatilisation/leaching
        self.fluxes.nloss = self.params.rateloss * self.state.inorgn

        # total nitrogen to allocate
        ntot = self.fluxes.nuptake + self.fluxes.retrans

        if self.control.deciduous_model:
            # allocate N to pools with fixed N:C ratios

            # N flux into new ring (immobile component -> structrual components)
            self.fluxes.npstemimm = (self.fluxes.wnimrate *
                                     self.state.growing_days[doy])

            # N flux into new ring (mobile component -> can be retrans for new
            # woody tissue)
            self.fluxes.npstemmob = (self.fluxes.wnmobrate *
                                     self.state.growing_days[doy])

            self.fluxes.nproot = self.state.n_to_alloc_root / days_in_yr

            self.fluxes.npleaf = (self.fluxes.lnrate *
                                  self.state.growing_days[doy])

            self.fluxes.npbranch = (self.fluxes.bnrate *
                                    self.state.growing_days[doy])
        else:
            # allocate N to pools with fixed N:C ratios

            # N flux into new ring (immobile component -> structural components)
            self.fluxes.npstemimm = self.fluxes.npp * self.state.alstem * ncwimm

            # N flux into new ring (mobile component -> can be retrans for new
            # woody tissue)
            self.fluxes.npstemmob = (self.fluxes.npp * self.state.alstem *
                                     (ncwnew - ncwimm))
            self.fluxes.npbranch = (self.fluxes.npp * self.state.albranch *
                                    ncbnew)

            # If we have allocated more N than we have available
            #  - cut back N prodn
            arg = (self.fluxes.npstemimm + self.fluxes.npstemmob +
                   self.fluxes.npbranch)

            if float_gt(arg, ntot) and self.control.fixleafnc == False:
                self.fluxes.npp *= (
                    ntot / (self.fluxes.npstemimm + self.fluxes.npstemmob +
                            self.fluxes.npbranch))

                # need to adjust growth values accordingly as well
                self.fluxes.cpleaf = self.fluxes.npp * self.state.alleaf
                self.fluxes.cproot = self.fluxes.npp * self.state.alroot
                self.fluxes.cpbranch = self.fluxes.npp * self.state.albranch
                self.fluxes.cpstem = self.fluxes.npp * self.state.alstem

                self.fluxes.npbranch = (self.fluxes.npp * self.state.albranch *
                                        ncbnew)
                self.fluxes.npstemimm = (self.fluxes.npp * self.state.alstem *
                                         ncwimm)
                self.fluxes.npstemmob = (self.fluxes.npp * self.state.alstem *
                                         (ncwnew - ncwimm))

            ntot -= (self.fluxes.npbranch + self.fluxes.npstemimm +
                     self.fluxes.npstemmob)

            # allocate remaining N to flexible-ratio pools
            self.fluxes.npleaf = (
                ntot * self.state.alleaf /
                (self.state.alleaf + self.state.alroot * self.params.ncrfac))
            self.fluxes.nproot = ntot - self.fluxes.npleaf

    def nitrogen_retrans(self, fdecay, rdecay, doy):
        """ Nitrogen retranslocated from senesced plant matter.
        Constant rate of n translocated from mobile pool

        Parameters:
        -----------
        fdecay : float
            foliage decay rate
        rdecay : float
            fine root decay rate

        Returns:
        --------
        N retrans : float
            N retranslocated plant matter

        """
        if self.control.deciduous_model:
            leafretransn = (self.params.fretrans * self.fluxes.lnrate *
                            self.state.remaining_days[doy])
        else:
            leafretransn = self.params.fretrans * fdecay * self.state.shootn

        arg1 = (leafretransn +
                self.params.rretrans * rdecay * self.state.rootn +
                self.params.bretrans * self.params.bdecay * self.state.branchn)
        arg2 = (
            self.params.wretrans * self.params.wdecay * self.state.stemnmob +
            self.params.retransmob * self.state.stemnmob)

        return arg1 + arg2

    def calculate_nuptake(self, project_day):
        """ N uptake depends on the rate at which soil mineral N is made 
        available to the plants.
        
        Returns:
        --------
        nuptake : float
            N uptake
            
        References:
        -----------
        * Dewar and McMurtrie, 1996, Tree Physiology, 16, 161-171.    
        * Raich et al. 1991, Ecological Applications, 1, 399-429.
            
        """
        if self.control.nuptake_model == 0:
            # Constant N uptake
            nuptake = self.params.nuptakez
        elif self.control.nuptake_model == 1:
            # evaluate nuptake : proportional to dynamic inorganic N pool
            nuptake = self.params.rateuptake * self.state.inorgn
        elif self.control.nuptake_model == 2:
            # N uptake is a saturating function on root biomass following
            # Dewar and McMurtrie, 1996.

            # supply rate of available mineral N
            U0 = self.params.rateuptake * self.state.inorgn
            Kr = self.params.kr
            nuptake = max(U0 * self.state.root / (self.state.root + Kr), 0.0)
        elif self.control.nuptake_model == 3:
            # N uptake is a function of available soil N, soil moisture
            # following a Michaelis-Menten approach
            # See Raich et al. 1991, pg. 423.

            vcn = 1.0 / 0.0215  # 46.4
            arg1 = (vcn * self.state.shootn) - self.state.shoot
            arg2 = (vcn * self.state.shootn) + self.state.shoot
            self.params.ac += self.params.adapt * arg1 / arg2
            self.params.ac = max(min(1.0, self.params.ac), 0.0)

            # soil moisture is assumed to influence nutrient diffusion rate
            # through the soil, ks [0,1]
            theta = self.state.pawater_root / self.params.wcapac_root
            ks = 0.9 * theta**3.0 + 0.1

            arg1 = self.params.nmax * ks * self.state.inorgn
            arg2 = self.params.knl + (ks * self.state.inorgn)
            arg3 = exp(0.0693 * self.met_data['tair'][project_day])
            arg4 = 1.0 - self.params.ac
            nuptake = (arg1 / arg2) * arg3 * arg4

            #print self.params.nmax, self.params.knl, ks, exp(0.0693 * tavg)

        else:
            raise AttributeError('Unknown N uptake option')

        return nuptake

    def carbon_allocation(self, nitfac, doy, days_in_yr):
        """ C distribution - allocate available C through system

        Parameters:
        -----------
        nitfac : float
            leaf N:C as a fraction of 'Ncmaxfyoung' (max 1.0)
        """
        if self.control.deciduous_model:
            days_left = self.state.growing_days[doy]
            self.fluxes.cpleaf = self.fluxes.lrate * days_left
            self.fluxes.cpbranch = self.fluxes.brate * days_left
            self.fluxes.cpstem = self.fluxes.wrate * days_left
            self.fluxes.cproot = self.state.c_to_alloc_root * 1.0 / days_in_yr
        else:
            self.fluxes.cpleaf = self.fluxes.npp * self.state.alleaf
            self.fluxes.cproot = self.fluxes.npp * self.state.alroot
            self.fluxes.cpbranch = self.fluxes.npp * self.state.albranch
            self.fluxes.cpstem = self.fluxes.npp * self.state.alstem

            #print self.fluxes.cpleaf, self.fluxes.npp, self.state.alleaf

        # evaluate SLA of new foliage accounting for variation in SLA
        # with tree and leaf age (Sands and Landsberg, 2002). Assume
        # SLA of new foliage is linearly related to leaf N:C ratio
        # via nitfac. Based on date from two E.globulus stands in SW Aus, see
        # Corbeels et al (2005) Ecological Modelling, 187, 449-474.
        # (m2 onesided/kg DW)
        self.state.sla = (self.params.slazero + nitfac *
                          (self.params.slamax - self.params.slazero))

        if self.control.deciduous_model:
            if float_eq(self.state.shoot, 0.0):
                self.state.lai = 0.0
            elif self.state.leaf_out_days[doy] > 0.0:
                self.state.lai += (
                    self.fluxes.cpleaf *
                    (self.state.sla * const.M2_AS_HA /
                     (const.KG_AS_TONNES * self.params.cfracts)) -
                    (self.fluxes.deadleaves + self.fluxes.ceaten) *
                    self.state.lai / self.state.shoot)
            else:
                self.state.lai = 0.0
        else:
            # update leaf area [m2 m-2]
            if float_eq(self.state.shoot, 0.0):
                self.state.lai = 0.0
            else:
                self.state.lai += (
                    self.fluxes.cpleaf *
                    (self.state.sla * const.M2_AS_HA /
                     (const.KG_AS_TONNES * self.params.cfracts)) -
                    (self.fluxes.deadleaves + self.fluxes.ceaten) *
                    self.state.lai / self.state.shoot)

    def precision_control(self, tolerance=1E-08):
        """ Detect very low values in state variables and force to zero to 
        avoid rounding and overflow errors """

        # C & N state variables
        if self.state.shoot < tolerance:
            self.fluxes.deadleaves += self.state.shoot
            self.fluxes.deadleafn += self.state.shootn
            self.state.shoot = 0.0
            self.state.shootn = 0.0

        if self.state.branch < tolerance:
            self.fluxes.deadbranch += self.state.branch
            self.fluxes.deadbranchn += self.state.branchn
            self.state.branch = 0.0
            self.state.branchn = 0.0

        if self.state.root < tolerance:
            self.fluxes.deadrootn += self.state.rootn
            self.fluxes.deadroots += self.state.root
            self.state.root = 0.0
            self.state.rootn = 0.0

        if self.state.stem < tolerance:
            self.fluxes.deadstems += self.state.stem
            self.fluxes.deadstemn += self.state.stemn
            self.state.stem = 0.0
            self.state.stemn = 0.0
            self.state.stemnimm = 0.0
            self.state.stemnmob = 0.0

        # need separate one as this will become very small if there is no
        # mobile stem N
        if self.state.stemnmob < tolerance:
            self.fluxes.deadstemn += self.state.stemnmob
            self.state.stemnmob = 0.0

        if self.state.stemnimm < tolerance:
            self.fluxes.deadstemn += self.state.stemnimm
            self.state.stemnimm = 0.0

    def update_plant_state(self, fdecay, rdecay, project_day, doy):
        """ Daily change in C content

        Parameters:
        -----------
        fdecay : float
            foliage decay rate
        rdecay : float
            fine root decay rate

        """
        #
        # Carbon pools
        #
        self.state.shoot += (self.fluxes.cpleaf - self.fluxes.deadleaves -
                             self.fluxes.ceaten)
        self.state.root += self.fluxes.cproot - self.fluxes.deadroots
        self.state.branch += self.fluxes.cpbranch - self.fluxes.deadbranch
        self.state.stem += self.fluxes.cpstem - self.fluxes.deadstems

        # annoying but can't see an easier way with the code as it is.
        # If we are modelling grases, i.e. no stem them without this
        # the sapwood will end up being reduced to a silly number as
        # deadsapwood will keep being removed from the pool, even though there
        # is no wood.
        if self.state.stem <= 0.01:
            self.state.sapwood = 0.01
        else:
            self.state.sapwood += self.fluxes.cpstem - self.fluxes.deadsapwood

        #
        # Nitrogen pools
        #
        if self.control.deciduous_model:
            self.state.shootn += (
                self.fluxes.npleaf -
                (self.fluxes.lnrate * self.state.remaining_days[doy]) -
                self.fluxes.neaten)
        else:
            self.state.shootn += (self.fluxes.npleaf -
                                  fdecay * self.state.shootn -
                                  self.fluxes.neaten)

        self.state.branchn += (self.fluxes.npbranch -
                               self.params.bdecay * self.state.branchn)
        self.state.rootn += self.fluxes.nproot - rdecay * self.state.rootn

        self.state.stemnimm += (self.fluxes.npstemimm -
                                self.params.wdecay * self.state.stemnimm)
        self.state.stemnmob += (self.fluxes.npstemmob -
                                self.params.wdecay * self.state.stemnmob -
                                self.params.retransmob * self.state.stemnmob)

        #print self.state.stemnmob, self.fluxes.npstemmob - self.params.wdecay * self.state.stemnmob, self.fluxes.npstemmob, self.params.wdecay * self.state.stemnmob

        self.state.stemn = self.state.stemnimm + self.state.stemnmob

        if self.control.deciduous_model:
            self.calculate_cn_store()

        #============================
        # Enforce maximum N:C ratios.
        # ===========================
        # This doesn't make sense for the deciduous model because of the ramp
        # function. The way the deciduous logic works we now before we start
        # how much N we have to allocate so it is impossible to allocate in
        # excess. Therefore this is only relevant for evergreen model.
        if not self.control.deciduous_model:

            # If foliage or root N/C exceeds its max, then N uptake is cut back

            # maximum leaf n:c ratio is function of stand age
            #  - switch off age effect by setting ncmaxfyoung = ncmaxfold
            age_effect = ((self.state.age - self.params.ageyoung) /
                          (self.params.ageold - self.params.ageyoung))

            ncmaxf = (
                self.params.ncmaxfyoung -
                (self.params.ncmaxfyoung - self.params.ncmaxfold) * age_effect)

            if float_lt(ncmaxf, self.params.ncmaxfold):
                ncmaxf = self.params.ncmaxfold

            if float_gt(ncmaxf, self.params.ncmaxfyoung):
                ncmaxf = self.params.ncmaxfyoung

            extras = 0.0
            if self.state.lai > 0.0:

                if float_gt(self.state.shootn, (self.state.shoot * ncmaxf)):
                    extras = self.state.shootn - self.state.shoot * ncmaxf

                    # Ensure N uptake cannot be reduced below zero.
                    if float_gt(extras, self.fluxes.nuptake):
                        extras = self.fluxes.nuptake

                    self.state.shootn -= extras
                    self.fluxes.nuptake -= extras

            # if root N:C ratio exceeds its max, then nitrogen uptake is cut
            # back. n.b. new ring n/c max is already set because it is related
            # to leaf n:c
            ncmaxr = ncmaxf * self.params.ncrfac  # max root n:c
            extrar = 0.0
            if float_gt(self.state.rootn, (self.state.root * ncmaxr)):

                extrar = self.state.rootn - self.state.root * ncmaxr

                # Ensure N uptake cannot be reduced below zero.
                if float_gt((extras + extrar), self.fluxes.nuptake):
                    extrar = self.fluxes.nuptake - extras

                self.state.rootn -= extrar
                self.fluxes.nuptake -= extrar

    def calculate_cn_store(self):
        """ Deciduous trees store carbohydrate during the winter which they then
        use in the following year to build new leaves (buds & budburst are 
        implied) 
        """
        # Total C & N storage to allocate annually.
        self.state.cstore += self.fluxes.npp
        self.state.nstore += self.fluxes.nuptake + self.fluxes.retrans
        self.state.anpp += self.fluxes.npp
Ejemplo n.º 7
0
class PlantGrowth(object):
    """ G'DAY plant growth module.

    Calls photosynthesis model, water balance and evolve plant state.
    Pools recieve C through allocation of accumulated photosynthate and N
    from both soil uptake and retranslocation within the plant.

    Key feedback through soil N mineralisation and plant N uptake

    * Note met_forcing is an object with radiation, temp, precip data, etc.
    """
    def __init__(self, control, params, state, fluxes, met_data):
        """
        Parameters
        ----------
        control : integers, object
            model control flags
        params: floats, object
            model parameters
        state: floats, object
            model state
        fluxes : floats, object
            model fluxes
        met_data : floats, dictionary
            meteorological forcing data

        """
        self.params = params
        self.fluxes = fluxes
        self.control = control
        self.state = state
        self.met_data = met_data
        self.bw = Bewdy(self.control, self.params, self.state, self.fluxes,
                        self.met_data)
        self.wb = WaterBalance(self.control, self.params, self.state,
                               self.fluxes, self.met_data)
        self.pp = PlantProdModel(self.control, self.params, self.state,
                                 self.fluxes, self.met_data)
        self.wl = WaterLimitedNPP(self.control, self.params, self.state,
                                  self.fluxes)

        self.mt = Mate(self.control, self.params, self.state, self.fluxes,
                       self.met_data)
        
        self.sm = SoilMoisture(self.control, self.params, self.state, 
                               self.fluxes)
        
        self.rm = RootingDepthModel(d0=0.35, r0=0.05, top_soil_depth=0.3)
        
    def calc_day_growth(self, project_day, fdecay, rdecay, daylen, doy, days_in_yr):
        """Evolve plant state, photosynthesis, distribute N and C"

        Parameters:
        -----------
        project_day : integer
            simulation day
        fdecay : float
            foliage decay rate
        rdecay : float
            fine root decay rate
        """
        # calculate NPP
        self.carbon_production(project_day, daylen)

        # calculate water balance and adjust C production for any water stress.
        # If we are using the MATE model then water stress is applied directly
        # through the Ci:Ca reln, so do not apply any scalar to production.
        if self.control.water_model == 1:
            self.wb.calculate_water_balance(project_day, daylen)
            # adjust carbon production for water limitations, all models except
            # MATE!
            if self.control.assim_model != 7:
                self.wl.adjust_cproduction(self.control.water_model)

        # leaf N:C as a fraction of Ncmaxyoung, i.e. the max N:C ratio of
        # foliage in young stand
        nitfac = min(1.0, self.state.shootnc / self.params.ncmaxfyoung)
        
        if not self.control.deciduous_model:
            # figure out allocation fractions for C for the evergreen model. For
            # the deciduous model these are calculated at the annual time step.
            self.allocate_carbon(nitfac)
        
        # Distribute new C and N through the system
        self.carbon_distribution(nitfac, doy, days_in_yr)
        
        (ncbnew, ncwimm, ncwnew) = self.calculate_ncwood_ratios(nitfac)
        self.nitrogen_distribution(ncbnew, ncwimm, ncwnew, fdecay, rdecay, doy)
        
        
        self.update_plant_state(fdecay, rdecay, project_day)
         
    def calculate_ncwood_ratios(self, nitfac):
        """ Estimate the N:C ratio in the branch and stem. Option to vary
        the N:C ratio of the stem following Jeffreys (1999) or keep it a fixed
        fraction

        Parameters:
        -----------
        nitfac : float
            leaf N:C as a fraction of the max N:C ratio of foliage in young
            stand

        Returns:
        --------
        ncbnew : float
            N:C ratio of branch
        ncwimm : float
            N:C ratio of immobile stem
        ncwnew : float
            N:C ratio of mobile stem

        References:
        ----------
        * Jeffreys, M. P. (1999) Dynamics of stemwood nitrogen in Pinus radiata
          with modelled implications for forest productivity under elevated
          atmospheric carbon dioxide. PhD.
        """
        # n:c ratio of new branch wood
        ncbnew = (self.params.ncbnew + nitfac *
                 (self.params.ncbnew - self.params.ncbnewz))
        
        # fixed N:C in the stemwood
        if self.control.fixed_stem_nc == 1:
            # n:c ratio of stemwood - immobile pool and new ring
            ncwimm = (self.params.ncwimm + nitfac *
                     (self.params.ncwimm - self.params.ncwimmz))
            
            # New stem ring N:C at critical leaf N:C (mobile)
            ncwnew = (self.params.ncwnew + nitfac *
                     (self.params.ncwnew - self.params.ncwnewz))
            
        # vary stem N:C based on reln with foliage, see Jeffreys. Jeffreys 1999
        # showed that N:C ratio of new wood increases with foliar N:C ratio,
        # modelled here based on evidence as a linear function.
        else:
            ncwimm = max(0.0, (0.0282 * self.state.shootnc + 0.000234) * 
                         self.params.fhw)

            # New stem ring N:C at critical leaf N:C (mobile)
            ncwnew = max(0.0, 0.162 * self.state.shootnc - 0.00143)
        
        return (ncbnew, ncwimm, ncwnew)

    def carbon_production(self, project_day, daylen):
        """ Calculate GPP, NPP and plant respiration

        Parameters:
        -----------
        project_day : integer
            simulation day
        daylen : float
            daytime length (hrs)

        References:
        -----------
        * Jackson, J. E. and Palmer, J. W. (1981) Annals of Botany, 47, 561-565.
        """

        # leaf nitrogen content
        if self.state.lai > 0.0:
            # Leaf N content (g m-2)                       
            self.state.ncontent = (self.state.shootnc * self.params.cfracts /
                                   self.state.sla * const.KG_AS_G)
        else:
            self.state.ncontent = 0.0
            
        # fractional ground cover.
        if float_lt(self.state.lai, self.params.lai_cover):
            frac_gcover = self.state.lai / self.params.lai_cover
        else:
            frac_gcover = 1.0

        # Radiance intercepted by the canopy, accounting for partial closure
        # Jackson and Palmer (1981), derived from beer's law
        if self.state.lai > 0.0:
            self.state.light_interception = ((1.0 - exp(-self.params.kext *
                                             self.state.lai / frac_gcover)) *
                                             frac_gcover)
        else:
            self.state.light_interception = 0.0
        
        if self.control.water_stress:
            # Calculate the soil moisture availability factors [0,1] in the 
            # topsoil and the entire root zone
            (self.state.wtfac_tsoil, 
                self.state.wtfac_root) = self.sm.calculate_soil_water_fac()
        else:
            # really this should only be a debugging option!
            self.state.wtfac_tsoil = 1.0
            self.state.wtfac_root = 1.0
            
        # Estimate photosynthesis using an empirical model
        if self.control.assim_model >=0 and self.control.assim_model <= 4:
            self.pp.calculate_photosynthesis(project_day)
        # Estimate photosynthesis using the mechanistic BEWDY model
        elif self.control.assim_model >=5 and self.control.assim_model <= 6:
            # calculate plant C uptake using bewdy
            self.bw.calculate_photosynthesis(frac_gcover, project_day, daylen)
        # Estimate photosynthesis using the mechanistic MATE model. Also need to
        # calculate a water availability scalar to determine Ci:Ca reln.
        elif self.control.assim_model ==7:
            self.mt.calculate_photosynthesis(project_day, daylen)
        else:
            raise AttributeError('Unknown assimilation model')

    def allocate_carbon(self, nitfac):
        """Carbon allocation fractions to move photosynthate through the plant.
        Allocations to foliage tends to decrease with stand age and wood stock
        increases (Makela and Hari, 1986; Cannell and Dewar, 1994; 
        Magnani et al, 2000). In stressed (soil/nutrient) regions fine root 
        allocations increases.

        Parameters:
        -----------
        nitfac : float
            leaf N:C as a fraction of 'Ncmaxfyoung' (max 1.0)

        Returns:
        --------
        alleaf : float
            allocation fraction for shoot
        alroot : float
            allocation fraction for fine roots
        albranch : float
            allocation fraction for branches
        alstem : float
            allocation fraction for stem
        alroot_exudate : float
            allocation fraction for root exudate 
       
        References:
        -----------
        McMurtrie, R. E. et al (2000) Plant and Soil, 224, 135-152.
        """
        self.state.alleaf = (self.params.callocf + nitfac *
                            (self.params.callocf - self.params.callocfz))
    
        self.state.alroot = (self.params.callocr + nitfac *
                            (self.params.callocr - self.params.callocrz))

        self.state.albranch = (self.params.callocb + nitfac *
                              (self.params.callocb - self.params.callocbz))
        
        # Remove some of the allocation to wood and instead allocate it to
        # root exudation. Following McMurtrie et al. 2000
        self.state.alroot_exudate = self.params.callocrx
        
        # allocate remainder to stem
        self.state.alstem = (1.0 - self.state.alleaf - self.state.alroot - 
                             self.state.albranch - self.state.alroot_exudate)
        
    def nitrogen_distribution(self, ncbnew, ncwimm, ncwnew, fdecay, rdecay, doy):
        """ Nitrogen distribution - allocate available N through system.
        N is first allocated to the woody component, surplus N is then allocated
        to the shoot and roots with flexible ratios.
        
        References:
        -----------
        McMurtrie, R. E. et al (2000) Plant and Soil, 224, 135-152.
        
        Parameters:
        -----------
        ncbnew : float
            N:C ratio of branch
        ncwimm : float
            N:C ratio of immobile stem
        ncwnew : float
            N:C ratio of mobile stem
        fdecay : float
            foliage decay rate
        rdecay : float
            fine root decay rate
        """
        # N retranslocated proportion from dying plant tissue and stored within
        # the plant
        self.fluxes.retrans = self.nitrogen_retrans(fdecay, rdecay)
        self.fluxes.nuptake = self.calculate_nuptake()
        
        # Ross's Root Model.
        # NOT WORKING YET
        if self.control.model_optroot == 1:    
                    
            # Attempt at floating rateuptake
            #slope = (20.0 - 0.1) / ((6.0) - 0.0)
            #y = slope * self.state.root + 0.0
            #self.fluxes.nuptake = (y/365.25) * self.state.inorgn
                
            # have you got all these units conversion right, they don't appear 
            # very consistent? you have grams and kg?!
            
            # convert t ha-1 d-1 to gN m-2 yr-1
            nsupply = self.calculate_nuptake() * 365.25 * 100.0 # t ha-1->gN m-2
            
            # covnert t ha-1 to kg m-2
            rtot = self.state.root * 0.1
            
            (root_depth, 
             self.fluxes.nuptake,
             self.fluxes.rabove) = self.rm.main(rtot, nsupply, depth_guess=1.0)
            
            # covert nuptake from gN m-2 yr-1 to t ha-1 d-1
            self.fluxes.nuptake = self.fluxes.nuptake * 0.01 / 365.25
            
            # covert from kg N m-2 to t ha-1
            self.fluxes.deadroots = self.params.rdecay * self.fluxes.rabove * 10
            self.fluxes.deadrootn = (self.state.rootnc * 
                                    (1.0 - self.params.rretrans) * 
                                    self.fluxes.deadroots)
            
            print  root_depth
            #print self.fluxes.gpp*100, self.state.lai, self.state.root*100, \
            #      self.fluxes.nuptake *100.
            #print self.fluxes.gpp*100, self.state.lai, self.state.root*100, \
            #      root_depth, self.fluxes.nuptake *100.
        
        
        # N lost from system is proportional to the soil inorganic N pool, 
        # where the rate constant empirically defines gaseous and leaching 
        # losses, see McMurtrie et al. 2001.
        self.fluxes.nloss = self.params.rateloss * self.state.inorgn
    
        # total nitrogen to allocate 
        ntot = self.fluxes.nuptake + self.fluxes.retrans
        
        # N flux into root exudation, see McMurtrie et al. 2000
        self.fluxes.nrootexudate = (self.fluxes.npp * self.state.alroot_exudate * 
                                    self.params.vxfix)
        
        if self.control.deciduous_model:
            # allocate N to pools with fixed N:C ratios
            # N flux into new ring (immobile component -> structrual components)
            self.fluxes.npstemimm = self.fluxes.cpstem * ncwimm
    
            # N flux into new ring (mobile component -> can be retrans for new
            # woody tissue)
            self.fluxes.npstemmob = self.fluxes.cpstem * (ncwnew - ncwimm)
            self.fluxes.npbranch = 0.0    
            self.fluxes.nproot = self.fluxes.cproot * self.state.rootnc
            self.fluxes.npleaf = (self.fluxes.lnrate * 
                                    self.state.growing_days[doy])
        else:
            # allocate N to pools with fixed N:C ratios
            
            # N flux into new ring (immobile component -> structrual components)
            self.fluxes.npstemimm = self.fluxes.npp * self.state.alstem * ncwimm
    
            # N flux into new ring (mobile component -> can be retrans for new
            # woody tissue)
            self.fluxes.npstemmob = (self.fluxes.npp * self.state.alstem * 
                                     (ncwnew - ncwimm))
            self.fluxes.npbranch = (self.fluxes.npp * self.state.albranch * 
                                     ncbnew)
            
            # If we have allocated more N than we have available 
            #  - cut back N prodn
            arg = (self.fluxes.npstemimm + self.fluxes.npstemmob +
                   self.fluxes.npbranch )
    
            if float_gt(arg, ntot) and not self.control.fixleafnc:
                self.fluxes.npp *= (ntot / (self.fluxes.npstemimm +
                                    self.fluxes.npstemmob + 
                                    self.fluxes.npbranch ))
                self.fluxes.npbranch = (self.fluxes.npp * self.state.albranch * 
                                        ncbnew)
                self.fluxes.npstemimm = (self.fluxes.npp * self.state.alstem * 
                                         ncwimm)
                self.fluxes.npstemmob = (self.fluxes.npp * self.state.alstem * 
                                        (ncwnew - ncwimm))
                
                
            ntot -= (self.fluxes.npbranch + self.fluxes.npstemimm +
                        self.fluxes.npstemmob)
            
            # allocate remaining N to flexible-ratio pools
            self.fluxes.npleaf = (ntot * self.state.alleaf / 
                                 (self.state.alleaf + self.state.alroot *
                                 self.params.ncrfac))
            self.fluxes.nproot = ntot - self.fluxes.npleaf
            
    def nitrogen_retrans(self, fdecay, rdecay):
        """ Nitrogen retranslocated from senesced plant matter.
        Constant rate of n translocated from mobile pool

        Parameters:
        -----------
        fdecay : float
            foliage decay rate
        rdecay : float
            fine root decay rate

        Returns:
        --------
        N retrans : float
            N retranslocated plant matter

        """
        if self.control.deciduous_model:
            arg1 = (self.fluxes.leafretransn  +
                    self.params.rretrans * rdecay * self.state.rootn +
                    self.params.bretrans * self.params.bdecay * 
                    self.state.branchn)
            arg2 = (self.params.wretrans * self.params.wdecay *
                    self.state.stemnmob + self.params.retransmob *
                    self.state.stemnmob)
        else:
            arg1 = (self.params.fretrans * fdecay * self.state.shootn +
                    self.params.rretrans * rdecay * self.state.rootn +
                    self.params.bretrans * self.params.bdecay *
                    self.state.branchn)
            arg2 = (self.params.wretrans * self.params.wdecay *
                    self.state.stemnmob + self.params.retransmob *
                    self.state.stemnmob)
        
        return arg1 + arg2
    
    def calculate_nuptake(self):
        """ N uptake from the soil, note as it stands root biomass does not
        affect N uptake.
        
        Returns:
        --------
        nuptake : float
            N uptake
            
        References:
        -----------
        * Dewar and McMurtrie, 1996, Tree Physiology, 16, 161-171.    
            
        """
        if self.control.nuptake_model == 0:
            # Constant N uptake
            nuptake = self.params.nuptakez
        elif self.control.nuptake_model == 1:
            # evaluate nuptake : proportional to dynamic inorganic N pool
            nuptake = self.params.rateuptake * self.state.inorgn
        elif self.control.nuptake_model == 2:
            # Assume N uptake depends on the rate at which soil mineral
            # N is made available (self.params.Uo) and the value or root C
            # at which 50% of the available N is taken up (Dewar and McM).
            arg = (self.params.uo * self.state.inorgn *
                    (self.state.root / (self.state.root + self.params.kr)))
            nuptake = max(arg, 0.0)
        else:
            raise AttributeError('Unknown N uptake assumption')
        
        return nuptake
    
    def carbon_distribution(self, nitfac, doy, days_in_yr):
        """ C distribution - allocate available C through system

        Parameters:
        -----------
        nitfac : float
            leaf N:C as a fraction of 'Ncmaxfyoung' (max 1.0)
        
        References:
        -----------
        
        * Hale, M. G. et al. (1981) Factors affecting root exudation and 
          significance for the rhizosphere ecoystems. Biological and chemical 
          interactions in the rhizosphere. Stockholm. Sweden: Ecological 
          Research Committee of NFR. pg. 43--71.
        * Lambers, J. T. and Poot, P. (2003) Structure and Functioning of 
          Cluster Roots and Plant Responses to Phosphate Deficiency.
        * Martin, J. K. and Puckjeridge, D. W. (1982) Carbon flow through the 
          rhizosphere of wheat crops in South Australia. The cyclcing of carbon,
          nitrogen, sulpher and phosphorous in terrestrial and aquatic
          ecosystems. Canberra: Australian Academy of Science. pg 77--82.
        * McMurtrie, R. E. et al (2000) Plant and Soil, 224, 135-152.
        
        Also see:
        * Rovira, A. D. (1969) Plant Root Exudates. Botanical Review, 35, 
          pg 35--57.
        """
        if self.control.deciduous_model:
            self.fluxes.cpleaf = self.fluxes.lrate * self.state.growing_days[doy]
            self.fluxes.cpbranch = 0.0
            self.fluxes.cpstem = self.fluxes.wrate * self.state.growing_days[doy]
            self.fluxes.cproot = self.state.c_to_alloc_root * 1.0 / days_in_yr
        else:
            self.fluxes.cpleaf = self.fluxes.npp * self.state.alleaf
            self.fluxes.cproot = self.fluxes.npp * self.state.alroot
            self.fluxes.cpbranch = self.fluxes.npp * self.state.albranch
            self.fluxes.cpstem = self.fluxes.npp * self.state.alstem
        
        # C flux into root exudation, see McMurtrie et al. 2000. There is no 
        # reference given for the 0.15 in McM, however 14c work by Hale et al and
        # Martin and Puckeridge suggest values range between 10-20% of NPP. So
        # presumably this is where this values of 0.15 (i.e. the average) comes
        # from
        self.fluxes.cprootexudate = self.fluxes.npp * self.state.alroot_exudate
        #self.fluxes.cprootexudate = 0.0
        
        
        # evaluate SLA of new foliage accounting for variation in SLA 
        # with tree and leaf age (Sands and Landsberg, 2002). Assume 
        # SLA of new foliage is linearly related to leaf N:C ratio 
        # via nitfac
        sla_new = (self.params.slazero + nitfac *
                  (self.params.slamax - self.params.slazero))
        sla_new_tonnes_ha_C = (sla_new * const.M2_AS_HA / 
                             (const.KG_AS_TONNES * self.params.cfracts))
        
        
        if self.control.deciduous_model:
            if self.state.shoot == 0.0:
                self.state.lai = 0.0
            elif self.state.leaf_out_days[doy] > 0.0:
                self.state.lai += (self.fluxes.cpleaf * sla_new_tonnes_ha_C -
                                  (self.fluxes.deadleaves + self.fluxes.ceaten)*
                                   self.state.lai / self.state.shoot) 
            else:
                self.state.lai = 0.0
        else:
            # update leaf area [m2 m-2]
            self.state.lai += (self.fluxes.cpleaf * sla_new_tonnes_ha_C -
                               (self.fluxes.deadleaves + self.fluxes.ceaten) *
                                self.state.lai / self.state.shoot)
            
            
    def update_plant_state(self, fdecay, rdecay, project_day):
        """ Daily change in C content

        Parameters:
        -----------
        fdecay : float
            foliage decay rate
        rdecay : float
            fine root decay rate

        """
        self.state.shoot += (self.fluxes.cpleaf - self.fluxes.deadleaves -
                                self.fluxes.ceaten)
        self.state.root += self.fluxes.cproot - self.fluxes.deadroots
        self.state.branch += self.fluxes.cpbranch - self.fluxes.deadbranch
        self.state.stem += self.fluxes.cpstem - self.fluxes.deadstems
        
        if self.control.deciduous_model:
            self.state.shootn += (self.fluxes.npleaf - 
                                 (self.fluxes.deadleafn - self.fluxes.neaten))
        else:
            self.state.shootn += (self.fluxes.npleaf - 
                                  fdecay * self.state.shootn - 
                                  self.fluxes.neaten)
        self.state.shootn = max(0.0, self.state.shootn)
        
        self.state.rootn += self.fluxes.nproot - rdecay * self.state.rootn
        self.state.branchn += (self.fluxes.npbranch - self.params.bdecay *
                                self.state.branchn)
        self.state.stemnimm += (self.fluxes.npstemimm - self.params.wdecay *
                                self.state.stemnimm)
        self.state.stemnmob += (self.fluxes.npstemmob - self.params.wdecay *
                                self.state.stemnmob -
                                self.params.retransmob * self.state.stemnmob)
        self.state.stemn = self.state.stemnimm + self.state.stemnmob
        
        self.state.exu_pool += self.fluxes.cprootexudate
        self.fluxes.microbial_resp = self.calc_microbial_resp(project_day)
        self.state.exu_pool -= self.fluxes.microbial_resp
        #print self.fluxes.microbial_resp, self.fluxes.cprootexudate
        
        self.calculate_cn_store()
        
        if not self.control.deciduous_model:
            
            # maximum leaf n:c ratio is function of stand age
            #  - switch off age effect by setting ncmaxfyoung = ncmaxfold
            age_effect = ((self.state.age - self.params.ageyoung) / 
                            (self.params.ageold - self.params.ageyoung))
            
            ncmaxf = (self.params.ncmaxfyoung - (self.params.ncmaxfyoung -
                        self.params.ncmaxfold) * age_effect)
                   
            if float_lt(ncmaxf, self.params.ncmaxfold):
                ncmaxf = self.params.ncmaxfold
    
            if float_gt(ncmaxf, self.params.ncmaxfyoung):
                ncmaxf = self.params.ncmaxfyoung
    
            # if foliage or root n:c ratio exceeds its max, then nitrogen 
            # uptake is cut back n.b. new ring n/c max is already set because 
            # it is related to leaf n:c
            extrar = 0.
            extras = 0.
            if float_gt(self.state.shootn, (self.state.shoot * ncmaxf)):
                extras = self.state.shootn - self.state.shoot * ncmaxf
    
                #n uptake cannot be reduced below zero.
                if float_gt(extras, self.fluxes.nuptake):
                    extras = self.fluxes.nuptake
    
                self.state.shootn -= extras
                self.fluxes.nuptake -= extras
    
            ncmaxr = ncmaxf * self.params.ncrfac  # max root n:c
    
            if float_gt(self.state.rootn, (self.state.root * ncmaxr)):
                extrar = self.state.rootn - self.state.root * ncmaxr
    
                #n uptake cannot be reduced below zero.
                if float_gt((extras + extrar), self.fluxes.nuptake):
                    extrar = self.fluxes.nuptake - extras
    
                self.state.rootn -= extrar
                self.fluxes.nuptake -= extrar 
            
    def calculate_cn_store(self, tolerance=1.0E-05):        
        cgrowth = (self.fluxes.cpleaf + self.fluxes.cproot + 
                   self.fluxes.cpbranch + self.fluxes.cpstem)
        ngrowth = (self.fluxes.npleaf + self.fluxes.nproot + 
                   self.fluxes.npbranch + self.fluxes.npstemimm + 
                   self.fluxes.npstemmob)
        
        # C storage as TNC
        self.state.cstore += self.fluxes.npp - cgrowth
        self.state.nstore += self.fluxes.nuptake + self.fluxes.retrans - ngrowth

    def calc_microbial_resp(self, project_day):
        """ Based on LPJ-why
        
        References:
        * Wania (2009) Integrating peatlands and permafrost into a dynamic 
          global vegetation model: 1. Evaluation and sensitivity of physical 
          land surface processes. GBC, 23, GB3014.
        * Also part 2 and the geosci paper in 2010
        """
        tsoil = self.met_data['tsoil'][project_day]
        
        # Lloyd and Taylor, 1994
        if tsoil < -10.0:
            temp_resp = 0.0
        else:
            temp_resp = (exp(308.56 * ((1.0 / 56.02) - (1.0 / 
                        (tsoil + const.DEG_TO_KELVIN - 227.13)))))
        
        moist_resp = ((1.0 - exp(-1.0 * self.state.wtfac_tsoil)) /  
                        (1.0 - exp(-1.0)))
        
        # Pool turnover rate = every 2 weeks -> days. Check you have this right
        # and turn into a parameter if so
        k_exu10 = 0.0714128571 
        k_exu = k_exu10 * temp_resp * moist_resp
        
        return  self.state.exu_pool * (1.0 - exp(-k_exu))