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 __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")
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)
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
def run_sim(self): """ Run model simulation! """ # class instances cf = CarbonFlows(self.control, self.params, self.state, self.fluxes, self.met_data) nf = NitrogenFlows(self.control, self.params, self.state, self.fluxes) lf = LitterProduction(self.control, self.params, self.state, self.fluxes) pg = PlantGrowth(self.control, self.params, self.state, self.fluxes, self.met_data) cpl = CarbonPools(self.control, self.params, self.state, self.fluxes) npl = NitrogenPools(self.control, self.params, self.state, self.fluxes, self.met_data) ################## self.control.deciduous_model = 1 self.control.numyears_sim = 12 self.state.nc_fy = 0.06 self.state.npp_store = 5.2154 ################## if self.control.deciduous_model: total = self.initialise_deciduous_model() # In the first year we don't have last years data, so I have # precalculated the average of all the november-jan chilling values last_yrs_accumulated_ncd = 17.0 P = Phenology(self.fluxes, self.state, last_yrs_accumulated_ncd) # calculate initial C:N ratios and zero annual flux sums self.derive(1, self.date, INIT=True) self.state.pawater_root = self.params.wcapac_root self.state.pawater_tsoil = self.state.pawater_tsoil project_day = 0 for yr in xrange(self.control.startyear, self.control.startyear+self.control.numyears_sim-1): yr_days = calc_days_in_year(self.date.year) daylen = calculate_daylength(self.date, yr_days, self.params.latitude) if self.control.deciduous_model: self.zero_annual_sums() P.calculate_phenology_flows(daylen, self.date, self.met_data, yr_days) for d in xrange(yr_days): # litterfall rate: C and N fluxes (fdecay, rdecay) = lf.calculate_litter_flows(d) # co2 assimilation, N uptake and loss pg.grow(project_day, self.date, fdecay, rdecay, daylen[d], doy=d) # soil model fluxes cf.calculate_cflows(project_day) nf.calculate_nflows() self.fluxes.nep = self.calculate_nep() # soil model - update pools (cact, cslo, cpas) = cpl.calculate_cpools() npl.calculate_npools(cact, cslo, cpas, project_day) # calculate C:N ratios and increment annual flux sums self.derive(project_day, self.date) print self.fluxes.gpp * 100, self.state.lai if self.control.print_options == 0: self.pr.save_daily_output(project_day + 1, self.date) self.increment_date(yr_days) project_day += 1 sys.exit() # =============== # # END OF THE YEAR # # =============== # if self.control.deciduous_model: self.allocate_stored_c_and_n(total) if self.control.print_options == 1: # need to save initial SLA to current one! 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() # house cleaning, close ouput files self.pr.tidy_up()
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)
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)
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