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)
Exemple #2
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))
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 and precip data
    """
    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)

    def grow(self, day, date, fdecay, rdecay, daylen, doy=None):
        """Evolve plant state, photosynthesis, distribute N and C"

        Parameters:
        -----------
        day : intefer
            simulation day
        date : date string object
            date object string (yr/mth/day)
        fdecay : float
            foliage decay rate
        rdecay : float
            fine root decay rate
        """
        # calculate NPP
        self.carbon_production(date, 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(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)
        
        # figure out allocation fractions for C
        (alleaf, alroot, albranch, alstem, 
            alroot_exudate) = self.allocate_carbon(nitfac)

        # Distribute new C and N through the system
        self.carbon_distribution(alleaf, alroot, albranch, alstem, 
                                 alroot_exudate, nitfac, doy)
        
        (ncbnew, ncwimm, ncwnew) = self.calculate_ncwood_ratios(nitfac)
        self.nitrogen_distribution(ncbnew, ncwimm, ncwnew, fdecay, rdecay, 
                                    alleaf, alroot, albranch, alstem, 
                                    alroot_exudate, doy)
        
        
        self.update_plant_state(fdecay, rdecay)
         
    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 = (0.0282 * self.state.shootnc + 0.000234) * self.params.fhw

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

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

        Parameters:
        -----------
        day : intefer
            simulation day
        date : date string object
            date object string (yr/mth/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 - math.exp(-self.params.kext *
                                             self.state.lai / frac_gcover)) *
                                             frac_gcover)
        else:
            self.state.light_interception = 0.0

        # 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.wb.calculate_soil_water_fac()
        
        
        
        
        # Estimate photosynthesis using an empirical model
        if self.control.assim_model >=0 and self.control.assim_model <= 4:
            self.pp.calculate_photosynthesis(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, date, 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(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.
        """
        alleaf = (self.params.callocf + nitfac *
                    (self.params.callocf - self.params.callocfz))
    
        alroot = (self.params.callocr + nitfac *
                    (self.params.callocr - self.params.callocrz))

        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
        alroot_exudate = self.params.callocrx
        
        # allocate remainder to stem
        alstem = 1.0 - alleaf - alroot - albranch - alroot_exudate
        #print alleaf, alroot, albranch, alstem
        #sys.exit()
        return (alleaf, alroot, albranch, alstem, alroot_exudate)

    def nitrogen_distribution(self, ncbnew, ncwimm, ncwnew, fdecay, rdecay, 
                                alleaf, alroot, albranch, alstem, 
                                alroot_exudate, 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
        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 exudation
        """
        # 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()
        
        # N lost from system is proportional to the 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 * alroot_exudate * 
        #                            self.params.vxfix)
        self.fluxes.nrootexudate = 0.0
        
        if self.control.deciduous_model:
            self.fluxes.npleaf = (self.fluxes.lnrate * 
                                    self.state.growing_days[doy])
            self.fluxes.npbranch = 0.0
            self.fluxes.npstemimm = self.fluxes.cpstem * self.state.nc_ws
            # N flux into new ring (mobile component -> can be retrans for new
            # woody tissue)
            self.fluxes.npstemmob = (self.fluxes.cpstem * 
                                    (self.state.nc_wnew - self.state.nc_ws))
            
            
            
            self.fluxes.nproot = self.fluxes.cproot * self.state.rootnc
        else:
            # allocate N to pools with fixed N:C ratios
            self.fluxes.npbranch = self.fluxes.npp * albranch * ncbnew
    
            # N flux into new ring (immobile component -> structrual components)
            self.fluxes.npstemimm = self.fluxes.npp * alstem * ncwimm
    
            # N flux into new ring (mobile component -> can be retrans for new
            # woody tissue)
            self.fluxes.npstemmob = self.fluxes.npp * alstem * (ncwnew - ncwimm)
            
            # 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.nrootexudate)
    
            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.nrootexudate))
                self.fluxes.npbranch = self.fluxes.npp * albranch * ncbnew
                self.fluxes.npstemimm = self.fluxes.npp * alstem * ncwimm
                self.fluxes.npstemmob = self.fluxes.npp * alstem * (ncwnew - ncwimm)
                self.fluxes.nrootexudate = (self.fluxes.npp * alroot_exudate * 
                                            self.params.vxfix)
                
            ntot -= (self.fluxes.npbranch + self.fluxes.npstemimm +
                        self.fluxes.npstemmob + self.fluxes.nrootexudate)
            
            # allocate remaining N to flexible-ratio pools
            self.fluxes.npleaf = (ntot * alleaf / 
                                    (alleaf + 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, alleaf, alroot, albranch, alstem, 
                            alroot_exudate, nitfac, doy):
        """ C distribution - allocate available C through system

        Parameters:
        -----------
        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
        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.Hr * 1.0 / 365.25
        else:
            self.fluxes.cpleaf = self.fluxes.npp * alleaf
            self.fluxes.cproot = self.fluxes.npp * alroot
            self.fluxes.cpbranch = self.fluxes.npp * albranch
            self.fluxes.cpstem = self.fluxes.npp * 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 * alroot_exudate
        self.fluxes.cprootexudate = 0.0
        
        # rhizresp = 0.5, unless changed of course! 1/3--2/3 of C
        # fixed by plants is respired, so assuming a value of 0.5, i.e. the avg.
        # (Lambers and Poot, 2003)
        #self.fluxes.microbial_resp = (self.fluxes.cprootexudate * 
        #                                self.params.rhizresp)
        #self.fluxes.cprootexudate -= self.fluxes.microbial_resp
        
        # 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) 
                
                """
                sla_tonnes_ha_C = (self.state.sla * const.M2_AS_HA / 
                                   (const.KG_AS_TONNES * self.params.cfracts))
                self.state.lai = self.state.shoot * sla_tonnes_ha_C
                """
            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)
            
            #(Cpleaf * sla_new_tonnes_ha_C - Deadleaves * Lai / Shoot ) 
        
        
    def update_plant_state(self, fdecay, rdecay):
        """ 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
        
        
        
        
        # 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 
        
        if self.control.deciduous_model:
            # update annual fluxes - store for next year
            self.state.clabile_store += self.fluxes.npp
            self.state.aroot_uptake += self.fluxes.nuptake
            
            self.state.aretrans += self.fluxes.retrans
            self.state.anloss += self.fluxes.nloss
            
            # update N:C of plant pools
            if float_eq(self.state.shoot, 0.0):
                self.state.shootnc = 0.0
            else:
                self.state.shootnc = max(self.state.shootn / self.state.shoot, 
                                         self.params.ncfmin)
                
            # N:C of stuctural wood as function of N:C of foliage 
            # (kgN kg-1C)(Medlyn et al 2000)  
            self.state.nc_ws = (self.params.nc_wsa + self.params.nc_wsb * 
                                self.state.shootnc)    
           
            # N:C of new wood as function of N:C of foliage
            self.state.nc_wnew = (self.params.nc_wnewa + self.params.nc_wnewb * 
                                  self.state.shootnc)