Beispiel #1
0
    def ligin_nratio(self):
        """ Estimate Lignin/N ratio, as this dictates the how plant litter is 
        seperated between metabolic and structural pools.

        Returns:
        --------
        lnleaf : float
            lignin:N ratio of leaf
        lnroot : float
            lignin:N ratio of fine root
        """
        nceleaf = self.ratio_of_litternc_to_live_leafnc()
        nceroot = self.ratio_of_litternc_to_live_rootnc()
        
        if float_eq(nceleaf, 0.0):
            # This is effectively a hack that results in fluxes being turned
            # off into the metabolic pool. It seems that the century code
            # would effectively result in no metabolic fluxes. Might be worth
            # looking at the latest century code to see how they get around 
            # this? 
            lnleaf = 1E20 
        else:
            lnleaf = self.params.ligshoot / self.params.cfracts / nceleaf

        if float_eq(nceroot, 0.0):
            # This is effectively a hack that results in fluxes being turned
            # off into the metabolic pool. It seems that the century code
            # would effectively result in no metabolic fluxes. Might be worth
            # looking at the latest century code to see how they get around 
            # this? 
            lnroot = 1E20 
        else:
            lnroot = self.params.ligroot / self.params.cfracts / nceroot

        return (lnleaf, lnroot)
Beispiel #2
0
    def ligin_nratio(self):
        """ Estimate Lignin/N ratio, as this dictates the how plant litter is 
        seperated between metabolic and structural pools.

        Returns:
        --------
        lnleaf : float
            lignin:N ratio of leaf
        lnroot : float
            lignin:N ratio of fine root
        """
        nc_leaf_litter = self.ratio_of_litternc_to_live_leafnc()
        nc_root_litter = self.ratio_of_litternc_to_live_rootnc()

        if float_eq(nc_leaf_litter, 0.0):
            # catch divide by zero if we have no leaves
            lnleaf = 0.0
        else:
            lnleaf = self.params.ligshoot / self.params.cfracts / nc_leaf_litter
            #print self.params.ligshoot

        if float_eq(nc_root_litter, 0.0):
            # catch divide by zero if we have no roots
            lnroot = 0.0
        else:
            lnroot = self.params.ligroot / self.params.cfracts / nc_root_litter

        return (lnleaf, lnroot)
Beispiel #3
0
    def partition_plant_litter_n(self, nsurf, nsoil):
        """ Partition litter N from the plant (surface) and roots into metabolic
        and structural pools  
        
        Parameters:
        -----------
        nsurf : float
            N input from surface pool
        nsoil : float
            N input from soil pool
        """
        # constant structural input n:c as per century
        if not self.control.strfloat:
        
            # dead plant litter -> structural pool

            # n flux -> surface structural pool
            self.fluxes.n_surf_struct_litter = (self.fluxes.surf_struct_litter / 
                                                self.params.structcn)
            # n flux -> soil structural pool
            self.fluxes.n_soil_struct_litter = (self.fluxes.soil_struct_litter / 
                                                self.params.structcn)                         
                                     
            # if not enough N for structural, all available N goes to structural
            if float_gt( self.fluxes.n_surf_struct_litter, nsurf):
                 self.fluxes.n_surf_struct_litter = nsurf
            if float_gt(self.fluxes.n_soil_struct_litter, nsoil):
                self.fluxes.n_soil_struct_litter = nsoil
        
        # structural input n:c is a fraction of metabolic
        else:
            c_surf_struct_litter = (self.fluxes.surf_struct_litter * 
                                    self.params.structrat +
                                    self.fluxes.surf_metab_litter)
            
            if float_eq(c_surf_struct_litter, 0.0):
                 self.fluxes.n_surf_struct_litter = 0.0
            else:
                 self.fluxes.n_surf_struct_litter = (nsurf * 
                                                     self.fluxes.surf_struct_litter *
                                                     self.params.structrat / 
                                                     c_surf_struct_litter)
            
            c_soil_struct_litter = (self.fluxes.soil_struct_litter * 
                                    self.params.structrat +
                                    self.fluxes.soil_metab_litter)
           
            if float_eq(c_soil_struct_litter, 0.0):
                self.fluxes.n_soil_struct_litter = 0.
            else:
                self.fluxes.n_soil_struct_litter = (nsurf * 
                                                    self.fluxes.soil_struct_litter *
                                                    self.params.structrat / 
                                                    c_soil_struct_litter)
        
        # remaining N goes to metabolic pools
        self.fluxes.n_surf_metab_litter = (nsurf - 
                                           self.fluxes.n_surf_struct_litter)
        self.fluxes.n_soil_metab_litter = (nsoil - 
                                           self.fluxes.n_soil_struct_litter)
Beispiel #4
0
    def ligin_nratio(self):
        """ Estimate Lignin/N ratio, as this dictates the how plant litter is 
        seperated between metabolic and structural pools.

        Returns:
        --------
        lnleaf : float
            lignin:N ratio of leaf
        lnroot : float
            lignin:N ratio of fine root
        """
        nc_leaf_litter = self.ratio_of_litternc_to_live_leafnc()
        nc_root_litter = self.ratio_of_litternc_to_live_rootnc()
        
        if float_eq(nc_leaf_litter, 0.0):
            # catch divide by zero if we have no leaves 
            lnleaf = 0.0 
        else:
            lnleaf = self.params.ligshoot / self.params.cfracts / nc_leaf_litter
            #print self.params.ligshoot

        if float_eq(nc_root_litter, 0.0):
            # catch divide by zero if we have no roots
            lnroot = 0.0 
        else:
            lnroot = self.params.ligroot / self.params.cfracts / nc_root_litter

        return (lnleaf, lnroot)
Beispiel #5
0
    def partition_plant_litter_n(self, nsurf, nsoil):
        """ Partition litter N from the plant (surface) and roots into metabolic
        and structural pools  
        
        Parameters:
        -----------
        nsurf : float
            N input from surface pool
        nsoil : float
            N input from soil pool
        """
        # constant structural input n:c as per century
        if not self.control.strfloat:
        
            # dead plant litter -> structural pool

            # n flux -> surface structural pool
            self.fluxes.n_surf_struct_litter = (self.fluxes.surf_struct_litter / 
                                                self.params.structcn)
            # n flux -> soil structural pool
            self.fluxes.n_soil_struct_litter = (self.fluxes.soil_struct_litter / 
                                                self.params.structcn)                         
                                     
            # if not enough N for structural, all available N goes to structural
            if float_gt( self.fluxes.n_surf_struct_litter, nsurf):
                 self.fluxes.n_surf_struct_litter = nsurf
            if float_gt(self.fluxes.n_soil_struct_litter, nsoil):
                self.fluxes.n_soil_struct_litter = nsoil
        
        # structural input n:c is a fraction of metabolic
        else:
            c_surf_struct_litter = (self.fluxes.surf_struct_litter * 
                                    self.params.structrat +
                                    self.fluxes.surf_metab_litter)
            
            if float_eq(c_surf_struct_litter, 0.0):
                 self.fluxes.n_surf_struct_litter = 0.0
            else:
                 self.fluxes.n_surf_struct_litter = (nsurf * 
                                                     self.fluxes.surf_struct_litter *
                                                     self.params.structrat / 
                                                     c_surf_struct_litter)
            
            c_soil_struct_litter = (self.fluxes.soil_struct_litter * 
                                    self.params.structrat +
                                    self.fluxes.soil_metab_litter)
           
            if float_eq(c_soil_struct_litter, 0.0):
                self.fluxes.n_soil_struct_litter = 0.
            else:
                self.fluxes.n_soil_struct_litter = (nsurf * 
                                                    self.fluxes.soil_struct_litter *
                                                    self.params.structrat / 
                                                    c_soil_struct_litter)
        
        # remaining N goes to metabolic pools
        self.fluxes.n_surf_metab_litter = (nsurf - 
                                           self.fluxes.n_surf_struct_litter)
        self.fluxes.n_soil_metab_litter = (nsoil - 
                                           self.fluxes.n_soil_struct_litter)
Beispiel #6
0
    def day_end_calculations(self, days_in_year=None, INIT=False):
        """Calculate derived values from state variables.

        Parameters:
        -----------
        day : integer
            day of simulation

        INIT : logical
            logical defining whether it is the first day of the simulation

        """
        # update N:C of plant pool
        if float_eq(self.state.shoot, 0.0):
            self.state.shootnc = 0.0
        else:
            self.state.shootnc = self.state.shootn / self.state.shoot

        #print self.state.rootn , self.state.roo
        if float_eq(self.state.root, 0.0):
            self.state.rootnc = 0.0
        else:
            self.state.rootnc = max(0.0, self.state.rootn / self.state.root)

        # total plant, soil & litter nitrogen
        self.state.soiln = (self.state.inorgn + self.state.activesoiln +
                            self.state.slowsoiln + self.state.passivesoiln)
        self.state.litternag = self.state.structsurfn + self.state.metabsurfn
        self.state.litternbg = self.state.structsoiln + self.state.metabsoiln
        self.state.littern = self.state.litternag + self.state.litternbg
        self.state.plantn = (self.state.shootn + self.state.rootn +
                             self.state.crootn + self.state.branchn + 
                             self.state.stemn)
        self.state.totaln = (self.state.plantn + self.state.littern +
                             self.state.soiln)

        # total plant, soil, litter and system carbon
        self.state.soilc = (self.state.activesoil + self.state.slowsoil +
                            self.state.passivesoil)
        self.state.littercag = self.state.structsurf + self.state.metabsurf
        self.state.littercbg = self.state.structsoil + self.state.metabsoil
        self.state.litterc = self.state.littercag + self.state.littercbg
        self.state.plantc = (self.state.root + self.state.croot + 
                             self.state.shoot + self.state.stem + 
                             self.state.branch)
        self.state.totalc = (self.state.soilc + self.state.litterc +
                             self.state.plantc)

        #self.state.plantnc = self.state.plantn / self.state.plantc
        #print self.state.plantnc
        # optional constant passive pool
        if self.control.passiveconst == True:
            self.state.passivesoil = self.params.passivesoilz
            self.state.passivesoiln = self.params.passivesoilnz

        if INIT == False:
            #Required so max leaf & root N:C can depend on Age
            self.state.age += 1.0 / days_in_year
Beispiel #7
0
    def hurricane(self):
        """ Specifically for the florida simulations - reduce LAI by 40% """
                 
        # Reduce LAI by 40%
        self.state.lai -= (self.state.lai * 0.4)
        
        # adjust C in the foliage
        orig_shoot_c = self.state.shoot
        self.state.shoot = (self.state.lai / (self.params.sla * 
                                               const.M2_AS_HA / 
                                               const.KG_AS_TONNES / 
                                               self.params.cfracts)) 
        lost_c = orig_shoot_c - self.state.shoot
        lost_n = self.state.shootnc * lost_c
        self.state.shootn -= lost_n
        
        # Drop straight to floor, no retranslocation
        
        # C -> structural
        if float_eq(lost_c, 0.0):
            nc_leaf_litter = 0.0
        else:
            nc_leaf_litter = lost_n / lost_c
        
        if float_eq(nc_leaf_litter, 0.0):
            # catch divide by zero if we have no leaves 
            lnleaf = 0.0 
        else:
            lnleaf = self.params.ligshoot / self.params.cfracts / nc_leaf_litter
        fmleaf = max(0.0, 0.85 - (0.018 * lnleaf))
        
        self.fluxes.surf_struct_litter += lost_c * (1.0 - fmleaf)

        # C -> metabolic 
        self.fluxes.surf_metab_litter += lost_c * fmleaf 
        
        # N -> structural
        if float_eq(self.fluxes.surf_struct_litter, 0.0):
            self.fluxes.n_surf_struct_litter += 0.0
        else:
            self.fluxes.n_surf_struct_litter += (lost_n * 
                                                 self.fluxes.surf_struct_litter *
                                                 self.params.structrat / 
                                                 self.fluxes.surf_struct_litter)
        
       
        
        # N -> metabolic pools
        self.fluxes.n_surf_metab_litter += (lost_n - 
                                            self.fluxes.n_surf_struct_litter)
        
        #self.state.structsurf += lost_c
        #self.state.structsurfn += lost_n
        
        
        
Beispiel #8
0
    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)
Beispiel #9
0
    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)
Beispiel #10
0
    def day_end_calculations(self, prjday, days_in_year=None, INIT=False):
        """Calculate derived values from state variables.

        Parameters:
        -----------
        day : integer
            day of simulation

        INIT : logical
            logical defining whether it is the first day of the simulation

        """
        # update N:C of plant pools
        if float_eq(self.state.shoot, 0.0):
            self.state.shootnc = 0.0
        else:
            self.state.shootnc = self.state.shootn / self.state.shoot

        #print self.state.rootn , self.state.root
        if float_eq(self.state.root, 0.0):
            self.state.rootnc = 0.0
        else:
            self.state.rootnc = max(0.0, self.state.rootn / self.state.root)

        # total plant, soil & litter nitrogen
        self.state.soiln = (self.state.inorgn + self.state.activesoiln +
                            self.state.slowsoiln + self.state.passivesoiln)
        self.state.litternag = self.state.structsurfn + self.state.metabsurfn
        self.state.litternbg = self.state.structsoiln + self.state.metabsoiln
        self.state.littern = self.state.litternag + self.state.litternbg
        self.state.plantn = (self.state.shootn + self.state.rootn +
                             self.state.branchn + self.state.stemn)
        self.state.totaln = (self.state.plantn + self.state.littern +
                             self.state.soiln)

        # total plant, soil, litter and system carbon
        self.state.soilc = (self.state.activesoil + self.state.slowsoil +
                            self.state.passivesoil)
        self.state.littercag = self.state.structsurf + self.state.metabsurf
        self.state.littercbg = self.state.structsoil + self.state.metabsoil
        self.state.litterc = self.state.littercag + self.state.littercbg
        self.state.plantc = (self.state.root + self.state.shoot +
                             self.state.stem + self.state.branch)
        self.state.totalc = (self.state.soilc + self.state.litterc +
                             self.state.plantc)

        # optional constant passive pool
        if self.control.passiveconst == True:
            self.state.passivesoil = self.params.passivesoilz
            self.state.passivesoiln = self.params.passivesoilnz

        if INIT == False:
            #Required so max leaf & root N:C can depend on Age
            self.state.age += 1.0 / days_in_year
Beispiel #11
0
    def print_output_file(self):
        """ Either print the daily output file (at the end of the year) or
        print the final state + param file. """

        # print the daily output file, this is done once at the end of each yr
        if self.control.print_options == "DAILY":
            if self.control.output_ascii:
                self.pr.write_daily_outputs_file(self.day_output) 
            else:
                self.pr.write_daily_outputs_file_to_binary(self.day_output) 
            
        # print the final state
        elif self.control.print_options == "END":
            if not self.control.deciduous_model:

                # This shouldn't do anything, but I will leave it here for
                # potential applications. SLA has a N dependancy, though I
                # always turn this off...because of this we require this step.
                # However this won't work for tress if all the leaves have
                # gone! Need to stop that happening! So I guess the sensible
                # thing would be to do nothing.
                if float_eq(self.state.shoot, 0.0):
                    pass #
                    #self.params.slainit = 0.01
                else:
                    # need to save initial SLA to current one!
                    self.params.sla = (self.state.lai / const.M2_AS_HA *
                                       const.KG_AS_TONNES *
                                       self.params.cfracts /self.state.shoot)

            self.correct_rate_constants(output=True)
            self.pr.save_state()
Beispiel #12
0
    def print_output_file(self):
        """ Either print the daily output file (at the end of the year) or
        print the final state + param file. """

        # print the daily output file, this is done once at the end of each yr
        if self.control.print_options == "DAILY":
            if self.control.output_ascii:
                self.pr.write_daily_outputs_file(self.day_output)
            else:
                self.pr.write_daily_outputs_file_to_binary(self.day_output)

        # print the final state
        elif self.control.print_options == "END":
            if not self.control.deciduous_model:

                # This shouldn't do anything, but I will leave it here for
                # potential applications. SLA has a N dependancy, though I
                # always turn this off...because of this we require this step.
                # However this won't work for tress if all the leaves have
                # gone! Need to stop that happening! So I guess the sensible
                # thing would be to do nothing.
                if float_eq(self.state.shoot, 0.0):
                    pass  #
                    #self.params.slainit = 0.01
                else:
                    # need to save initial SLA to current one!
                    self.params.sla = (self.state.lai / const.M2_AS_HA *
                                       const.KG_AS_TONNES *
                                       self.params.cfracts / self.state.shoot)

            self.correct_rate_constants(output=True)
            self.pr.save_state()
    def model_zero(self, day):
        """
        References:
        -----------
        * Kirschbaum et al 1994 pc & e

        Parameters:
        -----------
        day : integer
            simulation day

        Returns:
        --------
        npp : float
            net primary productivity

        """
        (sw_rad, ca, temp) = self.get_met_data(day)
        if float_eq(sw_rad, 0.0):
            sw_rad = 0.000001

        gpp_max = ((self.params.cfracts * sw_rad / const.M2_AS_HA *
                    self.params.epsilon * const.G_AS_TONNES) * temp_dep(temp) /
                    temp_dep(14.0))

        lue = (1.21) * self.state.shootnc  / (0.0076 + self.state.shootnc)

        return lue * gpp_max * self.state.light_interception
Beispiel #14
0
 def adjust_residence_time_of_slow_pool(self):
     """ Priming simulations the residence time of the slow pool is flexible,
     as the flux out of the active pool (factive) increases the residence
     time of the slow pool decreases.        
     """
     # total flux out of the factive pool
     self.fluxes.factive = (self.fluxes.active_to_slow +
                            self.fluxes.active_to_passive +
                            self.fluxes.co2_to_air[4])
     
     if float_eq(self.fluxes.factive, 0.0):
         # Need to correct units of rate constant
         residence_time_slow_pool = (1.0 / (self.params.kdec6 * 
                                             const.NDAYS_IN_YR))
     else:
         residence_time_slow_pool = (1.0 / self.params.prime_y  * 
                                     (self.fluxes.factive / 
                                     (self.fluxes.factive + 
                                      self.params.prime_z)))
         
         # GDAY uses decay rates rather than residence times...
         self.params.kdec6 = 1.0 / residence_time_slow_pool 
         
         # rate constant needs to be per day inside GDAY
         self.params.kdec6 /= const.NDAYS_IN_YR
     
     # Save for outputting purposes only
     self.fluxes.rtslow = residence_time_slow_pool  
Beispiel #15
0
    def are_we_dead(self):
        """ Simplistic scheme to allow GDAY to die and re-establish the
        following year """

        if float_eq(self.state.lai, 0.0):
            print "DEAD"
            # i.e. we have just died put stem C into struct litter
            # works for grasses as this would be zero anyway
            if not self.dead:
                self.state.structsurf = self.state.stem
                self.state.structsurfn = self.state.stemn

            # Need to zero stuff for output state and fluxes.
            self.state.age = 0.0
            self.state.branch = 0.0
            self.state.branchn = 0.0
            self.state.cstore = 0.0
            self.state.nstore = 0.0
            self.state.root = 0.0
            self.state.rootn = 0.0
            self.state.sapwood = 0.0
            self.state.shoot = 0.0
            self.state.shootn = 0.0
            self.state.stem = 0.0
            self.state.stemn = 0.0
            self.state.stemnimm = 0.0
            self.state.stemnmob = 0.0

            self.dead = True  # johnny 5 is dead
            print "dead"
Beispiel #16
0
 def calc_root_exudation_uptake_of_C(self):        
     """ The amount of C which enters the active pool varies according to the
     CUE of SOM in response to root exudation (REXCUE). REXCUE determines 
     the fraction of REXC that enters the active pool as C. The remaining
     flux is respired.
     
     
     REXCUE determines which fraction of REXC enters the active pool as C 
     (delta_Cact). The remaining fraction of REXC is respired as CO2.
     
     """
     active_CN = self.state.activesoil / self.state.activesoiln
     
     if self.params.root_exu_CUE == -1.0: # flexible CUE
         # flexible cue
         # The constraint of 0.3<=REXCUE<=0.6 is based on observations of the 
         # physical limits of microbes
         
         if float_eq(self.fluxes.root_exc, 0.0):
             rex_NC = 0.0
         else:
             rex_NC = self.fluxes.root_exn / self.fluxes.root_exc
         self.fluxes.rexc_cue = max(0.3, min(0.6, rex_NC * active_CN))        
     else:
         self.fluxes.rexc_cue = self.params.root_exu_CUE
         
     C_to_active_pool = self.fluxes.root_exc * self.fluxes.rexc_cue
     self.state.activesoil += C_to_active_pool
     
     # update respiration fluxes.
     self.fluxes.co2_released_exud = (self.fluxes.root_exc * 
                                      (1.0 - self.fluxes.rexc_cue))
     self.fluxes.hetero_resp += self.fluxes.co2_released_exud
Beispiel #17
0
    def are_we_dead(self):
        """ Simplistic scheme to allow GDAY to die and re-establish the
        following year """

        if float_eq(self.state.lai, 0.0):
            print "DEAD"
            # i.e. we have just died put stem C into struct litter
            # works for grasses as this would be zero anyway
            if not self.dead:
                self.state.structsurf = self.state.stem
                self.state.structsurfn = self.state.stemn

            # Need to zero stuff for output state and fluxes.
            self.state.age = 0.0
            self.state.branch = 0.0
            self.state.branchn = 0.0
            self.state.cstore = 0.0
            self.state.nstore = 0.0
            self.state.root = 0.0
            self.state.rootn = 0.0
            self.state.sapwood = 0.0
            self.state.shoot = 0.0
            self.state.shootn = 0.0
            self.state.stem = 0.0
            self.state.stemn = 0.0
            self.state.stemnimm = 0.0
            self.state.stemnmob = 0.0

            self.dead = True # johnny 5 is dead
            print "dead"
Beispiel #18
0
    def hurricane(self):
        """ Specifically for the florida simulations - reduce LAI by 40% """

        # Reduce LAI by 40%
        self.state.lai -= (self.state.lai * 0.4)

        # adjust C in the foliage
        orig_shoot_c = self.state.shoot
        self.state.shoot = (self.state.lai /
                            (self.params.sla * const.M2_AS_HA /
                             const.KG_AS_TONNES / self.params.cfracts))
        lost_c = orig_shoot_c - self.state.shoot
        lost_n = self.state.shootnc * lost_c
        self.state.shootn -= lost_n

        # Drop straight to floor, no retranslocation

        # C -> structural
        if float_eq(lost_c, 0.0):
            nc_leaf_litter = 0.0
        else:
            nc_leaf_litter = lost_n / lost_c

        if float_eq(nc_leaf_litter, 0.0):
            # catch divide by zero if we have no leaves
            lnleaf = 0.0
        else:
            lnleaf = self.params.ligshoot / self.params.cfracts / nc_leaf_litter
        fmleaf = max(0.0, 0.85 - (0.018 * lnleaf))

        self.fluxes.surf_struct_litter += lost_c * (1.0 - fmleaf)

        # C -> metabolic
        self.fluxes.surf_metab_litter += lost_c * fmleaf

        # N -> structural
        if float_eq(self.fluxes.surf_struct_litter, 0.0):
            self.fluxes.n_surf_struct_litter += 0.0
        else:
            self.fluxes.n_surf_struct_litter += (
                lost_n * self.fluxes.surf_struct_litter *
                self.params.structrat / self.fluxes.surf_struct_litter)

        # N -> metabolic pools
        self.fluxes.n_surf_metab_litter += (lost_n -
                                            self.fluxes.n_surf_struct_litter)
Beispiel #19
0
    def inputs_from_structrual_pool(self, nsurf, nsoil):
        """structural pool input fluxes

        Parameters:
        -----------
        nsurf : float
            N input from surface pool
        nsoil : float
            N input from soil pool
        """
        # constant structural input n:c as per century
        
        if not self.control.strfloat:
            # dead plant -> structural

            # surface
            self.fluxes.nresid[0] = self.fluxes.cresid[0] / self.params.structcn

            # soil
            self.fluxes.nresid[1] = self.fluxes.cresid[1] / self.params.structcn

            # if not enough N for structural, all available N goes to structural
            if float_gt(self.fluxes.nresid[0], nsurf):
                self.fluxes.nresid[0] = nsurf
            if float_gt(self.fluxes.nresid[1], nsoil):
                self.fluxes.nresid[1] = nsoil
        else:
            # structural input n:c is a fraction of metabolic
            cwgtsu = (self.fluxes.cresid[0] * self.params.structrat +
                      self.fluxes.cresid[2])
            if float_eq(cwgtsu, 0.0):
                self.fluxes.nresid[0] = 0.0
            else:
                self.fluxes.nresid[0] = (nsurf * self.fluxes.cresid[0] *
                                         self.params.structrat / cwgtsu)

            cwgtsl = (self.fluxes.cresid[1] * self.params.structrat +
                      self.fluxes.cresid[3])
            if float_eq(cwgtsl, 0.0):
                self.fluxes.nresid[1] = 0.
            else:
                self.fluxes.nresid[1] = (nsurf * self.fluxes.cresid[1] *
                                         self.params.structrat / cwgtsl)
    def model_one(self, day):
        """ old g'day n:c and temperature factors

        References:
        -----------
        * mcmurtrie et al 1992 aust j bot

        Parameters:
        -----------
        day : integer
            simulation day

        Returns:
        --------
        npp : float
            net primary productivity

        """
        (sw_rad, ca, temp) = self.get_met_data(day)

        if float_eq(sw_rad, 0.0):
            sw_rad = 0.000001
        rco2 = 1.632 * (ca - 60.9) / (ca + 121.8)
        gpp_max = ((self.params.cfracts * sw_rad / const.M2_AS_HA *
                    self.params.epsilon * const.G_AS_TONNES) * rco2)

        # leaf n:c effect on photosynthetic efficiency (theta = 1).
        if float_gt(self.state.shootnc, self.params.n_crit):
            lue = 1.0
        else:
            rat = self.state.shootnc / self.params.n_crit
            if float_eq(self.params.ncpower, 1.0):
                lue = rat
            else:
                lue = pow(rat, self.params.ncpower)

        return lue * gpp_max * self.state.light_interception
    def calc_wue(self, vpd, ca, amb_co2):
        """water use efficiency

        Not sure of units conversions here, have to ask BM

        Parameters:
        -----------
        vpd : float
            average daily vpd [kPa]
        ca : float
            atmospheric co2, depending on flag set in param file this will be
            ambient or elevated. [umol mol-1]

        """
        if self.control.wue_model == 0:
            # Gday original implementation
            # (gC / kg H20)
            if float_gt(vpd, 0.0):
                # WUE Power law dependence on co2, Pepper et al 2005.
                
                co2_ratio = (ca / amb_co2)
                co2_adjustment = co2_ratio**self.params.co2_effect_on_wue

                # wue inversely proportional to daily mean vpd
                self.fluxes.wue = self.params.wue0 * co2_adjustment / vpd
            else:
                self.fluxes.wue = 0.0
        elif self.control.wue_model == 1 and self.control.assim_model == 7:
            conv = const.MOL_C_TO_GRAMS_C / const.MOL_WATER_TO_GRAMS_WATER
            self.fluxes.wue = (conv * 1000.0 * (ca * const.UMOL_TO_MOL *
                                (1.0 - self.fluxes.cica_avg) /
                                (1.6 * vpd / 101.0)))
                        #if self.fluxes.wue > 20.0: self.fluxes.wue = 20.0    # FIX THIS!!!
            # what is this? ask BM

        elif self.control.wue_model == 2:
            self.fluxes.wue = (self.params.wue0 * 0.27273 / vpd *
                                ca / amb_co2)
        elif self.control.wue_model == 3 :
            if float_eq(self.fluxes.transpiration, 0.0):
                self.fluxes.wue = 0.0
            else:
                self.fluxes.wue = (self.fluxes.gpp_gCm2 /
                                    self.fluxes.transpiration)
        else:
            raise AttributeError('Unknown WUE calculation option')
Beispiel #22
0
    def ratio_of_litternc_to_live_leafnc(self):
        """ratio of litter N:C to live leaf N:C

        Returns:
        --------
        nc_leaf_litter : float
            N:C ratio of litter to foliage

        """
        if self.control.use_eff_nc:
            nc_leaf_litter = self.params.liteffnc * (1.0 - self.params.fretrans)
        else:
            if float_eq(self.fluxes.deadleaves, 0.0):
                nc_leaf_litter = 0.0
            else:
                nc_leaf_litter = self.fluxes.deadleafn / self.fluxes.deadleaves

        return nc_leaf_litter
Beispiel #23
0
    def ratio_of_litternc_to_live_leafnc(self):
        """ratio of litter N:C to live leaf N:C

        Returns:
        --------
        nc_leaf_litter : float
            N:C ratio of litter to foliage

        """
        if self.control.use_eff_nc:
            nc_leaf_litter = self.params.liteffnc * (1.0 - self.params.fretrans)
        else:
            if float_eq(self.fluxes.deadleaves, 0.0):
                nc_leaf_litter = 0.0
            else:
                nc_leaf_litter = self.fluxes.deadleafn / self.fluxes.deadleaves

        return nc_leaf_litter
Beispiel #24
0
    def ratio_of_litternc_to_live_rootnc(self):
        """ratio of litter N:C to live root N:C

        Returns:
        --------
        nc_root_litter : float
            N:C ratio of litter to live root

        """
        if self.control.use_eff_nc:
            nc_root_litter = (self.params.liteffnc * self.params.ncrfac *
                              (1.0 - self.params.rretrans))
        else:
            if float_eq(self.fluxes.deadroots, 0.0):
                nc_root_litter = 0.0
            else:
                nc_root_litter = self.fluxes.deadrootn / self.fluxes.deadroots

        return nc_root_litter
Beispiel #25
0
    def ratio_of_litternc_to_live_rootnc(self):
        """ratio of litter N:C to live root N:C

        Returns:
        --------
        nc_root_litter : float
            N:C ratio of litter to live root

        """
        if self.control.use_eff_nc:
            nc_root_litter = (self.params.liteffnc * self.params.ncrfac *
                             (1.0 - self.params.rretrans))
        else:
            if float_eq(self.fluxes.deadroots, 0.0):
                nc_root_litter = 0.0
            else:
                nc_root_litter = self.fluxes.deadrootn / self.fluxes.deadroots

        return nc_root_litter
    def model_three(self, day):
        """
        Parameters:
        -----------
        day : integer
            simulation day

        Returns:
        --------
        npp : float
            net primary productivity

        """
        (sw_rad, ca, temp) = self.get_met_data(day)

        if float_eq(sw_rad, 0.0):
            sw_rad = 0.000001
        rco2 = 1.82 * (ca - 50.0) / (ca + 197.0)
        lue = (1.45 * self.state.ncontent) / (self.state.ncontent + 2.21) * rco2
        gpp_max = sw_rad / const.M2_AS_HA * const.G_AS_TONNES


        return lue * gpp_max * self.state.light_interception
Beispiel #27
0
    def print_output_file(self):
        """ Either print the daily output file (at the end of the year) or
        print the final state + param file. """

        # print the daily output file, this is done once at the end of each yr
        if self.control.print_options == "DAILY":
            self.pr.write_daily_outputs_file(self.day_output)

        # print the final state
        elif self.control.print_options == "END":
            if not self.control.deciduous_model:

                if float_eq(self.state.shoot, 0.0):
                    self.params.slainit = 0.01
                else:
                    # need to save initial SLA to current one!
                    conv = const.M2_AS_HA * const.KG_AS_TONNES
                    self.params.slainit = (
                        self.state.lai / const.M2_AS_HA * const.KG_AS_TONNES * self.params.cfracts / self.state.shoot
                    )

            self.correct_rate_constants(output=True)
            self.pr.save_state()
Beispiel #28
0
    def print_output_file(self):
        """ Either print the daily output file (at the end of the year) or
        print the final state + param file. """

        # print the daily output file, this is done once at the end of each yr
        if self.control.print_options == "DAILY":
            self.pr.write_daily_outputs_file(self.day_output)

        # print the final state
        elif self.control.print_options == "END":
            if not self.control.deciduous_model:

                if float_eq(self.state.shoot, 0.0):
                    self.params.slainit = 0.01
                else:
                    # need to save initial SLA to current one!
                    conv = const.M2_AS_HA * const.KG_AS_TONNES
                    self.params.slainit = (self.state.lai / const.M2_AS_HA *
                                           const.KG_AS_TONNES *
                                           self.params.cfracts /
                                           self.state.shoot)

            self.correct_rate_constants(output=True)
            self.pr.save_state()
Beispiel #29
0
    def calculate_photosynthesis(self, day, daylen):
        """ Photosynthesis is calculated assuming GPP is proportional to APAR,
        a commonly assumed reln (e.g. Potter 1993, Myneni 2002). The slope of
        relationship btw GPP and APAR, i.e. LUE is modelled using the
        photosynthesis eqns from Sands.

        Assumptions:
        ------------
        (1) photosynthetic light response is a non-rectangular hyperbolic func
            of photon-flux density with a light-saturatred photosynthetic rate
            (Amax), quantum yield (alpha) and curvature (theta).
        (2) the canopy is horizontally uniform.
        (3) PAR distribution within the canopy obeys Beer's law.
        (4) light-saturated photosynthetic rate declines with canopy depth in
            proportion to decline in PAR
        (5) alpha + theta do not vary within the canopy
        (6) dirunal variation of PAR is sinusoidal.
        (7) The model makes no assumption about N within the canopy, however
            this version assumes N declines exponentially through the cnaopy.
        (8) Leaf temperature is the same as the air temperature.

        Parameters:
        ----------
        day : int
            project day.
        daylen : float
            length of day in hours.

        Returns:
        -------
        Nothing
            Method calculates GPP, NPP and Ra.
        """
        # local var for tidyness
        (Tk_am, Tk_pm, par, vpd_am, vpd_pm, ca) = self.get_met_data(day)

        # calculate mate params & account for temperature dependencies
        N0 = self.calculate_top_of_canopy_n()

        gamma_star_am = self.calculate_co2_compensation_point(Tk_am)
        gamma_star_pm = self.calculate_co2_compensation_point(Tk_pm)

        Km_am = self.calculate_michaelis_menten_parameter(Tk_am)
        Km_pm = self.calculate_michaelis_menten_parameter(Tk_pm)

        (jmax_am, vcmax_am) = self.calculate_jmax_and_vcmax(Tk_am, N0)
        (jmax_pm, vcmax_pm) = self.calculate_jmax_and_vcmax(Tk_pm, N0)

        ci_am = self.calculate_ci(vpd_am, ca)
        ci_pm = self.calculate_ci(vpd_pm, ca)

        # quantum efficiency calculated for C3 plants
        alpha_am = self.calculate_quantum_efficiency(ci_am, gamma_star_am)
        alpha_pm = self.calculate_quantum_efficiency(ci_pm, gamma_star_pm)

        # Reducing assimilation if we encounter frost. Frost is assumed to
        # impact on the maximum photosynthetic capacity and alpha_j
        # So there is only an indirect effect on LAI, this could be changed...
        if self.control.frost:
            Tmax = self.met_data['tmax'][day]
            Tmin = self.met_data['tmin'][day]

            Thard = self.calc_frost_hardiness(daylen, Tmin, Tmax)
            (total_alpha_limf,
             total_amax_limf) = self.calc_frost_impact_factors(
                 Thard, Tmin, Tmax)
            alpha_am *= total_alpha_limf
            alpha_pm *= total_alpha_limf

        # Rubisco carboxylation limited rate of photosynthesis
        ac_am = self.assim(ci_am, gamma_star_am, a1=vcmax_am, a2=Km_am)
        ac_pm = self.assim(ci_pm, gamma_star_pm, a1=vcmax_pm, a2=Km_pm)

        # Light-limited rate of photosynthesis allowed by RuBP regeneration
        aj_am = self.assim(ci_am,
                           gamma_star_am,
                           a1=jmax_am / 4.0,
                           a2=2.0 * gamma_star_am)
        aj_pm = self.assim(ci_pm,
                           gamma_star_pm,
                           a1=jmax_pm / 4.0,
                           a2=2.0 * gamma_star_pm)

        # light-saturated photosynthesis rate at the top of the canopy (gross)
        asat_am = min(aj_am, ac_am)
        asat_pm = min(aj_pm, ac_pm)
        if self.control.frost:
            asat_am *= total_amax_limf
            asat_pm *= total_amax_limf

        # LUE (umol C umol-1 PAR)
        lue_am = self.epsilon(asat_am, par, daylen, alpha_am)
        lue_pm = self.epsilon(asat_pm, par, daylen, alpha_pm)
        # use average to simulate canopy photosynthesis
        lue_avg = (lue_am + lue_pm) / 2.0

        if float_eq(self.state.lai, 0.0):
            self.fluxes.apar = 0.0
        else:
            # absorbed photosynthetically active radiation (umol m-2 s-1)
            self.fluxes.apar = par * self.state.fipar
        apar_half_day = self.fluxes.apar / 2.0

        # convert umol m-2 d-1 -> gC m-2 d-1
        self.fluxes.gpp_gCm2 = self.fluxes.apar * lue_avg * const.UMOL_2_GRAMS_C
        self.fluxes.gpp_am = apar_half_day * lue_am * const.UMOL_2_GRAMS_C
        self.fluxes.gpp_pm = apar_half_day * lue_pm * const.UMOL_2_GRAMS_C

        # g C m-2 to tonnes hectare-1 day-1
        self.fluxes.gpp = self.fluxes.gpp_gCm2 * const.GRAM_C_2_TONNES_HA

        if self.control.nuptake_model == 3:
            self.fluxes.gpp_gCm2 *= self.params.ac
            self.fluxes.gpp_am *= self.params.ac
            self.fluxes.gpp_pm *= self.params.ac
Beispiel #30
0
    def fire(self, growth_obj):
        """
        Fire...

        * 100 percent of aboveground biomass 
        * 100 percent of surface litter
        * 50 percent of N volatilized to the atmosphere
        * 50 percent of N returned to inorgn pool"
        * Coarse roots are not damaged by fire!

        vaguely following ...
        http://treephys.oxfordjournals.org/content/24/7/765.full.pdf
        """
        totaln = (self.state.branchn + self.state.shootn + self.state.stemn + 
                  self.state.structsurfn)
        self.state.inorgn += totaln / 2.0
        
        # re-establish everything with C/N ~ 25.
        if self.control.alloc_model == "GRASSES":
            self.state.branch = 0.0
            self.state.branchn = 0.0
            self.state.sapwood = 0.0
            self.state.stem = 0.0
            self.state.stemn = 0.0
            self.state.stemnimm = 0.0
            self.state.stemnmob = 0.0
        else:
            self.state.branch = 0.001
            self.state.branchn = 0.00004
            self.state.sapwood = 0.001
            self.state.stem = 0.001
            self.state.stemn = 0.00004
            self.state.stemnimm = 0.00004
            self.state.stemnmob = 0.0
        
        self.state.age = 0.0
        self.state.lai = 0.01
        self.state.metabsurf = 0.0
        self.state.metabsurfn = 0.0
        self.state.prev_sma = 1.0
        self.state.root = 0.001
        self.state.rootn = 0.00004
        self.state.shoot = 0.001
        self.state.shootn = 0.00004
        self.state.structsurf = 0.001
        self.state.structsurfn = 0.00004  
        
        # reset litter flows
        self.fluxes.deadroots = 0.0
        self.fluxes.deadstems = 0.0
        self.fluxes.deadbranch = 0.0
        self.fluxes.deadsapwood = 0.0
        self.fluxes.deadleafn = 0.0
        self.fluxes.deadrootn = 0.0
        self.fluxes.deadbranchn = 0.0
        self.fluxes.deadstemn = 0.0
        
        # update N:C of plant pools
        if float_eq(self.state.shoot, 0.0):
            self.state.shootnc = 0.0
        else:
            self.state.shootnc = self.state.shootn / self.state.shoot
        
        #print self.state.rootn , self.state.root
        if float_eq(self.state.root, 0.0):
            self.state.rootnc = 0.0
        else:
            self.state.rootnc = max(0.0, self.state.rootn / self.state.root)
        
        growth_obj.sma.reset_stream() # reset any stress limitation
        self.state.prev_sma = 1.0
Beispiel #31
0
    def calculate_photosynthesis(self, day, daylen):
        """ Photosynthesis is calculated assuming GPP is proportional to APAR,
        a commonly assumed reln (e.g. Potter 1993, Myneni 2002). The slope of
        relationship btw GPP and APAR, i.e. LUE is modelled using the
        photosynthesis eqns from Sands.

        Assumptions:
        ------------
        (1) photosynthetic light response is a non-rectangular hyperbolic func
            of photon-flux density with a light-saturatred photosynthetic rate
            (Amax), quantum yield (alpha) and curvature (theta).
        (2) the canopy is horizontally uniform.
        (3) PAR distribution within the canopy obeys Beer's law.
        (4) light-saturated photosynthetic rate declines with canopy depth in
            proportion to decline in PAR
        (5) alpha + theta do not vary within the canopy
        (6) dirunal variation of PAR is sinusoidal.
        (7) The model makes no assumption about N within the canopy, however
            this version assumes N declines exponentially through the cnaopy.
        (8) Leaf temperature is the same as the air temperature.

        Parameters:
        ----------
        day : int
            project day.
        daylen : float
            length of day in hours.

        Returns:
        -------
        Nothing
            Method calculates GPP, NPP and Ra.
        """
        # local var for tidyness
        (am, pm) = self.am, self.pm # morning/afternoon
        (Tair_K, par, vpd, ca) = self.get_met_data(day)
        
        # calculate mate params & account for temperature dependencies
        gamma_star = self.calculate_co2_compensation_point(Tair_K)
        Km = self.calculate_michaelis_menten_parameter(Tair_K)
        N0 = self.calculate_top_of_canopy_n()
        (jmax, vcmax) = self.calculate_jmax_and_vcmax(Tair_K, N0)
        ci = [self.calculate_ci(vpd[k], ca) for k in am, pm]
        alpha = self.calculate_quantum_efficiency(ci, gamma_star)
        
        # Rubisco carboxylation limited rate of photosynthesis
        ac = [self.assim(ci[k], gamma_star[k], a1=vcmax[k], a2=Km[k]) \
              for k in am, pm]
        
        # Light-limited rate of photosynthesis allowed by RuBP regeneration
        aj = [self.assim(ci[k], gamma_star[k], a1=jmax[k]/4.0, \
              a2=2.0*gamma_star[k]) for k in am, pm]
        
        # light-saturated photosynthesis rate at the top of the canopy (gross)
        asat = [min(aj[k], ac[k]) for k in am, pm]
        
        # Assumption that the integral is symmetric about noon, so we average
        # the LUE accounting for variability in temperature, but importantly
        # not PAR
        lue = [self.epsilon(asat[k], par, daylen, alpha[k]) for k in am, pm]
        
        # mol C mol-1 PAR - use average to simulate canopy photosynthesis
        lue_avg = sum(lue) / 2.0

        if float_eq(self.state.lai, 0.0):
            self.fluxes.apar = 0.0
        else:
            par_mol = par * const.UMOL_TO_MOL
            # absorbed photosynthetically active radiation
            self.fluxes.apar = par_mol * self.state.fipar

        # gC m-2 d-1
        self.fluxes.gpp_gCm2 = (self.fluxes.apar * lue_avg * 
                                const.MOL_C_TO_GRAMS_C)
        self.fluxes.gpp_am_pm[am] = ((self.fluxes.apar / 2.0) * lue[am] * 
                                      const.MOL_C_TO_GRAMS_C)
        self.fluxes.gpp_am_pm[pm] = ((self.fluxes.apar / 2.0) * lue[pm] * 
                                      const.MOL_C_TO_GRAMS_C)
        
        self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue
        
        if self.control.nuptake_model == 3:
            self.fluxes.gpp_gCm2 *= self.params.ac
            self.fluxes.gpp_am_pm[am] *= self.params.ac
            self.fluxes.gpp_am_pm[pm] *= self.params.ac
            self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue
            
        # g C m-2 to tonnes hectare-1 day-1
        conv = const.G_AS_TONNES / const.M2_AS_HA
        self.fluxes.gpp = self.fluxes.gpp_gCm2 * conv
        self.fluxes.npp = self.fluxes.npp_gCm2 * conv
        
        # Plant respiration assuming carbon-use efficiency.
        self.fluxes.auto_resp = self.fluxes.gpp - self.fluxes.npp
Beispiel #32
0
    def calculate_photosynthesis(self, day, daylen):
        """ Photosynthesis is calculated assuming GPP is proportional to APAR,
        a commonly assumed reln (e.g. Potter 1993, Myneni 2002). The slope of
        relationship btw GPP and APAR, i.e. LUE is modelled using the
        photosynthesis eqns from Sands.

        Assumptions:
        ------------
        (1) photosynthetic light response is a non-rectangular hyperbolic func
            of photon-flux density with a light-saturatred photosynthetic rate
            (Amax), quantum yield (alpha) and curvature (theta).
        (2) the canopy is horizontally uniform.
        (3) PAR distribution within the canopy obeys Beer's law.
        (4) light-saturated photosynthetic rate declines with canopy depth in
            proportion to decline in PAR
        (5) alpha + theta do not vary within the canopy
        (6) dirunal variation of PAR is sinusoidal.
        (7) The model makes no assumption about N within the canopy, however
            this version assumes N declines exponentially through the cnaopy.
        (8) Leaf temperature is the same as the air temperature.

        Parameters:
        ----------
        day : int
            project day.
        daylen : float
            length of day in hours.

        Returns:
        -------
        Nothing
            Method calculates GPP, NPP and Ra.
        """
        # local var for tidyness
        (am, pm) = self.am, self.pm # morning/afternoon
        (Tair_K, par, vpd, ca) = self.get_met_data(day)

        ci = [self.calculate_ci(vpd[k], ca) for k in am, pm]
        N0 = self.calculate_top_of_canopy_n()
        
        # Quantum efficiency (umol mol-1), no Ci or temp dependancey in c4
        # plants
        # Ehleringer, J. R., 1978: Implications of quantum yield differences 
        # on the distributions of C3 and C4 grasses.  Oecologia, 31, 255-267. 
        alpha = 0.04
        
        
        # C4 assimilation following from Collatz et al. 1992
        
        #?? Exponential factor in the equation defining kt
        #alpharf = 0.067			# mol/mol
        kslope = 0.7		    # initial slope of photosynthetic CO2 response (mol m-2 s-1), Collatz table 2
        theta = 0.83			# curvature parameter, Collatz table 2
        beta  = 0.93			# curvature parameter, Collatz table 2
        
        
        # http://www.cesm.ucar.edu/models/cesm1.0/clm/CLM4_Tech_Note.pdf
        # Table 8.2 has PFT values...
        vcmax25 = self.params.vcmaxna * N0 + self.params.vcmaxnb          
        
        # Massad et al. 2007
        Eav = 67294.0
        Hdv = 144568.0
        deltaSv = 472.0
        vcmax = [self.peaked_arrh(vcmax25, Eav, Tair_K[k], deltaSv, Hdv) \
                 for k in am, pm]
        
        # reduce photosynthetic capacity with moisture stress
        vcmax = [self.state.wtfac_root * vcmax[k] for k in am, pm] 
        
        #Je = vcmax
        #Jc = k / vmax * vcmax * ci
        #Ji = alpha * I
        
        
        # Rubisco and light limited capacity
        par_per_sec = par / (60.0 * 60.0 * daylen)
        M = [self.quadratic(theta, -(alpha*par_per_sec+vcmax[k]), alpha*par_per_sec*vcmax[k]) for k in am, pm]

        # M and CO2 limitation
        A = [self.quadratic(beta, -(M[k]+kslope*ci[k]), M[k]*kslope*ci[k]) for k in am, pm]

        # These respiration terms are just for assimilation calculations,
        # autotrophic respiration is stil assumed to be half of GPP
        (Rd, Rm) = self.calc_respiration(Tair_K, vcmax)    


        # Net (saturated) photosynthetic rate, not sure if this
        # makes sense.
        Asat = [A[k] - Rd[k] for k in am, pm]
        
        """    
        
            # calculate mate parameters, e.g. accounting for temp dependancy
            (Km, Kc, Ko, Kp) = self.calculate_michaelis_menten_parameter(Tair_K)
            gamma_star = self.calculate_co2_compensation_point(Tair_K)
        
            
    
        
            # Currently i dont have any information on how these depednancies 
            # vary with N, nor do I have site parameters so going to use
            # values at 25 degrees from the literature, hardwired till this is
            # resolved.
            # Values from table 4.1, in von Caemmerer 2000, pg 100.
            vcmax25 = 60.0
            vpmax25 = 120.0 
            jmax25 = 400.0
    
            if self.control.modeljm == True: 
                jmax = self.calculate_jmax_parameter(Tair_K, jmax25)
                vcmax = self.calculate_vcmax_parameter(Tair_K, vcmax25)
                vpmax = self.calculate_vpmax_parameter(Tair_K, vpmax25)
            else:
                jmax = [self.params.jmax, self.params.jmax]
                vcmax = [self.params.vcmax, self.params.vcmax]
                vpmax = [self.params.vpmax, self.params.vpmax]
        
        
        
            # reduce photosynthetic capacity with moisture stress
            #jmax = [self.state.wtfac_root * jmax[k] for k in am, pm]
            #vcmax = [self.state.wtfac_root * vcmax[k] for k in am, pm] 
            #vpmax = [self.state.wtfac_root * vpmax[k] for k in am, pm] 
    
            # These respiration terms are just for assimilation calculations,
            # autotrophic respiration is stil assumed to be half of GPP
            (Rd, Rm) = self.calc_respiration(Tair_K, vcmax)    
        
            # Calculate assimilation rates.
            Ac = self.calc_enzyme_limited_assim(Km, Kc, Ko, Kp, ci, vpmax, vcmax, 
                                                Rd, Rm)
            par_per_sec = par / (60.0 * 60.0 * daylen)
            Aj = self.calc_light_limited_assim(par_per_sec, jmax, ci, Rd, Rm)
            Asat = [min(Aj[k], Ac[k]) for k in am, pm]
        """
        
        # Assumption that the integral is symmetric about noon, so we average
        # the LUE accounting for variability in temperature, but importantly
        # not PAR
        lue = [self.epsilon(Asat[k], par, daylen, alpha) for k in am, pm]

        # mol C mol-1 PAR - use average to simulate canopy photosynthesis
        lue_avg = sum(lue) / 2.0

        if float_eq(self.state.lai, 0.0):
            self.fluxes.apar = 0.0
        else:
            par_mol = par * const.UMOL_TO_MOL
            # absorbed photosynthetically active radiation
            self.fluxes.apar = par_mol * self.state.fipar
        
        # gC m-2 d-1
        self.fluxes.gpp_gCm2 = (self.fluxes.apar * lue_avg * 
                                const.MOL_C_TO_GRAMS_C)
        self.fluxes.gpp_am_pm[am] = ((self.fluxes.apar / 2.0) * lue[am] * 
                                      const.MOL_C_TO_GRAMS_C)
        self.fluxes.gpp_am_pm[pm] = ((self.fluxes.apar / 2.0) * lue[pm] * 
                                      const.MOL_C_TO_GRAMS_C)
        
        #print self.fluxes.gpp_gCm2
        self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue
        
        if self.control.nuptake_model == 3:
            self.fluxes.gpp_gCm2 *= self.params.ac
            self.fluxes.gpp_am_pm[am] *= self.params.ac
            self.fluxes.gpp_am_pm[pm] *= self.params.ac
            self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue
            
        # g C m-2 to tonnes hectare-1 day-1
        conv = const.G_AS_TONNES / const.M2_AS_HA
        self.fluxes.gpp = self.fluxes.gpp_gCm2 * conv
        self.fluxes.npp = self.fluxes.npp_gCm2 * conv
        
        # Plant respiration assuming carbon-use efficiency.
        self.fluxes.auto_resp = self.fluxes.gpp - self.fluxes.npp
    def calculate_photosynthesis(self, day, daylen):
        """ Photosynthesis is calculated assuming GPP is proportional to APAR,
        a commonly assumed reln (e.g. Potter 1993, Myneni 2002). The slope of
        relationship btw GPP and APAR, i.e. LUE is modelled using the
        photosynthesis eqns from Sands.

        Assumptions:
        ------------
        (1) photosynthetic light response is a non-rectangular hyperbolic func
            of photon-flux density with a light-saturatred photosynthetic rate
            (Amax), quantum yield (alpha) and curvature (theta).
        (2) the canopy is horizontally uniform.
        (3) PAR distribution within the canopy obeys Beer's law.
        (4) light-saturated photosynthetic rate declines with canopy depth in
            proportion to decline in PAR
        (5) alpha + theta do not vary within the canopy
        (6) dirunal variation of PAR is sinusoidal.
        (7) The model makes no assumption about N within the canopy, however
            this version assumes N declines exponentially through the cnaopy.
        (8) Leaf temperature is the same as the air temperature.

        Parameters:
        ----------
        day : int
            project day.
        daylen : float
            length of day in hours.

        Returns:
        -------
        Nothing
            Method calculates GPP, NPP and Ra.
        """
        # local var for tidyness
        (am, pm) = self.am, self.pm # morning/afternoon
        (temp, par, vpd, ca) = self.get_met_data(day)
        Tk = [temp[k] + const.DEG_TO_KELVIN for k in am, pm]
        
        # calculate mate parameters, e.g. accounting for temp dependancy
        gamma_star = self.calculate_co2_compensation_point(Tk)
        km = self.calculate_michaelis_menten_parameter(Tk)
        N0 = self.calculate_leafn()
        jmax = self.calculate_jmax_parameter(Tk, N0)
        vcmax = self.calculate_vcmax_parameter(Tk, N0)
        alpha = self.calculate_quantum_efficiency(temp)
        
        # calculate ratio of intercellular to atmospheric CO2 concentration.
        # Also allows productivity to be water limited through stomatal opening.
        cica = [self.calculate_ci_ca_ratio(vpd[k]) for k in am, pm]
        ci = [i * ca for i in cica]
        
        # store value as needed in water balance calculation
        self.fluxes.cica_avg = sum(cica) / len(cica)
        
        # Rubisco-limited rate of photosynthesis
        ac = [self.aclim(ci[k], gamma_star[k], km[k], vcmax[k]) for k in am, pm]
        
        # Light-limited rate of photosynthesis allowed by RuBP regeneration
        aj = [self.ajlim(jmax[k], ci[k], gamma_star[k]) for k in am, pm]
        
        # Note that these are gross photosynthetic rates. Response to elevated
        # [CO2] is reduced if N declines, but increases as gs declines.
        asat = [min(aj[k], ac[k]) for k in am, pm]
        
        # GPP is assumed to be proportional to APAR, where the LUE defines the
        # slope of this relationship. LUE, calculation is performed for morning 
        # and afternnon periods.
        lue = [self.epsilon(asat[k], par, daylen, alpha[k]) for k in am, pm]
        
        # mol C mol-1 PAR - use average to simulate canopy photosynthesis
        lue_avg = sum(lue) / len(lue)
        
        if float_eq(self.state.lai, 0.0):
            self.fluxes.apar = 0.0
        else:
            par_mol = par * const.UMOL_TO_MOL
            self.fluxes.apar = par_mol * self.state.light_interception

        # gC m-2 d-1
        self.fluxes.gpp_gCm2 = (self.fluxes.apar * lue_avg * 
                                const.MOL_C_TO_GRAMS_C)
        self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue
        
        
        self.fluxes.gpp_am_gCm2 = ((self.fluxes.apar / 2.0) * lue[am] * 
                                    const.MOL_C_TO_GRAMS_C)
        self.fluxes.gpp_pm_gCm2 = ((self.fluxes.apar / 2.0) * lue[pm] * 
                                    const.MOL_C_TO_GRAMS_C)
        
        # tonnes hectare-1 day-1
        conv = const.G_AS_TONNES / const.M2_AS_HA
        self.fluxes.gpp = self.fluxes.gpp_gCm2 * conv
        self.fluxes.npp = self.fluxes.npp_gCm2 * conv
    
        # Plant respiration assuming carbon-use efficiency.
        self.fluxes.auto_resp = self.fluxes.gpp - self.fluxes.npp
Beispiel #34
0
    def __init__(self, fname=None, DUMP=False, spin_up=False, met_header=4):
        """ Set up model

        * Read meterological forcing file
        * Read user config file and adjust the model parameters, control or
          initial state attributes that are used within the code.
        * Setup all class instances...perhaps this isn't the tidyest place for
          this?
        * Initialise things, zero stores etc.

        Parameters:
        ----------
        fname : string
            filename of model parameters, including path
        chk_cmd_line : logical
            parse the cmd line?
        DUMP : logical
            dump a the default parameters to a file
        met_header : in
            row number of met file header with variable name
        Returns:
        -------
        Nothing
            Controlling class of the model, runs things.

        """
        self.day_output = []  # store daily output
        # initialise model structures and read met data
        (self.control, self.params, self.state, self.files, self.fluxes,
         self.met_data, self.print_opts) = initialise_model_data(fname,
                                                                 met_header,
                                                                 DUMP=DUMP)

        # params are defined in per year, needs to be per day
        # Important this is done here as rate constants elsewhere in the code
        # are assumed to be in units of days not years!
        self.correct_rate_constants(output=False)

        # class instance
        self.cs = CarbonSoilFlows(self.control, self.params, self.state,
                                  self.fluxes, self.met_data)

        self.ns = NitrogenSoilFlows(self.control, self.params, self.state,
                                    self.fluxes, self.met_data)

        self.lf = Litter(self.control, self.params, self.state, self.fluxes)

        self.pg = PlantGrowth(self.control, self.params, self.state,
                              self.fluxes, self.met_data)

        self.cb = CheckBalance(self.control, self.params, self.state,
                               self.fluxes, self.met_data)

        self.db = Disturbance(self.control, self.params, self.state,
                              self.fluxes, self.met_data)

        if self.control.deciduous_model:
            if self.state.max_lai is None:
                self.state.max_lai = 0.01  # initialise to something really low
                self.state.max_shoot = 0.01  # initialise to something really low

            # Are we reading in last years average growing season?
            if (float_eq(self.state.avg_alleaf, 0.0)
                    and float_eq(self.state.avg_alstem, 0.0)
                    and float_eq(self.state.avg_albranch, 0.0)
                    and float_eq(self.state.avg_alleaf, 0.0)
                    and float_eq(self.state.avg_alroot, 0.0)
                    and float_eq(self.state.avg_alcroot, 0.0)):
                self.pg.calc_carbon_allocation_fracs(0.0)  #comment this!!
            else:

                self.fluxes.alleaf = self.state.avg_alleaf
                self.fluxes.alstem = self.state.avg_alstem
                self.fluxes.albranch = self.state.avg_albranch
                self.fluxes.alroot = self.state.avg_alroot
                self.fluxes.alcroot = self.state.avg_alcroot

            self.pg.allocate_stored_c_and_n(init=True)
            #self.pg.enforce_sensible_nstore()
            self.P = Phenology(
                self.fluxes,
                self.state,
                self.control,
                self.params.previous_ncd,
                store_transfer_len=self.params.store_transfer_len)

        self.pr = PrintOutput(self.params, self.state, self.fluxes,
                              self.control, self.files, self.print_opts)

        # build list of variables to prin
        (self.print_state, self.print_fluxes) = self.pr.get_vars_to_print()

        # print model defaul
        if DUMP == True:
            self.pr.save_default_parameters()
            sys.exit(0)

        self.dead = False  # johnny 5 is alive

        # calculate initial stuff, e.g. C:N ratios and zero annual flux sum
        self.day_end_calculations(INIT=True)
        self.state.pawater_root = self.params.wcapac_root
        self.state.pawater_topsoil = self.params.wcapac_topsoil
        self.spin_up = spin_up
        self.state.lai = max(
            0.01, (self.params.sla * const.M2_AS_HA / const.KG_AS_TONNES /
                   self.params.cfracts * self.state.shoot))

        # figure out the number of years for simulation and the number of
        # days in each year
        self.years = uniq(self.met_data["year"])
        self.days_in_year = [
            self.met_data["year"].count(yr) for yr in self.years
        ]

        if self.control.water_stress == False:
            sys.stderr.write("**** You have turned off the drought stress")
            sys.stderr.write(", I assume you're debugging??!\n")
Beispiel #35
0
    def day_end_calculations(self, prjday, days_in_year=None, INIT=False):
        """Calculate derived values from state variables.

        Parameters:
        -----------
        day : integer
            day of simulation

        INIT : logical
            logical defining whether it is the first day of the simulation

        """
        self.fluxes.ninflow = self.met_data['ndep'][prjday]
        
        # update N:C of plant pools
        if float_eq(self.state.shoot, 0.0):
            self.state.shootnc = 0.0
        else:
            self.state.shootnc = self.state.shootn / self.state.shoot
        
        self.state.rootnc = max(0.0, self.state.rootn / self.state.root)
                
        # total plant, soil & litter nitrogen
        self.state.soiln = (self.state.inorgn + self.state.activesoiln +
                            self.state.slowsoiln + self.state.passivesoiln)
        self.state.litternag = self.state.structsurfn + self.state.metabsurfn
        self.state.litternbg = self.state.structsoiln + self.state.metabsoiln
        self.state.littern = self.state.litternag + self.state.litternbg
        self.state.plantn = (self.state.shootn + self.state.rootn +
                             self.state.branchn + self.state.stemn)
        self.state.totaln = (self.state.plantn + self.state.littern +
                             self.state.soiln)

        # total plant, soil, litter and system carbon
        self.state.soilc = (self.state.activesoil + self.state.slowsoil +
                            self.state.passivesoil)
        self.state.littercag = self.state.structsurf + self.state.metabsurf
        self.state.littercbg = self.state.structsoil + self.state.metabsoil
        self.state.litterc = self.state.littercag + self.state.littercbg
        self.state.plantc = (self.state.root + self.state.shoot +
                             self.state.stem + self.state.branch)
        self.state.totalc = (self.state.soilc + self.state.litterc +
                             self.state.plantc)

        # optional constant passive pool
        if self.control.passiveconst == True:
            self.state.passivesoil = self.params.passivesoilz
            self.state.passivesoiln = self.params.passivesoilnz

        if INIT == False:
            #Required so max leaf & root N:C can depend on Age
            self.state.age += 1.0 / days_in_year

            # N Net mineralisation, i.e. excess of N outflows over inflows
            self.fluxes.nmineralisation = (self.fluxes.ninflow +
                                            self.fluxes.ngross +
                                            self.fluxes.nrootexudate -
                                            self.fluxes.nimmob +
                                            self.fluxes.nlittrelease)
            # calculate NEP
            self.fluxes.nep = (self.fluxes.npp - self.fluxes.hetero_resp -
                               self.fluxes.ceaten * 
                               (1. - self.params.fracfaeces))
Beispiel #36
0
    def fire(self, growth_obj):
        """
        Fire...

        * 100 percent of aboveground biomass 
        * 100 percent of surface litter
        * 50 percent of N volatilized to the atmosphere
        * 50 percent of N returned to inorgn pool"
        * Coarse roots are not damaged by fire!

        vaguely following ...
        http://treephys.oxfordjournals.org/content/24/7/765.full.pdf
        """

        totaln = (self.state.branchn + self.state.shootn + self.state.stemn +
                  self.state.structsurfn)
        self.state.inorgn += totaln / 2.0

        # re-establish everything with C/N ~ 25.
        if self.control.alloc_model == "GRASSES":
            self.state.branch = 0.0
            self.state.branchn = 0.0
            self.state.sapwood = 0.0
            self.state.stem = 0.0
            self.state.stemn = 0.0
            self.state.stemnimm = 0.0
            self.state.stemnmob = 0.0
        else:
            self.state.branch = 0.001
            self.state.branchn = 0.00004
            self.state.sapwood = 0.001
            self.state.stem = 0.001
            self.state.stemn = 0.00004
            self.state.stemnimm = 0.00004
            self.state.stemnmob = 0.0

        self.state.age = 0.0
        self.state.metabsurf = 0.0
        self.state.metabsurfn = 0.0
        self.state.prev_sma = 1.0
        self.state.root = 0.001
        self.state.rootn = 0.00004
        self.state.shoot = 0.001
        self.state.lai = (self.params.sla * const.M2_AS_HA /
                          const.KG_AS_TONNES / self.params.cfracts *
                          self.state.shoot)
        self.state.shootn = 0.00004
        self.state.structsurf = 0.001
        self.state.structsurfn = 0.00004

        # reset litter flows
        self.fluxes.deadroots = 0.0
        self.fluxes.deadstems = 0.0
        self.fluxes.deadbranch = 0.0
        self.fluxes.deadsapwood = 0.0
        self.fluxes.deadleafn = 0.0
        self.fluxes.deadrootn = 0.0
        self.fluxes.deadbranchn = 0.0
        self.fluxes.deadstemn = 0.0

        # update N:C of plant pools
        if float_eq(self.state.shoot, 0.0):
            self.state.shootnc = 0.0
        else:
            self.state.shootnc = self.state.shootn / self.state.shoot

        if self.control.ncycle == False:
            self.state.shootnc = self.params.prescribed_leaf_NC

        #print self.state.rootn , self.state.root
        if float_eq(self.state.root, 0.0):
            self.state.rootnc = 0.0
        else:
            self.state.rootnc = max(0.0, self.state.rootn / self.state.root)

        growth_obj.sma.reset_stream()  # reset any stress limitation
        self.state.prev_sma = 1.0
Beispiel #37
0
    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)
Beispiel #38
0
    def calculate_soil_water_fac(self):
        """ Estimate a relative water availability factor [0..1]

        A drying soil results in physiological stress that can induce stomatal
        closure and reduce transpiration. Further, N mineralisation depends on 
        top soil moisture.
        
        self.params.qs = 0.2 in SDGVM
        
        References:
        -----------
        * Landsberg and Waring (1997) Forest Ecology and Management, 95, 209-228.
          See --> Figure 2.
        * Egea et al. (2011) Agricultural Forest Meteorology, 151, 1370-1384.
          
        But similarly see:
        * van Genuchten (1981) Soil Sci. Soc. Am. J, 44, 892--898.
        * Wang and Leuning (1998) Ag Forest Met, 91, 89-111.
        
        * Pepper et al. (2008) Functional Change Biology, 35, 493-508
       
        Returns:
        --------
        wtfac_topsoil : float
            water availability factor for the top soil [0,1]
        wtfac_root : float
            water availability factor for the root zone [0,1]    
        """
        # turn into fraction...
        smc_topsoil = self.state.pawater_topsoil / self.params.wcapac_topsoil
        smc_root = self.state.pawater_root / self.params.wcapac_root
        
        if self.control.sw_stress_model == 0:
            wtfac_topsoil = smc_topsoil**self.params.qs  
            wtfac_root = smc_root**self.params.qs  
            
        elif self.control.sw_stress_model == 1:
            wtfac_topsoil = self.calc_sw_modifier(smc_topsoil, 
                                                  self.params.ctheta_topsoil, 
                                                  self.params.ntheta_topsoil)
  
            wtfac_root = self.calc_sw_modifier(smc_root, 
                                               self.params.ctheta_root, 
                                               self.params.ntheta_root)
            
        elif self.control.sw_stress_model == 2:
            
            # Stomatal limitaiton
            # Exponetial function to reduce g1 with soil water limitation
            # based on Zhou et al. 2013, AFM, following Makela et al 1996.
            # For the moment I have hardwired the PFT parameter as I am still
            # testing.
            # Because the model is a daily model we are assuming that LWP is
            # well approximated by the night SWP.
            
            if float_eq(smc_topsoil, 0.0):
                psi_swp_topsoil = -1.5
            else:
                arg1 = self.params.psi_sat_topsoil
                arg2 = smc_topsoil /self.params.theta_sat_topsoil
                arg3 = -self.params.b_topsoil
                psi_swp_topsoil = arg1 * arg2**arg3
            
            if float_eq(smc_root, 0.0):
                psi_swp_root = -1.5
            else:
                arg1 = self.params.psi_sat_root
                arg2 = smc_root/self.params.theta_sat_root
                arg3 = -self.params.b_root
                psi_swp_root = arg1 * arg2**arg3
            
            # multipliy these by g1, same as eqn 3 in Zhou et al. 2013.
            b = 0.66
            
            wtfac_topsoil = exp(b * psi_swp_topsoil)
            wtfac_root = exp(b * psi_swp_root)
        
        #print self.state.pawater_root,wtfac_root    
        return (wtfac_topsoil, wtfac_root) 
    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)  
Beispiel #40
0
    def calculate_photosynthesis(self, day, daylen):
        """ Photosynthesis is calculated assuming GPP is proportional to APAR,
        a commonly assumed reln (e.g. Potter 1993, Myneni 2002). The slope of
        relationship btw GPP and APAR, i.e. LUE is modelled using the
        photosynthesis eqns from Sands.

        Assumptions:
        ------------
        (1) photosynthetic light response is a non-rectangular hyperbolic func
            of photon-flux density with a light-saturatred photosynthetic rate
            (Amax), quantum yield (alpha) and curvature (theta).
        (2) the canopy is horizontally uniform.
        (3) PAR distribution within the canopy obeys Beer's law.
        (4) light-saturated photosynthetic rate declines with canopy depth in
            proportion to decline in PAR
        (5) alpha + theta do not vary within the canopy
        (6) dirunal variation of PAR is sinusoidal.
        (7) The model makes no assumption about N within the canopy, however
            this version assumes N declines exponentially through the cnaopy.
        (8) Leaf temperature is the same as the air temperature.

        Parameters:
        ----------
        day : int
            project day.
        daylen : float
            length of day in hours.

        Returns:
        -------
        Nothing
            Method calculates GPP, NPP and Ra.
        """
        # local var for tidyness
        (Tair_K, par, vpd, ca) = self.get_met_data(day)

        ci = [self.calculate_ci(vpd[k], ca) for k in self.am, self.pm]
        N0 = self.calculate_top_of_canopy_n()
        alpha = self.params.alpha_c4

        # Temp dependancies from Massad et al. 2007
        (vcmax, vcmax25) = self.calculate_vcmax_parameter(Tair_K, N0)

        # Rubisco and light-limited capacity (Appendix, 2B)
        par_per_sec = par / (60.0 * 60.0 * daylen)
        M = [
            self.quadratic(a=self.beta1, b=-(vcmax[k] + alpha * par_per_sec), c=(vcmax[k] * alpha * par_per_sec))
            for k in self.am, self.pm
        ]

        # The limitation of the overall rate by M and CO2 limited flux:
        A = [
            self.quadratic(a=self.beta2, b=-(M[k] + self.kslope * ci[k]), c=(M[k] * self.kslope * ci[k]))
            for k in self.am, self.pm
        ]

        # These respiration terms are just for assimilation calculations,
        # autotrophic respiration is stil assumed to be half of GPP
        (Rd) = self.calc_respiration(Tair_K, vcmax25)

        # Net (saturated) photosynthetic rate, not sure if this
        # makes sense.
        asat = [A[k] - Rd[k] for k in self.am, self.pm]

        # LUE (umol C umol-1 PAR)
        lue = [self.epsilon(asat[k], par, daylen, alpha) for k in self.am, self.pm]
        lue_avg = sum(lue) / 2.0  # use average to simulate canopy photosynthesis

        if float_eq(self.state.lai, 0.0):
            self.fluxes.apar = 0.0
        else:
            # absorbed photosynthetically active radiation (umol m-2 s-1)
            self.fluxes.apar = par * self.state.fipar

        # gC m-2 d-1
        self.fluxes.gpp_gCm2 = self.fluxes.apar * lue_avg * const.UMOL_TO_MOL * const.MOL_C_TO_GRAMS_C
        self.fluxes.gpp_am_pm[self.am] = (
            (self.fluxes.apar / 2.0) * lue[self.am] * const.UMOL_TO_MOL * const.MOL_C_TO_GRAMS_C
        )
        self.fluxes.gpp_am_pm[self.pm] = (
            (self.fluxes.apar / 2.0) * lue[self.pm] * const.UMOL_TO_MOL * const.MOL_C_TO_GRAMS_C
        )

        self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue

        if self.control.nuptake_model == 3:
            self.fluxes.gpp_gCm2 *= self.params.ac
            self.fluxes.gpp_am_pm[am] *= self.params.ac
            self.fluxes.gpp_am_pm[pm] *= self.params.ac
            self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue

        # g C m-2 to tonnes hectare-1 day-1
        conv = const.G_AS_TONNES / const.M2_AS_HA
        self.fluxes.gpp = self.fluxes.gpp_gCm2 * conv
        self.fluxes.npp = self.fluxes.npp_gCm2 * conv

        # Plant respiration assuming carbon-use efficiency.
        self.fluxes.auto_resp = self.fluxes.gpp - self.fluxes.npp
Beispiel #41
0
    def __init__(self, fname=None, DUMP=False, spin_up=False, met_header=4):

        """ Set up model

        * Read meterological forcing file
        * Read user config file and adjust the model parameters, control or
          initial state attributes that are used within the code.
        * Setup all class instances...perhaps this isn't the tidyest place for
          this?
        * Initialise things, zero stores etc.

        Parameters:
        ----------
        fname : string
            filename of model parameters, including path
        chk_cmd_line : logical
            parse the cmd line?
        DUMP : logical
            dump a the default parameters to a file
        met_header : in
            row number of met file header with variable name
        Returns:
        -------
        Nothing
            Controlling class of the model, runs things.

        """
        self.day_output = [] # store daily output
        # initialise model structures and read met data
        (self.control, self.params,
         self.state, self.files,
         self.fluxes, self.met_data,
         self.print_opts) = initialise_model_data(fname, met_header, DUMP=DUMP)

        # params are defined in per year, needs to be per day
        # Important this is done here as rate constants elsewhere in the code
        # are assumed to be in units of days not years!
        self.correct_rate_constants(output=False)

        # class instance
        self.cs = CarbonSoilFlows(self.control, self.params, self.state,
                                  self.fluxes, self.met_data)

        self.ns = NitrogenSoilFlows(self.control, self.params, self.state,
                                    self.fluxes, self.met_data)

        self.lf = Litter(self.control, self.params, self.state, self.fluxes)

        self.pg = PlantGrowth(self.control, self.params, self.state,
                              self.fluxes, self.met_data)

        self.cb = CheckBalance(self.control, self.params, self.state,
                               self.fluxes, self.met_data)

        self.db = Disturbance(self.control, self.params, self.state,
                              self.fluxes, self.met_data)

        if self.control.deciduous_model:
            if self.state.max_lai is None:
                self.state.max_lai = 0.01 # initialise to something really low
                self.state.max_shoot = 0.01 # initialise to something really low
            
            # Are we reading in last years average growing season?
            if (float_eq(self.state.avg_alleaf, 0.0) and 
                float_eq(self.state.avg_alstem, 0.0) and 
                float_eq(self.state.avg_albranch, 0.0) and 
                float_eq(self.state.avg_alleaf, 0.0) and 
                float_eq(self.state.avg_alroot, 0.0) and 
                float_eq(self.state.avg_alcroot, 0.0)): 
                self.pg.calc_carbon_allocation_fracs(0.0) #comment this!!
            else:
                
                self.fluxes.alleaf = self.state.avg_alleaf
                self.fluxes.alstem = self.state.avg_alstem 
                self.fluxes.albranch = self.state.avg_albranch 
                self.fluxes.alroot = self.state.avg_alroot 
                self.fluxes.alcroot = self.state.avg_alcroot
            
            
            self.pg.allocate_stored_c_and_n(init=True)
            #self.pg.enforce_sensible_nstore()
            self.P = Phenology(self.fluxes, self.state, self.control,
                               self.params.previous_ncd,
                              store_transfer_len=self.params.store_transfer_len)

        self.pr = PrintOutput(self.params, self.state, self.fluxes,
                              self.control, self.files, self.print_opts)

        # build list of variables to prin
        (self.print_state, self.print_fluxes) = self.pr.get_vars_to_print()

        # print model defaul
        if DUMP == True:
            self.pr.save_default_parameters()
            sys.exit(0)

        self.dead = False # johnny 5 is alive

        # calculate initial stuff, e.g. C:N ratios and zero annual flux sum
        self.day_end_calculations(INIT=True)
        self.state.pawater_root = self.params.wcapac_root
        self.state.pawater_topsoil = self.params.wcapac_topsoil
        self.spin_up = spin_up
        self.state.lai = max(0.01, (self.params.sla * const.M2_AS_HA /
                                    const.KG_AS_TONNES / self.params.cfracts *
                                    self.state.shoot))

        # figure out the number of years for simulation and the number of
        # days in each year
        self.years = uniq(self.met_data["year"])
        self.days_in_year = [self.met_data["year"].count(yr)
                             for yr in self.years]

        if self.control.water_stress == False:
            sys.stderr.write("**** You have turned off the drought stress")
            sys.stderr.write(", I assume you're debugging??!\n")
Beispiel #42
0
    def calculate_photosynthesis(self, day, daylen):
        """ Photosynthesis is calculated assuming GPP is proportional to APAR,
        a commonly assumed reln (e.g. Potter 1993, Myneni 2002). The slope of
        relationship btw GPP and APAR, i.e. LUE is modelled using the
        photosynthesis eqns from Sands.

        Assumptions:
        ------------
        (1) photosynthetic light response is a non-rectangular hyperbolic func
            of photon-flux density with a light-saturatred photosynthetic rate
            (Amax), quantum yield (alpha) and curvature (theta).
        (2) the canopy is horizontally uniform.
        (3) PAR distribution within the canopy obeys Beer's law.
        (4) light-saturated photosynthetic rate declines with canopy depth in
            proportion to decline in PAR
        (5) alpha + theta do not vary within the canopy
        (6) dirunal variation of PAR is sinusoidal.
        (7) The model makes no assumption about N within the canopy, however
            this version assumes N declines exponentially through the cnaopy.
        (8) Leaf temperature is the same as the air temperature.

        Parameters:
        ----------
        day : int
            project day.
        daylen : float
            length of day in hours.

        Returns:
        -------
        Nothing
            Method calculates GPP, NPP and Ra.
        """
        # local var for tidyness
        (Tk_am, Tk_pm, par, vpd_am, vpd_pm, ca) = self.get_met_data(day)

        # calculate mate params & account for temperature dependencies
        N0 = self.calculate_top_of_canopy_n()

        ci_am = self.calculate_ci(vpd_am, ca)
        ci_pm = self.calculate_ci(vpd_pm, ca)

        alpha = self.params.alpha_c4

        # Temp dependancies from Massad et al. 2007
        (vcmax_am, vcmax25_am) = self.calculate_vcmax_parameter(Tk_am, N0)
        (vcmax_pm, vcmax25_pm) = self.calculate_vcmax_parameter(Tk_pm, N0)

        # Rubisco and light-limited capacity (Appendix, 2B)
        par_per_sec = par / (60.0 * 60.0 * daylen)
        M_am = self.quadratic(a=self.beta1,
                              b=-(vcmax_am + alpha * par_per_sec),
                              c=(vcmax_am * alpha * par_per_sec))
        M_pm = self.quadratic(a=self.beta1,
                              b=-(vcmax_pm + alpha * par_per_sec),
                              c=(vcmax_pm * alpha * par_per_sec))

        # The limitation of the overall rate by M and CO2 limited flux:
        A_am = self.quadratic(a=self.beta2,
                              b=-(M_am + self.kslope * ci_am),
                              c=(M_am * self.kslope * ci_am))
        A_pm = self.quadratic(a=self.beta2,
                              b=-(M_pm + self.kslope * ci_pm),
                              c=(M_pm * self.kslope * ci_pm))

        # These respiration terms are just for assimilation calculations,
        # autotrophic respiration is stil assumed to be half of GPP
        Rd_am = self.calc_respiration(Tk_am, vcmax25_am)
        Rd_pm = self.calc_respiration(Tk_pm, vcmax25_pm)

        # Net (saturated) photosynthetic rate, not sure if this
        # makes sense.
        asat_am = A_am - Rd_am
        asat_pm = A_pm - Rd_pm

        # LUE (umol C umol-1 PAR)
        lue_am = self.epsilon(asat_am, par, daylen, alpha)
        lue_pm = self.epsilon(asat_pm, par, daylen, alpha)
        # use average to simulate canopy photosynthesis
        lue_avg = (lue_am + lue_pm) / 2.0

        if float_eq(self.state.lai, 0.0):
            self.fluxes.apar = 0.0
        else:
            # absorbed photosynthetically active radiation (umol m-2 s-1)
            self.fluxes.apar = par * self.state.fipar
        apar_half_day = self.fluxes.apar / 2.0

        # convert umol m-2 d-1 -> gC m-2 d-1
        self.fluxes.gpp_gCm2 = self.fluxes.apar * lue_avg * const.UMOL_2_GRAMS_C
        self.fluxes.gpp_am = apar_half_day * lue_am * const.UMOL_2_GRAMS_C
        self.fluxes.gpp_pm = apar_half_day * lue_pm * const.UMOL_2_GRAMS_C
        self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue

        if self.control.nuptake_model == 3:
            self.fluxes.gpp_gCm2 *= self.params.ac
            self.fluxes.gpp_am *= self.params.ac
            self.fluxes.gpp_pm *= self.params.ac
            self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue

        # g C m-2 to tonnes hectare-1 day-1
        self.fluxes.gpp = self.fluxes.gpp_gCm2 * const.GRAM_C_2_TONNES_HA
        self.fluxes.npp = self.fluxes.npp_gCm2 * const.GRAM_C_2_TONNES_HA

        # Plant respiration assuming carbon-use efficiency.
        self.fluxes.auto_resp = self.fluxes.gpp - self.fluxes.npp
Beispiel #43
0
    def calculate_photosynthesis(self, day, daylen):
        """ Photosynthesis is calculated assuming GPP is proportional to APAR,
        a commonly assumed reln (e.g. Potter 1993, Myneni 2002). The slope of
        relationship btw GPP and APAR, i.e. LUE is modelled using the
        photosynthesis eqns from Sands.

        Assumptions:
        ------------
        (1) photosynthetic light response is a non-rectangular hyperbolic func
            of photon-flux density with a light-saturatred photosynthetic rate
            (Amax), quantum yield (alpha) and curvature (theta).
        (2) the canopy is horizontally uniform.
        (3) PAR distribution within the canopy obeys Beer's law.
        (4) light-saturated photosynthetic rate declines with canopy depth in
            proportion to decline in PAR
        (5) alpha + theta do not vary within the canopy
        (6) dirunal variation of PAR is sinusoidal.
        (7) The model makes no assumption about N within the canopy, however
            this version assumes N declines exponentially through the cnaopy.
        (8) Leaf temperature is the same as the air temperature.

        Parameters:
        ----------
        day : int
            project day.
        daylen : float
            length of day in hours.

        Returns:
        -------
        Nothing
            Method calculates GPP, NPP and Ra.
        """
        # local var for tidyness
        (am, pm) = self.am, self.pm  # morning/afternoon
        (Tair_K, par, vpd, ca) = self.get_met_data(day)

        ci = [self.calculate_ci(vpd[k], ca) for k in am, pm]
        N0 = self.calculate_top_of_canopy_n()
        alpha = self.params.alpha_c4

        # Temp dependancies from Massad et al. 2007
        (vcmax, vcmax25) = self.calculate_vcmax_parameter(Tair_K, N0)

        # Rubisco and light-limited capacity (Appendix, 2B)
        par_per_sec = par / (60.0 * 60.0 * daylen)
        M = [
            self.quadratic(a=self.beta1,
                           b=-(vcmax[k] + alpha * par_per_sec),
                           c=(vcmax[k] * alpha * par_per_sec)) for k in am, pm
        ]

        # The limitation of the overall rate by M and CO2 limited flux:
        A = [
            self.quadratic(a=self.beta2,
                           b=-(M[k] + self.kslope * ci[k]),
                           c=(M[k] * self.kslope * ci[k])) for k in am, pm
        ]

        # These respiration terms are just for assimilation calculations,
        # autotrophic respiration is stil assumed to be half of GPP
        (Rd) = self.calc_respiration(Tair_K, vcmax25)

        # Net (saturated) photosynthetic rate, not sure if this
        # makes sense.
        Asat = [A[k] - Rd[k] for k in am, pm]

        # Assumption that the integral is symmetric about noon, so we average
        # the LUE accounting for variability in temperature, but importantly
        # not PAR
        lue = [self.epsilon(Asat[k], par, daylen, alpha) for k in am, pm]

        # mol C mol-1 PAR - use average to simulate canopy photosynthesis
        lue_avg = sum(lue) / 2.0

        if float_eq(self.state.lai, 0.0):
            self.fluxes.apar = 0.0
        else:
            par_mol = par * const.UMOL_TO_MOL
            # absorbed photosynthetically active radiation
            self.fluxes.apar = par_mol * self.state.fipar

        # gC m-2 d-1
        self.fluxes.gpp_gCm2 = (self.fluxes.apar * lue_avg *
                                const.MOL_C_TO_GRAMS_C)
        self.fluxes.gpp_am_pm[am] = ((self.fluxes.apar / 2.0) * lue[am] *
                                     const.MOL_C_TO_GRAMS_C)
        self.fluxes.gpp_am_pm[pm] = ((self.fluxes.apar / 2.0) * lue[pm] *
                                     const.MOL_C_TO_GRAMS_C)

        #print self.fluxes.gpp_gCm2
        self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue

        if self.control.nuptake_model == 3:
            self.fluxes.gpp_gCm2 *= self.params.ac
            self.fluxes.gpp_am_pm[am] *= self.params.ac
            self.fluxes.gpp_am_pm[pm] *= self.params.ac
            self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue

        # g C m-2 to tonnes hectare-1 day-1
        conv = const.G_AS_TONNES / const.M2_AS_HA
        self.fluxes.gpp = self.fluxes.gpp_gCm2 * conv
        self.fluxes.npp = self.fluxes.npp_gCm2 * conv

        # Plant respiration assuming carbon-use efficiency.
        self.fluxes.auto_resp = self.fluxes.gpp - self.fluxes.npp
Beispiel #44
0
    def calculate_photosynthesis(self, day, daylen):
        """ Photosynthesis is calculated assuming GPP is proportional to APAR,
        a commonly assumed reln (e.g. Potter 1993, Myneni 2002). The slope of
        relationship btw GPP and APAR, i.e. LUE is modelled using the
        photosynthesis eqns from Sands.

        Assumptions:
        ------------
        (1) photosynthetic light response is a non-rectangular hyperbolic func
            of photon-flux density with a light-saturatred photosynthetic rate
            (Amax), quantum yield (alpha) and curvature (theta).
        (2) the canopy is horizontally uniform.
        (3) PAR distribution within the canopy obeys Beer's law.
        (4) light-saturated photosynthetic rate declines with canopy depth in
            proportion to decline in PAR
        (5) alpha + theta do not vary within the canopy
        (6) dirunal variation of PAR is sinusoidal.
        (7) The model makes no assumption about N within the canopy, however
            this version assumes N declines exponentially through the cnaopy.
        (8) Leaf temperature is the same as the air temperature.

        Parameters:
        ----------
        day : int
            project day.
        daylen : float
            length of day in hours.

        Returns:
        -------
        Nothing
            Method calculates GPP, NPP and Ra.
        """
        # local var for tidyness
        (Tk_am, Tk_pm, par, vpd_am, vpd_pm, ca) = self.get_met_data(day)
        
        
        # calculate mate params & account for temperature dependencies
        N0 = self.calculate_top_of_canopy_n()
        
        gamma_star_am = self.calculate_co2_compensation_point(Tk_am)
        gamma_star_pm = self.calculate_co2_compensation_point(Tk_pm)
        
        Km_am = self.calculate_michaelis_menten_parameter(Tk_am)
        Km_pm = self.calculate_michaelis_menten_parameter(Tk_pm)
        
        (jmax_am, vcmax_am) = self.calculate_jmax_and_vcmax(Tk_am, N0)
        (jmax_pm, vcmax_pm) = self.calculate_jmax_and_vcmax(Tk_pm, N0)
        
        ci_am = self.calculate_ci(vpd_am, ca) 
        ci_pm = self.calculate_ci(vpd_pm, ca) 
        
        # quantum efficiency calculated for C3 plants
        alpha_am = self.calculate_quantum_efficiency(ci_am, gamma_star_am)
        alpha_pm = self.calculate_quantum_efficiency(ci_pm, gamma_star_pm)
        
        # Reducing assimilation if we encounter frost. Frost is assumed to 
        # impact on the maximum photosynthetic capacity and alpha_j
        # So there is only an indirect effect on LAI, this could be changed...
        if self.control.frost:
            Tmax = self.met_data['tmax'][day]
            Tmin = self.met_data['tmin'][day]
            
            Thard = self.calc_frost_hardiness(daylen, Tmin, Tmax)
            (total_alpha_limf, 
            total_amax_limf) = self.calc_frost_impact_factors(Thard, Tmin, Tmax)
            alpha_am *= total_alpha_limf
            alpha_pm *= total_alpha_limf
     
        # Rubisco carboxylation limited rate of photosynthesis
        ac_am = self.assim(ci_am, gamma_star_am, a1=vcmax_am, a2=Km_am) 
        ac_pm = self.assim(ci_pm, gamma_star_pm, a1=vcmax_pm, a2=Km_pm) 
        
        # Light-limited rate of photosynthesis allowed by RuBP regeneration
        aj_am = self.assim(ci_am, gamma_star_am, a1=jmax_am/4.0,
                           a2=2.0*gamma_star_am)
        aj_pm = self.assim(ci_pm, gamma_star_pm, a1=jmax_pm/4.0,
                           a2=2.0*gamma_star_pm)
        
        # light-saturated photosynthesis rate at the top of the canopy (gross)
        asat_am = min(aj_am, ac_am) 
        asat_pm = min(aj_pm, ac_pm) 
        if self.control.frost:
            asat_am *= total_amax_limf
            asat_pm *= total_amax_limf
            
        # LUE (umol C umol-1 PAR)
        lue_am = self.epsilon(asat_am, par, daylen, alpha_am)
        lue_pm = self.epsilon(asat_pm, par, daylen, alpha_pm)
        # use average to simulate canopy photosynthesis
        lue_avg = (lue_am + lue_pm) / 2.0 
        
        if float_eq(self.state.lai, 0.0):
            self.fluxes.apar = 0.0
        else:
            # absorbed photosynthetically active radiation (umol m-2 s-1)
            self.fluxes.apar = par * self.state.fipar
        apar_half_day = self.fluxes.apar / 2.0
        
        
        # convert umol m-2 d-1 -> gC m-2 d-1
        self.fluxes.gpp_gCm2 = self.fluxes.apar * lue_avg * const.UMOL_2_GRAMS_C
        self.fluxes.gpp_am = apar_half_day * lue_am * const.UMOL_2_GRAMS_C
        self.fluxes.gpp_pm = apar_half_day * lue_pm * const.UMOL_2_GRAMS_C
        
        # g C m-2 to tonnes hectare-1 day-1
        self.fluxes.gpp = self.fluxes.gpp_gCm2 * const.GRAM_C_2_TONNES_HA
        
        if self.control.nuptake_model == 3:
            self.fluxes.gpp_gCm2 *= self.params.ac
            self.fluxes.gpp_am *= self.params.ac
            self.fluxes.gpp_pm *= self.params.ac
Beispiel #45
0
    def calculate_photosynthesis(self, day, daylen):
        """ Photosynthesis is calculated assuming GPP is proportional to APAR,
        a commonly assumed reln (e.g. Potter 1993, Myneni 2002). The slope of
        relationship btw GPP and APAR, i.e. LUE is modelled using the
        photosynthesis eqns from Sands.

        Assumptions:
        ------------
        (1) photosynthetic light response is a non-rectangular hyperbolic func
            of photon-flux density with a light-saturatred photosynthetic rate
            (Amax), quantum yield (alpha) and curvature (theta).
        (2) the canopy is horizontally uniform.
        (3) PAR distribution within the canopy obeys Beer's law.
        (4) light-saturated photosynthetic rate declines with canopy depth in
            proportion to decline in PAR
        (5) alpha + theta do not vary within the canopy
        (6) dirunal variation of PAR is sinusoidal.
        (7) The model makes no assumption about N within the canopy, however
            this version assumes N declines exponentially through the cnaopy.
        (8) Leaf temperature is the same as the air temperature.

        Parameters:
        ----------
        day : int
            project day.
        daylen : float
            length of day in hours.

        Returns:
        -------
        Nothing
            Method calculates GPP, NPP and Ra.
        """
        # local var for tidyness
        (Tair_K, par, vpd, ca) = self.get_met_data(day)

        # calculate mate params & account for temperature dependencies
        gamma_star = self.calculate_co2_compensation_point(Tair_K)
        Km = self.calculate_michaelis_menten_parameter(Tair_K)
        N0 = self.calculate_top_of_canopy_n()
        (jmax, vcmax) = self.calculate_jmax_and_vcmax(Tair_K, N0)
        ci = [self.calculate_ci(vpd[k], ca) for k in self.am, self.pm]

        # quantum efficiency calculated for C3 plants
        alpha = self.calculate_quantum_efficiency(ci, gamma_star)

        # Rubisco carboxylation limited rate of photosynthesis
        ac = [self.assim(ci[k], gamma_star[k], a1=vcmax[k], a2=Km[k]) for k in self.am, self.pm]

        # Light-limited rate of photosynthesis allowed by RuBP regeneration
        aj = [self.assim(ci[k], gamma_star[k], a1=jmax[k] / 4.0, a2=2.0 * gamma_star[k]) for k in self.am, self.pm]

        # light-saturated photosynthesis rate at the top of the canopy (gross)
        asat = [min(aj[k], ac[k]) for k in self.am, self.pm]

        # LUE (umol C umol-1 PAR)
        lue = [self.epsilon(asat[k], par, daylen, alpha[k]) for k in self.am, self.pm]
        lue_avg = sum(lue) / 2.0  # use average to simulate canopy photosynthesis

        if float_eq(self.state.lai, 0.0):
            self.fluxes.apar = 0.0
        else:
            # absorbed photosynthetically active radiation (umol m-2 s-1)
            self.fluxes.apar = par * self.state.fipar

        # gC m-2 d-1
        self.fluxes.gpp_gCm2 = self.fluxes.apar * lue_avg * const.UMOL_TO_MOL * const.MOL_C_TO_GRAMS_C
        self.fluxes.gpp_am_pm[self.am] = (
            (self.fluxes.apar / 2.0) * lue[self.am] * const.UMOL_TO_MOL * const.MOL_C_TO_GRAMS_C
        )
        self.fluxes.gpp_am_pm[self.pm] = (
            (self.fluxes.apar / 2.0) * lue[self.pm] * const.UMOL_TO_MOL * const.MOL_C_TO_GRAMS_C
        )

        self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue

        if self.control.nuptake_model == 3:
            self.fluxes.gpp_gCm2 *= self.params.ac
            self.fluxes.gpp_am_pm[self.am] *= self.params.ac
            self.fluxes.gpp_am_pm[self.pm] *= self.params.ac
            self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue

        # g C m-2 to tonnes hectare-1 day-1
        conv = const.G_AS_TONNES / const.M2_AS_HA
        self.fluxes.gpp = self.fluxes.gpp_gCm2 * conv
        self.fluxes.npp = self.fluxes.npp_gCm2 * conv

        # Plant respiration assuming carbon-use efficiency.
        self.fluxes.auto_resp = self.fluxes.gpp - self.fluxes.npp
Beispiel #46
0
    def calculate_soil_water_fac(self):
        """ Estimate a relative water availability factor [0..1]

        A drying soil results in physiological stress that can induce stomatal
        closure and reduce transpiration. Further, N mineralisation depends on 
        top soil moisture.
        
        self.params.qs = 0.2 in SDGVM
        
        References:
        -----------
        * Landsberg and Waring (1997) Forest Ecology and Management, 95, 209-228.
          See --> Figure 2.
        * Egea et al. (2011) Agricultural Forest Meteorology, 151, 1370-1384.
          
        But similarly see:
        * van Genuchten (1981) Soil Sci. Soc. Am. J, 44, 892--898.
        * Wang and Leuning (1998) Ag Forest Met, 91, 89-111.
        
        * Pepper et al. (2008) Functional Change Biology, 35, 493-508
       
        Returns:
        --------
        wtfac_topsoil : float
            water availability factor for the top soil [0,1]
        wtfac_root : float
            water availability factor for the root zone [0,1]    
        """
        # turn into fraction...
        smc_topsoil = self.state.pawater_topsoil / self.params.wcapac_topsoil
        smc_root = self.state.pawater_root / self.params.wcapac_root
        
        if self.control.sw_stress_model == 0:
            wtfac_topsoil = smc_topsoil**self.params.qs  
            wtfac_root = smc_root**self.params.qs  
            
        elif self.control.sw_stress_model == 1:
            wtfac_topsoil = self.calc_sw_modifier(smc_topsoil, 
                                                  self.params.ctheta_topsoil, 
                                                  self.params.ntheta_topsoil)
  
            wtfac_root = self.calc_sw_modifier(smc_root, 
                                               self.params.ctheta_root, 
                                               self.params.ntheta_root)
            
        elif self.control.sw_stress_model == 2:
            
            # Stomatal limitaiton
            # Exponetial function to reduce g1 with soil water limitation
            # based on Zhou et al. 2013, AFM, following Makela et al 1996.
            # For the moment I have hardwired the PFT parameter as I am still
            # testing.
            # Because the model is a daily model we are assuming that LWP is
            # well approximated by the night SWP.
            
            if float_eq(smc_topsoil, 0.0):
                psi_swp_topsoil = -1.5
            else:
                arg1 = self.params.psi_sat_topsoil
                arg2 = smc_topsoil /self.params.theta_sat_topsoil
                arg3 = -self.params.b_topsoil
                psi_swp_topsoil = arg1 * arg2**arg3
            
            if float_eq(smc_root, 0.0):
                psi_swp_root = -1.5
            else:
                arg1 = self.params.psi_sat_root
                arg2 = smc_root/self.params.theta_sat_root
                arg3 = -self.params.b_root
                psi_swp_root = arg1 * arg2**arg3
            
            # multipliy these by g1, same as eqn 3 in Zhou et al. 2013.
            b = 0.66
            
            wtfac_topsoil = exp(b * psi_swp_topsoil)
            wtfac_root = exp(b * psi_swp_root)
        
        #print self.state.pawater_root,wtfac_root    
        return (wtfac_topsoil, wtfac_root)