Ejemplo n.º 1
0
Archivo: gday.py Proyecto: npp97/GDAY
class Gday(object):
    """ The G'DAY (Generic Decomposition And Yield) model.

    GDAY simulates C, N and water cycling between the plant and the soil. The
    model is structured into three plant pools (foliage, wood and fine roots),
    four litter pools (above/below metabolic and structural litter) and three
    soil organic matter (SOM) pools with varying turnover rates (active, slow
    and passive). An adapted implementation of the CENTURY model simulates soil
    carbon and nutrient dynamics. There is an additional simple soil water
    balance module which can be used to limit growth. Model pools can be thought
    of as buckets as they don't have dimensions.

    References:
    ----------
    * Comins, H. N. and McMurtrie, R. E. (1993) Ecological Applications, 3,
      666-681.
    * Medlyn, B. E. et al (2000) Canadian Journal of Forest Research, 30,
      873-888.
    * And any of the other McMurtrie papers!
    """
    def __init__(self, fname=None, DUMP=False, spin_up=False, met_header=4):
        """ Set up model

        * Read meterological forcing file
        * Read user config file and adjust the model parameters, control or
          initial state attributes that are used within the code.
        * Setup all class instances...perhaps this isn't the tidyest place for 
          this?
        * Initialise things, zero stores etc.
        
        Parameters:
        ----------
        fname : string
            filename of model parameters, including path
        chk_cmd_line : logical
            parse the cmd line?
        DUMP : logical
            dump a the default parameters to a file
        met_header : int
            row number of met file header with variable names
        Returns:
        -------
        Nothing
            Controlling class of the model, runs things.

        """
        self.day_output = []  # store daily outputs

        # initialise model structures and read met data
        (self.control, self.params, self.state, self.files, self.fluxes,
         self.met_data, self.print_opts) = initialise_model_data(fname,
                                                                 met_header,
                                                                 DUMP=DUMP)

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

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

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

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

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

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

        if self.control.deciduous_model:
            self.pg.calc_carbon_allocation_fracs(0.0, 0, 0)  #comment this!!
            self.pg.allocate_stored_c_and_n(init=True)
            self.P = Phenology(
                self.fluxes,
                self.state,
                self.control,
                self.params.previous_ncd,
                store_transfer_len=self.params.store_transfer_len)

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

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

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

        # calculate initial stuff, e.g. C:N ratios and zero annual flux sums
        self.day_end_calculations(0, INIT=True)
        self.state.pawater_root = self.params.wcapac_root
        self.state.pawater_topsoil = self.params.wcapac_topsoil
        self.spin_up = spin_up
        self.state.sla = self.params.slainit  # Specific leaf area (m2/kg DW)
        self.state.lai = (self.params.slainit * const.M2_AS_HA /
                          const.KG_AS_TONNES / self.params.cfracts *
                          self.state.shoot)

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

        if self.control.water_stress == False:
            sys.stderr.write("**** You have turned off the drought stress")
            sys.stderr.write(", I assume you're debugging??!\n")

    def run_sim(self):
        """ Run model simulation! """
        # local variables
        years = self.years
        days_in_year = self.days_in_year

        # =============== #
        #   YEAR LOOP     #
        # =============== #
        project_day = 0
        for i, yr in enumerate(years):
            self.day_output = []  # empty daily storage list for outputs
            daylen = calculate_daylength(days_in_year[i], self.params.latitude)
            if self.control.deciduous_model:
                self.P.calculate_phenology_flows(daylen, self.met_data,
                                                 days_in_year[i], project_day)
                self.zero_stuff()
            # =============== #
            #   DAY LOOP      #
            # =============== #
            for doy in xrange(days_in_year[i]):

                #for pft in xrange(4):

                # litterfall rate: C and N fluxes
                (fdecay, rdecay) = self.lf.calculate_litter(doy)

                # co2 assimilation, N uptake and loss
                self.pg.calc_day_growth(project_day, fdecay,
                                        rdecay, daylen[doy], doy,
                                        float(days_in_year[i]), i)

                # soil C & N model fluxes
                self.cs.calculate_csoil_flows(project_day)
                self.ns.calculate_nsoil_flows(project_day)

                # calculate C:N ratios and increment annual flux sums
                self.day_end_calculations(project_day, days_in_year[i])

                #print self.state.lai, self.fluxes.gpp*100, self.state.pawater_root, self.state.shootnc
                # =============== #
                #   END OF DAY    #
                # =============== #
                if not self.spin_up:
                    self.save_daily_outputs(yr, doy + 1)

                # check the daily water balance
                #self.cb.check_water_balance(project_day)

                project_day += 1

            # =============== #
            #   END OF YEAR   #
            # =============== #
            if self.control.deciduous_model:
                print "**", self.state.cstore
                self.pg.allocate_stored_c_and_n(init=False)

            if self.control.print_options == "DAILY" and not self.spin_up:
                self.print_output_file()

        # close output files
        if self.control.print_options == "END" and not self.spin_up:
            self.print_output_file()

        # only close printing file if not in spin up mode as we have yet
        # written the daily output...
        if self.spin_up:
            return (yr, doy + 1)
        else:
            self.pr.clean_up()

    def spin_up_pools(self, tolerance=1E-03):
        """ Spin Up model plant, soil and litter pools.
        -> Examine sequences of 1000 years and check if C pools are changing
           by more than 0.005 units per 1000 yrs.

        References:
        ----------
        Adapted from...
        * Murty, D and McMurtrie, R. E. (2000) Ecological Modelling, 134,
          185-205, specifically page 196.
        """
        prev_plantc = 9999.9
        prev_soilc = 9999.9
        prev_litterc = 9999.9
        while True:
            if (fabs(prev_plantc - self.state.plantc) < tolerance
                    and fabs(prev_soilc - self.state.soilc) < tolerance
                    and fabs(prev_litterc - self.state.litterc) < tolerance):
                break
            else:
                prev_plantc = self.state.plantc
                prev_soilc = self.state.soilc
                prev_litterc = self.state.litterc
                (yr, doy) = self.run_sim()  # run the model...

                # Have we reached a steady state?
                msg = "Spinup: Plant C - %f, Soil C - %f, Litter C - %f\n" % \
                       (self.state.plantc, self.state.soilc, self.state.litterc)
                sys.stderr.write(msg)

        self.save_daily_outputs(yr, doy)
        self.pr.clean_up()
        self.print_output_file()

    def print_output_file(self):
        """ Either print the daily output file (at the end of the year) or
        print the final state + param file. """

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

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

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

            self.correct_rate_constants(output=True)
            self.pr.save_state()

    def zero_stuff(self):
        self.state.shoot = 0.0
        self.state.shootn = 0.0
        self.state.shootnc = 0.0
        self.state.lai = 0.0
        self.state.cstore = 0.0
        self.state.nstore = 0.0
        self.state.anpp = 0.0

    def correct_rate_constants(self, output=False):
        """ adjust rate constants for the number of days in years """
        time_constants = [
            'rateuptake', 'rateloss', 'retransmob', 'fdecay', 'fdecaydry',
            'rdecay', 'rdecaydry', 'bdecay', 'wdecay', 'sapturnover', 'kdec1',
            'kdec2', 'kdec3', 'kdec4', 'kdec5', 'kdec6', 'kdec7', 'nuptakez',
            'nmax', 'adapt'
        ]
        conv = const.NDAYS_IN_YR

        if output == False:
            for i in time_constants:
                setattr(self.params, i, getattr(self.params, i) / conv)
        else:
            for i in time_constants:
                setattr(self.params, i, getattr(self.params, i) * conv)

    def day_end_calculations(self, prjday, days_in_year=None, INIT=False):
        """Calculate derived values from state variables.

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

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

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

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

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

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

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

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

    def save_daily_outputs(self, year, doy):
        """ Save the daily fluxes + state in a big list.

        This should be a more efficient way to write the daily output in a
        single step at the end of the year.

        Parameters:
        -----------
        project_day : integer
            simulation day
        """
        output = [year, doy]
        for var in self.print_state:
            output.append(getattr(self.state, var))
        for var in self.print_fluxes:
            output.append(getattr(self.fluxes, var))
        self.day_output.append(output)
Ejemplo n.º 2
0
Archivo: gday.py Proyecto: npp97/GDAY
    def __init__(self, fname=None, DUMP=False, spin_up=False, met_header=4):
        """ Set up model

        * Read meterological forcing file
        * Read user config file and adjust the model parameters, control or
          initial state attributes that are used within the code.
        * Setup all class instances...perhaps this isn't the tidyest place for 
          this?
        * Initialise things, zero stores etc.
        
        Parameters:
        ----------
        fname : string
            filename of model parameters, including path
        chk_cmd_line : logical
            parse the cmd line?
        DUMP : logical
            dump a the default parameters to a file
        met_header : int
            row number of met file header with variable names
        Returns:
        -------
        Nothing
            Controlling class of the model, runs things.

        """
        self.day_output = []  # store daily outputs

        # initialise model structures and read met data
        (self.control, self.params, self.state, self.files, self.fluxes,
         self.met_data, self.print_opts) = initialise_model_data(fname,
                                                                 met_header,
                                                                 DUMP=DUMP)

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

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

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

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

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

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

        if self.control.deciduous_model:
            self.pg.calc_carbon_allocation_fracs(0.0, 0, 0)  #comment this!!
            self.pg.allocate_stored_c_and_n(init=True)
            self.P = Phenology(
                self.fluxes,
                self.state,
                self.control,
                self.params.previous_ncd,
                store_transfer_len=self.params.store_transfer_len)

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

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

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

        # calculate initial stuff, e.g. C:N ratios and zero annual flux sums
        self.day_end_calculations(0, INIT=True)
        self.state.pawater_root = self.params.wcapac_root
        self.state.pawater_topsoil = self.params.wcapac_topsoil
        self.spin_up = spin_up
        self.state.sla = self.params.slainit  # Specific leaf area (m2/kg DW)
        self.state.lai = (self.params.slainit * const.M2_AS_HA /
                          const.KG_AS_TONNES / self.params.cfracts *
                          self.state.shoot)

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

        if self.control.water_stress == False:
            sys.stderr.write("**** You have turned off the drought stress")
            sys.stderr.write(", I assume you're debugging??!\n")
Ejemplo n.º 3
0
    def __init__(self, fname=None, DUMP=False, spin_up=False, met_header=4):

        """ Set up model

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        self.dead = False # johnny 5 is alive

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

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

        if self.control.water_stress == False:
            sys.stderr.write("**** You have turned off the drought stress")
            sys.stderr.write(", I assume you're debugging??!\n")
Ejemplo n.º 4
0
class Gday(object):
    """ The G'DAY (Generic Decomposition And Yield) model.

    GDAY simulates C, N and water cycling between the plant and the soil. The
    model is structured into three plant pools (foliage, wood and fine roots),
    four litter pools (above/below metabolic and structural litter) and three
    soil organic matter (SOM) pools with varying turnover rates (active, slow
    and passive). An adapted implementation of the CENTURY model simulates soil
    carbon and nutrient dynamics. There is an additional simple soil water
    balance module which can be used to limit growth. Model pools can be though
    of as buckets as they don't have dimensions.

    References:
    ----------
    * Comins, H. N. and McMurtrie, R. E. (1993) Ecological Applications, 3,
      666-681.
    * Medlyn, B. E. et al (2000) Canadian Journal of Forest Research, 30,
      873-888.
    * And any of the other McMurtrie papers!
    """
    def __init__(self, fname=None, DUMP=False, spin_up=False, met_header=4):

        """ Set up model

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        self.dead = False # johnny 5 is alive

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

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

        if self.control.water_stress == False:
            sys.stderr.write("**** You have turned off the drought stress")
            sys.stderr.write(", I assume you're debugging??!\n")
        
    def run_sim(self):
        """ Run model simulation! """
        # local variable
        years = self.years
        days_in_year = self.days_in_year

        if self.control.disturbance:
            # Figure out if any years have a disturbance
            self.db.initialise(years)

        
        # ===================== #
        #   Y E A R   L O O P   #
        # ===================== #
        project_day = 0
        for i, yr in enumerate(years):
            self.day_output = [] # empty daily storage list for outpu
            daylen = calculate_daylength(days_in_year[i], self.params.latitude)
            if self.control.deciduous_model:
                self.P.calculate_phenology_flows(daylen, self.met_data,
                                            days_in_year[i], project_day)

                # Change window size to length of growing season
                self.pg.sma.window_size = self.P.growing_seas_len
                self.zero_stuff()
                
            # =================== #
            #   D A Y   L O O P   #
            # =================== #
            for doy in xrange(days_in_year[i]):

                # standard litter calculation
                # litterfall rate: C and N fluxe
                (fdecay, rdecay) = self.lf.calculate_litter(doy)
                
                
                
                # Fire Disturbance?
                if (self.control.disturbance != 0 and
                    self.params.disturbance_doy == doy):
                    self.db.check_for_fire(yr, self.pg)
                # Hurricane?
                elif (self.control.hurricane == 1 and
                      self.params.hurricane_yr == yr and
                      self.params.hurricane_doy == doy):
                    self.db.hurricane()

                # photosynthesis & growth
                fsoilT = self.cs.soil_temp_factor(project_day)
                self.pg.calc_day_growth(project_day, fdecay, rdecay,
                                        daylen[doy], doy,
                                        float(days_in_year[i]), i, fsoilT)
    
                # soil C & N calculation
                self.cs.calculate_csoil_flows(project_day, doy)
                self.ns.calculate_nsoil_flows(project_day, doy)

                if self.control.ncycle == False:
                    # Turn off all N calculations
                    self.reset_all_n_pools_and_fluxes()

                # calculate C:N ratios and increment annual flux sum
                self.day_end_calculations(days_in_year[i])

                # checking if we died during the timestep
                #   - added for desert simulation
                if (not self.control.deciduous_model and
                    self.control.disturbance == 0):
                    self.are_we_dead()

                #print self.state.plantc, self.state.soilc
                #print yr, doy, self.state.lai, self.fluxes.gpp*100
                
                #print self.fluxes.gpp*100, self.state.prev_sma
                # ======================= #
                #   E N D   O F   D A Y   #
                # ======================= #
                if not self.spin_up:
                    self.save_daily_outputs(yr, doy+1)

                # check the daily water balance
                #self.cb.check_water_balance(project_day)
                
                project_day += 1
            
            # ========================= #
            #   E N D   O F   Y E A R   #
            # ========================= #
            
            # Allocate stored C&N for the following year
            if self.control.deciduous_model:
                # Using average alloc fracs across growing season instead
                #self.pg.calc_carbon_allocation_fracs(0.0) #comment this!!
                self.pg.calculate_average_alloc_fractions(self.P.growing_seas_len)
                self.pg.allocate_stored_c_and_n(init=False)
                
                # reset the stress buffer at the end of the growing season
                self.pg.sma.reset_stream()
                
                #print self.fluxes.alleaf, self.fluxes.alroot, \
                #    (self.fluxes.albranch+self.fluxes.alstem)
                #print
                
                
            # GDAY died in the previous year, re-establish gday for the next yr
            #   - added for desert simulation
            if (self.dead and not
                self.control.deciduous_model and
                self.control.disturbance == 0):
                self.re_establish_gday()

            if self.control.print_options == "DAILY" and not self.spin_up:
                self.print_output_file()
                
        # close output file
        if self.control.print_options == "END" and not self.spin_up:
            self.print_output_file()

        # only close printing file if not in spin up mode as we have ye
        # written the daily output...
        if self.spin_up:
            return (yr, doy+1)
        else:
            # Need to pass the project day to calculate NROWS for .hdr file
            self.pr.clean_up(project_day)
        
    def are_we_dead(self):
        """ Simplistic scheme to allow GDAY to die and re-establish the
        following year """

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

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

            self.dead = True # johnny 5 is dead
            print "dead"

    def re_establish_gday(self):
        """ grow from seed the following year following death. Concept is tha
        somewhere along the line GDAY saved enough C to be able to
        re-establish in a new year. C isnt explicitly accounted for, so thi
        C just appears. Perhaps this makes sense, i.e. wind/animals dropping
        seeds, alternatively we could force GDAY to save a tiny amount of C
        for reproduction. For the moment -> it just appears."""
        self.state.lai = 0.01

        # C/N = 25 of default pools.
        if self.control.alloc_model == "GRASSES":
            self.state.age = 0.0
            self.state.branch = 0.0
            self.state.branchn = 0.0
            self.state.cstore = 0.001
            self.state.nstore = 0.00004
            self.state.root = 0.001
            self.state.rootn = 0.00004
            self.state.sapwood = 0.0
            self.state.shoot = 0.001
            self.state.shootn = 0.00004
            self.state.stem = 0.0
            self.state.stemn = 0.0
            self.state.stemnimm = 0.0
            self.state.stemnmob = 0.0
            self.state.croot = 0.0
            self.state.crootn = 0.0
        else:
            self.state.branch = 0.001
            self.state.branchn = 0.00004
            self.state.croot = 0.001
            self.state.crootn = 0.00004
            self.state.sapwood = 0.001
            self.state.stem = 0.001
            self.state.stemn = 0.00004
            self.state.stemnimm = 0.00004
            self.state.stemnmob = 0.0
            self.state.sapwood = 0.001
        print "re-seeding"

    def spin_up_pools(self, tol=5E-03):
        """ Spin up model plant & soil pools to equilibrium.

        - Examine sequences of 50 years and check if C pools are changing
          by more than 0.005 units per 1000 yrs. Note this check is done in
          units of: kg m-2.

        References:
        ----------
        Adapted from...
        * Murty, D and McMurtrie, R. E. (2000) Ecological Modelling, 134,
          185-205, specifically page 196.
        """
        prev_plantc = 99999.9
        prev_soilc = 99999.9

        # check for convergences in units of kg/m2
        conv = const.TONNES_HA_2_KG_M2

        # If we are prescribing disturbance, first allow the forest to
        # establish
        if self.control.disturbance != 0:
            cntrl_flag = self.control.disturbance
            self.control.disturbance = 0
            # 200 years (50 yrs x 4 cycles)
            for spin_num in xrange(4):
                (yr, doy) = self.run_sim() # run the model...
            self.control.disturbance = cntrl_flag

            while True:
                if fabs((prev_soilc*conv) - (self.state.soilc*conv)) < tol:
                    break
                else:
                    prev_soilc = self.state.soilc

                    # 1000 years (50 yrs x 20 cycles)
                    for spin_num in xrange(20):
                        (yr, doy) = self.run_sim() # run the model...

                    # Have we reached a steady state?
                    msg = "Spinup: Soil C - %f\n" % (self.state.soilc)
                    sys.stderr.write(msg)
        else:

            while True:
                if (fabs((prev_plantc*conv) - (self.state.plantc*conv)) < tol and
                    fabs((prev_soilc*conv) - (self.state.soilc*conv)) < tol):
                    break
                else:
                    prev_plantc = self.state.plantc
                    prev_soilc = self.state.soilc

                    # 1000 years (50 yrs x 20 cycles)
                    for spin_num in xrange(20):
                        (yr, doy) = self.run_sim() # run the model...

                    # Have we reached a steady state?
                    msg = "Spinup: Plant C - %f, Soil C - %f\n" % \
                          (self.state.plantc, self.state.soilc)
                    sys.stderr.write(msg)

        self.save_daily_outputs(yr, doy)
        self.pr.clean_up()
        self.print_output_file()

    def print_output_file(self):
        """ Either print the daily output file (at the end of the year) or
        print the final state + param file. """

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

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

            self.correct_rate_constants(output=True)
            self.pr.save_state()

    def zero_stuff(self):
        self.state.shoot = 0.0
        self.state.shootn = 0.0
        self.state.shootnc = 0.0
        self.state.lai = 0.0
        self.state.max_lai = 0.0
        self.state.max_shoot = 0.0
        self.state.cstore = 0.0
        self.state.nstore = 0.0
        self.state.anpp = 0.0
        self.state.grw_seas_stress = 1.0
        
        if self.control.deciduous_model:
            self.state.avg_alleaf = 0.0
            self.state.avg_alroot = 0.0
            self.state.avg_alcroot = 0.0
            self.state.avg_albranch  = 0.0
            self.state.avg_alstem = 0.0
        
    def correct_rate_constants(self, output=False):
        """ adjust rate constants for the number of days in years """
        time_constants = ['rateuptake', 'rateloss', 'retransmob',
                          'fdecay', 'fdecaydry', 'crdecay','rdecay',
                          'rdecaydry', 'bdecay', 'wdecay', 'sapturnover',
                          'kdec1', 'kdec2', 'kdec3', 'kdec4', 'kdec5', 'kdec6',
                          'kdec7', 'nuptakez','nmax', 'adapt']
        conv = const.NDAYS_IN_YR

        if output == False:
            for i in time_constants:
                setattr(self.params, i, getattr(self.params, i) / conv)
        else:
            for i in time_constants:
                setattr(self.params, i, getattr(self.params, i) * conv)

    def day_end_calculations(self, days_in_year=None, INIT=False):
        """Calculate derived values from state variables.

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

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

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

        # Explicitly set the shoot N:C
        if self.control.ncycle == False:
            self.state.shootnc = self.params.prescribed_leaf_NC

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

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

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

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

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

    def save_daily_outputs(self, year, doy):
        """ Save the daily fluxes + state in a big list.

        This should be a more efficient way to write the daily output in a
        single step at the end of the year.

        Parameters:
        -----------
        project_day : integer
            simulation day
        """
        output = [year, doy]
        output.extend(getattr(self.state, var) for var in self.print_state)
        output.extend(getattr(self.fluxes, var) for var in self.print_fluxes)
        self.day_output.append(output)
       
    def reset_all_n_pools_and_fluxes(self):
        """ If the N-Cycle is turned off the way I am implementing this is to
        do all the calculations and then reset everything at the end. This is a
        waste of resources but saves on multiple IF statements.
        """
        # State
        self.state.shootn = 0.0
        self.state.rootn = 0.0
        self.state.crootn = 0.0
        self.state.branchn = 0.0
        self.state.stemnimm = 0.0
        self.state.stemnmob = 0.0
        self.state.structsurfn = 0.0
        self.state.metabsurfn = 0.0
        self.state.structsoiln = 0.0
        self.state.metabsoiln = 0.0
        self.state.activesoiln = 0.0
        self.state.slowsoiln = 0.0
        self.state.passivesoiln = 0.0
        self.state.inorgn = 0.0
        self.state.stemn = 0.0
        self.state.stemnimm = 0.0
        self.state.stemnmob = 0.0
        self.state.nstore = 0.0

        # Fluxes
        self.fluxes.nuptake = 0.0
        self.fluxes.nloss = 0.0
        self.fluxes.npassive = 0.0
        self.fluxes.ngross = 0.0
        self.fluxes.nimmob = 0.0
        self.fluxes.nlittrelease = 0.0
        self.fluxes.nmineralisation = 0.0
        self.fluxes.npleaf = 0.0
        self.fluxes.nproot = 0.0
        self.fluxes.npcroot = 0.0
        self.fluxes.npbranch = 0.0
        self.fluxes.npstemimm = 0.0
        self.fluxes.npstemmob = 0.0
        self.fluxes.deadleafn = 0.0
        self.fluxes.deadrootn = 0.0
        self.fluxes.deadcrootn = 0.0
        self.fluxes.deadbranchn = 0.0
        self.fluxes.deadstemn = 0.0
        self.fluxes.neaten = 0.0
        self.fluxes.nurine = 0.0
        self.fluxes.leafretransn = 0.0
        self.fluxes.n_surf_struct_litter = 0.0
        self.fluxes.n_surf_metab_litter = 0.0
        self.fluxes.n_soil_struct_litter = 0.0
        self.fluxes.n_soil_metab_litter = 0.0
        self.fluxes.n_surf_struct_to_slow = 0.0
        self.fluxes.n_soil_struct_to_slow = 0.0
        self.fluxes.n_surf_struct_to_active = 0.0
        self.fluxes.n_soil_struct_to_active = 0.0
        self.fluxes.n_surf_metab_to_active = 0.0
        self.fluxes.n_surf_metab_to_active = 0.0
        self.fluxes.n_active_to_slow = 0.0
        self.fluxes.n_active_to_passive = 0.0
        self.fluxes.n_slow_to_active = 0.0
        self.fluxes.n_slow_to_passive = 0.0
        self.fluxes.n_passive_to_active = 0.0
Ejemplo n.º 5
0
    def __init__(self, fname=None, DUMP=False, spin_up=False):

        """ Set up model

        Read meterological forcing file and user config file and adjust the
        model parameters, control or initial state attributes that are used
        within the code.

        Parameters:
        ----------
        fname : string
            filename of model parameters, including path
        chk_cmd_line : logical
            parse the cmd line?
        DUMP : logical
            dump a the default parameters to a file

        Returns:
        -------
        Nothing
            Controlling class of the model, runs things.

        """
        self.day_output = [] # store daily outputs
        (self.control, self.params,
            self.state, self.files,
            self.fluxes, self.met_data,
            self.print_opts) = initialise_model_data(fname, DUMP=DUMP)
        
        if self.control.water_stress == False:
            sys.stderr.write("**** You have turned off the drought stress")
            sys.stderr.write(", I assume you're debugging??!\n")

        # printing stuff
        self.pr = PrintOutput(self.params, self.state, self.fluxes,
                              self.control, self.files, self.print_opts)
        
        # build list of variables to print
        (self.print_state, self.print_fluxes) = self.pr.get_vars_to_print()
        
        # print model defaults
        if DUMP == True:
            self.pr.save_default_parameters()
            sys.exit(0)
        
        self.correct_rate_constants(output=False)

        # class instances
        self.cs = CarbonSoilFlows(self.control, self.params, self.state,
                                  self.fluxes, self.met_data)
        self.ns = NitrogenSoilFlows(self.control, self.params, self.state,
                                    self.fluxes, self.met_data)
        self.lf = Litter(self.control, self.params, self.state, self.fluxes)
        self.pg = PlantGrowth(self.control, self.params, self.state,
                              self.fluxes, self.met_data)
        
        if self.control.deciduous_model:
            self.pg.calc_carbon_allocation_fracs(0.0)
            self.pg.allocate_stored_c_and_n(init=True)
            self.P = Phenology(self.fluxes, self.state, self.control,
                               self.params.previous_ncd,
                               store_transfer_len=self.params.store_transfer_len)

        # calculate initial C:N ratios and zero annual flux sums
        self.day_end_calculations(0, INIT=True)
        self.state.pawater_root = self.params.wcapac_root
        self.state.pawater_tsoil = self.params.wcapac_topsoil
        self.spin_up = spin_up
        self.state.sla = self.params.slainit # Specific leaf area (m2/kg DW)
        self.state.lai = (self.params.slainit * const.M2_AS_HA /
                          const.KG_AS_TONNES / self.params.cfracts *
                          self.state.shoot)
Ejemplo n.º 6
0
class Gday(object):
    """ The G'DAY (Generic Decomposition And Yield) model.

    GDAY simulates C, N and water cycling between the plant and the soil. The
    model is structured into three plant pools (foliage, wood and fine roots),
    four litter pools (above/below metabolic and structural litter) and three
    soil organic matter (SOM) pools with varying turnover rates (active, slow
    and passive). An adapted implementation of the CENTURY model simulates soil
    carbon and nutrient dynamics. There is an additional simple soil water
    balance module which can be used to limit growth. Model pools can be thought
    of as buckets as they don't have dimensions.

    References:
    ----------
    * Comins, H. N. and McMurtrie, R. E. (1993) Ecological Applications, 3,
      666-681.
    * Medlyn, B. E. et al (2000) Canadian Journal of Forest Research, 30,
      873-888.

    """
    def __init__(self, fname=None, DUMP=False, spin_up=False):

        """ Set up model

        Read meterological forcing file and user config file and adjust the
        model parameters, control or initial state attributes that are used
        within the code.

        Parameters:
        ----------
        fname : string
            filename of model parameters, including path
        chk_cmd_line : logical
            parse the cmd line?
        DUMP : logical
            dump a the default parameters to a file

        Returns:
        -------
        Nothing
            Controlling class of the model, runs things.

        """
        self.day_output = [] # store daily outputs
        (self.control, self.params,
            self.state, self.files,
            self.fluxes, self.met_data,
            self.print_opts) = initialise_model_data(fname, DUMP=DUMP)
        
        if self.control.water_stress == False:
            sys.stderr.write("**** You have turned off the drought stress")
            sys.stderr.write(", I assume you're debugging??!\n")

        # printing stuff
        self.pr = PrintOutput(self.params, self.state, self.fluxes,
                              self.control, self.files, self.print_opts)
        
        # build list of variables to print
        (self.print_state, self.print_fluxes) = self.pr.get_vars_to_print()
        
        # print model defaults
        if DUMP == True:
            self.pr.save_default_parameters()
            sys.exit(0)
        
        self.correct_rate_constants(output=False)

        # class instances
        self.cs = CarbonSoilFlows(self.control, self.params, self.state,
                                  self.fluxes, self.met_data)
        self.ns = NitrogenSoilFlows(self.control, self.params, self.state,
                                    self.fluxes, self.met_data)
        self.lf = Litter(self.control, self.params, self.state, self.fluxes)
        self.pg = PlantGrowth(self.control, self.params, self.state,
                              self.fluxes, self.met_data)
        
        if self.control.deciduous_model:
            self.pg.calc_carbon_allocation_fracs(0.0)
            self.pg.allocate_stored_c_and_n(init=True)
            self.P = Phenology(self.fluxes, self.state, self.control,
                               self.params.previous_ncd,
                               store_transfer_len=self.params.store_transfer_len)

        # calculate initial C:N ratios and zero annual flux sums
        self.day_end_calculations(0, INIT=True)
        self.state.pawater_root = self.params.wcapac_root
        self.state.pawater_tsoil = self.params.wcapac_topsoil
        self.spin_up = spin_up
        self.state.sla = self.params.slainit # Specific leaf area (m2/kg DW)
        self.state.lai = (self.params.slainit * const.M2_AS_HA /
                          const.KG_AS_TONNES / self.params.cfracts *
                          self.state.shoot)
                          
    def spin_up_pools(self, tolerance=1E-03):
        """ Spin Up model plant, soil and litter pools.
        -> Examine sequences of 1000 years and check if C pools are changing
           or at steady state to 3 d.p.

        References:
        ----------
        Adapted from...
        * Murty, D and McMurtrie, R. E. (2000) Ecological Modelling, 134,
          185-205, specifically page 196.
        """
        prev_plantc = 9999.9
        prev_soilc = 9999.9
        
        while True:
            if (fabs(prev_plantc - self.state.plantc) < tolerance and
                fabs(prev_soilc - self.state.soilc) < tolerance):
                break
            else:            
                prev_plantc = self.state.plantc
                prev_soilc = self.state.soilc
                self.run_sim() # run the model...
                
                # Have we reached a steady state?
                sys.stderr.write("Spinup: Plant C - %f, Soil C - %f\n" % \
                                (self.state.plantc, self.state.soilc))
        self.print_output_file()
    
    def run_sim(self):
        """ Run model simulation! """
        project_day = 0
        
        # figure out the number of years for simulation and the number of
        # days in each year
        years = uniq(self.met_data["year"])
        days_in_year = [self.met_data["year"].count(yr) for yr in years]
        
        # =============== #
        #   YEAR LOOP     #
        # =============== #
        for i, yr in enumerate(years):
            self.day_output = [] # empty daily storage list for outputs
            daylen = calculate_daylength(days_in_year[i], self.params.latitude)
            if self.control.deciduous_model:
                self.P.calculate_phenology_flows(daylen, self.met_data,
                                            days_in_year[i], project_day)
                self.zero_stuff()
            # =============== #
            #   DAY LOOP      #
            # =============== #  
            for doy in xrange(days_in_year[i]):
                
                # litterfall rate: C and N fluxes
                (fdecay, rdecay) = self.lf.calculate_litter(doy)
                
                # co2 assimilation, N uptake and loss
                self.pg.calc_day_growth(project_day, fdecay, rdecay,
                                        daylen[doy], doy, 
                                        float(days_in_year[i]))
            
                # soil C & N model fluxes
                self.cs.calculate_csoil_flows(project_day)
                self.ns.calculate_nsoil_flows(project_day)
    
                # calculate C:N ratios and increment annual flux sums
                self.day_end_calculations(project_day, days_in_year[i])
                
                #print self.state.shoot, self.state.lai
                #print self.fluxes.gpp * 100, self.state.lai
                # =============== #
                #   END OF DAY    #
                # =============== #
                self.save_daily_outputs(yr, doy+1)
                project_day += 1
            # =============== #
            #   END OF YEAR   #
            # =============== #
            if self.control.deciduous_model:
                self.pg.allocate_stored_c_and_n(init=False)

            if self.control.print_options == "DAILY" and self.spin_up == False:
                self.print_output_file()
            
        # close output files
        if self.control.print_options == "END" and self.spin_up == False:
            self.print_output_file()
        self.pr.clean_up()

    def print_output_file(self):
        """ Either print the daily output file (at the end of the year) or
        print the final state + param file. """

        # print the daily output file, this is done once at the end of each yr
        if self.control.print_options == "DAILY":
            self.pr.write_daily_outputs_file(self.day_output)
        
        # print the final state
        elif self.control.print_options == "END":
            if not self.control.deciduous_model:
                # need to save initial SLA to current one!
                conv = const.M2_AS_HA * const.KG_AS_TONNES
                self.params.slainit = (self.state.lai / const.M2_AS_HA *
                                       const.KG_AS_TONNES *
                                       self.params.cfracts /self.state.shoot)

            self.correct_rate_constants(output=True)
            self.pr.save_state()

    def zero_stuff(self):
        self.state.shoot = 0.0
        self.state.shootn = 0.0
        self.state.shootnc = 0.0
        self.state.lai = 0.0
        self.state.cstore = 0.0
        self.state.nstore = 0.0
        self.state.anpp = 0.0
        
    def correct_rate_constants(self, output=False):
        """ adjust rate constants for the number of days in years """
        time_constants = ['rateuptake', 'rateloss', 'retransmob',
                          'fdecay', 'fdecaydry', 'rdecay', 'rdecaydry',
                          'bdecay', 'wdecay', 'kdec1', 'kdec2', 'kdec3',
                          'kdec4', 'kdec5', 'kdec6', 'kdec7', 'nuptakez']
        conv = const.NDAYS_IN_YR
        
        if output == False:
            for i in time_constants:
                setattr(self.params, i, getattr(self.params, i) / conv)
        else:
            for i in time_constants:
                setattr(self.params, i, getattr(self.params, i) * conv)

    def day_end_calculations(self, prjday, days_in_year=None, INIT=False):
        """Calculate derived values from state variables.

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

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

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

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

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

        if INIT == False:
            #Required so max leaf & root N:C can depend on Age
            self.state.age += 1.0 / days_in_year
               
    def save_daily_outputs(self, year, doy):
        """ Save the daily fluxes + state in a big list.

        This should be a more efficient way to write the daily output in a
        single step at the end of the year.

        Parameters:
        -----------
        project_day : integer
            simulation day
        """
        output = [year, doy]
        for var in self.print_state:
            output.append(getattr(self.state, var))
        for var in self.print_fluxes:
            output.append(getattr(self.fluxes, var))
        self.day_output.append(output)
Ejemplo n.º 7
0
class Gday(object):
    """ The G'DAY (Generic Decomposition And Yield) model.

    GDAY simulates C, N and water cycling between the plant and the soil. The
    model is structured into three plant pools (foliage, wood and fine roots),
    four litter pools (above/below metabolic and structural litter) and three
    soil organic matter (SOM) pools with varying turnover rates (active, slow
    and passive). An adapted implementation of the CENTURY model simulates soil
    carbon and nutrient dynamics. There is an additional simple soil water
    balance module which can be used to limit growth. Model pools can be though
    of as buckets as they don't have dimensions.

    References:
    ----------
    * Comins, H. N. and McMurtrie, R. E. (1993) Ecological Applications, 3,
      666-681.
    * Medlyn, B. E. et al (2000) Canadian Journal of Forest Research, 30,
      873-888.
    * And any of the other McMurtrie papers!
    """
    def __init__(self, fname=None, DUMP=False, spin_up=False, met_header=4):
        """ Set up model

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        self.dead = False  # johnny 5 is alive

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

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

        if self.control.water_stress == False:
            sys.stderr.write("**** You have turned off the drought stress")
            sys.stderr.write(", I assume you're debugging??!\n")

    def run_sim(self):
        """ Run model simulation! """
        # local variable
        years = self.years
        days_in_year = self.days_in_year

        if self.control.disturbance:
            # Figure out if any years have a disturbance
            self.db.initialise(years)

        # ===================== #
        #   Y E A R   L O O P   #
        # ===================== #
        project_day = 0
        for i, yr in enumerate(years):
            self.day_output = []  # empty daily storage list for outpu
            daylen = calculate_daylength(days_in_year[i], self.params.latitude)
            if self.control.deciduous_model:
                self.P.calculate_phenology_flows(daylen, self.met_data,
                                                 days_in_year[i], project_day)

                # Change window size to length of growing season
                self.pg.sma.window_size = self.P.growing_seas_len
                self.zero_stuff()

            # =================== #
            #   D A Y   L O O P   #
            # =================== #
            for doy in xrange(days_in_year[i]):

                # standard litter calculation
                # litterfall rate: C and N fluxe
                (fdecay, rdecay) = self.lf.calculate_litter(doy)

                # Fire Disturbance?
                if (self.control.disturbance != 0
                        and self.params.disturbance_doy == doy):
                    self.db.check_for_fire(yr, self.pg)
                # Hurricane?
                elif (self.control.hurricane == 1
                      and self.params.hurricane_yr == yr
                      and self.params.hurricane_doy == doy):
                    self.db.hurricane()

                # photosynthesis & growth
                fsoilT = self.cs.soil_temp_factor(project_day)
                self.pg.calc_day_growth(project_day, fdecay,
                                        rdecay, daylen[doy], doy,
                                        float(days_in_year[i]), i, fsoilT)

                # soil C & N calculation
                self.cs.calculate_csoil_flows(project_day, doy)
                self.ns.calculate_nsoil_flows(project_day, doy)

                if self.control.ncycle == False:
                    # Turn off all N calculations
                    self.reset_all_n_pools_and_fluxes()

                # calculate C:N ratios and increment annual flux sum
                self.day_end_calculations(days_in_year[i])

                # checking if we died during the timestep
                #   - added for desert simulation
                if (not self.control.deciduous_model
                        and self.control.disturbance == 0):
                    self.are_we_dead()

                #print self.state.plantc, self.state.soilc
                #print yr, doy, self.state.lai, self.fluxes.gpp*100

                #print self.fluxes.gpp*100, self.state.prev_sma
                # ======================= #
                #   E N D   O F   D A Y   #
                # ======================= #
                if not self.spin_up:
                    self.save_daily_outputs(yr, doy + 1)

                # check the daily water balance
                #self.cb.check_water_balance(project_day)

                project_day += 1

            # ========================= #
            #   E N D   O F   Y E A R   #
            # ========================= #

            # Allocate stored C&N for the following year
            if self.control.deciduous_model:
                # Using average alloc fracs across growing season instead
                #self.pg.calc_carbon_allocation_fracs(0.0) #comment this!!
                self.pg.calculate_average_alloc_fractions(
                    self.P.growing_seas_len)
                self.pg.allocate_stored_c_and_n(init=False)

                # reset the stress buffer at the end of the growing season
                self.pg.sma.reset_stream()

                #print self.fluxes.alleaf, self.fluxes.alroot, \
                #    (self.fluxes.albranch+self.fluxes.alstem)
                #print

            # GDAY died in the previous year, re-establish gday for the next yr
            #   - added for desert simulation
            if (self.dead and not self.control.deciduous_model
                    and self.control.disturbance == 0):
                self.re_establish_gday()

            if self.control.print_options == "DAILY" and not self.spin_up:
                self.print_output_file()

        # close output file
        if self.control.print_options == "END" and not self.spin_up:
            self.print_output_file()

        # only close printing file if not in spin up mode as we have ye
        # written the daily output...
        if self.spin_up:
            return (yr, doy + 1)
        else:
            # Need to pass the project day to calculate NROWS for .hdr file
            self.pr.clean_up(project_day)

    def are_we_dead(self):
        """ Simplistic scheme to allow GDAY to die and re-establish the
        following year """

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

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

            self.dead = True  # johnny 5 is dead
            print "dead"

    def re_establish_gday(self):
        """ grow from seed the following year following death. Concept is tha
        somewhere along the line GDAY saved enough C to be able to
        re-establish in a new year. C isnt explicitly accounted for, so thi
        C just appears. Perhaps this makes sense, i.e. wind/animals dropping
        seeds, alternatively we could force GDAY to save a tiny amount of C
        for reproduction. For the moment -> it just appears."""
        self.state.lai = 0.01

        # C/N = 25 of default pools.
        if self.control.alloc_model == "GRASSES":
            self.state.age = 0.0
            self.state.branch = 0.0
            self.state.branchn = 0.0
            self.state.cstore = 0.001
            self.state.nstore = 0.00004
            self.state.root = 0.001
            self.state.rootn = 0.00004
            self.state.sapwood = 0.0
            self.state.shoot = 0.001
            self.state.shootn = 0.00004
            self.state.stem = 0.0
            self.state.stemn = 0.0
            self.state.stemnimm = 0.0
            self.state.stemnmob = 0.0
            self.state.croot = 0.0
            self.state.crootn = 0.0
        else:
            self.state.branch = 0.001
            self.state.branchn = 0.00004
            self.state.croot = 0.001
            self.state.crootn = 0.00004
            self.state.sapwood = 0.001
            self.state.stem = 0.001
            self.state.stemn = 0.00004
            self.state.stemnimm = 0.00004
            self.state.stemnmob = 0.0
            self.state.sapwood = 0.001
        print "re-seeding"

    def spin_up_pools(self, tol=5E-03):
        """ Spin up model plant & soil pools to equilibrium.

        - Examine sequences of 50 years and check if C pools are changing
          by more than 0.005 units per 1000 yrs. Note this check is done in
          units of: kg m-2.

        References:
        ----------
        Adapted from...
        * Murty, D and McMurtrie, R. E. (2000) Ecological Modelling, 134,
          185-205, specifically page 196.
        """
        prev_plantc = 99999.9
        prev_soilc = 99999.9

        # check for convergences in units of kg/m2
        conv = const.TONNES_HA_2_KG_M2

        # If we are prescribing disturbance, first allow the forest to
        # establish
        if self.control.disturbance != 0:
            cntrl_flag = self.control.disturbance
            self.control.disturbance = 0
            # 200 years (50 yrs x 4 cycles)
            for spin_num in xrange(4):
                (yr, doy) = self.run_sim()  # run the model...
            self.control.disturbance = cntrl_flag

            while True:
                if fabs((prev_soilc * conv) - (self.state.soilc * conv)) < tol:
                    break
                else:
                    prev_soilc = self.state.soilc

                    # 1000 years (50 yrs x 20 cycles)
                    for spin_num in xrange(20):
                        (yr, doy) = self.run_sim()  # run the model...

                    # Have we reached a steady state?
                    msg = "Spinup: Soil C - %f\n" % (self.state.soilc)
                    sys.stderr.write(msg)
        else:

            while True:
                if (fabs((prev_plantc * conv) -
                         (self.state.plantc * conv)) < tol
                        and fabs((prev_soilc * conv) -
                                 (self.state.soilc * conv)) < tol):
                    break
                else:
                    prev_plantc = self.state.plantc
                    prev_soilc = self.state.soilc

                    # 1000 years (50 yrs x 20 cycles)
                    for spin_num in xrange(20):
                        (yr, doy) = self.run_sim()  # run the model...

                    # Have we reached a steady state?
                    msg = "Spinup: Plant C - %f, Soil C - %f\n" % \
                          (self.state.plantc, self.state.soilc)
                    sys.stderr.write(msg)

        self.save_daily_outputs(yr, doy)
        self.pr.clean_up()
        self.print_output_file()

    def print_output_file(self):
        """ Either print the daily output file (at the end of the year) or
        print the final state + param file. """

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

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

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

            self.correct_rate_constants(output=True)
            self.pr.save_state()

    def zero_stuff(self):
        self.state.shoot = 0.0
        self.state.shootn = 0.0
        self.state.shootnc = 0.0
        self.state.lai = 0.0
        self.state.max_lai = 0.0
        self.state.max_shoot = 0.0
        self.state.cstore = 0.0
        self.state.nstore = 0.0
        self.state.anpp = 0.0
        self.state.grw_seas_stress = 1.0

        if self.control.deciduous_model:
            self.state.avg_alleaf = 0.0
            self.state.avg_alroot = 0.0
            self.state.avg_alcroot = 0.0
            self.state.avg_albranch = 0.0
            self.state.avg_alstem = 0.0

    def correct_rate_constants(self, output=False):
        """ adjust rate constants for the number of days in years """
        time_constants = [
            'rateuptake', 'rateloss', 'retransmob', 'fdecay', 'fdecaydry',
            'crdecay', 'rdecay', 'rdecaydry', 'bdecay', 'wdecay',
            'sapturnover', 'kdec1', 'kdec2', 'kdec3', 'kdec4', 'kdec5',
            'kdec6', 'kdec7', 'nuptakez', 'nmax', 'adapt'
        ]
        conv = const.NDAYS_IN_YR

        if output == False:
            for i in time_constants:
                setattr(self.params, i, getattr(self.params, i) / conv)
        else:
            for i in time_constants:
                setattr(self.params, i, getattr(self.params, i) * conv)

    def day_end_calculations(self, days_in_year=None, INIT=False):
        """Calculate derived values from state variables.

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

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

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

        # Explicitly set the shoot N:C
        if self.control.ncycle == False:
            self.state.shootnc = self.params.prescribed_leaf_NC

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

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

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

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

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

    def save_daily_outputs(self, year, doy):
        """ Save the daily fluxes + state in a big list.

        This should be a more efficient way to write the daily output in a
        single step at the end of the year.

        Parameters:
        -----------
        project_day : integer
            simulation day
        """
        output = [year, doy]
        output.extend(getattr(self.state, var) for var in self.print_state)
        output.extend(getattr(self.fluxes, var) for var in self.print_fluxes)
        self.day_output.append(output)

    def reset_all_n_pools_and_fluxes(self):
        """ If the N-Cycle is turned off the way I am implementing this is to
        do all the calculations and then reset everything at the end. This is a
        waste of resources but saves on multiple IF statements.
        """
        # State
        self.state.shootn = 0.0
        self.state.rootn = 0.0
        self.state.crootn = 0.0
        self.state.branchn = 0.0
        self.state.stemnimm = 0.0
        self.state.stemnmob = 0.0
        self.state.structsurfn = 0.0
        self.state.metabsurfn = 0.0
        self.state.structsoiln = 0.0
        self.state.metabsoiln = 0.0
        self.state.activesoiln = 0.0
        self.state.slowsoiln = 0.0
        self.state.passivesoiln = 0.0
        self.state.inorgn = 0.0
        self.state.stemn = 0.0
        self.state.stemnimm = 0.0
        self.state.stemnmob = 0.0
        self.state.nstore = 0.0

        # Fluxes
        self.fluxes.nuptake = 0.0
        self.fluxes.nloss = 0.0
        self.fluxes.npassive = 0.0
        self.fluxes.ngross = 0.0
        self.fluxes.nimmob = 0.0
        self.fluxes.nlittrelease = 0.0
        self.fluxes.nmineralisation = 0.0
        self.fluxes.npleaf = 0.0
        self.fluxes.nproot = 0.0
        self.fluxes.npcroot = 0.0
        self.fluxes.npbranch = 0.0
        self.fluxes.npstemimm = 0.0
        self.fluxes.npstemmob = 0.0
        self.fluxes.deadleafn = 0.0
        self.fluxes.deadrootn = 0.0
        self.fluxes.deadcrootn = 0.0
        self.fluxes.deadbranchn = 0.0
        self.fluxes.deadstemn = 0.0
        self.fluxes.neaten = 0.0
        self.fluxes.nurine = 0.0
        self.fluxes.leafretransn = 0.0
        self.fluxes.n_surf_struct_litter = 0.0
        self.fluxes.n_surf_metab_litter = 0.0
        self.fluxes.n_soil_struct_litter = 0.0
        self.fluxes.n_soil_metab_litter = 0.0
        self.fluxes.n_surf_struct_to_slow = 0.0
        self.fluxes.n_soil_struct_to_slow = 0.0
        self.fluxes.n_surf_struct_to_active = 0.0
        self.fluxes.n_soil_struct_to_active = 0.0
        self.fluxes.n_surf_metab_to_active = 0.0
        self.fluxes.n_surf_metab_to_active = 0.0
        self.fluxes.n_active_to_slow = 0.0
        self.fluxes.n_active_to_passive = 0.0
        self.fluxes.n_slow_to_active = 0.0
        self.fluxes.n_slow_to_passive = 0.0
        self.fluxes.n_passive_to_active = 0.0