def __init__(self, pgen, pcpy, ptop, soildata, gisdata, cpy_outputs=False, bu_outputs=False, top_outputs=False, flatten=True): self.dt = pgen['dt'] # s self.id = pgen['catchment_id'] self.spinup_end = pgen['spinup_end'] self.pgen = pgen self.step_nr = 0 self.ncf_file = pgen['ncf_file'] self.GisData = gisdata self.cmask = self.GisData['cmask'] self.gridshape = np.shape(self.cmask) cmask = self.cmask.copy() flowacc = gisdata['flowacc'].copy() slope = gisdata['slope'].copy() """ flatten=True omits cells outside catchment """ if flatten: ix = np.where(np.isfinite(cmask)) cmask = cmask[ix].copy() flowacc = flowacc[ix].copy() slope = slope[ix].copy() for key in pcpy['state']: pcpy['state'][key] = pcpy['state'][key][ix].copy() for key in soildata: soildata[key] = soildata[key][ix].copy() self.ix = ix # indices to locate back to 2d grid """--- initialize CanopyGrid ---""" self.cpy = CanopyGrid(pcpy, pcpy['state'], outputs=cpy_outputs) """--- initialize BucketGrid ---""" self.bu = BucketGrid(spara=soildata, outputs=bu_outputs) """ --- initialize Topmodel --- """ self.top = Topmodel(ptop, self.GisData['cellsize']**2, cmask, flowacc, slope, outputs=top_outputs)
def __init__(self, pgen, pcpy, pbu, FORC, cmask=np.ones(1), cpy_outputs=True, bu_outputs=True): """ creates SpaFHy_point -object Args: pgen - parameter dict pcpy - canopy parameter dict pbu - bucket model parameter dict FORC - forcing data (pd.DataFrame) cmask - catchment mask; in case multiple cells are simulated. np.array cpy_outputs - saves canopy outputs within self.cpy.results bu_outputs - saves bucket outputs within self.cpy.results Returns: object """ self.dt = pgen['dt'] # s self.id = pgen['catchment_id'] self.spinup_end = pgen['spinup_end'] self.pgen = pgen self.FORC = FORC self.Nsteps = len(self.FORC) """--- initialize CanopyGrid and BucketGrid ---""" # sub-models require states as np.array; so multiply with 'cmask' cstate = pcpy['state'].copy() for key in cstate.keys(): cstate[key] *= cmask self.cpy = CanopyGrid(pcpy, cstate, outputs=cpy_outputs) for key in pbu.keys(): pbu[key] *= cmask self.bu = BucketGrid(pbu, outputs=bu_outputs)
class SpaFHy(): """ SpaFHy model class """ def __init__(self, pgen, pcpy, ptop, soildata, gisdata, cpy_outputs=False, bu_outputs=False, top_outputs=False, flatten=False): self.dt = pgen['dt'] # s self.id = pgen['catchment_id'] self.spinup_end = pgen['spinup_end'] self.pgen = pgen self.step_nr = 0 self.ncf_file = pgen['ncf_file'] self.GisData = gisdata self.cmask = self.GisData['cmask'] self.gridshape = np.shape(self.cmask) cmask= self.cmask.copy() flowacc = gisdata['flowacc'].copy() slope = gisdata['slope'].copy() """ flatten=True omits cells outside catchment """ if flatten: ix = np.where(np.isfinite(cmask)) cmask = cmask[ix].copy() # sdata = sdata[ix].copy() flowacc = flowacc[ix].copy() slope = slope[ix].copy() for key in pcpy['state']: pcpy['state'][key] = pcpy['state'][key][ix].copy() for key in soildata: soildata[key] = soildata[key][ix].copy() self.ix = ix # indices to locate back to 2d grid """--- initialize CanopyGrid ---""" self.cpy = CanopyGrid(pcpy, pcpy['state'], outputs=cpy_outputs) """--- initialize BucketGrid ---""" self.bu = BucketGrid(spara=soildata, outputs=bu_outputs) """ --- initialize Topmodel --- """ self.top=Topmodel(ptop, self.GisData['cellsize']**2, cmask, flowacc, slope, outputs=top_outputs) def run_timestep(self, forc, ncf=False, flx=False, ave_flx=False): """ Runs SpaFHy for one timestep starting from current state Args: forc - dictionary or pd.DataFrame containing forcing values for the timestep ncf - netCDF -file handle, for outputs flx - returns flux and state grids to caller as dict ave_flx - returns averaged fluxes and states to caller as dict Returns: optional """ doy = forc['doy'] ta = forc['T'] vpd = forc['VPD'] + eps rg = forc['Rg'] par = forc['Par'] + eps prec = forc['Prec'] co2 = forc['CO2'] u = forc['U'] + eps # run Topmodel # catchment average ground water recharge [m per unit area] RR = self.bu._drainage_to_gw * self.top.CellArea / self.top.CatchmentArea qb, _, qr, fsat = self.top.run_timestep(RR) # run CanopyGrid potinf, trfall, interc, evap, et, transpi, efloor, mbe = \ self.cpy.run_timestep(doy, self.dt, ta, prec, rg, par, vpd, U=u, CO2=co2, beta=self.bu.Ree, Rew=self.bu.Rew, P=101300.0) # run BucketGrid water balance infi, infi_ex, drain, tr, eva, mbes = self.bu.watbal(dt=self.dt, rr=1e-3*potinf, tr=1e-3*transpi, evap=1e-3*efloor, retflow=qr) # catchment average [m per unit area] saturation excess --> goes to stream # as surface runoff qs = np.nansum(infi_ex)*self.top.CellArea / self.top.CatchmentArea """ outputs """ # updates state and returns results as dict if hasattr(self.top, 'results'): self.top.results['Qt'].append(qb + qs + eps) # total runoff if ncf: # writes to netCDF -file at every timestep; bit slow - should # accumulate into temporary variables and save every 10 days? # for netCDF output, must run in Flatten=True k = self.step_nr # canopygrid ncf['cpy']['W'][k,:,:] = self._to_grid(self.cpy.W) ncf['cpy']['SWE'][k,:,:] = self._to_grid(self.cpy.SWE) ncf['cpy']['Trfall'][k,:,:] = self._to_grid(trfall) ncf['cpy']['Potinf'][k,:,:] = self._to_grid(potinf) ncf['cpy']['ET'][k,:,:] = self._to_grid(et) ncf['cpy']['Transpi'][k,:,:] = self._to_grid(transpi) ncf['cpy']['Efloor'][k,:,:] = self._to_grid(efloor) ncf['cpy']['Evap'][k,:,:] = self._to_grid(evap) ncf['cpy']['Inter'][k,:,:] = self._to_grid(interc) ncf['cpy']['Mbe'][k,:,:] = self._to_grid(mbe) # bucketgrid ncf['bu']['Drain'][k,:,:] = self._to_grid(drain) ncf['bu']['Infil'][k,:,:] = self._to_grid(infi) ncf['bu']['Wliq'][k,:,:] = self._to_grid(self.bu.Wliq) ncf['bu']['Wliq_top'][k,:,:] = self._to_grid(self.bu.Wliq_top) ncf['bu']['PondSto'][k,:,:] = self._to_grid(self.bu.PondSto) ncf['bu']['Mbe'][k,:,:] = self._to_grid(mbes) # topmodel ncf['top']['Qb'][k] = qb ncf['top']['Qs'][k] = qs ncf['top']['Qt'][k] = qb + qs # total runoff ncf['top']['R'][k] = RR ncf['top']['fsat'][k] = fsat ncf['top']['S'][k] = self.top.S ss = self.top.local_s(self.top.S) ss[ss < 0] = 0.0 ncf['top']['Sloc'][k,:,:] = self._to_grid(ss) del ss # update step number self.step_nr += 1 if flx: # returns flux and state variables as grids flx = {'cpy': {'ET': et, 'Transpi': transpi, 'Evap': evap, 'Efloor': efloor, 'Inter': interc, 'Trfall': trfall, 'Potinf': potinf}, 'bu': {'Infil': infi, 'Drain': drain}, 'top': {'Qt': qb + qs, 'Qb': qb, 'Qs': qs, 'fsat': fsat} } return flx if ave_flx: # returns average fluxes and state variables flx = { 'ET': np.nanmean(et), 'E': np.nanmean(evap), 'Ef': np.nanmean(efloor), 'Tr': np.nanmean(transpi), 'SWE': np.nanmean(self.cpy.SWE), 'Drain': 1e3*RR, 'Qt': 1e3 * (qb + qs), 'S': self.top.S, 'fsat': fsat, 'Prec': prec * self.dt, 'Rg': rg, 'Ta': ta, 'VPD': vpd } return flx def _to_grid(self, x): """ converts variable x back to original grid for NetCDF outputs """ if self.ix: a = np.full(self.gridshape, np.NaN) a[self.ix] = x else: # for non-flattened, return a = x return a
def run_susi(forc, wpara, cpara, org_para, spara, outpara, photopara, start_yr, end_yr, wlocation=None, mottifile=None, peat=None, photosite=None, folderName=None, hdomSim=None, volSim=None, ageSim=None, sarkaSim=None, sfc=None, susiPath=None, simLAI=None, kaista=None, sitename=None): print( '******** Susi-peatland simulator v.9 (2021) c Ari Laurén *********************' ) print(' ') print('Initializing stand and site:') dtc = cpara['dt'] # canopy model timestep start_date = datetime.datetime(start_yr, 1, 1) end_date = datetime.datetime(end_yr, 12, 31) length = (end_date - start_date).days + 1 yrs = end_yr - start_yr + 1 ts = get_temp_sum(forc) # temperature sum lat = forc['lat'][0] lon = forc['lon'][ 0] # location of weather file, determines the simulation location print(' - Weather input:', wpara['description'], ', start:', start_yr, ', end:', end_yr) print(' - Latitude:', lat, ', Longitude:', lon) susi_io.print_site_description(spara) # Describe site parameters for user agearray = ageSim + np.arange(0, length, 1.) / 365. hdom, LAI, vol, yi, bm, ba, \ stems, bmToLeafMass, bmToLAI, bmToHdom, bmToYi, bmToBa, yiToVol, yiToBm, \ ageToVol, bmToLitter, bmToStems, volToLogs, volToPulp, sp, \ N_demand, P_demand, K_demand = motti_development(spara, agearray, mottifile) # dom hright m, LAI m2m-2, vol m3/ha, yield m3/ha, biomass kg/ha if outpara['static stand']: print(' // Working with static stand, hdom', spara['hdom'], 'LAI', LAI[0], 'vol', spara['vol']) hdom = np.ones(length) * spara['hdom'] LAI = np.ones(length) * simLAI vol = np.ones(length) * spara['vol'] else: spara['vol'] = vol[0] #these are for printing purposes only spara['hdom'] = hdom[0] #********* Above ground hydrology initialization *************** cmask = np.ones( spara['n'] ) # compute canopy and moss for each soil column (0, and n-1 are ditches??) cstate = cpara['state'].copy() for key in cstate.keys(): cstate[key] *= cmask cpy = CanopyGrid(cpara, cstate, outputs=False) # initialize above ground hydrology model for key in org_para.keys(): org_para[key] *= cmask moss = MossLayer(org_para, outputs=True) # initialize moss layer hydrologu print('Canopy and moss layer hydrology initialized') #******** Soil and strip parameterization ************************* stp = StripHydrology(spara) # initialize soil hycrology model pt = PeatTemperature(spara, forc['T'].mean()) # initialize peat temperature model n = spara['n'] docs = DocModel(spara['nLyrs'], n, spara['bd top'], spara['bd bottom'], spara['dzLyr'], length) # initialize DOC model print('Soil hydrology, temperature and DOC models initialized') """ change these to nodewise deltas, ets """ # number of computation nodes deltas = np.zeros((length, n)) # Infliltration-evapotranspiration, mm/day ets = np.zeros((length, n)) # Evapotranspiration, mm/day dt = 1. # time step, days summer_dwt = [] co2_respi = [] # output variables for figures #initialize result arrays scen = spara['scenario name'] rounds = len(spara['ditch depth east']) dwts = np.zeros( (rounds, int(length / dt), n), dtype=float ) # water table depths, m, ndarray(scenarios, days, number of nodes) afps = np.zeros( (rounds, int(length / dt), n), dtype=float ) # air-filled porosity (m3 m-3), ndarray(scenarios, days, number of nodes) hts = np.zeros( (rounds, int(length / dt), n), dtype=float ) # water table depths, m, ndarray(scenarios, days, number of nodes) air_ratios = np.zeros((rounds, int(length / dt), n), dtype=float) # ratio of afp in root zone to total co2release = np.zeros((rounds, int(length / dt)), dtype=float) peat_temperatures = np.zeros( (rounds, int(length / dt), spara['nLyrs'])) # daily peat temperature profiles c_bals_yr = np.zeros( (rounds, yrs, n), dtype=float) # annual spoil/peat C balance in nodes kg C ha-1 c_balstrees_yr = np.zeros( (rounds, yrs, n), dtype=float) # annual stand C balance in nodes kg C ha-1 n_export_yr = np.zeros( (rounds, yrs, n), dtype=float) # annual leaching of N to water course kg ha-1 p_export_yr = np.zeros((rounds, yrs, n), dtype=float) k_export_yr = np.zeros((rounds, yrs, n), dtype=float) CH4_yr = np.zeros((rounds, yrs, n), dtype=float) npps = np.zeros((rounds, yrs, n), dtype=float) # net primary production kg biomass ha-1 yr-1 het = np.zeros((rounds, yrs, n), dtype=float) # heterotrophic respiration kg CO2 ha-1 yr-1 growths = np.zeros((rounds, yrs, n), dtype=float) # no used g_npps = np.zeros( (rounds, yrs, n), dtype=float ) # stand volume allowed by npp and physical restrictions m3 ha-1 g_npps_pot = np.zeros( (rounds, yrs, n), dtype=float) # potential stand volume allowed by npp alone m3 ha-1 g_nuts = np.zeros( (rounds, yrs, n), dtype=float ) # stand volume allowed by supply of growth-limiting nutrient m3 ha-1 g_Ns = np.zeros((rounds, yrs, n), dtype=float) # stand volume allowed by supply of N m3 ha-1 g_Ps = np.zeros((rounds, yrs, n), dtype=float) # stand volume allowed by supply of P m3 ha-1 g_Ks = np.zeros((rounds, yrs, n), dtype=float) # stand volume allowed by supply of K m3 ha-1 yis = np.zeros((rounds, yrs, n), dtype=float) # not used vols = np.zeros((rounds, yrs, n), dtype=float) # realized stand volumes m3 ha-1 end_vols = np.zeros((rounds, n), dtype=float) c_bals = np.zeros((rounds, n), dtype=float) c_bals_trees = np.zeros((rounds, n), dtype=float) sdwt = np.zeros((rounds, n), dtype=float) CH4release = np.zeros((rounds, n), dtype=float) Nrelease = np.zeros((rounds, n), dtype=float) Prelease = np.zeros((rounds, n), dtype=float) Krelease = np.zeros((rounds, n), dtype=float) biomass_gr = np.zeros((rounds, n)) litterfall_gv_cumul = np.zeros((rounds, n)) runoff = np.zeros((rounds, int(length / dt)), dtype=float) swes = np.zeros((rounds, int(length / dt)), dtype=float) Nstorage = np.zeros( n, dtype=float) # Nodewise nutrient storage in the rooting zone kg/ha Pstorage = np.zeros( n, dtype=float) # Nodewise nutrient storage in the rooting zone kg/ha Kstorage = np.zeros( n, dtype=float) # Nodewise nutrient storage in the rooting zone kg/ha Nout = np.zeros((rounds, n)) # Nodewise potential for N export kg/ha Pout = np.zeros((rounds, n)) # Nodewise potential for P export kg/ha Kout = np.zeros((rounds, n)) # Nodewise potential for K export kg/ha HMWDOCout = np.zeros( (rounds, n)) # Nodewise potential for HMWDOC export kg/ha LMWDOCout = np.zeros( (rounds, n)) # Nodewise potential for LMWDOC export kg/ha for r, dr in enumerate( zip(spara['ditch depth west'], spara['ditch depth 20y west'], spara['ditch depth east'], spara['ditch depth 20y east'])): #SCENARIO loop dwt = spara['initial h'] * np.ones(spara['n']) hdr_west, hdr20y_west, hdr_east, hdr20y_east = dr # drain depth [m] in the beginning and after 20 yrs h0ts_west = drain_depth_development( length, hdr_west, hdr20y_west ) # compute daily values for drain bottom boundary condition h0ts_east = drain_depth_development( length, hdr_east, hdr20y_east ) # compute daily values for drain bottom boundary condition v, leaf_mass, hc, b = vol[0] * np.ones(n), bmToLeafMass( bm[0]) * np.ones(n), hdom[0] * np.ones(n), bm[0] * np.ones(n) b_ini = yiToBm(v) #b.copy() b = b_ini.copy() BA0 = bmToBa(b) yi0 = v.copy() stems0 = bmToStems(b) ageyrs = ageSim + np.array(range(yrs)) + 1 # ---- Initialize integrative output arrays (outputs in nodewise sums) ------------------------------- start = 0 litter_cumul = np.zeros(n) Crelease = np.zeros(n) Nleach = np.zeros(n) Pleach = np.zeros(n) Kleach = np.zeros(n) DOCleach = np.zeros(n) HMWleach = np.zeros(n) bm_deadtrees = np.zeros(n) stems = bmToStems(b_ini) #current number of stems in the stand (ha-1) n_deadtrees = np.maximum((stems - bmToStems(b)), np.zeros(n)) print('***********************************') print('Computing canopy and soil hydrology ', length, ' days', 'scenario:', scen[r]) stp.reset_domain() pt.reset_domain() docs.reset_domain() d = 0 # day index start = 0 year = 0 for yr in range(start_yr, end_yr + 1): # year loop days = (datetime.datetime(yr, 12, 31) - datetime.datetime(yr, 1, 1)).days + 1 hc = bmToHdom(b) lai = bmToLAI(b) for dd in range(days): # day loop #-------Canopy hydrology-------------------------- reww = rew_drylimit( dwt ) # for each column: moisture limitation from ground water level (Feddes-function) doy = forc.iloc[d, 14] ta = forc.iloc[d, 4] vpd = forc.iloc[d, 13] rg = forc.iloc[d, 8] par = forc.iloc[d, 10] prec = forc.iloc[d, 7] / 86400. potinf, trfall, interc, evap, ET, transpi, efloor, MBE, SWE = cpy.run_timestep( doy, dtc, ta, prec, rg, par, vpd, hc=hc, LAIconif=lai, Rew=reww, beta=moss.Ree) # kaikki (käytä tätä) potinf, efloor, MBE2 = moss.interception(potinf, efloor) deltas[d] = potinf - transpi ets[d] = efloor + transpi if d % 365 == 0: print(' - day #', d, ' hdom ', np.round(np.mean(hc), 2), ' m, ', 'LAI ', np.round(np.mean(lai), 2), ' m2 m-2') #--------Soil hydrology----------------- dwt, ht, roff, air_ratio, afp = stp.run_timestep( d, h0ts_west[d], h0ts_east[d], deltas[d, :], moss) dwts[r, d, :] = dwt hts[r, d, :] = ht air_ratios[r, d, :] = air_ratio afps[r, d, :] = afp runoff[r, d] = roff swes[r, d] = np.mean(SWE) z, peat_temperature = pt.run_timestep(ta, np.mean(SWE), np.mean(efloor)) peat_temperatures[r, d, :] = peat_temperature d += 1 #----- Hydrology and temperature-related variables to time-indexed dataframes ----------------- sday = datetime.datetime(yr, 1, 1) t5 = pd.DataFrame( peat_temperatures[r, start:start + days, 1], index=pd.date_range( sday, periods=days)) #Peat temperature in 5 cm depth, deg C df_peat_temperatures = pd.DataFrame( peat_temperatures[r, start:start + days, :], index=pd.date_range(sday, periods=days)) dfwt = pd.DataFrame(dwts[r, start:start + days, :], index=pd.date_range(sday, periods=days)) dfair_r = pd.DataFrame(air_ratios[r, start:start + days, :], index=pd.date_range(sday, periods=days)) dfafp = pd.DataFrame(afps[r, start:start + days, :], index=pd.date_range(sday, periods=days)) # ---------- Computing biogeochemistry ----------------------- nup_gv, pup_gv, kup_gv, litterfall_gv, gv_leafmass = understory_uptake( spara['n'], lat, lon, BA0, bmToBa(b), stems0, bmToStems(b), yi0, bmToYi(b), sp, ts, 1, spara['sfc'], ageSim + year) litterfall_gv_cumul[ r, :] = litterfall_gv_cumul[r, :] + litterfall_gv _, co2, Rhet, Rhet_root = heterotrophic_respiration_yr( t5, yr, dfwt, dfair_r, v, spara) #Rhet in kg/ha/yr CO2 days = len(co2) CH4, CH4mean, CH4asCO2eq = CH4_flux_yr( yr, dfwt ) # annual ch4 nodewise (kg ha-1 yr-1), mean ch4, and mean ch4 as co2 equivalent co2release[ r, start:start + days] = co2 # mean daily time series for co2 efflux kg/ ha/day CO2 Ns, Ps, Ks = nutrient_release( spara['sfc'], spara['sfc_specification'], Rhet_root, N=spara['peatN'], P=spara['peatP'], K=spara['peatK'] ) # N P K release in kg/ha/yr #supply of N,P,K kg/ha/timestep Nstot, Pstot, Kstot = nutrient_release(spara['sfc'], spara['sfc_specification'], Rhet, N=spara['peatN'], P=spara['peatP'], K=spara['peatK']) Nleach = Nleach + Nstot - Ns Pleach = Pleach + Pstot - Ps Kleach = Kleach + Kstot - Ks n_export_yr[r, year, :] = Nstot - Ns p_export_yr[r, year, :] = Pstot - Ps k_export_yr[r, year, :] = Kstot - Ks CH4_yr[r, year, :] = CH4 doc, hmw = docs.doc_release(df_peat_temperatures.loc[str(yr)], dfwt.loc[str(yr)]) DOCleach = DOCleach + doc HMWleach = HMWleach + hmw #------------------fertilization-------------------------------- fert = {'N': 0.0, 'P': 0.0, 'K': 0.0} if yr >= spara['fertilization']['application year']: tfert = yr - spara['fertilization']['application year'] for nutr in ['N', 'P', 'K']: nut_efficiency = spara['fertilization'][nutr]['eff'] dose = spara['fertilization'][nutr]['dose'] decay_k = spara['fertilization'][nutr]['decay_k'] fert[nutr] = (dose * np.exp(-decay_k * tfert) - dose * np.exp(-decay_k * (tfert + 1))) * nut_efficiency #---------------------------------------------------------------- Ns, Ps, Ks = Ns + spara['depoN'] + fert['N'], Ps + spara[ 'depoP'] + fert['P'], Ks + spara['depoK'] + fert[ 'K'] #decomposition + deposition from Ruoho-Airola et al 2003 Fig.4 Nrelease[r, :] = Nrelease[r, :] + Ns Prelease[r, :] = Prelease[r, :] + Ps Krelease[r, :] = Krelease[r, :] + Ks Crelease = Crelease + Rhet * ( 12. / 44) # CO2 to C, annual sum, nodewise in kg C ha-1 CH4release[r, :] = CH4release[ r, :] + CH4 # Total nodewise CH4 release in the simulation, kg CH4 ha-1 NPP, NPP_pot = assimilation_yr( photopara, forc.loc[str(yr)], dfwt.loc[str(yr)], dfafp.loc[str(yr)], leaf_mass, hc, species=spara['species'] ) # NPP nodewise, kg organic matter /ha /yr sum over the year bm_change = NPP - n_deadtrees / stems * b - bmToLitter(b) * 365. bm_change_pot = NPP_pot - n_deadtrees / stems * b - bmToLitter( b) * 365. new_bm = b + np.maximum( bm_change, np.zeros(n)) # suggestion for new biomass kg/ha new_bm_pot = b + np.maximum(bm_change_pot, np.zeros(n)) g_npp = bmToYi(new_bm) # suggested bm to new volume as yield m3/ha g_npp_pot = bmToYi(new_bm_pot) g_npps[r, yr - start_yr, :] = g_npp g_npps_pot[r, yr - start_yr, :] = g_npp_pot g_N, g_P, g_K = nut_to_vol( v, Ns, Ps, Ks, bmToLitter(b) * 365., nup_gv, pup_gv, kup_gv, leaf_mass * 1000, gv_leafmass ) # volume growth allowed by nutrient release litter here in kg/ha/yr g_Ns[r, yr - start_yr, :] = g_N g_Ps[r, yr - start_yr, :] = g_P g_Ks[r, yr - start_yr, :] = g_K lim_nut_gr = np.minimum(g_K, g_P) # find the growth limiting factor lim_nut_gr = np.minimum(lim_nut_gr, g_N) #g_nut = v + lim_nut_gr g_nuts[r, yr - start_yr, :] = lim_nut_gr v = np.minimum(lim_nut_gr, g_npp) # new volume as yield BA0 = bmToBa(b) stems0 = bmToStems(b) yi0 = bmToYi(b) #update old: basal area, stem number, volume vols[r, yr - start_yr, :] = v bm_restr = yiToBm(v) #------Annual balances-------------------- c_bals_yr[r, yr - start_yr, :] = ( bmToLitter(b) * 365. + n_deadtrees / stems * b + litterfall_gv) * 0.5 - Rhet * (12. / 44) #(rounds, yrs,n) c_balstrees_yr[r, yr - start_yr, :] = ( (bm_restr - b) + bmToLitter(b) * 365. + n_deadtrees / stems * b + litterfall_gv) * 0.5 - Rhet * (12. / 44) # (rounds, yrs,n) litter_cumul = litter_cumul + bmToLitter(b) * 365 leaf_mass, hc, b = bmToLeafMass(bm_restr), bmToHdom( bm_restr), bm_restr n_deadtrees = np.maximum((stems - bmToStems(b)), np.zeros(n)) bm_deadtrees = bm_deadtrees + n_deadtrees / stems * b stems = bmToStems(b) start = start + days year += 1 # ------------------ End biogeochemistry loop, end yr loop------------------------------------- # Carbon balance of the stand in kg / ha / simulation time #docs.draw_doc(sitename, yrs) end_vols[r, :] = v c_bals_trees[r, :] = ( (b - b_ini) + litter_cumul + bm_deadtrees + litterfall_gv_cumul[r, :] ) * 0.5 - Crelease # in kg C /ha/time, 600 is the contribution of ground vegetation (Minkkinen et al. 20018) c_bals[r, :] = ( litter_cumul + bm_deadtrees + litterfall_gv_cumul[r, :] ) * 0.5 - Crelease # in kg C /ha/time, 600 is the contribution of ground vegetation (Minkkinen et al. 20018) biomass_gr[r, :] = (b - b_ini) + litter_cumul + bm_deadtrees Nout[r, :] = Nleach Pout[r, :] = Pleach Kout[r, :] = Kleach HMWDOCout[r, :] = HMWleach LMWDOCout[r, :] = DOCleach - HMWleach v_start = vol[0] * np.ones(n) potential_gr = [] phys_restr = [] chem_restr = [] N_gr = [] P_gr = [] K_gr = [] phys_gr = [] for yr in range(start_yr, end_yr + 1): vgrowth = vols[0, yr - start_yr, :] - v_start pot_growth = g_npps_pot[0, yr - start_yr, :] - v_start phys_growth = g_npps[0, yr - start_yr, :] - v_start chem_growth = g_nuts[0, yr - start_yr, :] - v_start n_gr = g_Ns[0, yr - start_yr, :] - v_start p_gr = g_Ps[0, yr - start_yr, :] - v_start k_gr = g_Ks[0, yr - start_yr, :] - v_start v_start = vols[0, yr - start_yr, :] potential_gr.append(np.mean(pot_growth)) phys_gr.append(np.mean(phys_growth)) phys_restr.append(np.mean(phys_growth) / np.mean(pot_growth)) chem_restr.append(np.mean(chem_growth) / np.mean(pot_growth)) N_gr.append(n_gr) P_gr.append(p_gr) K_gr.append(k_gr) print('***********Growth restrictions*********************') print(np.mean(np.array(potential_gr)), np.mean(np.array(phys_restr)), np.mean(np.array(chem_restr))) print ('grs', np.mean(np.array(potential_gr)), np.mean(np.array(vgrowth)), \ np.mean(np.array(N_gr)), np.mean(np.array(P_gr)), np.mean(np.array(K_gr)), np.mean(np.array(phys_gr))) #if outpara['figs']: # susi_io.fig_stand_growth_node(r, rounds, ageSim, start_yr, end_yr, ageToVol, # ageyrs,g_npps[:,:, 1:-1], g_nuts[:,:, 1:-1], spara['scenario name'][r]) #daily_dwt = [np.mean(dwts[r,d,1:-1]) for d in range(length)] # ******** Outputs ************************************* # Change delatas ets to np.mean(deltas), np.mean(ets) susi_io.print_scenario_nodes(r, c_bals_trees, np.mean(deltas, axis=1), np.mean(ets, axis=1), h0ts_west, h0ts_east, dwts, bmToYi, g_nuts, end_vols, yi) co2_respi.append(np.mean(np.sum(het[r, :, 1:-1], axis=0))) summer_mean_dwt, summer = susi_io.output_dwt_growing_season( dwts[r, :], length, start_yr, end_yr, start_date, outpara, wpara, scen[r]) sdwt[r, :] = summer_mean_dwt if outpara['to_file']: susi_io.dwt_to_excel(dwts[r, :, :], outpara, scen[r]) susi_io.runoff_to_excel(runoff[r, :] * 1000., swes[r, :], outpara, scen[r]) #susi_io.write_excel(wlocation, wpara, spara, outpara, LAI, hdom, h0ts_west[0], h0ts_east[0],summer, summer_median_dwt) susi_io.c_and_nut_to_excel(c_bals_yr[r, :, :], c_balstrees_yr[r, :, :], n_export_yr[r, :, :], p_export_yr[r, :, :], k_export_yr[r, :, :], outpara, scen[r]) # Change delatas ets to np.mean(deltas), np.mean(ets) if outpara['hydfig']: susi_io.fig_hydro(stp.ele, hts[r,:,:], spara, wpara, wlocation, np.mean(ets*1000., axis=1), forc['Prec'].values, \ forc['T'].values, co2release[r,:], LAI, hdr_west, hdr_east, runoff[r,:], scen[r]) # ******************* End ditch depth scenario loop********************* #if outpara['figs']: susi_io.outfig(summer_dwt, co2_respi, growths-growths[0],spara['ditch depth'], relative_response, rounds) if outpara['figs']: susi_io.fig_stand_growth_node(rounds, ageSim, start_yr, end_yr, ageToVol, ageyrs, vols[:, :, 1:-1], spara['scenario name'], dwts) #These are for MESE-simulations only until return opt_strip = True #True: normal simulation, False: mese-simulations if opt_strip or kaista is None: fr = 1 to = -1 frw = 1 tow = -1 #Change yield volumes to standing volumes end_vols = yiToVol(end_vols) gr = np.array([end_vols[k] - end_vols[0] for k in range(rounds)]) gr = np.array([np.mean(gr[k][fr:to]) for k in range(rounds)]) cbt = np.array([c_bals_trees[k] - 0.0 for k in range(rounds)]) cbt = np.array([np.mean(cbt[k][fr:to]) / yrs for k in range(rounds)]) dcbt = np.array([c_bals_trees[k] - c_bals_trees[0] for k in range(rounds)]) dcbt = np.array([np.mean(dcbt[k][fr:to]) / yrs for k in range(rounds)]) cb = np.array([c_bals[k] - 0.0 for k in range(rounds)]) cb = np.array([np.mean(cb[k][fr:to]) for k in range(rounds)]) dcb = np.array([c_bals[k] - c_bals[0] for k in range(rounds)]) dcb = np.array([np.mean(dcb[k][fr:to]) for k in range(rounds)]) w = np.array([sdwt[k] - 0.0 for k in range(rounds)]) w = np.array([np.mean(w[k][fr:to]) for k in range(rounds)]) dw = np.array([sdwt[k] - sdwt[0] for k in range(rounds)]) dw = np.array([np.mean(dw[k][fr:to]) for k in range(rounds)]) v_end = np.array([end_vols[k] - 0.0 for k in range(rounds)]) v_end = np.array([np.mean(v_end[k][fr:to]) for k in range(rounds)]) logs = np.array(volToLogs(v_end)) pulp = np.array(volToPulp(v_end)) dv = np.array([v_end[k] - v_end[0] for k in range(rounds)]) dlogs = np.array([logs[k] - logs[0] for k in range(rounds)]) dpulp = np.array([pulp[k] - pulp[0] for k in range(rounds)]) bms = np.array([biomass_gr[k] for k in range(rounds)]) bms = np.array([np.mean(bms[k][fr:to]) for k in range(rounds)]) annual_runoff = np.sum(runoff, axis=1) * 1000. / yrs drunoff = [annual_runoff[k] - annual_runoff[0] for k in range(rounds)] nout = [np.mean(Nout[k]) / yrs for k in range(rounds)] pout = [np.mean(Pout[k]) / yrs for k in range(rounds)] kout = [np.mean(Kout[k]) / yrs for k in range(rounds)] hmwdocout = [np.mean(HMWDOCout[k]) / yrs for k in range(rounds)] lmwdocout = [np.mean(LMWDOCout[k]) / yrs for k in range(rounds)] nrelease = [np.mean(Nrelease[k]) / yrs for k in range(rounds)] prelease = [np.mean(Prelease[k]) / yrs for k in range(rounds)] krelease = [np.mean(Krelease[k]) / yrs for k in range(rounds)] ch4release = [np.mean(CH4release[k]) / yrs for k in range(rounds)] return vol[0], v_end, (v_end-vol[0])/yrs, cbt, dcbt, cb, dcb, w, dw, logs, pulp, dv, dlogs, dpulp, yrs, bms/yrs, \ nout, pout, kout, hmwdocout, annual_runoff, nrelease, prelease, krelease, ch4release
def run_cal(self, gsref=None, f=None, kmelt=None, wmax=None, wmaxsnow=None, rw=None, rwmin=None): """runs CanopyGrid for varying parameter values""" """ initialize CanopyGrid: identical to pure Spathy except that now self.params are used """ #adjust parameter dicts for new parameter guesses if gsref: self.params['gsref'] = gsref if f: self.params['f'] = f # if kmelt: self.params['kmelt'] = kmelt # if wmax: self.params['wmax'] = wmax # if wmaxsnow: self.params['wmaxsnow'] = wmaxsnow if rw: self.params['rw'] = rw if rwmin: self.params['rwmin'] = rwmin # initialize canopygri cpy = CanopyGrid(self.params, cmask=np.ones(3)) # print cpy.X # print 'run : ' +str(self.simcount) self.simcount += 1 res = { 'ET0': [None] * self.Nsteps, 'ET1': [None] * self.Nsteps, 'ET2': [None] * self.Nsteps } for k in range(0, self.Nsteps): # print 'k='+str(k) # forcing doy = self.FORC['doy'].iloc[k] ta = self.FORC['Ta'].iloc[k] vpd = self.FORC['VPD'].iloc[k] rg = self.FORC['Rg'].iloc[k] par = self.FORC['Par'].iloc[k] prec = self.FORC['Prec'].iloc[k] rew = self.FORC['Rew'].iloc[k] rew2 = np.ones(3) rew2[0] = rew # run CanopyGrid potinf, trfall, interc, evap, et, mbe = cpy.run_CanopyGrid( doy, self.dt, ta, prec, rg, par, vpd, Rew=rew2, P=101300.0) res['ET0'][k] = et[0] res['ET1'][k] = et[1] res['ET2'][k] = et[2] # return simulated, take same 'samples' as in evaldata res = pd.DataFrame.from_dict(res) res.index = self.FORC.index simudata = [ res['ET0'].ix[self.evaldates[0]].values.tolist(), res['ET1'].ix[self.evaldates[1]].values.tolist(), res['ET2'].ix[self.evaldates[2]].values.tolist() ] return simudata #,res
def run_cal(self, paramset=None, gsref=None, f=None, kmelt=None, wmax=None, wmaxsnow=None, rw=None, rwmin=None, resu=False): """runs CanopyGrid for varying parameter values""" """ initialize CanopyGrid: identical to pure Spathy except that now self.params are used """ #adjust parameter dicts for new parameter guesses if paramset: if self.Evalvar is 'ET': self.params['gsref'] = paramset[0] self.params['f'] = paramset[1] self.params['rw'] = paramset[2] self.params['rwmin'] = paramset[3] if self.Evalvar is 'Trfall': self.params['Wmax'] = paramset[0] if self.Evalvar is 'SWE': self.params['Wmaxsnow'] = paramset[0] self.params['kmelt'] = paramset[1] # self.params['kmt'] = paramset[1] # self.params['kmr'] = paramset[2] else: if gsref: self.params['gsref'] = gsref if f: self.params['f'] = f if kmelt: self.params['kmelt'] = kmelt if wmax: self.params['wmax'] = wmax if wmaxsnow: self.params['wmaxsnow'] = wmaxsnow if rw: self.params['rw'] = rw if rwmin: self.params['rwmin'] = rwmin # initialize canopygrid; make needs at least 2 cells; # later values extracted from [0] cpy = CanopyGrid(self.params, cmask=np.ones(1)) # print cpy.X # print 'run : ' +str(self.simcount) self.simcount += 1 res = { 'ET': [None] * self.Nsteps, 'Evap': [None] * self.Nsteps, 'Trfall': [None] * self.Nsteps, 'SWE': [None] * self.Nsteps, 'fQ': [None] * self.Nsteps, 'fD': [None] * self.Nsteps, 'fRew': [None] * self.Nsteps, 'fPheno': [None] * self.Nsteps, 'X': [None] * self.Nsteps, 'Mbe': [None] * self.Nsteps } for k in range(0, self.Nsteps): #print 'k='+str(k) doy = self.FORC['doy'].iloc[k] ta = self.FORC['Ta'].iloc[k] vpd = self.FORC['VPD'].iloc[k] rg = self.FORC['Rg'].iloc[k] par = self.FORC['Par'].iloc[k] prec = self.FORC['Prec'].iloc[k] rew = self.FORC['Rew'].iloc[k] # run CanopyGrid potinf, trfall, interc, evap, et, mbe = cpy.run_CanopyGrid( doy, self.dt, ta, prec, rg, par, vpd, Rew=rew, P=101300.0) res['ET'][k] = et[0] res['Evap'][k] = evap[0] res['Trfall'][k] = trfall[0] res['SWE'][k] = cpy.SWE[0] res['X'][k] = cpy.X res['Mbe'][k] = mbe # res['fQ'][k]=fQ; res['fD'][k]=fD; res['fRew'][k]=fRew; res['fPheno'][k]=fPheno; # return simulated, take same 'samples' as in evaldata res = pd.DataFrame.from_dict(res) res.index = self.FORC.index # simudata=[res['ET'].ix[self.evaldates].values.tolist(), res['TF'].ix[self.evaldates[1]].values.tolist(), # res['SWE'].ix[self.evaldates[2]].values.tolist() ] if resu: return res, cpy else: # check what to return simudata = res[self.Evalvar].ix[self.evaldates].values.tolist() return simudata
class SpaFHy_point(): """ SpaFHy for point-scale simulation. Couples Canopygrid and Bucketdrid -classes """ def __init__(self, pgen, pcpy, pbu, FORC, cmask=np.ones(1), cpy_outputs=True, bu_outputs=True): """ creates SpaFHy_point -object Args: pgen - parameter dict pcpy - canopy parameter dict pbu - bucket model parameter dict FORC - forcing data (pd.DataFrame) cmask - catchment mask; in case multiple cells are simulated. np.array cpy_outputs - saves canopy outputs within self.cpy.results bu_outputs - saves bucket outputs within self.cpy.results Returns: object """ self.dt = pgen['dt'] # s self.id = pgen['catchment_id'] self.spinup_end = pgen['spinup_end'] self.pgen = pgen self.FORC = FORC self.Nsteps = len(self.FORC) """--- initialize CanopyGrid and BucketGrid ---""" # sub-models require states as np.array; so multiply with 'cmask' cstate = pcpy['state'].copy() for key in cstate.keys(): cstate[key] *= cmask self.cpy = CanopyGrid(pcpy, cstate, outputs=cpy_outputs) for key in pbu.keys(): pbu[key] *= cmask self.bu = BucketGrid(pbu, outputs=bu_outputs) def _run(self, fstep, Nsteps, soil_feedbacks=True, results=False): """ Runs SpaFHy_point IN: fstep - index of starting point [int] Nsteps - number of timesteps [int] soil_feedbacks - False sets REW and REE = 1 and ignores feedback from soil state to Transpi and Efloor results - True returns results dict OUT: updated state, optionally saves results within self.cpy.results and self.bu.results and/or returns at each timestep as dict 'res' """ dt = self.dt for k in range(fstep, fstep + Nsteps): print('k=' + str(k)) # forcing doy = self.FORC['doy'].iloc[k] ta = self.FORC['T'].iloc[k] vpd = self.FORC['VPD'].iloc[k] rg = self.FORC['Rg'].iloc[k] par = self.FORC['Par'].iloc[k] prec = self.FORC['Prec'].iloc[k] co2 = self.FORC['CO2'].iloc[k] u = self.FORC['U'].iloc[k] if not np.isfinite(u): u = 2.0 if soil_feedbacks: beta0 = self.bu.Ree # affects surface evaporation rew0 = self.bu.Rew # affects transpiration else: beta0 = 1.0 rew0 = 1.0 # run CanopyGrid potinf, trfall, interc, evap, et, transpi, efloor, mbe = \ self.cpy.run_timestep(doy, dt, ta, prec, rg, par, vpd, U=u, CO2=co2, beta=beta0, Rew=rew0, P=101300.0) # run BucketGrid water balance infi, infi_ex, drain, tr, eva, mbes = self.bu.watbal( dt=dt, rr=1e-3 * potinf, tr=1e-3 * transpi, evap=1e-3 * efloor, retflow=0.0) if results == True: # returns fluxes and and state in dictionary res = { 'Wliq': self.bu.Wliq, 'Wliq_top': self.bu.Wliq_top, 'Evap': evap, 'Transpi': transpi, 'Efloor': efloor, 'Interc': interc, 'Drain': 1e3 * drain, 'Infil': 1e3 * infi, 'Qs': 1e3 * infi_ex } return res
def __init__(self, pgen, pcpy, pbu, ptop, gisdata, ave_outputs=False, cpy_outputs=False, bu_outputs=False, top_outputs=False, flatten=False): self.dt = pgen['dt'] # s self.id = pgen['catchment_id'] self.spinup_end = pgen['spinup_end'] self.pgen = pgen """ read forcing data and catchment runoff file """ FORC = read_FMI_weather(pgen['catchment_id'], pgen['start_date'], pgen['end_date'], sourcefile=pgen['forcing_file']) FORC['Prec'] = FORC['Prec'] / self.dt # mms-1 self.FORC = FORC self.Nsteps = len(FORC) # read runoff measurements if pgen['runoff_file'] is not '': self.Qmeas = read_SVE_runoff(pgen['catchment_id'], pgen['start_date'], pgen['end_date'], pgen['runoff_file']) else: self.Qmeas = None self.GisData = gisdata self.cmask = self.GisData['cmask'] self.gridshape = np.shape(self.cmask) cmask= self.cmask.copy() lai_conif = gisdata['LAI_conif'].copy() lai_decid = gisdata['LAI_decid'].copy() hc = gisdata['hc'].copy() cf = gisdata['cf'].copy() sdata = gisdata['soil'].copy() flowacc = gisdata['flowacc'].copy() slope = gisdata['slope'].copy() """ flatten=True omits cells outside catchment """ if flatten: ix = np.where(np.isfinite(cmask)) cmask = cmask[ix].copy() lai_conif = lai_conif[ix].copy() lai_decid = lai_decid[ix].copy() hc = hc[ix].copy() cf = cf[ix].copy() sdata = sdata[ix].copy() flowacc = flowacc[ix].copy() slope = slope[ix].copy() self.ix = ix # indices """--- initialize CanopyGrid and BucketGrid ---""" if pgen['spatial_cpy'] == 'no': # spatially constant stand properties self.cpy = CanopyGrid(pcpy, cmask=cmask, outputs=cpy_outputs) else: self.cpy = CanopyGrid(pcpy, lai_conif=lai_conif, lai_decid = lai_decid, cf=cf, hc=hc, cmask=cmask, outputs=cpy_outputs) if pgen['spatial_soil'] == 'no': # spatially constant soil properties self.bu = BucketGrid(pbu, cmask=cmask, outputs=bu_outputs) else: self.bu = BucketGrid(pbu, soiltypefile=pgen['soil_file'], sdata=sdata, outputs=bu_outputs) """ --- initialize homogenous topmodel --- """ self.top=Topmodel(ptop, self.GisData['cellsize']**2, cmask, flowacc, slope, outputs=top_outputs) if ave_outputs: self.results = {'SWE': np.ones(self.Nsteps)*np.NaN, 'ET': np.ones(self.Nsteps)*np.NaN, 'Tr': np.ones(self.Nsteps)*np.NaN, 'Prec': np.ones(self.Nsteps)*np.NaN, 'Ef': np.ones(self.Nsteps)*np.NaN, 'Evap': np.ones(self.Nsteps)*np.NaN, 'Qt': np.ones(self.Nsteps)*np.NaN, 'S': np.ones(self.Nsteps)*np.NaN, 'fsat': np.ones(self.Nsteps)*np.NaN, 'Wliq': np.ones(self.Nsteps)*np.NaN, 'Rg': np.ones(self.Nsteps)*np.NaN, 'Ta': np.ones(self.Nsteps)*np.NaN, 'VPD': np.ones(self.Nsteps)*np.NaN, 'Drain': np.ones(self.Nsteps)*np.NaN, 'time': self.FORC.index, 'Qmeas': self.Qmeas }
class SpatHy(): """ SpatHy model class """ def __init__(self, pgen, pcpy, pbu, ptop, gisdata, ave_outputs=False, cpy_outputs=False, bu_outputs=False, top_outputs=False, flatten=False): self.dt = pgen['dt'] # s self.id = pgen['catchment_id'] self.spinup_end = pgen['spinup_end'] self.pgen = pgen """ read forcing data and catchment runoff file """ FORC = read_FMI_weather(pgen['catchment_id'], pgen['start_date'], pgen['end_date'], sourcefile=pgen['forcing_file']) FORC['Prec'] = FORC['Prec'] / self.dt # mms-1 self.FORC = FORC self.Nsteps = len(FORC) # read runoff measurements if pgen['runoff_file'] is not '': self.Qmeas = read_SVE_runoff(pgen['catchment_id'], pgen['start_date'], pgen['end_date'], pgen['runoff_file']) else: self.Qmeas = None self.GisData = gisdata self.cmask = self.GisData['cmask'] self.gridshape = np.shape(self.cmask) cmask= self.cmask.copy() lai_conif = gisdata['LAI_conif'].copy() lai_decid = gisdata['LAI_decid'].copy() hc = gisdata['hc'].copy() cf = gisdata['cf'].copy() sdata = gisdata['soil'].copy() flowacc = gisdata['flowacc'].copy() slope = gisdata['slope'].copy() """ flatten=True omits cells outside catchment """ if flatten: ix = np.where(np.isfinite(cmask)) cmask = cmask[ix].copy() lai_conif = lai_conif[ix].copy() lai_decid = lai_decid[ix].copy() hc = hc[ix].copy() cf = cf[ix].copy() sdata = sdata[ix].copy() flowacc = flowacc[ix].copy() slope = slope[ix].copy() self.ix = ix # indices """--- initialize CanopyGrid and BucketGrid ---""" if pgen['spatial_cpy'] == 'no': # spatially constant stand properties self.cpy = CanopyGrid(pcpy, cmask=cmask, outputs=cpy_outputs) else: self.cpy = CanopyGrid(pcpy, lai_conif=lai_conif, lai_decid = lai_decid, cf=cf, hc=hc, cmask=cmask, outputs=cpy_outputs) if pgen['spatial_soil'] == 'no': # spatially constant soil properties self.bu = BucketGrid(pbu, cmask=cmask, outputs=bu_outputs) else: self.bu = BucketGrid(pbu, soiltypefile=pgen['soil_file'], sdata=sdata, outputs=bu_outputs) """ --- initialize homogenous topmodel --- """ self.top=Topmodel(ptop, self.GisData['cellsize']**2, cmask, flowacc, slope, outputs=top_outputs) if ave_outputs: self.results = {'SWE': np.ones(self.Nsteps)*np.NaN, 'ET': np.ones(self.Nsteps)*np.NaN, 'Tr': np.ones(self.Nsteps)*np.NaN, 'Prec': np.ones(self.Nsteps)*np.NaN, 'Ef': np.ones(self.Nsteps)*np.NaN, 'Evap': np.ones(self.Nsteps)*np.NaN, 'Qt': np.ones(self.Nsteps)*np.NaN, 'S': np.ones(self.Nsteps)*np.NaN, 'fsat': np.ones(self.Nsteps)*np.NaN, 'Wliq': np.ones(self.Nsteps)*np.NaN, 'Rg': np.ones(self.Nsteps)*np.NaN, 'Ta': np.ones(self.Nsteps)*np.NaN, 'VPD': np.ones(self.Nsteps)*np.NaN, 'Drain': np.ones(self.Nsteps)*np.NaN, 'time': self.FORC.index, 'Qmeas': self.Qmeas } def _run(self, fstep, Nsteps, calibr=False, ncf=False, sve=False): """ Runs Spathy IN: fstep - index of starting point [int] Nsteps - number of timesteps [int] calibr - set True for parameter optimization, returns Qmod ncf - netCDF -file handle, for outputs OUT: res - modeled streamflow at catchment outlet [mm/d] """ dt = self.dt # for calibration run, return res if calibr: res={'Qm':[None]*self.Nsteps} # 'RR':[None]*self.Nsteps, 'ET':[None]*self.Nsteps, 'Inter':[None]*self.Nsteps, 'Mbet':[None]*self.Nsteps} print('M', self.top.M, 'to', self.top.To) RR = 0.0 # initial value for recharge [m] for k in range(fstep, fstep + Nsteps): #print 'k=' + str(k) # forcing doy = self.FORC['doy'].iloc[k] ta = self.FORC['T'].iloc[k] vpd = self.FORC['VPD'].iloc[k] + eps rg = self.FORC['Rg'].iloc[k] par = self.FORC['Par'].iloc[k] + eps prec = self.FORC['Prec'].iloc[k] co2 = self.FORC['CO2'].iloc[k] u = 2.0 # beta0 = self.bu.WatStoTop / self.bu.MaxStoTop # beta0 = self.bu.relcond # run Topmodel, take recharge RR from prev. timestep # mean baseflow [m], none, returnflow grid [m], sat.area [-] qb, _, qr, fsat = self.top.run_timestep(RR) # run CanopyGrid potinf, trfall, interc, evap, et, transpi, efloor, mbe = \ self.cpy.run_timestep(doy, dt, ta, prec, rg, par, vpd, U=u, CO2=co2, beta=self.bu.Ree, Rew=self.bu.Rew, P=101300.0) # run BucketGrid water balance infi, infi_ex, drain, tr, eva, mbes, rflow_to_rootzone = self.bu.watbal(dt=dt, rr=1e-3*potinf, tr=1e-3*transpi, evap=1e-3*efloor, retflow=qr) # catchment average [m per unit area] saturation excess --> goes to stream # as surface runoff qs = np.nansum(infi_ex)*self.top.CellArea / self.top.CatchmentArea # catchment average ground water recharge [m per unit area] RR = np.nansum(drain)*self.top.CellArea / self.top.CatchmentArea """ outputs """ if hasattr(self.top, 'results'): self.top.results['Qt'].append(qb + qs + eps) # total runoff if ncf: # writes to netCDF -file at every timestep; bit slow - should # accumulate into temporary variables and save every 10 days? # canopygrid # ncf['cpy']['W'][k,:,:] = self._to_grid(self.cpy.W) ncf['cpy']['SWE'][k,:,:] = self._to_grid(self.cpy.SWE) # ncf['cpy']['Trfall'][k,:,:] = self._to_grid(trfall) # ncf['cpy']['Potinf'][k,:,:] = self._to_grid(potinf) ncf['cpy']['ET'][k,:,:] = self._to_grid(et) ncf['cpy']['Transpi'][k,:,:] = self._to_grid(transpi) ncf['cpy']['Efloor'][k,:,:] = self._to_grid(efloor) ncf['cpy']['Evap'][k,:,:] = self._to_grid(evap) # ncf['cpy']['Inter'][k,:,:] = self._to_grid(interc) # ncf['cpy']['Mbe'][k,:,:] = self._to_grid(mbe) # bucketgrid ncf['bu']['Drain'][k,:,:] = 1e3 * self._to_grid(drain) ncf['bu']['Infil'][k,:,:] = 1e3 * self._to_grid(infi) ncf['bu']['Wliq'][k,:,:] = self._to_grid(self.bu.Wliq) ncf['bu']['Wliqtop'][k,:,:] = self._to_grid(self.bu.Wliq_top) # root zone rescahrge from return flow. ncf['bu']['RetFlow'][k,:,:] = 1e3 * self._to_grid(rflow_to_rootzone) # ncf['bu']['Wsto'][k,:,:] = self._to_grid(self.bu.WatSto) # ncf['bu']['PondSto'][k,:,:] = self._to_grid(self.bu.PondSto) # ncf['bu']['Mbe'][k,:,:] = self._to_grid(mbes) # topmodel ncf['top']['Qb'][k] = qb ncf['top']['Qs'][k] = qs ncf['top']['Qt'][k] = qb + qs # total runoff ncf['top']['R'][k] = RR ncf['top']['fsat'][k] = fsat ncf['top']['S'][k] = self.top.S if calibr: # calibration run, return only streamflow res['Qm'][k] = 1e3*(qb + qs) if hasattr(self, 'results'): self.results['SWE'][k] = np.nanmean(self.cpy.SWE) self.results['Wliq'][k] = np.nanmean(self.bu.Wliq) self.results['ET'][k] = np.nanmean(transpi + efloor + evap) self.results['Tr'][k] = np.nanmean(transpi) self.results['Ef'][k] = np.nanmean(efloor) self.results['Evap'][k] = np.nanmean(evap) self.results['Drain'][k] = 1e3*RR self.results['Qt'][k] = 1e3*(qb + qs) self.results['S'][k] = self.top.S self.results['fsat'][k] = fsat self.results['Prec'][k] = prec*self.dt self.results['Rg'][k] = rg self.results['Ta'][k] = ta self.results['VPD'][k] = vpd # end of time loop if hasattr(self, 'results'): res = pd.DataFrame.from_dict(self.results) res.index = self.FORC.index res = res[res.index > self.spinup_end] self.results = res if ncf: ncf.close() # close netCDF-file if calibr: # return streamflow in dataframe res = pd.DataFrame.from_dict(res) res.index = self.FORC.index return res def _to_grid(self, x): """ converts variable x back to original grid for NetCDF outputs """ a = np.full(self.gridshape, np.NaN) a[self.ix] = x return a