Example #1
0
    def jmax_and_vcmax_func(self, temp):
        """ Maximum rate of electron transport (jmax) and of rubisco activity

        Parameters:
        -----------
        temp : float
            air temperature

        Returns:
        --------
        jmax : float
            maximum rate of electron transport
        vcmax : float
            maximum rate of Rubisco activity
        """
        if float_gt(temp, 10.0):
            jmax = (self.params.jmaxna * (1.0 + (temp - 25.0) *
                                          (0.05 + (temp - 25.0) *
                                           (-1.81 * 1E-3 + (temp - 25.0) *
                                            (-1.37 * 1E-4)))))
            vcmax = (self.params.vcmaxna * (1.0 + (temp - 25.0) *
                                            (0.0485 + (temp - 25.0) *
                                             (-6.93 * 1E-4 + (temp - 25.0) *
                                              (-3.9 * 1E-5)))))
        elif float_gt(temp, 0.0):
            jmax = self.params.jmaxna * 0.0305 * temp
            vcmax = self.params.vcmaxna * 0.0238 * temp
        else:
            jmax = 0.0
            vcmax = 0.0

        return jmax, vcmax
Example #2
0
    def jmax_and_vcmax_func(self, temp):
        """ Maximum rate of electron transport (jmax) and of rubisco activity

        Parameters:
        -----------
        temp : float
            air temperature

        Returns:
        --------
        jmax : float
            maximum rate of electron transport
        vcmax : float
            maximum rate of Rubisco activity
        """
        if float_gt(temp, 10.0):
            jmax = (self.params.jmaxna * (1.0 + (temp - 25.0) * (0.05 +
                    (temp - 25.0) * (-1.81 * 1E-3 + (temp - 25.0) *
                    (-1.37 * 1E-4)))))
            vcmax = (self.params.vcmaxna * (1.0 + (temp - 25.0) *
                    (0.0485 + (temp - 25.0) * (-6.93 * 1E-4 + (temp - 25.0) *
                    (-3.9 * 1E-5)))))
        elif float_gt(temp, 0.0):
            jmax = self.params.jmaxna * 0.0305 * temp
            vcmax = self.params.vcmaxna * 0.0238 * temp
        else:
            jmax = 0.0
            vcmax = 0.0

        return jmax, vcmax
Example #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)
Example #4
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)
Example #5
0
    def soil_temp_factor(self, project_day):
        """Soil-temperature activity factor (A9).

        Parameters:
        -----------
        project_day : int
            current simulation day (index)

        Returns:
        --------
        tfac : float
            soil temperature factor [degC]

        """
        tsoil = self.met_data["tsoil"][project_day]

        if float_gt(tsoil, 0.0):
            tfac = 0.0326 + 0.00351 * tsoil ** 1.652 - (tsoil / 41.748) ** 7.19
            if float_lt(tfac, 0.0):
                tfac = 0.0
        else:
            # negative number cannot be raised to a fractional power
            # number would need to be complex
            tfac = 0.0

        return tfac
Example #6
0
File: mate.py Project: kelvinn/GDAY
    def epsilon(self, asat, par, daylen, alpha):
        """ Canopy scale LUE using method from Sands 1995, 1996. 
        
        Sands derived daily canopy LUE from Asat by modelling the light response
        of photosysnthesis as a non-rectangular hyperbola with a curvature 
        (theta) and a quantum efficiency (alpha). 
        
        Assumptions of the approach are:
         - horizontally uniform canopy
         - PAR varies sinusoidally during daylight hours
         - extinction coefficient is constant all day
         - Asat and incident radiation decline through the canopy following 
           Beer's Law.
         - leaf transmission is assumed to be zero.
           
        * Numerical integration of "g" is simplified to 6 intervals. 

        Parameters:
        ----------
        asat : float
            Light-saturated photosynthetic rate at the top of the canopy
        par : float
            photosyntetically active radiation (umol m-2 d-1)
        daylen : float
            length of day (hrs).
        theta : float
            curvature of photosynthetic light response curve 
        alpha : float
            quantum yield of photosynthesis (mol mol-1)
            
        Returns:
        -------
        lue : float
            integrated light use efficiency over the canopy (umol C umol-1 PAR)

        References:
        -----------
        See assumptions above...
        * Sands, P. J. (1995) Australian Journal of Plant Physiology, 
          22, 601-14.

        """
        delta = 0.16666666667  # subintervals scaler, i.e. 6 intervals
        h = daylen * const.SECS_IN_HOUR  # number of seconds of daylight

        if float_gt(asat, 0.0):
            # normalised daily irradiance
            q = pi * self.params.kext * alpha * par / (2.0 * h * asat)
            integral_g = 0.0
            for i in xrange(1, 13, 2):
                sinx = sin(pi * i / 24.0)
                arg1 = sinx
                arg2 = 1.0 + q * sinx
                arg3 = sqrt((1.0 + q * sinx) ** 2.0 - 4.0 * self.params.theta * q * sinx)
                integral_g += arg1 / (arg2 + arg3) * delta
            lue = alpha * integral_g * pi
        else:
            lue = 0.0

        return lue
Example #7
0
    def calc_soil_evaporation(self, tavg, net_rad, press, daylen, sw_rad):
        """ Use Penman eqn to calculate top soil evaporation flux at the
        potential rate.

        Soil evaporation is dependent upon soil wetness and plant cover. The net
        radiation term is scaled for the canopy cover passed to this func and
        the impact of soil wetness is accounted for in the wtfac term. As the
        soil dries the evaporation component reduces significantly.

        Key assumptions from Ritchie...

        * When plant provides shade for the soil surface, evaporation will not
        be the same as bare soil evaporation. Wind speed, net radiation and VPD
        will all belowered in proportion to the canopy density. Following
        Ritchie role ofwind, VPD are assumed to be negligible and are therefore
        ignored.

        These assumptions are based on work with crops and whether this holds
        for tree shading where the height from the soil to the base of the
        crown is larger is questionable.

        units = (mm/day)

        References:
        -----------
        * Ritchie, 1972, Water Resources Research, 8, 1204-1213.

        Parameters:
        -----------
        tavg : float
            average daytime temp [degC]
        net_rad : float
            net radiation [mj m-2 day-1]
        press : float
            average daytime pressure [kPa]

        Returns:
        --------
        soil_evap : float
            soil evaporation [mm d-1]

        """
        P = Penman()
        soil_evap = P.calc_evaporation(net_rad, tavg, press)
        
        # Surface radiation is reduced by overstory LAI cover. This empirical
        # fit comes from Ritchie (1972) and is formed by a fit between the LAI
        # of 5 crops types and the fraction of observed net radiation at the
        # surface. Whilst the LAI does cover a large range, nominal 0–6, there
        # are only 12 measurements and only three from LAI > 3. So this might
        # not hold as well for a forest canopy?
        # Ritchie 1972, Water Resources Research, 8, 1204-1213.
        if float_gt(self.state.lai, 0.0):
            soil_evap *= exp(-0.398 * self.state.lai)
        
        # reduce soil evaporation if top soil is dry
        soil_evap *= self.state.wtfac_topsoil
        tconv = 60.0 * 60.0 * daylen # seconds to day
        
        return soil_evap * tconv
Example #8
0
    def soil_temp_factor(self, project_day):
        """Soil-temperature activity factor (A9). Fit to Parton's fig 2a 

        Parameters:
        -----------
        project_day : int
            current simulation day (index)

        Returns:
        --------
        tfac : float
            soil temperature factor [degC]

        """
        tsoil = self.met_data['tsoil'][project_day]

        if float_gt(tsoil, 0.0):
            self.fluxes.tfac_soil_decomp = (0.0326 + 0.00351 * tsoil**1.652 -
                                            (tsoil / 41.748)**7.19)
            if float_lt(self.fluxes.tfac_soil_decomp, 0.0):
                self.fluxes.tfac_soil_decomp = 0.0
        else:
            # negative number cannot be raised to a fractional power
            # number would need to be complex
            self.fluxes.tfac_soil_decomp = 0.0

        return self.fluxes.tfac_soil_decomp
    def calc_transpiration(self):
        """ units mm/day """

        if float_gt(self.fluxes.wue, 0.0):
            self.fluxes.transpiration = self.fluxes.gpp_gCm2 / self.fluxes.wue
        else:
            self.fluxes.transpiration = 0.0
Example #10
0
    def calc_soil_evaporation(self, tavg, net_rad, press, daylen, sw_rad):
        """ Use Penman eqn to calculate top soil evaporation flux at the
        potential rate.

        Soil evaporation is dependent upon soil wetness and plant cover. The net
        radiation term is scaled for the canopy cover passed to this func and
        the impact of soil wetness is accounted for in the wtfac term. As the
        soil dries the evaporation component reduces significantly.

        Key assumptions from Ritchie...

        * When plant provides shade for the soil surface, evaporation will not
        be the same as bare soil evaporation. Wind speed, net radiation and VPD
        will all belowered in proportion to the canopy density. Following
        Ritchie role ofwind, VPD are assumed to be negligible and are therefore
        ignored.

        These assumptions are based on work with crops and whether this holds
        for tree shading where the height from the soil to the base of the
        crown is larger is questionable.

        units = (mm/day)

        References:
        -----------
        * Ritchie, 1972, Water Resources Research, 8, 1204-1213.

        Parameters:
        -----------
        tavg : float
            average daytime temp [degC]
        net_rad : float
            net radiation [mj m-2 day-1]
        press : float
            average daytime pressure [kPa]

        Returns:
        --------
        soil_evap : float
            soil evaporation [mm d-1]

        """
        P = Penman()
        soil_evap = P.calc_evaporation(net_rad, tavg, press)
        
        # Surface radiation is reduced by overstory LAI cover. This empirical
        # fit comes from Ritchie (1972) and is formed by a fit between the LAI
        # of 5 crops types and the fraction of observed net radiation at the
        # surface. Whilst the LAI does cover a large range, nominal 0–6, there
        # are only 12 measurements and only three from LAI > 3. So this might
        # not hold as well for a forest canopy?
        # Ritchie 1972, Water Resources Research, 8, 1204-1213.
        if float_gt(self.state.lai, 0.0):
            soil_evap *= exp(-0.398 * self.state.lai)
        
        # reduce soil evaporation if top soil is dry
        soil_evap *= self.state.wtfac_topsoil
        tconv = 60.0 * 60.0 * daylen # seconds to day
        
        return soil_evap * tconv
Example #11
0
    def epsilon(self, asat, par, daylen, alpha):
        """ Canopy scale LUE using method from Sands 1995, 1996. 
        
        Sands derived daily canopy LUE from Asat by modelling the light response
        of photosysnthesis as a non-rectangular hyperbola with a curvature 
        (theta) and a quantum efficiency (alpha). 
        
        Assumptions of the approach are:
         - horizontally uniform canopy
         - PAR varies sinusoidally during daylight hours
         - extinction coefficient is constant all day
         - Asat and incident radiation decline through the canopy following 
           Beer's Law.
         - leaf transmission is assumed to be zero.
           
        * Numerical integration of "g" is simplified to 6 intervals. 

        Parameters:
        ----------
        asat : float
            photosynthetic rate at the top of the canopy
        par : float
            incident photosyntetically active radiation
        daylen : float
            length of day (hrs).
        theta : float
            curvature of photosynthetic light response curve 
        alpha : float
            quantum yield of photosynthesis (mol mol-1)
            
        Returns:
        -------
        lue : float
            integrated light use efficiency over the canopy (mol C mol-1 PAR)

        References:
        -----------
        See assumptions above...
        * Sands, P. J. (1995) Australian Journal of Plant Physiology, 
          22, 601-14.

        """
        delta = 0.16666666667 # subintervals scaler, i.e. 6 intervals
        h = daylen * const.HRS_TO_SECS 
        theta = self.params.theta # local var
        
        if float_gt(asat, 0.0):
            q = pi * self.params.kext * alpha * par / (2.0 * h * asat)
            integral_g = 0.0 
            for i in xrange(1, 13, 2):
                sinx = sin(pi * i / 24.)
                arg1 = sinx
                arg2 = 1.0 + q * sinx 
                arg3 = sqrt((1.0 + q * sinx)**2.0 - 4.0 * theta * q * sinx)
                integral_g += arg1 / (arg2 + arg3) * delta
            lue = alpha * integral_g * pi
        else:
            lue = 0.0
        
        return lue
Example #12
0
    def decay_in_dry_soils(self, decay_rate, decay_rate_dry):
        """Decay rates (e.g. leaf litterfall) can increase in dry soil, adjust
        decay param

        Parameters:
        -----------
        decay_rate : float
            default model parameter decay rate [tonnes C/ha/day]
        decay_rate_dry : float
            default model parameter dry deacy rate [tonnes C/ha/day]

        Returns:
        --------
        decay_rate : float
            adjusted deacy rate if the soil is dry [tonnes C/ha/day]

        """
        # turn into fraction...
        smc_root = self.state.pawater_root / self.params.wcapac_root
        
        new_decay_rate = (decay_rate_dry - (decay_rate_dry - decay_rate) * 
                         (smc_root - self.params.watdecaydry) /
                         (self.params.watdecaywet - self.params.watdecaydry))

        if float_lt(new_decay_rate, decay_rate):
            new_decay_rate = decay_rate

        if float_gt(new_decay_rate, decay_rate_dry):
            new_decay_rate = decay_rate_dry

        return new_decay_rate
Example #13
0
    def soil_temp_factor(self, project_day):
        """Soil-temperature activity factor (A9). Fit to Parton's fig 2a 

        Parameters:
        -----------
        project_day : int
            current simulation day (index)

        Returns:
        --------
        tfac : float
            soil temperature factor [degC]

        """
        tsoil = self.met_data['tsoil'][project_day]

        if float_gt(tsoil, 0.0):
            self.fluxes.tfac_soil_decomp = (0.0326 + 0.00351 * tsoil**1.652 - 
                                            (tsoil / 41.748)**7.19)
            if float_lt(self.fluxes.tfac_soil_decomp, 0.0):
                self.fluxes.tfac_soil_decomp = 0.0
        else:
            # negative number cannot be raised to a fractional power
            # number would need to be complex
            self.fluxes.tfac_soil_decomp = 0.0

        return self.fluxes.tfac_soil_decomp
Example #14
0
    def nc_limit(self, cpool, npool, ncmin, ncmax):
        """ Release N to 'Inorgn' pool or fix N from 'Inorgn', in order to keep
        the  N:C ratio of a litter pool within the range 'ncmin' to 'ncmax'.

        Parameters:
        -----------
        cpool : float
            various C pool (state)
        npool : float
            various N pool (state)
        ncmin : float
            maximum N:C ratio
        ncmax : float
            minimum N:C ratio

        Returns:
        --------
        fix/rel : float
            amount of N to be added/released from the inorganic pool

        """
        nmax = cpool * ncmax
        nmin = cpool * ncmin
    
        if float_gt(npool, nmax):  #release
            rel = npool - nmax
            self.fluxes.nlittrelease += rel 
            return -rel
        elif float_lt(npool, nmin):   #fix
            fix = nmin - npool
            self.fluxes.nlittrelease -= fix
            return fix
        else:
            return 0.0 
Example #15
0
    def calc_infiltration(self, rain):
        """ Estimate "effective" rain, or infiltration I guess.

        Simple assumption that infiltration relates to leaf area
        and therefore canopy storage capacity (wetloss). Interception is
        likely to be ("more") erroneous if a canopy is subject to frequent daily
        rainfall I would suggest.

        Parameters:
        -------
        rain : float
            rainfall [mm d-1]

        """

        if float_gt(rain, 0.0):
            self.fluxes.interception = self.state.lai * self.params.wetloss
            self.fluxes.interception = clip(self.fluxes.interception,
                                            min=self.fluxes.interception,
                                            max=rain)

            self.fluxes.erain = (rain * self.params.rfmult -
                                    self.fluxes.interception)
        else:
            self.fluxes.interception = 0.0
            self.fluxes.erain = 0.0
Example #16
0
    def nc_limit(self, cpool, npool, ncmin, ncmax):
        """ Release N to 'Inorgn' pool or fix N from 'Inorgn', in order to keep
        the  N:C ratio of a litter pool within the range 'ncmin' to 'ncmax'.

        Parameters:
        -----------
        cpool : float
            various C pool (state)
        npool : float
            various N pool (state)
        ncmin : float
            maximum N:C ratio
        ncmax : float
            minimum N:C ratio

        Returns:
        --------
        fix/rel : float
            amount of N to be added/released from the inorganic pool

        """
        nmax = cpool * ncmax
        nmin = cpool * ncmin

        if float_gt(npool, nmax):  #release
            rel = npool - nmax
            self.fluxes.nlittrelease += rel
            return -rel
        elif float_lt(npool, nmin):  #fix
            fix = nmin - npool
            self.fluxes.nlittrelease -= fix
            return fix
        else:
            return 0.0
Example #17
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)
Example #18
0
File: mate.py Project: kelvinn/GDAY
    def calculate_top_of_canopy_n(self):
        """ Calculate the canopy N at the top of the canopy (g N m-2), N0.
        See notes and Chen et al 93, Oecologia, 93,63-69. 
        """

        if float_gt(self.state.lai, 0.0):
            # calculation for canopy N content at the top of the canopy
            N0 = self.state.ncontent * self.params.kext / (1.0 - exp(-self.params.kext * self.state.lai))
        else:
            N0 = 0.0
        return N0
    def adjust_cproduction(self, option):
        """ select model?

        It seems hybrid is the same as biomass, so check this and remove. I
        have set it up so that a call to hybrid just calls biomass

        Parameters:
        -----------
        option : integer
            model option

        """
        if option == 1 or option == 3:
            self.monteith_rescap_model()
        elif option == 2 and float_gt(self.params.fwpmax, self.params.fwpmin):
            self.biomass_model()
        elif option == 3 and float_gt(self.params.fwpmax, self.params.fwpmin):
            self.hybrid_model() # same as calling biomass model!
        else:
            err_msg = "Unknown water bal model (try 1-3): %s\n" % option
            raise RuntimeError, err_msg
Example #20
0
def clip(value, min=None, max=None):
    """ clip value btw defined range """
    if float_lt(value, min):
        value = min
    elif float_gt(value, max):
        value = max
    return value
    

 
    
    
Example #21
0
 def calculate_top_of_canopy_n(self):  
     """ Calculate the canopy N at the top of the canopy (g N m-2), N0.
     See notes and Chen et al 93, Oecologia, 93,63-69. 
     """
     
     if float_gt(self.state.lai, 0.0):
         # calculation for canopy N content at the top of the canopy                   
         N0 = (self.state.ncontent * self.params.kext /
              (1.0 - exp(-self.params.kext * self.state.lai)))
     else:
         N0 = 0.0
     return N0
Example #22
0
    def calc_evaporation(self, vpd, wind, gs, net_rad, tavg, press, canht=None, 
                         ga=None):

        """
        Parameters:
        -----------
        vpd : float
            vapour pressure def [kPa]
        wind : float
            average daytime wind speed [m s-1]
        gs : float
            stomatal conductance [m s-1]
        net_rad : float
            net radiation [mj m-2 s-1] 
        tavg : float
            daytime average temperature [degC]
        press : float
            average daytime pressure [kPa]

        Returns:
        --------
        et : float
            evapotranspiration [mm d-1]

        """
        # if not read from met file calculate atmospheric pressure from sea lev
        if press == None:
            press = self.calc_atmos_pressure()
        
        lambdax = self.calc_latent_heat_of_vapourisation(tavg)
        gamma = self.calc_pyschrometric_constant(lambdax, press)
        slope = self.calc_slope_of_saturation_vapour_pressure_curve(tavg)
        rho = self.calc_density_of_air(tavg)
        if ga is None:
            ga = self.canopy_boundary_layer_conductance(wind, canht)
       
        if float_gt(gs, 0.0):
            # decoupling coefficent, Jarvis and McNaughton, 1986
            # when omega is close to zero, it is said to be well coupled and
            # gs is the dominant controller of water loss (gs<ga).
            e = slope / gamma # chg of latent heat relative to sensible heat of air
            omega = (e + 1.0) / (e + 1.0 + (ga / gs))
            
            arg1 = ((slope * net_rad ) + (rho * self.cp * vpd * ga))
            arg2 = slope + gamma * (1.0 + (ga / gs))
            et = (arg1 / arg2) / lambdax
        else:
            et = 0.0
            omega = 0.0
        
        return et, omega
Example #23
0
    def calc_evaporation(self, vpd, wind, gs, net_rad, tavg, press, canht=None, 
                         ga=None):

        """
        Parameters:
        -----------
        vpd : float
            vapour pressure def [kPa]
        wind : float
            average daytime wind speed [m s-1]
        gs : float
            stomatal conductance [m s-1]
        net_rad : float
            net radiation [mj m-2 s-1] 
        tavg : float
            daytime average temperature [degC]
        press : float
            average daytime pressure [kPa]

        Returns:
        --------
        et : float
            evapotranspiration [mm d-1]

        """
        # if not read from met file calculate atmospheric pressure from sea lev
        if press == None:
            press = self.calc_atmos_pressure()
        
        lambdax = self.calc_latent_heat_of_vapourisation(tavg)
        gamma = self.calc_pyschrometric_constant(lambdax, press)
        slope = self.calc_slope_of_saturation_vapour_pressure_curve(tavg)
        rho = self.calc_density_of_air(tavg)
        if ga is None:
            ga = self.canopy_boundary_layer_conductance(wind, canht)
       
        if float_gt(gs, 0.0):
            # decoupling coefficent, Jarvis and McNaughton, 1986
            # when omega is close to zero, it is said to be well coupled and
            # gs is the dominant controller of water loss (gs<ga).
            e = slope / gamma # chg of latent heat relative to sensible heat of air
            omega = (e + 1.0) / (e + 1.0 + (ga / gs))
            
            arg1 = ((slope * net_rad ) + (rho * self.cp * vpd * ga))
            arg2 = slope + gamma * (1.0 + (ga / gs))
            et = (arg1 / arg2) / lambdax
        else:
            et = 0.0
            omega = 0.0
        
        return et, omega
    def calc_evaporation(self, vpd, wind, gs, net_rad, tavg, press):

        """
        Parameters:
        -----------
        vpd : float
            vapour pressure def [kPa]
        wind : float
            average daytime wind speed [m s-1]
        gs : float
            stomatal conductance [m s-1]
        net_rad : float
            net radiation [mj m-2 s-1] 
        tavg : float
            daytime average temperature [degC]
        press : float
            average daytime pressure [kPa]

        Returns:
        --------
        et : float
            evapotranspiration [mm d-1]

        """
        # if not read from met file calculate atmospheric pressure from sea lev
        if press == None:
            press = self.calc_atmos_pressure()
        
        lambdax = self.calc_latent_heat_of_vapourisation(tavg)
        gamma = self.calc_pyschrometric_constant(lambdax, press)
        slope = self.calc_slope_of_saturation_vapour_pressure_curve(tavg)
        rho = self.calc_density_of_air(tavg)
        
        ga = self.calc_atmos_boundary_layer_conductance(wind)
       
        # our model is a big leaf, so canopy conductance, gc = gs
        gc = gs
        
        if float_gt(gc, 0.0):
            # decoupling coefficent, Jarvis and McNaughton, 1986
            e = slope / gamma # chg of latent heat relative to sensible heat of air
            omega = (e + 1.0) / (e + 1.0 + (ga / gc))
            
            arg1 = ((slope * net_rad ) + (rho * self.cp * vpd * ga))
            arg2 = slope + gamma * (1.0 + ga / gc)
            et = (arg1 / arg2) / lambdax
        else:
            et = 0.0
            omega = 0.0
        
        return et
Example #25
0
    def epsilon(self, amax, par, daylen, alpha):
        """ Canopy scale LUE using method from Sands 1995, 1996. 
        
        Numerical integration of g is simplified to 6 intervals. Leaf 
        transmission is assumed to be zero.

        Parameters:
        ----------
        amax : float
            photosynthetic rate at the top of the canopy
        par : float
            incident photosyntetically active radiation
        daylen : float
            length of day in hours.
        theta : float
            curvature of photosynthetic light response curve 
        alpha : float
            quantum yield of photosynthesis (mol mol-1)
            
        Returns:
        -------
        lue : float
            integrated light use efficiency over the canopy

        References:
        -----------
        See assumptions above...
        * Sands, P. J. (1995) Australian Journal of Plant Physiology, 
          22, 601-14.
        * LUE stuff comes from Sands 1996

        """
        delta = 0.16666666667 # subintervals scaler, i.e. 6 intervals
        h = daylen * const.HRS_TO_SECS 
        theta = self.params.theta # local var
        
        if float_gt(amax, 0.0):
            q = pi * self.params.kext * alpha * par / (2.0 * h * amax)
            integral = 0.0
            for i in xrange(1, 13, 2):
                sinx = sin(pi * i / 24.)
                arg1 = sinx
                arg2 = 1.0 + q * sinx 
                arg3 = sqrt((1.0 + q * sinx)**2.0 - 4.0 * theta * q * sinx)
                integral += arg1 / (arg2 + arg3) * delta
            lue = alpha * integral * pi
        else:
            lue = 0.0
        
        return lue
    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')
Example #27
0
File: x.py Project: jgomezdans/GDAY
    def epsilon(self, amax, par, daylen, alpha):
        """ Canopy scale LUE using method from Sands 1995, 1996.

        Parameters:
        ----------
        amax : float
            photosynthetic rate at the top of the canopy
        par : float
            incident photosyntetically active radiation
        daylen : float
            length of day in hours.
        theta : float
            curvature of photosynthetic light response curve 
        alpha : float
            quantum yield of photosynthesis (mol mol-1)
            
        Returns:
        -------
        lue : float
            integrated light use efficiency over the canopy

        References:
        -----------
        See assumptions above...
        * Sands, P. J. (1995) Australian Journal of Plant Physiology, 22, 601-14.

        """
        
        if float_gt(amax, 0.0):
            q = (math.pi * self.params.kext * alpha * par /
                    (2.0 * daylen * const.HRS_TO_SECS * amax))

            # check sands but shouldn't it be 2 * q * sin x on the top?
            f = (lambda x: x / (1.0 + q * x + math.sqrt((1.0 + q * x)**2.0 -
                            4.0 * self.params.theta * q * x)))
            g = [f(math.sin(math.pi * i / 24.)) for i in xrange(1, 13, 2)]
            
            #Trapezoidal rule - seems more accurate
            gg = 0.16666666667 * sum(g)

            lue = alpha * gg * math.pi
        else:
            lue = 0.0

        return lue
Example #28
0
def day_length(date, latitude):
    """ Figure out number of sunlight hours, (hours day-1)
    
    Routine from sdgvm. date is a python object, see datetime library for 
    more info 
    
    Parameters:
    -----------
    date : date format string
        date object, yr/month/day
    latitude : float    
        latitude [degrees]
        
    Returns:
    --------
    dayl : float 
        daylength [hrs]
    
    """
    conv = math.pi / 180.0
    
    # day of year 1-365/366
    doy = int(date.strftime('%j'))
    
    # Total number of days in year
    if calendar.isleap(date.year):
        yr_days = 366.
    else:
        yr_days = 365.
    
    solar_declin = -23.4 * math.cos(conv * yr_days * (doy + 10.0) / yr_days)
    temx = -math.tan(latitude * conv) * math.tan(solar_declin * conv) 
    
    if float_lt(math.fabs(temx), 1.0):
        has = math.acos(temx) / conv
        dayl = 2.0 * has / 15.0
    elif float_gt(temx, 0.0):
        dayl = 0.0
    else:
        dayl = 24.0
    
    return dayl
Example #29
0
    def calculate_mineralisation(self):
        """Mineralisation calculations.
        Plant N uptake, soil N loss, N gross mineralisation, N immobilisation
        """
        # gross n mineralisation (t/ha/yr)
        self.fluxes.ngross = (sum(self.fluxes.nstruct) +
                                sum(self.fluxes.nmetab) +
                                sum(self.fluxes.nactive) +
                                sum(self.fluxes.nslow) + self.fluxes.npassive)

        # N:C new SOM - slope is an object containing active, slow and passive
        slope = self.calc_new_nc_slope_params()

        # Mineral N pool
        (numer1, numer2, denom) = self.calc_mineral_npool(slope)

        # evaluate n immobilisation in new SOM
        self.fluxes.nimmob = numer1 + denom * self.state.inorgn
        if float_gt(self.fluxes.nimmob, numer2):
            self.fluxes.nimmob = numer2
Example #30
0
    def grazer_inputs(self):
        """ Grazer inputs from faeces and urine, flux detd by faeces c:n """
        if self.control.grazing:
            self.params.faecesn = self.fluxes.faecesc / self.params.faecescn
        else:
            self.params.faecesn = 0.0

        # make sure faecesn <= total n input to soil from grazing
        arg = self.fluxes.neaten * self.params.fractosoil
        if float_gt(self.params.faecesn, arg):
            self.params.faecesn = self.fluxes.neaten * self.params.fractosoil

        # urine=total-faeces
        if self.control.grazing:
            self.fluxes.nurine = self.fluxes.neaten * self.params.fractosoil - self.params.faecesn
        else:
            self.fluxes.nurine = 0.0

        if float_lt(self.fluxes.nurine, 0.0):
            self.fluxes.nurine = 0.0
Example #31
0
    def grazer_inputs(self):
        """ Grazer inputs from faeces and urine, flux detd by faeces c:n """
        if self.control.grazing:
            self.params.faecesn = self.fluxes.faecesc / self.params.faecescn
        else:
            self.params.faecesn = 0.0

        # make sure faecesn <= total n input to soil from grazing
        arg = self.fluxes.neaten * self.params.fractosoil
        if float_gt(self.params.faecesn, arg):
            self.params.faecesn = self.fluxes.neaten * self.params.fractosoil

        # urine=total-faeces
        if self.control.grazing:
            self.fluxes.nurine = (self.fluxes.neaten * self.params.fractosoil -
                                  self.params.faecesn)
        else:
            self.fluxes.nurine = 0.0

        if float_lt(self.fluxes.nurine, 0.0):
            self.fluxes.nurine = 0.0
    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
Example #33
0
    def decay_in_dry_soils(self, decay_rate, decay_rate_dry):
        """Decay rates (e.g. leaf litterfall) can increase in dry soil, adjust
        decay param. This is based on field measurements by F. J. Hingston 
        (unpublished) cited in Corbeels.

        Parameters:
        -----------
        decay_rate : float
            default model parameter decay rate [tonnes C/ha/day]
        decay_rate_dry : float
            default model parameter dry deacy rate [tonnes C/ha/day]

        Returns:
        --------
        decay_rate : float
            adjusted deacy rate if the soil is dry [tonnes C/ha/day]
        
        Reference:
        ----------
        Corbeels et al. (2005) Ecological Modelling, 187, 449-474.
        
        """
        # turn into fraction...
        smc_root = self.state.pawater_root / self.params.wcapac_root

        new_decay_rate = decay_rate_dry - (decay_rate_dry - decay_rate) * (smc_root - self.params.watdecaydry) / (
            self.params.watdecaywet - self.params.watdecaydry
        )

        if float_lt(new_decay_rate, decay_rate):
            new_decay_rate = decay_rate

        if float_gt(new_decay_rate, decay_rate_dry):
            new_decay_rate = decay_rate_dry

        return new_decay_rate
Example #34
0
    def decay_in_dry_soils(self, decay_rate, decay_rate_dry):
        """Decay rates (e.g. leaf litterfall) can increase in dry soil, adjust
        decay param. This is based on field measurements by F. J. Hingston 
        (unpublished) cited in Corbeels.

        Parameters:
        -----------
        decay_rate : float
            default model parameter decay rate [tonnes C/ha/day]
        decay_rate_dry : float
            default model parameter dry deacy rate [tonnes C/ha/day]

        Returns:
        --------
        decay_rate : float
            adjusted deacy rate if the soil is dry [tonnes C/ha/day]
        
        Reference:
        ----------
        Corbeels et al. (2005) Ecological Modelling, 187, 449-474.
        
        """
        # turn into fraction...
        smc_root = self.state.pawater_root / self.params.wcapac_root

        new_decay_rate = (decay_rate_dry - (decay_rate_dry - decay_rate) *
                          (smc_root - self.params.watdecaydry) /
                          (self.params.watdecaywet - self.params.watdecaydry))

        if float_lt(new_decay_rate, decay_rate):
            new_decay_rate = decay_rate

        if float_gt(new_decay_rate, decay_rate_dry):
            new_decay_rate = decay_rate_dry

        return new_decay_rate
Example #35
0
    def calculate_nimmobilisation(self):
        """ Calculated N immobilised in new soil organic matter
        
         General equation for new soil N:C ratio vs Nmin, expressed as linear 
         equation passing through point Nmin0, actncmin (etc). Values can be 
         Nmin0=0, Actnc0=Actncmin 
         
         if Nmin < Nmincrit:
            New soil N:C = soil N:C (when Nmin=0) + slope * Nmin
         
         if Nmin > Nmincrit
            New soil N:C = max soil N:C       
        
        NB N:C ratio of new passive SOM can change even if assume Passiveconst
        
        Returns:
        --------
        nimob : float
            N immobilsed
        """
        # N:C new SOM - active, slow and passive
        self.state.actncslope = self.calculate_nc_slope(self.params.actncmax, 
                                                        self.params.actncmin)
        self.state.slowncslope = self.calculate_nc_slope(self.params.slowncmax, 
                                                         self.params.slowncmin)
        self.state.passncslope = self.calculate_nc_slope(self.params.passncmax, 
                                                         self.params.passncmin) 

        nmin = self.params.nmin0 * const.G_M2_2_TONNES_HA
        arg1 = ((self.fluxes.cactive[1] + self.fluxes.cslow[1]) *
                (self.params.passncmin - self.state.passncslope * nmin))
        arg2 = ((self.fluxes.cstruct[0] + self.fluxes.cstruct[2] +
                 self.fluxes.cactive[0]) *
                (self.params.slowncmin - self.state.slowncslope * nmin))
        arg3 = ((self.fluxes.cstruct[1] + self.fluxes.cstruct[3] +
                 sum(self.fluxes.cmetab) + self.fluxes.cslow[0] +
                 self.fluxes.passive) * (self.params.actncmin - 
                 self.state.actncslope * nmin))
        numer1 = arg1 + arg2 + arg3
        
        arg1 = ((self.fluxes.cactive[1] + self.fluxes.cslow[1]) *
                 self.params.passncmax)
        arg2 = ((self.fluxes.cstruct[0] + self.fluxes.cstruct[2] +
                 self.fluxes.cactive[0]) * self.params.slowncmax)
        arg3 = ((self.fluxes.cstruct[1] + self.fluxes.cstruct[3] +
                 sum(self.fluxes.cmetab) + self.fluxes.cslow[0] +
                 self.fluxes.passive) * self.params.actncmax)
        numer2 = arg1 + arg2 + arg3

        arg1 = ((self.fluxes.cactive[1] + self.fluxes.cslow[1]) * 
                 self.state.passncslope)
        arg2 = ((self.fluxes.cstruct[0] + self.fluxes.cstruct[2] +
                 self.fluxes.cactive[0]) * self.state.slowncslope)
        arg3 = ((self.fluxes.cstruct[1] + self.fluxes.cstruct[3] +
                 sum(self.fluxes.cmetab) + self.fluxes.cslow[0] +
                 self.fluxes.passive) * self.state.actncslope)
        denom = arg1 + arg2 + arg3
        
        # evaluate N immobilisation in new SOM
        nimmob = numer1 + denom * self.state.inorgn
        if float_gt(nimmob, numer2):
            nimmob = numer2
        
        return nimmob
Example #36
0
    def calculate_n_immobilisation(self):
        """ N immobilised in new soil organic matter, the reverse of
        mineralisation. Micro-organisms in the soil compete with plants for N.
        Immobilisation is the process by which nitrate and ammonium are taken up
        by the soil organisms and thus become unavailable to the plant 
        (->organic N).
        
        When C:N ratio is high the microorganisms need more nitrogen from 
        the soil to decompose the carbon in organic materials. This N will be
        immobilised until these microorganisms die and the nitrogen is 
        released.
        
        General equation for new soil N:C ratio vs Nmin, expressed as linear 
        equation passing through point Nmin0, actncmin (etc). Values can be 
        Nmin0=0, Actnc0=Actncmin 

        if Nmin < Nmincrit:
            New soil N:C = soil N:C (when Nmin=0) + slope * Nmin

        if Nmin > Nmincrit
            New soil N:C = max soil N:C       
        
        NB N:C ratio of new passive SOM can change even if assume Passiveconst
        
        Returns:
        --------
        nimob : float
            N immobilsed
        """
        # N:C new SOM - active, slow and passive
        active_nc_slope = self.calculate_nc_slope(self.params.actncmax, 
                                                  self.params.actncmin)
        slow_nc_slope = self.calculate_nc_slope(self.params.slowncmax, 
                                                self.params.slowncmin)
        passive_nc_slope = self.calculate_nc_slope(self.params.passncmax, 
                                                   self.params.passncmin) 
        
        # C flux entering SOM pools - use short names
        active_influxes = self.fluxes.c_into_active
        slow_influxes = self.fluxes.c_into_slow
        passive_influxes = self.fluxes.c_into_passive
        
        # convert units
        nmin = self.params.nmin0 / const.M2_AS_HA * const.G_AS_TONNES              
        
        arg1 = ((self.params.passncmin - passive_nc_slope * nmin) * 
                 passive_influxes)
        arg2 = (self.params.slowncmin - slow_nc_slope * nmin) * slow_influxes
        arg3 = active_influxes * (self.params.actncmin - active_nc_slope * nmin)
        numer1 = arg1 + arg2 + arg3
        
        arg1 = passive_influxes * self.params.passncmax
        arg2 = slow_influxes * self.params.slowncmax
        arg3 = active_influxes * self.params.actncmax
        numer2 = arg1 + arg2 + arg3

        arg1 = passive_influxes * passive_nc_slope
        arg2 = slow_influxes * slow_nc_slope
        arg3 = active_influxes * active_nc_slope
        denom = arg1 + arg2 + arg3
        
        # evaluate N immobilisation in new SOM
        nimmob = numer1 + denom * self.state.inorgn
        if float_gt(nimmob, numer2):
            nimmob = numer2
        
        return (nimmob, active_nc_slope, slow_nc_slope, passive_nc_slope)
Example #37
0
    def calculate_npools(self, active_nc_slope, slow_nc_slope, 
                         passive_nc_slope):
        """  
        Update N pools in the soil
        
        Parameters
        ----------
        active_nc_slope : float
            active NC slope
        slow_nc_slope: float
            slow NC slope
        passive_nc_slope : float
            passive NC slope
        
        """ 
        # net N release implied by separation of litter into structural
        # & metabolic. The following pools only fix or release N at their 
        # limiting n:c values. 
        
        # N released or fixed from the N inorganic pool is incremented with
        # each call to nc_limit and stored in self.fluxes.nlittrelease
        self.fluxes.nlittrelease = 0.0
        
        self.state.structsurfn += (self.fluxes.n_surf_struct_litter - 
                                  (self.fluxes.n_surf_struct_to_slow + 
                                   self.fluxes.n_surf_struct_to_active))
                                   
        if not self.control.strfloat:
            self.state.structsurfn += self.nc_limit(self.state.structsurf,
                                                    self.state.structsurfn,
                                                    1.0/self.params.structcn,
                                                    1.0/self.params.structcn)
        
        self.state.structsoiln += (self.fluxes.n_soil_struct_litter - 
                                  (self.fluxes.n_soil_struct_to_slow + 
                                   self.fluxes.n_soil_struct_to_active))
                                  
        if not self.control.strfloat:
            self.state.structsoiln += self.nc_limit(self.state.structsoil,
                                                    self.state.structsoiln,
                                                    1.0/self.params.structcn,
                                                    1.0/self.params.structcn)
        
        self.state.metabsurfn += (self.fluxes.n_surf_metab_litter - 
                                  self.fluxes.n_surf_metab_to_active)
        self.state.metabsurfn += self.nc_limit(self.state.metabsurf,
                                               self.state.metabsurfn,
                                               1.0/25.0, 1.0/10.0)
        
        self.state.metabsoiln += (self.fluxes.n_soil_metab_litter - 
                                  self.fluxes.n_soil_metab_to_active)
        self.state.metabsoiln += self.nc_limit(self.state.metabsoil,
                                               self.state.metabsoiln,
                                               1.0/25.0, 1.0/10.0)
        
        # When nothing is being added to the metabolic pools, there is the 
        # potential scenario with the way the model works for tiny bits to be
        # removed with each timestep. Effectively with time this value which is
        # zero can end up becoming zero but to a silly decimal place
        self.precision_control()
        
        # Update SOM pools
        
        n_into_active = (self.fluxes.n_surf_struct_to_active + 
                         self.fluxes.n_soil_struct_to_active +
                         self.fluxes.n_surf_metab_to_active + 
                         self.fluxes.n_soil_metab_to_active +
                         self.fluxes.n_slow_to_active + 
                         self.fluxes.n_passive_to_active)
        
        n_out_of_active = (self.fluxes.n_active_to_slow +
                           self.fluxes.n_active_to_passive)
        
        n_into_slow = (self.fluxes.n_surf_struct_to_slow + 
                       self.fluxes.n_soil_struct_to_slow +
                       self.fluxes.n_active_to_slow)
        
        n_out_of_slow = (self.fluxes.n_slow_to_active + 
                         self.fluxes.n_slow_to_passive)
                                   
        n_into_passive = (self.fluxes.n_active_to_passive + 
                          self.fluxes.n_slow_to_passive)

        n_out_of_passive = self.fluxes.n_passive_to_active
        
        # N:C of the SOM pools increases linearly btw prescribed min and max 
        # values as the Nconc of the soil increases.
        arg = (self.state.inorgn - self.params.nmin0 / const.M2_AS_HA * 
                const.G_AS_TONNES)
        # active
        active_nc = self.params.actncmin + active_nc_slope * arg
        if float_gt(active_nc, self.params.actncmax):
            active_nc = self.params.actncmax
        
        # release N to Inorganic pool or fix N from the Inorganic pool in order
        # to normalise the N:C ratio of a net flux
        fixn = self.nc_flux(self.fluxes.c_into_active, n_into_active, active_nc)
        self.state.activesoiln += n_into_active + fixn - n_out_of_active

        # slow
        slow_nc = self.params.slowncmin + slow_nc_slope * arg
        if float_gt(slow_nc, self.params.slowncmax):
            slow_nc = self.params.slowncmax
        
        # release N to Inorganic pool or fix N from the Inorganic pool in order
        # to normalise the N:C ratio of a net flux
        fixn = self.nc_flux(self.fluxes.c_into_slow, n_into_slow, slow_nc)
        self.state.slowsoiln += n_into_slow + fixn - n_out_of_slow
                                
        # passive, update passive pool only if passiveconst=0
        pass_nc = self.params.passncmin + passive_nc_slope * arg
        if float_gt(pass_nc, self.params.passncmax):
            pass_nc = self.params.passncmax
        
        # release N to Inorganic pool or fix N from the Inorganic pool in order
        # to normalise the N:C ratio of a net flux
        fixn = self.nc_flux(self.fluxes.c_into_passive, n_into_passive, pass_nc)
        self.state.passivesoiln += n_into_passive + fixn - n_out_of_passive
                                    

        # Daily increment of soil inorganic N pool, diff btw in and effluxes
        # (grazer urine n goes directly into inorganic pool) nb inorgn may be
        # unstable if rateuptake is large
        self.state.inorgn += ((self.fluxes.ngross + self.fluxes.ninflow + 
                               self.fluxes.nurine - self.fluxes.nimmob - 
                               self.fluxes.nloss - self.fluxes.nuptake) + 
                               self.fluxes.nlittrelease)
Example #38
0
    def update_plant_state(self, fdecay, rdecay, project_day, doy):
        """ Daily change in C content

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

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

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

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

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

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

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

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

                self.state.rootn -= extrar
                self.fluxes.nuptake -= extrar 
Example #39
0
    def calculate_n_immobilisation(self):
        """ N immobilised in new soil organic matter, the reverse of
        mineralisation. Micro-organisms in the soil compete with plants for N.
        Immobilisation is the process by which nitrate and ammonium are taken up
        by the soil organisms and thus become unavailable to the plant 
        (->organic N).
        
        When C:N ratio is high the microorganisms need more nitrogen from 
        the soil to decompose the carbon in organic materials. This N will be
        immobilised until these microorganisms die and the nitrogen is 
        released.
        
        General equation for new soil N:C ratio vs Nmin, expressed as linear 
        equation passing through point Nmin0, actncmin (etc). Values can be 
        Nmin0=0, Actnc0=Actncmin 

        if Nmin < Nmincrit:
            New soil N:C = soil N:C (when Nmin=0) + slope * Nmin

        if Nmin > Nmincrit
            New soil N:C = max soil N:C       
        
        NB N:C ratio of new passive SOM can change even if assume Passiveconst
        
        Returns:
        --------
        nimob : float
            N immobilsed
        """
        # N:C new SOM - active, slow and passive
        active_nc_slope = self.calculate_nc_slope(self.params.actncmax,
                                                  self.params.actncmin)
        slow_nc_slope = self.calculate_nc_slope(self.params.slowncmax,
                                                self.params.slowncmin)
        passive_nc_slope = self.calculate_nc_slope(self.params.passncmax,
                                                   self.params.passncmin)

        # C flux entering SOM pools - use short names
        active_influxes = self.fluxes.c_into_active
        slow_influxes = self.fluxes.c_into_slow
        passive_influxes = self.fluxes.c_into_passive

        # convert units
        nmin = self.params.nmin0 / const.M2_AS_HA * const.G_AS_TONNES

        arg1 = ((self.params.passncmin - passive_nc_slope * nmin) *
                passive_influxes)
        arg2 = (self.params.slowncmin - slow_nc_slope * nmin) * slow_influxes
        arg3 = active_influxes * (self.params.actncmin -
                                  active_nc_slope * nmin)
        numer1 = arg1 + arg2 + arg3

        arg1 = passive_influxes * self.params.passncmax
        arg2 = slow_influxes * self.params.slowncmax
        arg3 = active_influxes * self.params.actncmax
        numer2 = arg1 + arg2 + arg3

        arg1 = passive_influxes * passive_nc_slope
        arg2 = slow_influxes * slow_nc_slope
        arg3 = active_influxes * active_nc_slope
        denom = arg1 + arg2 + arg3

        # evaluate N immobilisation in new SOM
        nimmob = numer1 + denom * self.state.inorgn
        if float_gt(nimmob, numer2):
            nimmob = numer2

        return (nimmob, active_nc_slope, slow_nc_slope, passive_nc_slope)
Example #40
0
    def calc_carbon_allocation_fracs(self, nitfac, yr_index, project_day):
        """Carbon allocation fractions to move photosynthate through the plant.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            leaf2sap = self.state.lai / sap_cross_sec_area

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

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

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

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

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

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

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

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

        # Total allocation should be one, if not print warning:
        total_alloc = (self.state.alroot + self.state.alleaf +
                       self.state.albranch + self.state.alstem)
        if float_gt(total_alloc, 1.0):
            raise RuntimeError, "Allocation fracs > 1"
Example #41
0
    def nitrogen_allocation(self, ncbnew, ncwimm, ncwnew, fdecay, rdecay, doy,
                            days_in_yr, project_day):
        """ Nitrogen distribution - allocate available N through system.
        N is first allocated to the woody component, surplus N is then allocated
        to the shoot and roots with flexible ratios.
        
        References:
        -----------
        McMurtrie, R. E. et al (2000) Plant and Soil, 224, 135-152.
        
        Parameters:
        -----------
        ncbnew : float
            N:C ratio of branch
        ncwimm : float
            N:C ratio of immobile stem
        ncwnew : float
            N:C ratio of mobile stem
        fdecay : float
            foliage decay rate
        rdecay : float
            fine root decay rate
        """
        # N retranslocated proportion from dying plant tissue and stored within
        # the plant
        self.fluxes.retrans = self.nitrogen_retrans(fdecay, rdecay, doy)
        self.fluxes.nuptake = self.calculate_nuptake(project_day)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            # allocate remaining N to flexible-ratio pools
            self.fluxes.npleaf = (
                ntot * self.state.alleaf /
                (self.state.alleaf + self.state.alroot * self.params.ncrfac))
            self.fluxes.nproot = ntot - self.fluxes.npleaf
Example #42
0
    def update_plant_state(self, fdecay, rdecay, project_day, doy):
        """ Daily change in C content

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        # net source fluxes.
        nstsu = self.fluxes.nresid[0]  # s surf
        nstsl = self.fluxes.nresid[1]  # s soil
        nmtsu = self.fluxes.nresid[2]  # m surf
        nmtsl = self.fluxes.nresid[3]  # m soil
        nact = (self.fluxes.nstruct[1] + self.fluxes.nstruct[3] +
                self.fluxes.nmetab[0] + self.fluxes.nmetab[1] +
                self.fluxes.nslow[0] + self.fluxes.npassive)
        nslo = (self.fluxes.nstruct[0] + self.fluxes.nstruct[2] +
                self.fluxes.nactive[0])
        npas = self.fluxes.nactive[1] + self.fluxes.nslow[1]

        # net effluxes.
        lstsu = (self.fluxes.nstruct[0] + self.fluxes.nstruct[1])   # s surf
        lstsl = (self.fluxes.nstruct[2] + self.fluxes.nstruct[3])   # s soil
        lmtsu = self.fluxes.nmetab[0]                               # m surf
        lmtsl = self.fluxes.nmetab[1]                               # m soil
        lact = (self.fluxes.nactive[0] + self.fluxes.nactive[1])
        lslo = (self.fluxes.nslow[0] + self.fluxes.nslow[1])
        lpas = self.fluxes.npassive

        # net N release implied by separation of litter into structural
        # & metabolic. The following pools only fix or release N at their 
        # limiting n:c values. 
        
        # N released or fixed from the N inorganic pool is incremented with
        # each call to nclimit and stored in self.fluxes.nlittrelease
        self.fluxes.nlittrelease = 0.0
        
        self.state.structsurfn += nstsu - lstsu
        if not self.control.strfloat:
            self.state.structsurfn += self.nclimit(self.state.structsurf,
                                                   self.state.structsurfn,
                                                   1.0/self.params.structcn,
                                                   1.0/self.params.structcn)
        
        self.state.structsoiln += nstsl - lstsl
        if not self.control.strfloat:
            self.state.structsoiln += self.nclimit(self.state.structsoil,
                                                   self.state.structsoiln,
                                                   1.0/self.params.structcn,
                                                   1.0/self.params.structcn)
        
        self.state.metabsurfn += nmtsu - lmtsu
        self.state.metabsurfn += self.nclimit(self.state.metabsurf,
                                              self.state.metabsurfn,
                                              1.0/25.0, 1.0/10.0)
        
        
        self.state.metabsoiln += nmtsl - lmtsl
        self.state.metabsoiln += self.nclimit(self.state.metabsoil,
                                              self.state.metabsoiln,
                                              1.0/25.0, 1.0/10.0)
        
        # When nothing is being added to the metabolic pools, there is the 
        # potential scenario with the way the model works for tiny bits to be
        # removed with each timestep. Effectively with time this value which is
        # zero can end up becoming zero but to a silly decimal place
        self.precision_control()
        
        # N:C of the SOM pools increases linearly btw prescribed min and max 
        # values as the Nconc of the soil increases.
        arg = (self.state.inorgn - self.params.nmin0 / const.M2_AS_HA * 
                const.G_AS_TONNES)
        # active
        actnc = self.params.actncmin + self.state.actncslope * arg
        if float_gt(actnc, self.params.actncmax):
            actnc = self.params.actncmax
        fixn = ncflux(self.fluxes.cact, nact, actnc)
        self.state.activesoiln += nact + fixn - lact

        # slow
        slownc = self.params.slowncmin + self.state.slowncslope * arg
        if float_gt(slownc, self.params.slowncmax):
            slownc = self.params.slowncmax
        fixn = ncflux(self.fluxes.cslo, nslo, slownc)
        self.state.slowsoiln += nslo + fixn - lslo

        # passive
        passnc = self.params.passncmin + self.state.passncslope * arg
        if float_gt(passnc, self.params.passncmax):
            passnc = self.params.passncmax
        fixn = ncflux(self.fluxes.cpas, npas, passnc)
        # update passive pool only if passiveconst=0
        self.state.passivesoiln += npas + fixn - lpas

        # Daily increment of soil inorganic N pool, diff btw in and effluxes
        # (grazer urine n goes directly into inorganic pool) nb inorgn may be
        # unstable if rateuptake is large
        self.state.inorgn += ((self.fluxes.ngross + self.fluxes.ninflow + 
                               self.fluxes.nurine - self.fluxes.nimmob - 
                               self.fluxes.nloss - self.fluxes.nuptake) + 
                               self.fluxes.nlittrelease)
Example #46
0
    def calculate_npools(self, active_nc_slope, slow_nc_slope,
                         passive_nc_slope):
        """  
        Update N pools in the soil
        
        Parameters
        ----------
        active_nc_slope : float
            active NC slope
        slow_nc_slope: float
            slow NC slope
        passive_nc_slope : float
            passive NC slope
        
        """
        # net N release implied by separation of litter into structural
        # & metabolic. The following pools only fix or release N at their
        # limiting n:c values.

        # N released or fixed from the N inorganic pool is incremented with
        # each call to nc_limit and stored in self.fluxes.nlittrelease
        self.fluxes.nlittrelease = 0.0

        self.state.structsurfn += (self.fluxes.n_surf_struct_litter -
                                   (self.fluxes.n_surf_struct_to_slow +
                                    self.fluxes.n_surf_struct_to_active))

        if not self.control.strfloat:
            self.state.structsurfn += self.nc_limit(self.state.structsurf,
                                                    self.state.structsurfn,
                                                    1.0 / self.params.structcn,
                                                    1.0 / self.params.structcn)

        self.state.structsoiln += (self.fluxes.n_soil_struct_litter -
                                   (self.fluxes.n_soil_struct_to_slow +
                                    self.fluxes.n_soil_struct_to_active))

        if not self.control.strfloat:
            self.state.structsoiln += self.nc_limit(self.state.structsoil,
                                                    self.state.structsoiln,
                                                    1.0 / self.params.structcn,
                                                    1.0 / self.params.structcn)

        self.state.metabsurfn += (self.fluxes.n_surf_metab_litter -
                                  self.fluxes.n_surf_metab_to_active)
        self.state.metabsurfn += self.nc_limit(self.state.metabsurf,
                                               self.state.metabsurfn,
                                               1.0 / 25.0, 1.0 / 10.0)

        self.state.metabsoiln += (self.fluxes.n_soil_metab_litter -
                                  self.fluxes.n_soil_metab_to_active)
        self.state.metabsoiln += self.nc_limit(self.state.metabsoil,
                                               self.state.metabsoiln,
                                               1.0 / 25.0, 1.0 / 10.0)

        # When nothing is being added to the metabolic pools, there is the
        # potential scenario with the way the model works for tiny bits to be
        # removed with each timestep. Effectively with time this value which is
        # zero can end up becoming zero but to a silly decimal place
        self.precision_control()

        # Update SOM pools

        n_into_active = (self.fluxes.n_surf_struct_to_active +
                         self.fluxes.n_soil_struct_to_active +
                         self.fluxes.n_surf_metab_to_active +
                         self.fluxes.n_soil_metab_to_active +
                         self.fluxes.n_slow_to_active +
                         self.fluxes.n_passive_to_active)

        n_out_of_active = (self.fluxes.n_active_to_slow +
                           self.fluxes.n_active_to_passive)

        n_into_slow = (self.fluxes.n_surf_struct_to_slow +
                       self.fluxes.n_soil_struct_to_slow +
                       self.fluxes.n_active_to_slow)

        n_out_of_slow = (self.fluxes.n_slow_to_active +
                         self.fluxes.n_slow_to_passive)

        n_into_passive = (self.fluxes.n_active_to_passive +
                          self.fluxes.n_slow_to_passive)

        n_out_of_passive = self.fluxes.n_passive_to_active

        # N:C of the SOM pools increases linearly btw prescribed min and max
        # values as the Nconc of the soil increases.
        arg = (self.state.inorgn -
               self.params.nmin0 / const.M2_AS_HA * const.G_AS_TONNES)
        # active
        active_nc = self.params.actncmin + active_nc_slope * arg
        if float_gt(active_nc, self.params.actncmax):
            active_nc = self.params.actncmax

        # release N to Inorganic pool or fix N from the Inorganic pool in order
        # to normalise the N:C ratio of a net flux
        fixn = self.nc_flux(self.fluxes.c_into_active, n_into_active,
                            active_nc)
        self.state.activesoiln += n_into_active + fixn - n_out_of_active

        # slow
        slow_nc = self.params.slowncmin + slow_nc_slope * arg
        if float_gt(slow_nc, self.params.slowncmax):
            slow_nc = self.params.slowncmax

        # release N to Inorganic pool or fix N from the Inorganic pool in order
        # to normalise the N:C ratio of a net flux
        fixn = self.nc_flux(self.fluxes.c_into_slow, n_into_slow, slow_nc)
        self.state.slowsoiln += n_into_slow + fixn - n_out_of_slow

        # passive, update passive pool only if passiveconst=0
        pass_nc = self.params.passncmin + passive_nc_slope * arg
        if float_gt(pass_nc, self.params.passncmax):
            pass_nc = self.params.passncmax

        # release N to Inorganic pool or fix N from the Inorganic pool in order
        # to normalise the N:C ratio of a net flux
        fixn = self.nc_flux(self.fluxes.c_into_passive, n_into_passive,
                            pass_nc)
        self.state.passivesoiln += n_into_passive + fixn - n_out_of_passive

        # Daily increment of soil inorganic N pool, diff btw in and effluxes
        # (grazer urine n goes directly into inorganic pool) nb inorgn may be
        # unstable if rateuptake is large
        self.state.inorgn += (
            (self.fluxes.ngross + self.fluxes.ninflow + self.fluxes.nurine -
             self.fluxes.nimmob - self.fluxes.nloss - self.fluxes.nuptake) +
            self.fluxes.nlittrelease)