def ligin_nratio(self): """ Estimate Lignin/N ratio, as this dictates the how plant litter is seperated between metabolic and structural pools. Returns: -------- lnleaf : float lignin:N ratio of leaf lnroot : float lignin:N ratio of fine root """ nceleaf = self.ratio_of_litternc_to_live_leafnc() nceroot = self.ratio_of_litternc_to_live_rootnc() if float_eq(nceleaf, 0.0): # This is effectively a hack that results in fluxes being turned # off into the metabolic pool. It seems that the century code # would effectively result in no metabolic fluxes. Might be worth # looking at the latest century code to see how they get around # this? lnleaf = 1E20 else: lnleaf = self.params.ligshoot / self.params.cfracts / nceleaf if float_eq(nceroot, 0.0): # This is effectively a hack that results in fluxes being turned # off into the metabolic pool. It seems that the century code # would effectively result in no metabolic fluxes. Might be worth # looking at the latest century code to see how they get around # this? lnroot = 1E20 else: lnroot = self.params.ligroot / self.params.cfracts / nceroot return (lnleaf, lnroot)
def ligin_nratio(self): """ Estimate Lignin/N ratio, as this dictates the how plant litter is seperated between metabolic and structural pools. Returns: -------- lnleaf : float lignin:N ratio of leaf lnroot : float lignin:N ratio of fine root """ nc_leaf_litter = self.ratio_of_litternc_to_live_leafnc() nc_root_litter = self.ratio_of_litternc_to_live_rootnc() if float_eq(nc_leaf_litter, 0.0): # catch divide by zero if we have no leaves lnleaf = 0.0 else: lnleaf = self.params.ligshoot / self.params.cfracts / nc_leaf_litter #print self.params.ligshoot if float_eq(nc_root_litter, 0.0): # catch divide by zero if we have no roots lnroot = 0.0 else: lnroot = self.params.ligroot / self.params.cfracts / nc_root_litter return (lnleaf, lnroot)
def partition_plant_litter_n(self, nsurf, nsoil): """ Partition litter N from the plant (surface) and roots into metabolic and structural pools Parameters: ----------- nsurf : float N input from surface pool nsoil : float N input from soil pool """ # constant structural input n:c as per century if not self.control.strfloat: # dead plant litter -> structural pool # n flux -> surface structural pool self.fluxes.n_surf_struct_litter = (self.fluxes.surf_struct_litter / self.params.structcn) # n flux -> soil structural pool self.fluxes.n_soil_struct_litter = (self.fluxes.soil_struct_litter / self.params.structcn) # if not enough N for structural, all available N goes to structural if float_gt( self.fluxes.n_surf_struct_litter, nsurf): self.fluxes.n_surf_struct_litter = nsurf if float_gt(self.fluxes.n_soil_struct_litter, nsoil): self.fluxes.n_soil_struct_litter = nsoil # structural input n:c is a fraction of metabolic else: c_surf_struct_litter = (self.fluxes.surf_struct_litter * self.params.structrat + self.fluxes.surf_metab_litter) if float_eq(c_surf_struct_litter, 0.0): self.fluxes.n_surf_struct_litter = 0.0 else: self.fluxes.n_surf_struct_litter = (nsurf * self.fluxes.surf_struct_litter * self.params.structrat / c_surf_struct_litter) c_soil_struct_litter = (self.fluxes.soil_struct_litter * self.params.structrat + self.fluxes.soil_metab_litter) if float_eq(c_soil_struct_litter, 0.0): self.fluxes.n_soil_struct_litter = 0. else: self.fluxes.n_soil_struct_litter = (nsurf * self.fluxes.soil_struct_litter * self.params.structrat / c_soil_struct_litter) # remaining N goes to metabolic pools self.fluxes.n_surf_metab_litter = (nsurf - self.fluxes.n_surf_struct_litter) self.fluxes.n_soil_metab_litter = (nsoil - self.fluxes.n_soil_struct_litter)
def day_end_calculations(self, days_in_year=None, INIT=False): """Calculate derived values from state variables. Parameters: ----------- day : integer day of simulation INIT : logical logical defining whether it is the first day of the simulation """ # update N:C of plant pool if float_eq(self.state.shoot, 0.0): self.state.shootnc = 0.0 else: self.state.shootnc = self.state.shootn / self.state.shoot #print self.state.rootn , self.state.roo if float_eq(self.state.root, 0.0): self.state.rootnc = 0.0 else: self.state.rootnc = max(0.0, self.state.rootn / self.state.root) # total plant, soil & litter nitrogen self.state.soiln = (self.state.inorgn + self.state.activesoiln + self.state.slowsoiln + self.state.passivesoiln) self.state.litternag = self.state.structsurfn + self.state.metabsurfn self.state.litternbg = self.state.structsoiln + self.state.metabsoiln self.state.littern = self.state.litternag + self.state.litternbg self.state.plantn = (self.state.shootn + self.state.rootn + self.state.crootn + self.state.branchn + self.state.stemn) self.state.totaln = (self.state.plantn + self.state.littern + self.state.soiln) # total plant, soil, litter and system carbon self.state.soilc = (self.state.activesoil + self.state.slowsoil + self.state.passivesoil) self.state.littercag = self.state.structsurf + self.state.metabsurf self.state.littercbg = self.state.structsoil + self.state.metabsoil self.state.litterc = self.state.littercag + self.state.littercbg self.state.plantc = (self.state.root + self.state.croot + self.state.shoot + self.state.stem + self.state.branch) self.state.totalc = (self.state.soilc + self.state.litterc + self.state.plantc) #self.state.plantnc = self.state.plantn / self.state.plantc #print self.state.plantnc # optional constant passive pool if self.control.passiveconst == True: self.state.passivesoil = self.params.passivesoilz self.state.passivesoiln = self.params.passivesoilnz if INIT == False: #Required so max leaf & root N:C can depend on Age self.state.age += 1.0 / days_in_year
def hurricane(self): """ Specifically for the florida simulations - reduce LAI by 40% """ # Reduce LAI by 40% self.state.lai -= (self.state.lai * 0.4) # adjust C in the foliage orig_shoot_c = self.state.shoot self.state.shoot = (self.state.lai / (self.params.sla * const.M2_AS_HA / const.KG_AS_TONNES / self.params.cfracts)) lost_c = orig_shoot_c - self.state.shoot lost_n = self.state.shootnc * lost_c self.state.shootn -= lost_n # Drop straight to floor, no retranslocation # C -> structural if float_eq(lost_c, 0.0): nc_leaf_litter = 0.0 else: nc_leaf_litter = lost_n / lost_c if float_eq(nc_leaf_litter, 0.0): # catch divide by zero if we have no leaves lnleaf = 0.0 else: lnleaf = self.params.ligshoot / self.params.cfracts / nc_leaf_litter fmleaf = max(0.0, 0.85 - (0.018 * lnleaf)) self.fluxes.surf_struct_litter += lost_c * (1.0 - fmleaf) # C -> metabolic self.fluxes.surf_metab_litter += lost_c * fmleaf # N -> structural if float_eq(self.fluxes.surf_struct_litter, 0.0): self.fluxes.n_surf_struct_litter += 0.0 else: self.fluxes.n_surf_struct_litter += (lost_n * self.fluxes.surf_struct_litter * self.params.structrat / self.fluxes.surf_struct_litter) # N -> metabolic pools self.fluxes.n_surf_metab_litter += (lost_n - self.fluxes.n_surf_struct_litter) #self.state.structsurf += lost_c #self.state.structsurfn += lost_n
def carbon_allocation(self, nitfac, doy, days_in_yr): """ C distribution - allocate available C through system Parameters: ----------- nitfac : float leaf N:C as a fraction of 'Ncmaxfyoung' (max 1.0) """ if self.control.deciduous_model: days_left = self.state.growing_days[doy] self.fluxes.cpleaf = self.fluxes.lrate * days_left self.fluxes.cpbranch = self.fluxes.brate * days_left self.fluxes.cpstem = self.fluxes.wrate * days_left self.fluxes.cproot = self.state.c_to_alloc_root * 1.0 / days_in_yr else: self.fluxes.cpleaf = self.fluxes.npp * self.state.alleaf self.fluxes.cproot = self.fluxes.npp * self.state.alroot self.fluxes.cpbranch = self.fluxes.npp * self.state.albranch self.fluxes.cpstem = self.fluxes.npp * self.state.alstem #print self.fluxes.cpleaf, self.fluxes.npp, self.state.alleaf # evaluate SLA of new foliage accounting for variation in SLA # with tree and leaf age (Sands and Landsberg, 2002). Assume # SLA of new foliage is linearly related to leaf N:C ratio # via nitfac. Based on date from two E.globulus stands in SW Aus, see # Corbeels et al (2005) Ecological Modelling, 187, 449-474. # (m2 onesided/kg DW) self.state.sla = (self.params.slazero + nitfac * (self.params.slamax - self.params.slazero)) if self.control.deciduous_model: if float_eq(self.state.shoot, 0.0): self.state.lai = 0.0 elif self.state.leaf_out_days[doy] > 0.0: self.state.lai += (self.fluxes.cpleaf * (self.state.sla * const.M2_AS_HA / (const.KG_AS_TONNES * self.params.cfracts)) - (self.fluxes.deadleaves + self.fluxes.ceaten) * self.state.lai / self.state.shoot) else: self.state.lai = 0.0 else: # update leaf area [m2 m-2] if float_eq(self.state.shoot, 0.0): self.state.lai = 0.0 else: self.state.lai += (self.fluxes.cpleaf * (self.state.sla * const.M2_AS_HA / (const.KG_AS_TONNES * self.params.cfracts)) - (self.fluxes.deadleaves + self.fluxes.ceaten) * self.state.lai / self.state.shoot)
def carbon_allocation(self, nitfac, doy, days_in_yr): """ C distribution - allocate available C through system Parameters: ----------- nitfac : float leaf N:C as a fraction of 'Ncmaxfyoung' (max 1.0) """ if self.control.deciduous_model: days_left = self.state.growing_days[doy] self.fluxes.cpleaf = self.fluxes.lrate * days_left self.fluxes.cpbranch = self.fluxes.brate * days_left self.fluxes.cpstem = self.fluxes.wrate * days_left self.fluxes.cproot = self.state.c_to_alloc_root * 1.0 / days_in_yr else: self.fluxes.cpleaf = self.fluxes.npp * self.state.alleaf self.fluxes.cproot = self.fluxes.npp * self.state.alroot self.fluxes.cpbranch = self.fluxes.npp * self.state.albranch self.fluxes.cpstem = self.fluxes.npp * self.state.alstem #print self.fluxes.cpleaf, self.fluxes.npp, self.state.alleaf # evaluate SLA of new foliage accounting for variation in SLA # with tree and leaf age (Sands and Landsberg, 2002). Assume # SLA of new foliage is linearly related to leaf N:C ratio # via nitfac. Based on date from two E.globulus stands in SW Aus, see # Corbeels et al (2005) Ecological Modelling, 187, 449-474. # (m2 onesided/kg DW) self.state.sla = (self.params.slazero + nitfac * (self.params.slamax - self.params.slazero)) if self.control.deciduous_model: if float_eq(self.state.shoot, 0.0): self.state.lai = 0.0 elif self.state.leaf_out_days[doy] > 0.0: self.state.lai += ( self.fluxes.cpleaf * (self.state.sla * const.M2_AS_HA / (const.KG_AS_TONNES * self.params.cfracts)) - (self.fluxes.deadleaves + self.fluxes.ceaten) * self.state.lai / self.state.shoot) else: self.state.lai = 0.0 else: # update leaf area [m2 m-2] if float_eq(self.state.shoot, 0.0): self.state.lai = 0.0 else: self.state.lai += ( self.fluxes.cpleaf * (self.state.sla * const.M2_AS_HA / (const.KG_AS_TONNES * self.params.cfracts)) - (self.fluxes.deadleaves + self.fluxes.ceaten) * self.state.lai / self.state.shoot)
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 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 print_output_file(self): """ Either print the daily output file (at the end of the year) or print the final state + param file. """ # print the daily output file, this is done once at the end of each yr if self.control.print_options == "DAILY": if self.control.output_ascii: self.pr.write_daily_outputs_file(self.day_output) else: self.pr.write_daily_outputs_file_to_binary(self.day_output) # print the final state elif self.control.print_options == "END": if not self.control.deciduous_model: # This shouldn't do anything, but I will leave it here for # potential applications. SLA has a N dependancy, though I # always turn this off...because of this we require this step. # However this won't work for tress if all the leaves have # gone! Need to stop that happening! So I guess the sensible # thing would be to do nothing. if float_eq(self.state.shoot, 0.0): pass # #self.params.slainit = 0.01 else: # need to save initial SLA to current one! self.params.sla = (self.state.lai / const.M2_AS_HA * const.KG_AS_TONNES * self.params.cfracts / self.state.shoot) self.correct_rate_constants(output=True) self.pr.save_state()
def model_zero(self, day): """ References: ----------- * Kirschbaum et al 1994 pc & e Parameters: ----------- day : integer simulation day Returns: -------- npp : float net primary productivity """ (sw_rad, ca, temp) = self.get_met_data(day) if float_eq(sw_rad, 0.0): sw_rad = 0.000001 gpp_max = ((self.params.cfracts * sw_rad / const.M2_AS_HA * self.params.epsilon * const.G_AS_TONNES) * temp_dep(temp) / temp_dep(14.0)) lue = (1.21) * self.state.shootnc / (0.0076 + self.state.shootnc) return lue * gpp_max * self.state.light_interception
def adjust_residence_time_of_slow_pool(self): """ Priming simulations the residence time of the slow pool is flexible, as the flux out of the active pool (factive) increases the residence time of the slow pool decreases. """ # total flux out of the factive pool self.fluxes.factive = (self.fluxes.active_to_slow + self.fluxes.active_to_passive + self.fluxes.co2_to_air[4]) if float_eq(self.fluxes.factive, 0.0): # Need to correct units of rate constant residence_time_slow_pool = (1.0 / (self.params.kdec6 * const.NDAYS_IN_YR)) else: residence_time_slow_pool = (1.0 / self.params.prime_y * (self.fluxes.factive / (self.fluxes.factive + self.params.prime_z))) # GDAY uses decay rates rather than residence times... self.params.kdec6 = 1.0 / residence_time_slow_pool # rate constant needs to be per day inside GDAY self.params.kdec6 /= const.NDAYS_IN_YR # Save for outputting purposes only self.fluxes.rtslow = residence_time_slow_pool
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 calc_root_exudation_uptake_of_C(self): """ The amount of C which enters the active pool varies according to the CUE of SOM in response to root exudation (REXCUE). REXCUE determines the fraction of REXC that enters the active pool as C. The remaining flux is respired. REXCUE determines which fraction of REXC enters the active pool as C (delta_Cact). The remaining fraction of REXC is respired as CO2. """ active_CN = self.state.activesoil / self.state.activesoiln if self.params.root_exu_CUE == -1.0: # flexible CUE # flexible cue # The constraint of 0.3<=REXCUE<=0.6 is based on observations of the # physical limits of microbes if float_eq(self.fluxes.root_exc, 0.0): rex_NC = 0.0 else: rex_NC = self.fluxes.root_exn / self.fluxes.root_exc self.fluxes.rexc_cue = max(0.3, min(0.6, rex_NC * active_CN)) else: self.fluxes.rexc_cue = self.params.root_exu_CUE C_to_active_pool = self.fluxes.root_exc * self.fluxes.rexc_cue self.state.activesoil += C_to_active_pool # update respiration fluxes. self.fluxes.co2_released_exud = (self.fluxes.root_exc * (1.0 - self.fluxes.rexc_cue)) self.fluxes.hetero_resp += self.fluxes.co2_released_exud
def hurricane(self): """ Specifically for the florida simulations - reduce LAI by 40% """ # Reduce LAI by 40% self.state.lai -= (self.state.lai * 0.4) # adjust C in the foliage orig_shoot_c = self.state.shoot self.state.shoot = (self.state.lai / (self.params.sla * const.M2_AS_HA / const.KG_AS_TONNES / self.params.cfracts)) lost_c = orig_shoot_c - self.state.shoot lost_n = self.state.shootnc * lost_c self.state.shootn -= lost_n # Drop straight to floor, no retranslocation # C -> structural if float_eq(lost_c, 0.0): nc_leaf_litter = 0.0 else: nc_leaf_litter = lost_n / lost_c if float_eq(nc_leaf_litter, 0.0): # catch divide by zero if we have no leaves lnleaf = 0.0 else: lnleaf = self.params.ligshoot / self.params.cfracts / nc_leaf_litter fmleaf = max(0.0, 0.85 - (0.018 * lnleaf)) self.fluxes.surf_struct_litter += lost_c * (1.0 - fmleaf) # C -> metabolic self.fluxes.surf_metab_litter += lost_c * fmleaf # N -> structural if float_eq(self.fluxes.surf_struct_litter, 0.0): self.fluxes.n_surf_struct_litter += 0.0 else: self.fluxes.n_surf_struct_litter += ( lost_n * self.fluxes.surf_struct_litter * self.params.structrat / self.fluxes.surf_struct_litter) # N -> metabolic pools self.fluxes.n_surf_metab_litter += (lost_n - self.fluxes.n_surf_struct_litter)
def inputs_from_structrual_pool(self, nsurf, nsoil): """structural pool input fluxes Parameters: ----------- nsurf : float N input from surface pool nsoil : float N input from soil pool """ # constant structural input n:c as per century if not self.control.strfloat: # dead plant -> structural # surface self.fluxes.nresid[0] = self.fluxes.cresid[0] / self.params.structcn # soil self.fluxes.nresid[1] = self.fluxes.cresid[1] / self.params.structcn # if not enough N for structural, all available N goes to structural if float_gt(self.fluxes.nresid[0], nsurf): self.fluxes.nresid[0] = nsurf if float_gt(self.fluxes.nresid[1], nsoil): self.fluxes.nresid[1] = nsoil else: # structural input n:c is a fraction of metabolic cwgtsu = (self.fluxes.cresid[0] * self.params.structrat + self.fluxes.cresid[2]) if float_eq(cwgtsu, 0.0): self.fluxes.nresid[0] = 0.0 else: self.fluxes.nresid[0] = (nsurf * self.fluxes.cresid[0] * self.params.structrat / cwgtsu) cwgtsl = (self.fluxes.cresid[1] * self.params.structrat + self.fluxes.cresid[3]) if float_eq(cwgtsl, 0.0): self.fluxes.nresid[1] = 0. else: self.fluxes.nresid[1] = (nsurf * self.fluxes.cresid[1] * self.params.structrat / cwgtsl)
def model_one(self, day): """ old g'day n:c and temperature factors References: ----------- * mcmurtrie et al 1992 aust j bot Parameters: ----------- day : integer simulation day Returns: -------- npp : float net primary productivity """ (sw_rad, ca, temp) = self.get_met_data(day) if float_eq(sw_rad, 0.0): sw_rad = 0.000001 rco2 = 1.632 * (ca - 60.9) / (ca + 121.8) gpp_max = ((self.params.cfracts * sw_rad / const.M2_AS_HA * self.params.epsilon * const.G_AS_TONNES) * rco2) # leaf n:c effect on photosynthetic efficiency (theta = 1). if float_gt(self.state.shootnc, self.params.n_crit): lue = 1.0 else: rat = self.state.shootnc / self.params.n_crit if float_eq(self.params.ncpower, 1.0): lue = rat else: lue = pow(rat, self.params.ncpower) return lue * gpp_max * self.state.light_interception
def calc_wue(self, vpd, ca, amb_co2): """water use efficiency Not sure of units conversions here, have to ask BM Parameters: ----------- vpd : float average daily vpd [kPa] ca : float atmospheric co2, depending on flag set in param file this will be ambient or elevated. [umol mol-1] """ if self.control.wue_model == 0: # Gday original implementation # (gC / kg H20) if float_gt(vpd, 0.0): # WUE Power law dependence on co2, Pepper et al 2005. co2_ratio = (ca / amb_co2) co2_adjustment = co2_ratio**self.params.co2_effect_on_wue # wue inversely proportional to daily mean vpd self.fluxes.wue = self.params.wue0 * co2_adjustment / vpd else: self.fluxes.wue = 0.0 elif self.control.wue_model == 1 and self.control.assim_model == 7: conv = const.MOL_C_TO_GRAMS_C / const.MOL_WATER_TO_GRAMS_WATER self.fluxes.wue = (conv * 1000.0 * (ca * const.UMOL_TO_MOL * (1.0 - self.fluxes.cica_avg) / (1.6 * vpd / 101.0))) #if self.fluxes.wue > 20.0: self.fluxes.wue = 20.0 # FIX THIS!!! # what is this? ask BM elif self.control.wue_model == 2: self.fluxes.wue = (self.params.wue0 * 0.27273 / vpd * ca / amb_co2) elif self.control.wue_model == 3 : if float_eq(self.fluxes.transpiration, 0.0): self.fluxes.wue = 0.0 else: self.fluxes.wue = (self.fluxes.gpp_gCm2 / self.fluxes.transpiration) else: raise AttributeError('Unknown WUE calculation option')
def ratio_of_litternc_to_live_leafnc(self): """ratio of litter N:C to live leaf N:C Returns: -------- nc_leaf_litter : float N:C ratio of litter to foliage """ if self.control.use_eff_nc: nc_leaf_litter = self.params.liteffnc * (1.0 - self.params.fretrans) else: if float_eq(self.fluxes.deadleaves, 0.0): nc_leaf_litter = 0.0 else: nc_leaf_litter = self.fluxes.deadleafn / self.fluxes.deadleaves return nc_leaf_litter
def ratio_of_litternc_to_live_rootnc(self): """ratio of litter N:C to live root N:C Returns: -------- nc_root_litter : float N:C ratio of litter to live root """ if self.control.use_eff_nc: nc_root_litter = (self.params.liteffnc * self.params.ncrfac * (1.0 - self.params.rretrans)) else: if float_eq(self.fluxes.deadroots, 0.0): nc_root_litter = 0.0 else: nc_root_litter = self.fluxes.deadrootn / self.fluxes.deadroots return nc_root_litter
def model_three(self, day): """ Parameters: ----------- day : integer simulation day Returns: -------- npp : float net primary productivity """ (sw_rad, ca, temp) = self.get_met_data(day) if float_eq(sw_rad, 0.0): sw_rad = 0.000001 rco2 = 1.82 * (ca - 50.0) / (ca + 197.0) lue = (1.45 * self.state.ncontent) / (self.state.ncontent + 2.21) * rco2 gpp_max = sw_rad / const.M2_AS_HA * const.G_AS_TONNES return lue * gpp_max * self.state.light_interception
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 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 calculate_photosynthesis(self, day, daylen): """ Photosynthesis is calculated assuming GPP is proportional to APAR, a commonly assumed reln (e.g. Potter 1993, Myneni 2002). The slope of relationship btw GPP and APAR, i.e. LUE is modelled using the photosynthesis eqns from Sands. Assumptions: ------------ (1) photosynthetic light response is a non-rectangular hyperbolic func of photon-flux density with a light-saturatred photosynthetic rate (Amax), quantum yield (alpha) and curvature (theta). (2) the canopy is horizontally uniform. (3) PAR distribution within the canopy obeys Beer's law. (4) light-saturated photosynthetic rate declines with canopy depth in proportion to decline in PAR (5) alpha + theta do not vary within the canopy (6) dirunal variation of PAR is sinusoidal. (7) The model makes no assumption about N within the canopy, however this version assumes N declines exponentially through the cnaopy. (8) Leaf temperature is the same as the air temperature. Parameters: ---------- day : int project day. daylen : float length of day in hours. Returns: ------- Nothing Method calculates GPP, NPP and Ra. """ # local var for tidyness (Tk_am, Tk_pm, par, vpd_am, vpd_pm, ca) = self.get_met_data(day) # calculate mate params & account for temperature dependencies N0 = self.calculate_top_of_canopy_n() gamma_star_am = self.calculate_co2_compensation_point(Tk_am) gamma_star_pm = self.calculate_co2_compensation_point(Tk_pm) Km_am = self.calculate_michaelis_menten_parameter(Tk_am) Km_pm = self.calculate_michaelis_menten_parameter(Tk_pm) (jmax_am, vcmax_am) = self.calculate_jmax_and_vcmax(Tk_am, N0) (jmax_pm, vcmax_pm) = self.calculate_jmax_and_vcmax(Tk_pm, N0) ci_am = self.calculate_ci(vpd_am, ca) ci_pm = self.calculate_ci(vpd_pm, ca) # quantum efficiency calculated for C3 plants alpha_am = self.calculate_quantum_efficiency(ci_am, gamma_star_am) alpha_pm = self.calculate_quantum_efficiency(ci_pm, gamma_star_pm) # Reducing assimilation if we encounter frost. Frost is assumed to # impact on the maximum photosynthetic capacity and alpha_j # So there is only an indirect effect on LAI, this could be changed... if self.control.frost: Tmax = self.met_data['tmax'][day] Tmin = self.met_data['tmin'][day] Thard = self.calc_frost_hardiness(daylen, Tmin, Tmax) (total_alpha_limf, total_amax_limf) = self.calc_frost_impact_factors( Thard, Tmin, Tmax) alpha_am *= total_alpha_limf alpha_pm *= total_alpha_limf # Rubisco carboxylation limited rate of photosynthesis ac_am = self.assim(ci_am, gamma_star_am, a1=vcmax_am, a2=Km_am) ac_pm = self.assim(ci_pm, gamma_star_pm, a1=vcmax_pm, a2=Km_pm) # Light-limited rate of photosynthesis allowed by RuBP regeneration aj_am = self.assim(ci_am, gamma_star_am, a1=jmax_am / 4.0, a2=2.0 * gamma_star_am) aj_pm = self.assim(ci_pm, gamma_star_pm, a1=jmax_pm / 4.0, a2=2.0 * gamma_star_pm) # light-saturated photosynthesis rate at the top of the canopy (gross) asat_am = min(aj_am, ac_am) asat_pm = min(aj_pm, ac_pm) if self.control.frost: asat_am *= total_amax_limf asat_pm *= total_amax_limf # LUE (umol C umol-1 PAR) lue_am = self.epsilon(asat_am, par, daylen, alpha_am) lue_pm = self.epsilon(asat_pm, par, daylen, alpha_pm) # use average to simulate canopy photosynthesis lue_avg = (lue_am + lue_pm) / 2.0 if float_eq(self.state.lai, 0.0): self.fluxes.apar = 0.0 else: # absorbed photosynthetically active radiation (umol m-2 s-1) self.fluxes.apar = par * self.state.fipar apar_half_day = self.fluxes.apar / 2.0 # convert umol m-2 d-1 -> gC m-2 d-1 self.fluxes.gpp_gCm2 = self.fluxes.apar * lue_avg * const.UMOL_2_GRAMS_C self.fluxes.gpp_am = apar_half_day * lue_am * const.UMOL_2_GRAMS_C self.fluxes.gpp_pm = apar_half_day * lue_pm * const.UMOL_2_GRAMS_C # g C m-2 to tonnes hectare-1 day-1 self.fluxes.gpp = self.fluxes.gpp_gCm2 * const.GRAM_C_2_TONNES_HA if self.control.nuptake_model == 3: self.fluxes.gpp_gCm2 *= self.params.ac self.fluxes.gpp_am *= self.params.ac self.fluxes.gpp_pm *= self.params.ac
def fire(self, growth_obj): """ Fire... * 100 percent of aboveground biomass * 100 percent of surface litter * 50 percent of N volatilized to the atmosphere * 50 percent of N returned to inorgn pool" * Coarse roots are not damaged by fire! vaguely following ... http://treephys.oxfordjournals.org/content/24/7/765.full.pdf """ totaln = (self.state.branchn + self.state.shootn + self.state.stemn + self.state.structsurfn) self.state.inorgn += totaln / 2.0 # re-establish everything with C/N ~ 25. if self.control.alloc_model == "GRASSES": self.state.branch = 0.0 self.state.branchn = 0.0 self.state.sapwood = 0.0 self.state.stem = 0.0 self.state.stemn = 0.0 self.state.stemnimm = 0.0 self.state.stemnmob = 0.0 else: self.state.branch = 0.001 self.state.branchn = 0.00004 self.state.sapwood = 0.001 self.state.stem = 0.001 self.state.stemn = 0.00004 self.state.stemnimm = 0.00004 self.state.stemnmob = 0.0 self.state.age = 0.0 self.state.lai = 0.01 self.state.metabsurf = 0.0 self.state.metabsurfn = 0.0 self.state.prev_sma = 1.0 self.state.root = 0.001 self.state.rootn = 0.00004 self.state.shoot = 0.001 self.state.shootn = 0.00004 self.state.structsurf = 0.001 self.state.structsurfn = 0.00004 # reset litter flows self.fluxes.deadroots = 0.0 self.fluxes.deadstems = 0.0 self.fluxes.deadbranch = 0.0 self.fluxes.deadsapwood = 0.0 self.fluxes.deadleafn = 0.0 self.fluxes.deadrootn = 0.0 self.fluxes.deadbranchn = 0.0 self.fluxes.deadstemn = 0.0 # update N:C of plant pools if float_eq(self.state.shoot, 0.0): self.state.shootnc = 0.0 else: self.state.shootnc = self.state.shootn / self.state.shoot #print self.state.rootn , self.state.root if float_eq(self.state.root, 0.0): self.state.rootnc = 0.0 else: self.state.rootnc = max(0.0, self.state.rootn / self.state.root) growth_obj.sma.reset_stream() # reset any stress limitation self.state.prev_sma = 1.0
def calculate_photosynthesis(self, day, daylen): """ Photosynthesis is calculated assuming GPP is proportional to APAR, a commonly assumed reln (e.g. Potter 1993, Myneni 2002). The slope of relationship btw GPP and APAR, i.e. LUE is modelled using the photosynthesis eqns from Sands. Assumptions: ------------ (1) photosynthetic light response is a non-rectangular hyperbolic func of photon-flux density with a light-saturatred photosynthetic rate (Amax), quantum yield (alpha) and curvature (theta). (2) the canopy is horizontally uniform. (3) PAR distribution within the canopy obeys Beer's law. (4) light-saturated photosynthetic rate declines with canopy depth in proportion to decline in PAR (5) alpha + theta do not vary within the canopy (6) dirunal variation of PAR is sinusoidal. (7) The model makes no assumption about N within the canopy, however this version assumes N declines exponentially through the cnaopy. (8) Leaf temperature is the same as the air temperature. Parameters: ---------- day : int project day. daylen : float length of day in hours. Returns: ------- Nothing Method calculates GPP, NPP and Ra. """ # local var for tidyness (am, pm) = self.am, self.pm # morning/afternoon (Tair_K, par, vpd, ca) = self.get_met_data(day) # calculate mate params & account for temperature dependencies gamma_star = self.calculate_co2_compensation_point(Tair_K) Km = self.calculate_michaelis_menten_parameter(Tair_K) N0 = self.calculate_top_of_canopy_n() (jmax, vcmax) = self.calculate_jmax_and_vcmax(Tair_K, N0) ci = [self.calculate_ci(vpd[k], ca) for k in am, pm] alpha = self.calculate_quantum_efficiency(ci, gamma_star) # Rubisco carboxylation limited rate of photosynthesis ac = [self.assim(ci[k], gamma_star[k], a1=vcmax[k], a2=Km[k]) \ for k in am, pm] # Light-limited rate of photosynthesis allowed by RuBP regeneration aj = [self.assim(ci[k], gamma_star[k], a1=jmax[k]/4.0, \ a2=2.0*gamma_star[k]) for k in am, pm] # light-saturated photosynthesis rate at the top of the canopy (gross) asat = [min(aj[k], ac[k]) for k in am, pm] # Assumption that the integral is symmetric about noon, so we average # the LUE accounting for variability in temperature, but importantly # not PAR lue = [self.epsilon(asat[k], par, daylen, alpha[k]) for k in am, pm] # mol C mol-1 PAR - use average to simulate canopy photosynthesis lue_avg = sum(lue) / 2.0 if float_eq(self.state.lai, 0.0): self.fluxes.apar = 0.0 else: par_mol = par * const.UMOL_TO_MOL # absorbed photosynthetically active radiation self.fluxes.apar = par_mol * self.state.fipar # gC m-2 d-1 self.fluxes.gpp_gCm2 = (self.fluxes.apar * lue_avg * const.MOL_C_TO_GRAMS_C) self.fluxes.gpp_am_pm[am] = ((self.fluxes.apar / 2.0) * lue[am] * const.MOL_C_TO_GRAMS_C) self.fluxes.gpp_am_pm[pm] = ((self.fluxes.apar / 2.0) * lue[pm] * const.MOL_C_TO_GRAMS_C) self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue if self.control.nuptake_model == 3: self.fluxes.gpp_gCm2 *= self.params.ac self.fluxes.gpp_am_pm[am] *= self.params.ac self.fluxes.gpp_am_pm[pm] *= self.params.ac self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue # g C m-2 to tonnes hectare-1 day-1 conv = const.G_AS_TONNES / const.M2_AS_HA self.fluxes.gpp = self.fluxes.gpp_gCm2 * conv self.fluxes.npp = self.fluxes.npp_gCm2 * conv # Plant respiration assuming carbon-use efficiency. self.fluxes.auto_resp = self.fluxes.gpp - self.fluxes.npp
def calculate_photosynthesis(self, day, daylen): """ Photosynthesis is calculated assuming GPP is proportional to APAR, a commonly assumed reln (e.g. Potter 1993, Myneni 2002). The slope of relationship btw GPP and APAR, i.e. LUE is modelled using the photosynthesis eqns from Sands. Assumptions: ------------ (1) photosynthetic light response is a non-rectangular hyperbolic func of photon-flux density with a light-saturatred photosynthetic rate (Amax), quantum yield (alpha) and curvature (theta). (2) the canopy is horizontally uniform. (3) PAR distribution within the canopy obeys Beer's law. (4) light-saturated photosynthetic rate declines with canopy depth in proportion to decline in PAR (5) alpha + theta do not vary within the canopy (6) dirunal variation of PAR is sinusoidal. (7) The model makes no assumption about N within the canopy, however this version assumes N declines exponentially through the cnaopy. (8) Leaf temperature is the same as the air temperature. Parameters: ---------- day : int project day. daylen : float length of day in hours. Returns: ------- Nothing Method calculates GPP, NPP and Ra. """ # local var for tidyness (am, pm) = self.am, self.pm # morning/afternoon (Tair_K, par, vpd, ca) = self.get_met_data(day) ci = [self.calculate_ci(vpd[k], ca) for k in am, pm] N0 = self.calculate_top_of_canopy_n() # Quantum efficiency (umol mol-1), no Ci or temp dependancey in c4 # plants # Ehleringer, J. R., 1978: Implications of quantum yield differences # on the distributions of C3 and C4 grasses. Oecologia, 31, 255-267. alpha = 0.04 # C4 assimilation following from Collatz et al. 1992 #?? Exponential factor in the equation defining kt #alpharf = 0.067 # mol/mol kslope = 0.7 # initial slope of photosynthetic CO2 response (mol m-2 s-1), Collatz table 2 theta = 0.83 # curvature parameter, Collatz table 2 beta = 0.93 # curvature parameter, Collatz table 2 # http://www.cesm.ucar.edu/models/cesm1.0/clm/CLM4_Tech_Note.pdf # Table 8.2 has PFT values... vcmax25 = self.params.vcmaxna * N0 + self.params.vcmaxnb # Massad et al. 2007 Eav = 67294.0 Hdv = 144568.0 deltaSv = 472.0 vcmax = [self.peaked_arrh(vcmax25, Eav, Tair_K[k], deltaSv, Hdv) \ for k in am, pm] # reduce photosynthetic capacity with moisture stress vcmax = [self.state.wtfac_root * vcmax[k] for k in am, pm] #Je = vcmax #Jc = k / vmax * vcmax * ci #Ji = alpha * I # Rubisco and light limited capacity par_per_sec = par / (60.0 * 60.0 * daylen) M = [self.quadratic(theta, -(alpha*par_per_sec+vcmax[k]), alpha*par_per_sec*vcmax[k]) for k in am, pm] # M and CO2 limitation A = [self.quadratic(beta, -(M[k]+kslope*ci[k]), M[k]*kslope*ci[k]) for k in am, pm] # These respiration terms are just for assimilation calculations, # autotrophic respiration is stil assumed to be half of GPP (Rd, Rm) = self.calc_respiration(Tair_K, vcmax) # Net (saturated) photosynthetic rate, not sure if this # makes sense. Asat = [A[k] - Rd[k] for k in am, pm] """ # calculate mate parameters, e.g. accounting for temp dependancy (Km, Kc, Ko, Kp) = self.calculate_michaelis_menten_parameter(Tair_K) gamma_star = self.calculate_co2_compensation_point(Tair_K) # Currently i dont have any information on how these depednancies # vary with N, nor do I have site parameters so going to use # values at 25 degrees from the literature, hardwired till this is # resolved. # Values from table 4.1, in von Caemmerer 2000, pg 100. vcmax25 = 60.0 vpmax25 = 120.0 jmax25 = 400.0 if self.control.modeljm == True: jmax = self.calculate_jmax_parameter(Tair_K, jmax25) vcmax = self.calculate_vcmax_parameter(Tair_K, vcmax25) vpmax = self.calculate_vpmax_parameter(Tair_K, vpmax25) else: jmax = [self.params.jmax, self.params.jmax] vcmax = [self.params.vcmax, self.params.vcmax] vpmax = [self.params.vpmax, self.params.vpmax] # reduce photosynthetic capacity with moisture stress #jmax = [self.state.wtfac_root * jmax[k] for k in am, pm] #vcmax = [self.state.wtfac_root * vcmax[k] for k in am, pm] #vpmax = [self.state.wtfac_root * vpmax[k] for k in am, pm] # These respiration terms are just for assimilation calculations, # autotrophic respiration is stil assumed to be half of GPP (Rd, Rm) = self.calc_respiration(Tair_K, vcmax) # Calculate assimilation rates. Ac = self.calc_enzyme_limited_assim(Km, Kc, Ko, Kp, ci, vpmax, vcmax, Rd, Rm) par_per_sec = par / (60.0 * 60.0 * daylen) Aj = self.calc_light_limited_assim(par_per_sec, jmax, ci, Rd, Rm) Asat = [min(Aj[k], Ac[k]) for k in am, pm] """ # Assumption that the integral is symmetric about noon, so we average # the LUE accounting for variability in temperature, but importantly # not PAR lue = [self.epsilon(Asat[k], par, daylen, alpha) for k in am, pm] # mol C mol-1 PAR - use average to simulate canopy photosynthesis lue_avg = sum(lue) / 2.0 if float_eq(self.state.lai, 0.0): self.fluxes.apar = 0.0 else: par_mol = par * const.UMOL_TO_MOL # absorbed photosynthetically active radiation self.fluxes.apar = par_mol * self.state.fipar # gC m-2 d-1 self.fluxes.gpp_gCm2 = (self.fluxes.apar * lue_avg * const.MOL_C_TO_GRAMS_C) self.fluxes.gpp_am_pm[am] = ((self.fluxes.apar / 2.0) * lue[am] * const.MOL_C_TO_GRAMS_C) self.fluxes.gpp_am_pm[pm] = ((self.fluxes.apar / 2.0) * lue[pm] * const.MOL_C_TO_GRAMS_C) #print self.fluxes.gpp_gCm2 self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue if self.control.nuptake_model == 3: self.fluxes.gpp_gCm2 *= self.params.ac self.fluxes.gpp_am_pm[am] *= self.params.ac self.fluxes.gpp_am_pm[pm] *= self.params.ac self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue # g C m-2 to tonnes hectare-1 day-1 conv = const.G_AS_TONNES / const.M2_AS_HA self.fluxes.gpp = self.fluxes.gpp_gCm2 * conv self.fluxes.npp = self.fluxes.npp_gCm2 * conv # Plant respiration assuming carbon-use efficiency. self.fluxes.auto_resp = self.fluxes.gpp - self.fluxes.npp
def calculate_photosynthesis(self, day, daylen): """ Photosynthesis is calculated assuming GPP is proportional to APAR, a commonly assumed reln (e.g. Potter 1993, Myneni 2002). The slope of relationship btw GPP and APAR, i.e. LUE is modelled using the photosynthesis eqns from Sands. Assumptions: ------------ (1) photosynthetic light response is a non-rectangular hyperbolic func of photon-flux density with a light-saturatred photosynthetic rate (Amax), quantum yield (alpha) and curvature (theta). (2) the canopy is horizontally uniform. (3) PAR distribution within the canopy obeys Beer's law. (4) light-saturated photosynthetic rate declines with canopy depth in proportion to decline in PAR (5) alpha + theta do not vary within the canopy (6) dirunal variation of PAR is sinusoidal. (7) The model makes no assumption about N within the canopy, however this version assumes N declines exponentially through the cnaopy. (8) Leaf temperature is the same as the air temperature. Parameters: ---------- day : int project day. daylen : float length of day in hours. Returns: ------- Nothing Method calculates GPP, NPP and Ra. """ # local var for tidyness (am, pm) = self.am, self.pm # morning/afternoon (temp, par, vpd, ca) = self.get_met_data(day) Tk = [temp[k] + const.DEG_TO_KELVIN for k in am, pm] # calculate mate parameters, e.g. accounting for temp dependancy gamma_star = self.calculate_co2_compensation_point(Tk) km = self.calculate_michaelis_menten_parameter(Tk) N0 = self.calculate_leafn() jmax = self.calculate_jmax_parameter(Tk, N0) vcmax = self.calculate_vcmax_parameter(Tk, N0) alpha = self.calculate_quantum_efficiency(temp) # calculate ratio of intercellular to atmospheric CO2 concentration. # Also allows productivity to be water limited through stomatal opening. cica = [self.calculate_ci_ca_ratio(vpd[k]) for k in am, pm] ci = [i * ca for i in cica] # store value as needed in water balance calculation self.fluxes.cica_avg = sum(cica) / len(cica) # Rubisco-limited rate of photosynthesis ac = [self.aclim(ci[k], gamma_star[k], km[k], vcmax[k]) for k in am, pm] # Light-limited rate of photosynthesis allowed by RuBP regeneration aj = [self.ajlim(jmax[k], ci[k], gamma_star[k]) for k in am, pm] # Note that these are gross photosynthetic rates. Response to elevated # [CO2] is reduced if N declines, but increases as gs declines. asat = [min(aj[k], ac[k]) for k in am, pm] # GPP is assumed to be proportional to APAR, where the LUE defines the # slope of this relationship. LUE, calculation is performed for morning # and afternnon periods. lue = [self.epsilon(asat[k], par, daylen, alpha[k]) for k in am, pm] # mol C mol-1 PAR - use average to simulate canopy photosynthesis lue_avg = sum(lue) / len(lue) if float_eq(self.state.lai, 0.0): self.fluxes.apar = 0.0 else: par_mol = par * const.UMOL_TO_MOL self.fluxes.apar = par_mol * self.state.light_interception # gC m-2 d-1 self.fluxes.gpp_gCm2 = (self.fluxes.apar * lue_avg * const.MOL_C_TO_GRAMS_C) self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue self.fluxes.gpp_am_gCm2 = ((self.fluxes.apar / 2.0) * lue[am] * const.MOL_C_TO_GRAMS_C) self.fluxes.gpp_pm_gCm2 = ((self.fluxes.apar / 2.0) * lue[pm] * const.MOL_C_TO_GRAMS_C) # tonnes hectare-1 day-1 conv = const.G_AS_TONNES / const.M2_AS_HA self.fluxes.gpp = self.fluxes.gpp_gCm2 * conv self.fluxes.npp = self.fluxes.npp_gCm2 * conv # Plant respiration assuming carbon-use efficiency. self.fluxes.auto_resp = self.fluxes.gpp - self.fluxes.npp
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 day_end_calculations(self, prjday, days_in_year=None, INIT=False): """Calculate derived values from state variables. Parameters: ----------- day : integer day of simulation INIT : logical logical defining whether it is the first day of the simulation """ self.fluxes.ninflow = self.met_data['ndep'][prjday] # update N:C of plant pools if float_eq(self.state.shoot, 0.0): self.state.shootnc = 0.0 else: self.state.shootnc = self.state.shootn / self.state.shoot self.state.rootnc = max(0.0, self.state.rootn / self.state.root) # total plant, soil & litter nitrogen self.state.soiln = (self.state.inorgn + self.state.activesoiln + self.state.slowsoiln + self.state.passivesoiln) self.state.litternag = self.state.structsurfn + self.state.metabsurfn self.state.litternbg = self.state.structsoiln + self.state.metabsoiln self.state.littern = self.state.litternag + self.state.litternbg self.state.plantn = (self.state.shootn + self.state.rootn + self.state.branchn + self.state.stemn) self.state.totaln = (self.state.plantn + self.state.littern + self.state.soiln) # total plant, soil, litter and system carbon self.state.soilc = (self.state.activesoil + self.state.slowsoil + self.state.passivesoil) self.state.littercag = self.state.structsurf + self.state.metabsurf self.state.littercbg = self.state.structsoil + self.state.metabsoil self.state.litterc = self.state.littercag + self.state.littercbg self.state.plantc = (self.state.root + self.state.shoot + self.state.stem + self.state.branch) self.state.totalc = (self.state.soilc + self.state.litterc + self.state.plantc) # optional constant passive pool if self.control.passiveconst == True: self.state.passivesoil = self.params.passivesoilz self.state.passivesoiln = self.params.passivesoilnz if INIT == False: #Required so max leaf & root N:C can depend on Age self.state.age += 1.0 / days_in_year # N Net mineralisation, i.e. excess of N outflows over inflows self.fluxes.nmineralisation = (self.fluxes.ninflow + self.fluxes.ngross + self.fluxes.nrootexudate - self.fluxes.nimmob + self.fluxes.nlittrelease) # calculate NEP self.fluxes.nep = (self.fluxes.npp - self.fluxes.hetero_resp - self.fluxes.ceaten * (1. - self.params.fracfaeces))
def fire(self, growth_obj): """ Fire... * 100 percent of aboveground biomass * 100 percent of surface litter * 50 percent of N volatilized to the atmosphere * 50 percent of N returned to inorgn pool" * Coarse roots are not damaged by fire! vaguely following ... http://treephys.oxfordjournals.org/content/24/7/765.full.pdf """ totaln = (self.state.branchn + self.state.shootn + self.state.stemn + self.state.structsurfn) self.state.inorgn += totaln / 2.0 # re-establish everything with C/N ~ 25. if self.control.alloc_model == "GRASSES": self.state.branch = 0.0 self.state.branchn = 0.0 self.state.sapwood = 0.0 self.state.stem = 0.0 self.state.stemn = 0.0 self.state.stemnimm = 0.0 self.state.stemnmob = 0.0 else: self.state.branch = 0.001 self.state.branchn = 0.00004 self.state.sapwood = 0.001 self.state.stem = 0.001 self.state.stemn = 0.00004 self.state.stemnimm = 0.00004 self.state.stemnmob = 0.0 self.state.age = 0.0 self.state.metabsurf = 0.0 self.state.metabsurfn = 0.0 self.state.prev_sma = 1.0 self.state.root = 0.001 self.state.rootn = 0.00004 self.state.shoot = 0.001 self.state.lai = (self.params.sla * const.M2_AS_HA / const.KG_AS_TONNES / self.params.cfracts * self.state.shoot) self.state.shootn = 0.00004 self.state.structsurf = 0.001 self.state.structsurfn = 0.00004 # reset litter flows self.fluxes.deadroots = 0.0 self.fluxes.deadstems = 0.0 self.fluxes.deadbranch = 0.0 self.fluxes.deadsapwood = 0.0 self.fluxes.deadleafn = 0.0 self.fluxes.deadrootn = 0.0 self.fluxes.deadbranchn = 0.0 self.fluxes.deadstemn = 0.0 # update N:C of plant pools if float_eq(self.state.shoot, 0.0): self.state.shootnc = 0.0 else: self.state.shootnc = self.state.shootn / self.state.shoot if self.control.ncycle == False: self.state.shootnc = self.params.prescribed_leaf_NC #print self.state.rootn , self.state.root if float_eq(self.state.root, 0.0): self.state.rootnc = 0.0 else: self.state.rootnc = max(0.0, self.state.rootn / self.state.root) growth_obj.sma.reset_stream() # reset any stress limitation self.state.prev_sma = 1.0
def carbon_allocation(self, nitfac, doy, days_in_yr): """ C distribution - allocate available C through system Parameters: ----------- nitfac : float leaf N:C as a fraction of 'Ncmaxfyoung' (max 1.0) References: ----------- * Hale, M. G. et al. (1981) Factors affecting root exudation and significance for the rhizosphere ecoystems. Biological and chemical interactions in the rhizosphere. Stockholm. Sweden: Ecological Research Committee of NFR. pg. 43--71. * Lambers, J. T. and Poot, P. (2003) Structure and Functioning of Cluster Roots and Plant Responses to Phosphate Deficiency. * Martin, J. K. and Puckjeridge, D. W. (1982) Carbon flow through the rhizosphere of wheat crops in South Australia. The cyclcing of carbon, nitrogen, sulpher and phosphorous in terrestrial and aquatic ecosystems. Canberra: Australian Academy of Science. pg 77--82. * McMurtrie, R. E. et al (2000) Plant and Soil, 224, 135-152. Also see: * Rovira, A. D. (1969) Plant Root Exudates. Botanical Review, 35, pg 35--57. """ if self.control.deciduous_model: days_left = self.state.growing_days[doy] self.fluxes.cpleaf = self.fluxes.lrate * days_left self.fluxes.cpbranch = self.fluxes.brate * days_left self.fluxes.cpstem = self.fluxes.wrate * days_left self.fluxes.cproot = self.state.c_to_alloc_root * 1.0 / days_in_yr else: self.fluxes.cpleaf = self.fluxes.npp * self.state.alleaf self.fluxes.cproot = self.fluxes.npp * self.state.alroot self.fluxes.cpbranch = self.fluxes.npp * self.state.albranch self.fluxes.cpstem = self.fluxes.npp * self.state.alstem # C flux into root exudation, see McMurtrie et al. 2000. There is no # reference given for the 0.15 in McM, however 14c work by Hale et al and # Martin and Puckeridge suggest values range between 10-20% of NPP. So # presumably this is where this values of 0.15 (i.e. the average) comes # from self.fluxes.cprootexudate = self.fluxes.npp * self.state.alroot_exudate #self.fluxes.cprootexudate = 0.0 # evaluate SLA of new foliage accounting for variation in SLA # with tree and leaf age (Sands and Landsberg, 2002). Assume # SLA of new foliage is linearly related to leaf N:C ratio # via nitfac # (m2 onesided/kg DW) self.state.sla = (self.params.slazero + nitfac * (self.params.slamax - self.params.slazero)) if self.control.deciduous_model: if float_eq(self.state.shoot, 0.0): self.state.lai = 0.0 elif self.state.leaf_out_days[doy] > 0.0: self.state.lai += (self.fluxes.cpleaf * (self.state.sla * const.M2_AS_HA / (const.KG_AS_TONNES * self.params.cfracts)) - (self.fluxes.deadleaves + self.fluxes.ceaten) * self.state.lai / self.state.shoot) else: self.state.lai = 0.0 else: # update leaf area [m2 m-2] self.state.lai += (self.fluxes.cpleaf * (self.state.sla * const.M2_AS_HA / (const.KG_AS_TONNES * self.params.cfracts)) - (self.fluxes.deadleaves + self.fluxes.ceaten) * self.state.lai / self.state.shoot)
def calculate_soil_water_fac(self): """ Estimate a relative water availability factor [0..1] A drying soil results in physiological stress that can induce stomatal closure and reduce transpiration. Further, N mineralisation depends on top soil moisture. self.params.qs = 0.2 in SDGVM References: ----------- * Landsberg and Waring (1997) Forest Ecology and Management, 95, 209-228. See --> Figure 2. * Egea et al. (2011) Agricultural Forest Meteorology, 151, 1370-1384. But similarly see: * van Genuchten (1981) Soil Sci. Soc. Am. J, 44, 892--898. * Wang and Leuning (1998) Ag Forest Met, 91, 89-111. * Pepper et al. (2008) Functional Change Biology, 35, 493-508 Returns: -------- wtfac_topsoil : float water availability factor for the top soil [0,1] wtfac_root : float water availability factor for the root zone [0,1] """ # turn into fraction... smc_topsoil = self.state.pawater_topsoil / self.params.wcapac_topsoil smc_root = self.state.pawater_root / self.params.wcapac_root if self.control.sw_stress_model == 0: wtfac_topsoil = smc_topsoil**self.params.qs wtfac_root = smc_root**self.params.qs elif self.control.sw_stress_model == 1: wtfac_topsoil = self.calc_sw_modifier(smc_topsoil, self.params.ctheta_topsoil, self.params.ntheta_topsoil) wtfac_root = self.calc_sw_modifier(smc_root, self.params.ctheta_root, self.params.ntheta_root) elif self.control.sw_stress_model == 2: # Stomatal limitaiton # Exponetial function to reduce g1 with soil water limitation # based on Zhou et al. 2013, AFM, following Makela et al 1996. # For the moment I have hardwired the PFT parameter as I am still # testing. # Because the model is a daily model we are assuming that LWP is # well approximated by the night SWP. if float_eq(smc_topsoil, 0.0): psi_swp_topsoil = -1.5 else: arg1 = self.params.psi_sat_topsoil arg2 = smc_topsoil /self.params.theta_sat_topsoil arg3 = -self.params.b_topsoil psi_swp_topsoil = arg1 * arg2**arg3 if float_eq(smc_root, 0.0): psi_swp_root = -1.5 else: arg1 = self.params.psi_sat_root arg2 = smc_root/self.params.theta_sat_root arg3 = -self.params.b_root psi_swp_root = arg1 * arg2**arg3 # multipliy these by g1, same as eqn 3 in Zhou et al. 2013. b = 0.66 wtfac_topsoil = exp(b * psi_swp_topsoil) wtfac_root = exp(b * psi_swp_root) #print self.state.pawater_root,wtfac_root return (wtfac_topsoil, wtfac_root)
def update_plant_state(self, fdecay, rdecay): """ Daily change in C content Parameters: ----------- fdecay : float foliage decay rate rdecay : float fine root decay rate """ self.state.shoot += (self.fluxes.cpleaf - self.fluxes.deadleaves - self.fluxes.ceaten) self.state.root += self.fluxes.cproot - self.fluxes.deadroots self.state.branch += self.fluxes.cpbranch - self.fluxes.deadbranch self.state.stem += self.fluxes.cpstem - self.fluxes.deadstems if self.control.deciduous_model: self.state.shootn += (self.fluxes.npleaf - (self.fluxes.deadleafn - self.fluxes.neaten)) else: self.state.shootn += (self.fluxes.npleaf - fdecay * self.state.shootn - self.fluxes.neaten) self.state.shootn = max(0.0, self.state.shootn) self.state.rootn += self.fluxes.nproot - rdecay * self.state.rootn self.state.branchn += (self.fluxes.npbranch - self.params.bdecay * self.state.branchn) self.state.stemnimm += (self.fluxes.npstemimm - self.params.wdecay * self.state.stemnimm) self.state.stemnmob += (self.fluxes.npstemmob - self.params.wdecay * self.state.stemnmob - self.params.retransmob * self.state.stemnmob) self.state.stemn = self.state.stemnimm + self.state.stemnmob # maximum leaf n:c ratio is function of stand age # - switch off age effect by setting ncmaxfyoung = ncmaxfold age_effect = ((self.state.age - self.params.ageyoung) / (self.params.ageold - self.params.ageyoung)) ncmaxf = (self.params.ncmaxfyoung - (self.params.ncmaxfyoung - self.params.ncmaxfold) * age_effect) if float_lt(ncmaxf, self.params.ncmaxfold): ncmaxf = self.params.ncmaxfold if float_gt(ncmaxf, self.params.ncmaxfyoung): ncmaxf = self.params.ncmaxfyoung # if foliage or root n:c ratio exceeds its max, then nitrogen uptake is # cut back n.b. new ring n/c max is already set because it is related # to leaf n:c extrar = 0. extras = 0. if float_gt(self.state.shootn, (self.state.shoot * ncmaxf)): extras = self.state.shootn - self.state.shoot * ncmaxf #n uptake cannot be reduced below zero. if float_gt(extras, self.fluxes.nuptake): extras = self.fluxes.nuptake self.state.shootn -= extras self.fluxes.nuptake -= extras ncmaxr = ncmaxf * self.params.ncrfac # max root n:c if float_gt(self.state.rootn, (self.state.root * ncmaxr)): extrar = self.state.rootn - self.state.root * ncmaxr #n uptake cannot be reduced below zero. if float_gt((extras + extrar), self.fluxes.nuptake): extrar = self.fluxes.nuptake - extras self.state.rootn -= extrar self.fluxes.nuptake -= extrar if self.control.deciduous_model: # update annual fluxes - store for next year self.state.clabile_store += self.fluxes.npp self.state.aroot_uptake += self.fluxes.nuptake self.state.aretrans += self.fluxes.retrans self.state.anloss += self.fluxes.nloss # update N:C of plant pools if float_eq(self.state.shoot, 0.0): self.state.shootnc = 0.0 else: self.state.shootnc = max(self.state.shootn / self.state.shoot, self.params.ncfmin) # N:C of stuctural wood as function of N:C of foliage # (kgN kg-1C)(Medlyn et al 2000) self.state.nc_ws = (self.params.nc_wsa + self.params.nc_wsb * self.state.shootnc) # N:C of new wood as function of N:C of foliage self.state.nc_wnew = (self.params.nc_wnewa + self.params.nc_wnewb * self.state.shootnc)
def calculate_photosynthesis(self, day, daylen): """ Photosynthesis is calculated assuming GPP is proportional to APAR, a commonly assumed reln (e.g. Potter 1993, Myneni 2002). The slope of relationship btw GPP and APAR, i.e. LUE is modelled using the photosynthesis eqns from Sands. Assumptions: ------------ (1) photosynthetic light response is a non-rectangular hyperbolic func of photon-flux density with a light-saturatred photosynthetic rate (Amax), quantum yield (alpha) and curvature (theta). (2) the canopy is horizontally uniform. (3) PAR distribution within the canopy obeys Beer's law. (4) light-saturated photosynthetic rate declines with canopy depth in proportion to decline in PAR (5) alpha + theta do not vary within the canopy (6) dirunal variation of PAR is sinusoidal. (7) The model makes no assumption about N within the canopy, however this version assumes N declines exponentially through the cnaopy. (8) Leaf temperature is the same as the air temperature. Parameters: ---------- day : int project day. daylen : float length of day in hours. Returns: ------- Nothing Method calculates GPP, NPP and Ra. """ # local var for tidyness (Tair_K, par, vpd, ca) = self.get_met_data(day) ci = [self.calculate_ci(vpd[k], ca) for k in self.am, self.pm] N0 = self.calculate_top_of_canopy_n() alpha = self.params.alpha_c4 # Temp dependancies from Massad et al. 2007 (vcmax, vcmax25) = self.calculate_vcmax_parameter(Tair_K, N0) # Rubisco and light-limited capacity (Appendix, 2B) par_per_sec = par / (60.0 * 60.0 * daylen) M = [ self.quadratic(a=self.beta1, b=-(vcmax[k] + alpha * par_per_sec), c=(vcmax[k] * alpha * par_per_sec)) for k in self.am, self.pm ] # The limitation of the overall rate by M and CO2 limited flux: A = [ self.quadratic(a=self.beta2, b=-(M[k] + self.kslope * ci[k]), c=(M[k] * self.kslope * ci[k])) for k in self.am, self.pm ] # These respiration terms are just for assimilation calculations, # autotrophic respiration is stil assumed to be half of GPP (Rd) = self.calc_respiration(Tair_K, vcmax25) # Net (saturated) photosynthetic rate, not sure if this # makes sense. asat = [A[k] - Rd[k] for k in self.am, self.pm] # LUE (umol C umol-1 PAR) lue = [self.epsilon(asat[k], par, daylen, alpha) for k in self.am, self.pm] lue_avg = sum(lue) / 2.0 # use average to simulate canopy photosynthesis if float_eq(self.state.lai, 0.0): self.fluxes.apar = 0.0 else: # absorbed photosynthetically active radiation (umol m-2 s-1) self.fluxes.apar = par * self.state.fipar # gC m-2 d-1 self.fluxes.gpp_gCm2 = self.fluxes.apar * lue_avg * const.UMOL_TO_MOL * const.MOL_C_TO_GRAMS_C self.fluxes.gpp_am_pm[self.am] = ( (self.fluxes.apar / 2.0) * lue[self.am] * const.UMOL_TO_MOL * const.MOL_C_TO_GRAMS_C ) self.fluxes.gpp_am_pm[self.pm] = ( (self.fluxes.apar / 2.0) * lue[self.pm] * const.UMOL_TO_MOL * const.MOL_C_TO_GRAMS_C ) self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue if self.control.nuptake_model == 3: self.fluxes.gpp_gCm2 *= self.params.ac self.fluxes.gpp_am_pm[am] *= self.params.ac self.fluxes.gpp_am_pm[pm] *= self.params.ac self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue # g C m-2 to tonnes hectare-1 day-1 conv = const.G_AS_TONNES / const.M2_AS_HA self.fluxes.gpp = self.fluxes.gpp_gCm2 * conv self.fluxes.npp = self.fluxes.npp_gCm2 * conv # Plant respiration assuming carbon-use efficiency. self.fluxes.auto_resp = self.fluxes.gpp - self.fluxes.npp
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 calculate_photosynthesis(self, day, daylen): """ Photosynthesis is calculated assuming GPP is proportional to APAR, a commonly assumed reln (e.g. Potter 1993, Myneni 2002). The slope of relationship btw GPP and APAR, i.e. LUE is modelled using the photosynthesis eqns from Sands. Assumptions: ------------ (1) photosynthetic light response is a non-rectangular hyperbolic func of photon-flux density with a light-saturatred photosynthetic rate (Amax), quantum yield (alpha) and curvature (theta). (2) the canopy is horizontally uniform. (3) PAR distribution within the canopy obeys Beer's law. (4) light-saturated photosynthetic rate declines with canopy depth in proportion to decline in PAR (5) alpha + theta do not vary within the canopy (6) dirunal variation of PAR is sinusoidal. (7) The model makes no assumption about N within the canopy, however this version assumes N declines exponentially through the cnaopy. (8) Leaf temperature is the same as the air temperature. Parameters: ---------- day : int project day. daylen : float length of day in hours. Returns: ------- Nothing Method calculates GPP, NPP and Ra. """ # local var for tidyness (Tk_am, Tk_pm, par, vpd_am, vpd_pm, ca) = self.get_met_data(day) # calculate mate params & account for temperature dependencies N0 = self.calculate_top_of_canopy_n() ci_am = self.calculate_ci(vpd_am, ca) ci_pm = self.calculate_ci(vpd_pm, ca) alpha = self.params.alpha_c4 # Temp dependancies from Massad et al. 2007 (vcmax_am, vcmax25_am) = self.calculate_vcmax_parameter(Tk_am, N0) (vcmax_pm, vcmax25_pm) = self.calculate_vcmax_parameter(Tk_pm, N0) # Rubisco and light-limited capacity (Appendix, 2B) par_per_sec = par / (60.0 * 60.0 * daylen) M_am = self.quadratic(a=self.beta1, b=-(vcmax_am + alpha * par_per_sec), c=(vcmax_am * alpha * par_per_sec)) M_pm = self.quadratic(a=self.beta1, b=-(vcmax_pm + alpha * par_per_sec), c=(vcmax_pm * alpha * par_per_sec)) # The limitation of the overall rate by M and CO2 limited flux: A_am = self.quadratic(a=self.beta2, b=-(M_am + self.kslope * ci_am), c=(M_am * self.kslope * ci_am)) A_pm = self.quadratic(a=self.beta2, b=-(M_pm + self.kslope * ci_pm), c=(M_pm * self.kslope * ci_pm)) # These respiration terms are just for assimilation calculations, # autotrophic respiration is stil assumed to be half of GPP Rd_am = self.calc_respiration(Tk_am, vcmax25_am) Rd_pm = self.calc_respiration(Tk_pm, vcmax25_pm) # Net (saturated) photosynthetic rate, not sure if this # makes sense. asat_am = A_am - Rd_am asat_pm = A_pm - Rd_pm # LUE (umol C umol-1 PAR) lue_am = self.epsilon(asat_am, par, daylen, alpha) lue_pm = self.epsilon(asat_pm, par, daylen, alpha) # use average to simulate canopy photosynthesis lue_avg = (lue_am + lue_pm) / 2.0 if float_eq(self.state.lai, 0.0): self.fluxes.apar = 0.0 else: # absorbed photosynthetically active radiation (umol m-2 s-1) self.fluxes.apar = par * self.state.fipar apar_half_day = self.fluxes.apar / 2.0 # convert umol m-2 d-1 -> gC m-2 d-1 self.fluxes.gpp_gCm2 = self.fluxes.apar * lue_avg * const.UMOL_2_GRAMS_C self.fluxes.gpp_am = apar_half_day * lue_am * const.UMOL_2_GRAMS_C self.fluxes.gpp_pm = apar_half_day * lue_pm * const.UMOL_2_GRAMS_C self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue if self.control.nuptake_model == 3: self.fluxes.gpp_gCm2 *= self.params.ac self.fluxes.gpp_am *= self.params.ac self.fluxes.gpp_pm *= self.params.ac self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue # g C m-2 to tonnes hectare-1 day-1 self.fluxes.gpp = self.fluxes.gpp_gCm2 * const.GRAM_C_2_TONNES_HA self.fluxes.npp = self.fluxes.npp_gCm2 * const.GRAM_C_2_TONNES_HA # Plant respiration assuming carbon-use efficiency. self.fluxes.auto_resp = self.fluxes.gpp - self.fluxes.npp
def calculate_photosynthesis(self, day, daylen): """ Photosynthesis is calculated assuming GPP is proportional to APAR, a commonly assumed reln (e.g. Potter 1993, Myneni 2002). The slope of relationship btw GPP and APAR, i.e. LUE is modelled using the photosynthesis eqns from Sands. Assumptions: ------------ (1) photosynthetic light response is a non-rectangular hyperbolic func of photon-flux density with a light-saturatred photosynthetic rate (Amax), quantum yield (alpha) and curvature (theta). (2) the canopy is horizontally uniform. (3) PAR distribution within the canopy obeys Beer's law. (4) light-saturated photosynthetic rate declines with canopy depth in proportion to decline in PAR (5) alpha + theta do not vary within the canopy (6) dirunal variation of PAR is sinusoidal. (7) The model makes no assumption about N within the canopy, however this version assumes N declines exponentially through the cnaopy. (8) Leaf temperature is the same as the air temperature. Parameters: ---------- day : int project day. daylen : float length of day in hours. Returns: ------- Nothing Method calculates GPP, NPP and Ra. """ # local var for tidyness (am, pm) = self.am, self.pm # morning/afternoon (Tair_K, par, vpd, ca) = self.get_met_data(day) ci = [self.calculate_ci(vpd[k], ca) for k in am, pm] N0 = self.calculate_top_of_canopy_n() alpha = self.params.alpha_c4 # Temp dependancies from Massad et al. 2007 (vcmax, vcmax25) = self.calculate_vcmax_parameter(Tair_K, N0) # Rubisco and light-limited capacity (Appendix, 2B) par_per_sec = par / (60.0 * 60.0 * daylen) M = [ self.quadratic(a=self.beta1, b=-(vcmax[k] + alpha * par_per_sec), c=(vcmax[k] * alpha * par_per_sec)) for k in am, pm ] # The limitation of the overall rate by M and CO2 limited flux: A = [ self.quadratic(a=self.beta2, b=-(M[k] + self.kslope * ci[k]), c=(M[k] * self.kslope * ci[k])) for k in am, pm ] # These respiration terms are just for assimilation calculations, # autotrophic respiration is stil assumed to be half of GPP (Rd) = self.calc_respiration(Tair_K, vcmax25) # Net (saturated) photosynthetic rate, not sure if this # makes sense. Asat = [A[k] - Rd[k] for k in am, pm] # Assumption that the integral is symmetric about noon, so we average # the LUE accounting for variability in temperature, but importantly # not PAR lue = [self.epsilon(Asat[k], par, daylen, alpha) for k in am, pm] # mol C mol-1 PAR - use average to simulate canopy photosynthesis lue_avg = sum(lue) / 2.0 if float_eq(self.state.lai, 0.0): self.fluxes.apar = 0.0 else: par_mol = par * const.UMOL_TO_MOL # absorbed photosynthetically active radiation self.fluxes.apar = par_mol * self.state.fipar # gC m-2 d-1 self.fluxes.gpp_gCm2 = (self.fluxes.apar * lue_avg * const.MOL_C_TO_GRAMS_C) self.fluxes.gpp_am_pm[am] = ((self.fluxes.apar / 2.0) * lue[am] * const.MOL_C_TO_GRAMS_C) self.fluxes.gpp_am_pm[pm] = ((self.fluxes.apar / 2.0) * lue[pm] * const.MOL_C_TO_GRAMS_C) #print self.fluxes.gpp_gCm2 self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue if self.control.nuptake_model == 3: self.fluxes.gpp_gCm2 *= self.params.ac self.fluxes.gpp_am_pm[am] *= self.params.ac self.fluxes.gpp_am_pm[pm] *= self.params.ac self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue # g C m-2 to tonnes hectare-1 day-1 conv = const.G_AS_TONNES / const.M2_AS_HA self.fluxes.gpp = self.fluxes.gpp_gCm2 * conv self.fluxes.npp = self.fluxes.npp_gCm2 * conv # Plant respiration assuming carbon-use efficiency. self.fluxes.auto_resp = self.fluxes.gpp - self.fluxes.npp
def calculate_photosynthesis(self, day, daylen): """ Photosynthesis is calculated assuming GPP is proportional to APAR, a commonly assumed reln (e.g. Potter 1993, Myneni 2002). The slope of relationship btw GPP and APAR, i.e. LUE is modelled using the photosynthesis eqns from Sands. Assumptions: ------------ (1) photosynthetic light response is a non-rectangular hyperbolic func of photon-flux density with a light-saturatred photosynthetic rate (Amax), quantum yield (alpha) and curvature (theta). (2) the canopy is horizontally uniform. (3) PAR distribution within the canopy obeys Beer's law. (4) light-saturated photosynthetic rate declines with canopy depth in proportion to decline in PAR (5) alpha + theta do not vary within the canopy (6) dirunal variation of PAR is sinusoidal. (7) The model makes no assumption about N within the canopy, however this version assumes N declines exponentially through the cnaopy. (8) Leaf temperature is the same as the air temperature. Parameters: ---------- day : int project day. daylen : float length of day in hours. Returns: ------- Nothing Method calculates GPP, NPP and Ra. """ # local var for tidyness (Tk_am, Tk_pm, par, vpd_am, vpd_pm, ca) = self.get_met_data(day) # calculate mate params & account for temperature dependencies N0 = self.calculate_top_of_canopy_n() gamma_star_am = self.calculate_co2_compensation_point(Tk_am) gamma_star_pm = self.calculate_co2_compensation_point(Tk_pm) Km_am = self.calculate_michaelis_menten_parameter(Tk_am) Km_pm = self.calculate_michaelis_menten_parameter(Tk_pm) (jmax_am, vcmax_am) = self.calculate_jmax_and_vcmax(Tk_am, N0) (jmax_pm, vcmax_pm) = self.calculate_jmax_and_vcmax(Tk_pm, N0) ci_am = self.calculate_ci(vpd_am, ca) ci_pm = self.calculate_ci(vpd_pm, ca) # quantum efficiency calculated for C3 plants alpha_am = self.calculate_quantum_efficiency(ci_am, gamma_star_am) alpha_pm = self.calculate_quantum_efficiency(ci_pm, gamma_star_pm) # Reducing assimilation if we encounter frost. Frost is assumed to # impact on the maximum photosynthetic capacity and alpha_j # So there is only an indirect effect on LAI, this could be changed... if self.control.frost: Tmax = self.met_data['tmax'][day] Tmin = self.met_data['tmin'][day] Thard = self.calc_frost_hardiness(daylen, Tmin, Tmax) (total_alpha_limf, total_amax_limf) = self.calc_frost_impact_factors(Thard, Tmin, Tmax) alpha_am *= total_alpha_limf alpha_pm *= total_alpha_limf # Rubisco carboxylation limited rate of photosynthesis ac_am = self.assim(ci_am, gamma_star_am, a1=vcmax_am, a2=Km_am) ac_pm = self.assim(ci_pm, gamma_star_pm, a1=vcmax_pm, a2=Km_pm) # Light-limited rate of photosynthesis allowed by RuBP regeneration aj_am = self.assim(ci_am, gamma_star_am, a1=jmax_am/4.0, a2=2.0*gamma_star_am) aj_pm = self.assim(ci_pm, gamma_star_pm, a1=jmax_pm/4.0, a2=2.0*gamma_star_pm) # light-saturated photosynthesis rate at the top of the canopy (gross) asat_am = min(aj_am, ac_am) asat_pm = min(aj_pm, ac_pm) if self.control.frost: asat_am *= total_amax_limf asat_pm *= total_amax_limf # LUE (umol C umol-1 PAR) lue_am = self.epsilon(asat_am, par, daylen, alpha_am) lue_pm = self.epsilon(asat_pm, par, daylen, alpha_pm) # use average to simulate canopy photosynthesis lue_avg = (lue_am + lue_pm) / 2.0 if float_eq(self.state.lai, 0.0): self.fluxes.apar = 0.0 else: # absorbed photosynthetically active radiation (umol m-2 s-1) self.fluxes.apar = par * self.state.fipar apar_half_day = self.fluxes.apar / 2.0 # convert umol m-2 d-1 -> gC m-2 d-1 self.fluxes.gpp_gCm2 = self.fluxes.apar * lue_avg * const.UMOL_2_GRAMS_C self.fluxes.gpp_am = apar_half_day * lue_am * const.UMOL_2_GRAMS_C self.fluxes.gpp_pm = apar_half_day * lue_pm * const.UMOL_2_GRAMS_C # g C m-2 to tonnes hectare-1 day-1 self.fluxes.gpp = self.fluxes.gpp_gCm2 * const.GRAM_C_2_TONNES_HA if self.control.nuptake_model == 3: self.fluxes.gpp_gCm2 *= self.params.ac self.fluxes.gpp_am *= self.params.ac self.fluxes.gpp_pm *= self.params.ac
def calculate_photosynthesis(self, day, daylen): """ Photosynthesis is calculated assuming GPP is proportional to APAR, a commonly assumed reln (e.g. Potter 1993, Myneni 2002). The slope of relationship btw GPP and APAR, i.e. LUE is modelled using the photosynthesis eqns from Sands. Assumptions: ------------ (1) photosynthetic light response is a non-rectangular hyperbolic func of photon-flux density with a light-saturatred photosynthetic rate (Amax), quantum yield (alpha) and curvature (theta). (2) the canopy is horizontally uniform. (3) PAR distribution within the canopy obeys Beer's law. (4) light-saturated photosynthetic rate declines with canopy depth in proportion to decline in PAR (5) alpha + theta do not vary within the canopy (6) dirunal variation of PAR is sinusoidal. (7) The model makes no assumption about N within the canopy, however this version assumes N declines exponentially through the cnaopy. (8) Leaf temperature is the same as the air temperature. Parameters: ---------- day : int project day. daylen : float length of day in hours. Returns: ------- Nothing Method calculates GPP, NPP and Ra. """ # local var for tidyness (Tair_K, par, vpd, ca) = self.get_met_data(day) # calculate mate params & account for temperature dependencies gamma_star = self.calculate_co2_compensation_point(Tair_K) Km = self.calculate_michaelis_menten_parameter(Tair_K) N0 = self.calculate_top_of_canopy_n() (jmax, vcmax) = self.calculate_jmax_and_vcmax(Tair_K, N0) ci = [self.calculate_ci(vpd[k], ca) for k in self.am, self.pm] # quantum efficiency calculated for C3 plants alpha = self.calculate_quantum_efficiency(ci, gamma_star) # Rubisco carboxylation limited rate of photosynthesis ac = [self.assim(ci[k], gamma_star[k], a1=vcmax[k], a2=Km[k]) for k in self.am, self.pm] # Light-limited rate of photosynthesis allowed by RuBP regeneration aj = [self.assim(ci[k], gamma_star[k], a1=jmax[k] / 4.0, a2=2.0 * gamma_star[k]) for k in self.am, self.pm] # light-saturated photosynthesis rate at the top of the canopy (gross) asat = [min(aj[k], ac[k]) for k in self.am, self.pm] # LUE (umol C umol-1 PAR) lue = [self.epsilon(asat[k], par, daylen, alpha[k]) for k in self.am, self.pm] lue_avg = sum(lue) / 2.0 # use average to simulate canopy photosynthesis if float_eq(self.state.lai, 0.0): self.fluxes.apar = 0.0 else: # absorbed photosynthetically active radiation (umol m-2 s-1) self.fluxes.apar = par * self.state.fipar # gC m-2 d-1 self.fluxes.gpp_gCm2 = self.fluxes.apar * lue_avg * const.UMOL_TO_MOL * const.MOL_C_TO_GRAMS_C self.fluxes.gpp_am_pm[self.am] = ( (self.fluxes.apar / 2.0) * lue[self.am] * const.UMOL_TO_MOL * const.MOL_C_TO_GRAMS_C ) self.fluxes.gpp_am_pm[self.pm] = ( (self.fluxes.apar / 2.0) * lue[self.pm] * const.UMOL_TO_MOL * const.MOL_C_TO_GRAMS_C ) self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue if self.control.nuptake_model == 3: self.fluxes.gpp_gCm2 *= self.params.ac self.fluxes.gpp_am_pm[self.am] *= self.params.ac self.fluxes.gpp_am_pm[self.pm] *= self.params.ac self.fluxes.npp_gCm2 = self.fluxes.gpp_gCm2 * self.params.cue # g C m-2 to tonnes hectare-1 day-1 conv = const.G_AS_TONNES / const.M2_AS_HA self.fluxes.gpp = self.fluxes.gpp_gCm2 * conv self.fluxes.npp = self.fluxes.npp_gCm2 * conv # Plant respiration assuming carbon-use efficiency. self.fluxes.auto_resp = self.fluxes.gpp - self.fluxes.npp