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
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