def swin1D(pob, sob, tob, stat, dates, index): # many arrays transposed """ toposcale surface pressure using hypsometric equation - move to own class index: index of station array (numeric) """ g = 9.81 R = 287.05 # Gas constant for dry air. tz = 0 # ERA5 is always utc0, used to compute sunvector ztemp = pob.z[:, index, :].T Ttemp = pob.t[:, index, :].T statz = stat.ele[index] * g dz = ztemp.T - statz # transpose not needed but now consistent with CGC surface pressure equations psf = [] # loop through timesteps #for i in range(starti,endi): for i in range(0, dates.size): # # find overlying layer thisp = dz[:, i] == np.min(dz[:, i][dz[:, i] > 0]) # booleen indexing T1 = Ttemp[i, thisp] z1 = ztemp[i, thisp] p1 = pob.levels[thisp] * 1e2 #Convert to Pa. Tbar = np.mean([T1, tob.t[i, index]], axis=0) """ Hypsometric equation.""" psf.append(p1 * np.exp((z1 - statz) * (g / (Tbar * R)))) psf = np.array(psf).squeeze() ## Specific humidity routine. # mrvf=0.622.*vpf./(psf-vpf); #Mixing ratio for water vapor at subgrid. # qf=mrvf./(1+mrvf); # Specific humidity at subgrid [kg/kg]. # fout.q(:,:,n)=qf; """ Maybe follow Dubayah's approach (as in Rittger and Girotto) instead for the shortwave downscaling, other than terrain effects. """ """ Height of the "grid" (coarse scale)""" Zc = sob.z[:, index] # should this be a single value or vector? """ toa """ SWtoa = sob.tisr[:, index] """ Downwelling shortwave flux of the "grid" using nearest neighbor.""" SWc = sob.ssrd[:, index] """Calculate the clearness index.""" kt = SWc / SWtoa #kt[is.na(kt)==T]<-0 # make sure 0/0 =0 #kt[is.infinite(kt)==T]<-0 # make sure 0/0 =0 kt[kt < 0] = 0 kt[kt > 1] = 0.8 #upper limit of kt kt = kt """ Calculate the diffuse fraction following the regression of Ruiz-Arias 2010 """ kd = 0.952 - 1.041 * np.exp(-1 * np.exp(2.3 - 4.702 * kt)) kd = kd """ Use this to calculate the downwelling diffuse and direct shortwave radiation at grid. """ SWcdiff = kd * SWc SWcdir = (1 - kd) * SWc SWcdiff = SWcdiff SWcdir = SWcdir """ Use the above with the sky-view fraction to calculate the downwelling diffuse shortwave radiation at subgrid. """ SWfdiff = stat.svf[index] * SWcdiff SWfdiff = np.nan_to_num(SWfdiff) # convert nans (night) to 0 """ Direct shortwave routine, modified from Joel. Get surface pressure at "grid" (coarse scale). Can remove this part once surface pressure field is downloaded, or just check for existance. """ ztemp = pob.z[:, index, :].T Ttemp = pob.t[:, index, :].T dz = ztemp.transpose() - sob.z[:, index] psc = [] for i in range(0, dz.shape[1]): #thisp.append(np.argmin(dz[:,i][dz[:,i]>0])) thisp = dz[:, i] == np.min(dz[:, i][dz[:, i] > 0]) z0 = sob.z[i, index] T0 = sob.t2m[i, index] T1 = Ttemp[i, thisp] z1 = ztemp[i, thisp] p1 = pob.levels[thisp] * 1e2 #Convert to Pa. Tbar = np.mean([T0, T1], axis=0) """ Hypsometric equation.""" psc.append(p1 * np.exp((z1 - z0) * (g / (Tbar * R)))) psc = np.array(psc).squeeze() #T1=Ttemp(thisp) #z1=ztemp(thisp) #p1=pob.levels(thisp)*1e2 #Convert to Pa. #Tbar=mean([T0 T1]) """compute julian dates""" jd = sg.to_jd(dates) """ Calculates a unit vector in the direction of the sun from the observer position. """ sunv = sg.sunvector(jd=jd, latitude=stat.lat[index], longitude=stat.lon[index], timezone=tz) """ Computes azimuth , zenith and sun elevation for each timestamp """ sp = sg.sunpos(sunv) sp = sp # Cosine of the zenith angle. sp.zen = sp.zen #sp.zen=sp.zen*(sp.zen>0) # Sun might be below the horizon. muz = np.cos(sp.zen) muz = muz # NB! psc must be in Pa (NOT hPA!). #if np.max(psc<1.5e3): # Obviously not in Pa #psc=psc*1e2 # Calculate the "broadband" absorption coefficient. Elevation correction # from Kris ka = (g * muz / (psc)) * np.log(SWtoa / SWcdir) #ka.set_fill_value(0) #ka = ka.filled() # set inf (from SWtoa/SWcdir at nigh, zero division) to 0 (night) ka[ka == -np.inf] = 0 ka[ka == np.inf] = 0 # Note this equation is obtained by inverting Beer's law, i.e. use #I_0=I_inf x exp[(-ka/mu) int_z0**inf rho dz] # Along with hydrostatic equation to convert to pressure coordinates then # solve for ka using p(z=inf)=0. # Now you can (finally) find the direct component at subgrid. SWfdir = SWtoa * np.exp(-ka * psf / (g * muz)) """ Then perform the terrain correction. [Corripio 2003 / rpackage insol port].""" """compute mean horizon elevation - why negative hor.el possible??? """ horel = (((np.arccos(np.sqrt(stat.svf[index])) * 180) / np.pi) * 2) - stat.slp[index] if horel < 0: horel = 0 meanhorel = horel """ normal vector - Calculates a unit vector normal to a surface defined by slope inclination and slope orientation. """ nv = sg.normalvector(slope=stat.slp[index], aspect=stat.asp[index]) """ Method 1: Computes the intensity according to the position of the sun (sunv) and dotproduct normal vector to slope. From corripio r package """ dotprod = np.dot(sunv, np.transpose(nv)) dprod = dotprod.squeeze() dprod[dprod < 0] = 0 #negative indicates selfshading dprod = dprod """Method 2: Illumination angles. Dozier""" saz = sp.azi cosis = muz * np.cos(stat.slp[index]) + np.sin( sp.zen) * np.sin(stat.slp[index]) * np.cos( sp.azi - stat.asp[index] ) # cosine of illumination angle at subgrid. cosic = muz # cosine of illumination angle at grid (slope=0). """ SUN ELEVATION below hor.el set to 0 - binary mask """ selMask = sp.sel selMask[selMask < horel] = 0 selMask[selMask > 0] = 1 selMask = selMask """ derive incident radiation on slope accounting for self shading and cast shadow and solar geometry BOTH formulations seem to be broken """ #SWfdirCor=selMask*(cosis/cosic)*SWfdir SWfdirCor = selMask * dprod * SWfdir SWfglob = SWfdiff + SWfdirCor return SWfglob """
def swin2D(pob,sob,tob, stat, dates): ''' main edit over standard function for points: - 3d tob,sob reduce to 2D (reshape) ''' timesize=len(dates) statsize=len(stat.ele) """ toposcale surface pressure using hypsometric equation - move to own class """ g=9.81 R=287.05 # Gas constant for dry air. #ztemp = pob.z # geopotential height b = np.transpose(a, (2, 0, 1)) ztemp = np.transpose(pob.z, (2, 0, 1)) # pob is originally ordered levels,stations/cells,time #Ttemp = pob.t Ttemp = np.transpose(pob.t, (2, 0, 1))# pob is originally ordered levels,stations/cells,time statz = np.array(stat.ele)*g #dz=ztemp.transpose()-statz[None,:,None] # transpose not needed but now consistent with CGC surface pressure equations dz=ztemp-statz # dimensions of dz : time, levels, stations # set all levels below surface to very big number so they canot be found by min newdz=dz newdz[newdz<0]=999999 psf =np.zeros( (dates.size, statz.shape[0]) ) # reshape tob.t here tob.tr=tob.t.reshape(tob.t.shape[0]*tob.t.shape[1], tob.t.shape[2], order='F') tob.trT=tob.tr.T # transpose to get right order # loop through timesteps for i in range(0,dates.size): # find overlying layer thisp = dz[i,:,:]==np.min(newdz[i,:,:],axis=0) # thisp is a booleen matrix of levels x stations with true indicating overlying plevel over station surface ele # flatten to 1 dimension order='Fortran' or row major thispVec =thisp.reshape(thisp.size,order='F') TtempVec = Ttemp.reshape(Ttemp.shape[0], Ttemp.shape[1]*Ttemp.shape[2], order='F') ztempVec = ztemp.reshape(ztemp.shape[0], ztemp.shape[1]*ztemp.shape[2], order='F') # booleen indexing to find temp and geopotential that correspond to lowesest overlying layer T1=TtempVec[i,thispVec] z1=ztempVec[i,thispVec] p1=np.tile(pob.levels[::-1],statz.shape[0])[thispVec]*1e2 #Convert to Pa. Reverse levels to ensure low ele (hig pressure) to high elel (low pressure) Tbar=np.mean([T1, tob.trT[i, :]],axis=0) # temperature midway between surface (toposcale T) and loweset overlying level (T1) """ Hypsometric equation.""" #P1 is above surface is this correct? Yes! psf[i,:]=(p1*np.exp((z1-statz)*(g/(Tbar*R)))) # exponent is positive ie increases pressure as surface is lower than pressure level """ Maybe follow Dubayah's approach (as in Rittger and Girotto) instead for the shortwave downscaling, other than terrain effects. """ """ Height of the "grid" (coarse scale)""" Zc=sob.z.reshape(sob.z.shape[0]*sob.z.shape[1], sob.z.shape[2]).T # reshape and transpose to remove dimension and make broadcastable """ toa """ SWtoa = sob.tisr.reshape(sob.tisr.shape[0]*sob.tisr.shape[1], sob.tisr.shape[2]).T """ Downwelling shortwave flux of the "grid" using nearest neighbor.""" SWc=sob.ssrd.reshape(sob.ssrd.shape[0]*sob.ssrd.shape[1], sob.ssrd.shape[2]).T """Calculate the clearness index.""" kt=SWc/SWtoa #kt[is.na(kt)==T]<-0 # make sure 0/0 =0 #kt[is.infinite(kt)==T]<-0 # make sure 0/0 =0 kt[kt<0]=0 kt[kt>1]=0.8 #upper limit of kt kt=kt """ Calculate the diffuse fraction following the regression of Ruiz-Arias 2010 """ kd=0.952-1.041*np.exp(-1*np.exp(2.3-4.702*kt)) kd = kd """ Use this to calculate the downwelling diffuse and direct shortwave radiation at grid. """ SWcdiff=kd*SWc SWcdir=(1-kd)*SWc SWcdiff=SWcdiff SWcdir=SWcdir """ Use the above with the sky-view fraction to calculate the downwelling diffuse shortwave radiation at subgrid. """ SWfdiff=SWcdiff SWfdiff = np.nan_to_num(SWfdiff) # convert nans (night) to 0 """ Direct shortwave routine, modified from Joel. Get surface pressure at "grid" (coarse scale). Can remove this part once surface pressure field is downloaded, or just check for existance. """ ztemp = np.transpose(pob.z, (0, 2, 1)) Ttemp = np.transpose(pob.t, (0, 2, 1)) dz=ztemp-Zc # dimensions of dz : levels, time, stations # set all levels below surface to very big number so they canot be found by min newdz=dz newdz[newdz<0]=999999 psc =np.zeros( (dates.size, statz.shape[0]) ) for i in range(0,dates.size): #thisp.append(np.argmin(dz[:,i][dz[:,i]>0])) # find overlying layer thisp = dz[:,i,:]==np.min(newdz[:,i,:],axis=0) # thisp is a booleen matrix of levels x stations with true indicating overlying plevel over station surface ele !! time index in middle this time!!! z0 = Zc[i,:] T0 = sob.t2m.reshape(sob.t2m.shape[0]*sob.t2m.shape[1], sob.t2m.shape[2]).T[i,:] # flatten to 1 dimension order='Fortran' or row major thispVec =thisp.reshape(thisp.size,order='F') TtempVec = Ttemp.reshape(Ttemp.shape[1], Ttemp.shape[0]*Ttemp.shape[2], order='F') # !! order of permutations is different from pressure at finegrid routine (time is middle dimension) ztempVec = ztemp.reshape(ztemp.shape[1], ztemp.shape[0]*ztemp.shape[2], order='F')# !! order of permutations is different from pressure at finegrid routine (time is middle dimension) # booleen indexing to find temp and geopotential that correspond to lowesest overlying layer T1=TtempVec[i,thispVec] z1=ztempVec[i,thispVec] p1=np.tile(pob.levels[::-1],statz.shape[0])[thispVec]*1e2 #Convert to Pa. Tbar=np.mean([T0, T1],axis=0) """ Hypsometric equation.""" psc[i,:] = (p1*np.exp((z1-z0)*(g/(Tbar*R)))) """compute julian dates""" jd= sg.to_jd(dates) """ Calculates a unit vector in the direction of the sun from the observer position. """ svx,svy,svz = sg.sunvectorMD(jd, stat.lat, stat.lon, stat.tz,statsize,timesize) sp=sg.sunposMD(svx,svy,svz) # Cosine of the zenith angle. #sp.zen=sp.zen*(sp.zen>0) # Sun might be below the horizon. muz=np.cos(sp.zen) muz = muz # NB! psc must be in Pa (NOT hPA!). # Calculate the "broadband" absorption coefficient. Elevation correction ka=(g*muz/(psc))*np.log(SWtoa/SWcdir) ka = np.nan_to_num(ka) # Now you can (finally) find the direct component at subgrid. SWfdir=SWtoa*np.exp(-ka*psf/(g*muz)) SWfdirCor=SWfdir #*dprod SWfglob = SWfdiff+ SWfdirCor print(" %f minutes for VECTORISED interpolation %s" % (round((time.time()/60 - start_time/60),2),"swin") ) return SWfglob
def main(coords, eraDir, outDir, startDT, endDT, startIndex): print(startDT) print(endDT) g = 9.81 # geopotential constant tz = 0 # timezone always utc0 for era5 data myyear = startDT.split('-')[0] zp_file = eraDir + "/PLEV_geopotential_" + myyear + ".nc" plevDict = { eraDir + "/PLEV_temperature_" + myyear + ".nc": "t", eraDir + "/PLEV_u_component_of_wind_" + myyear + ".nc": "u", eraDir + "/PLEV_v_component_of_wind_" + myyear + ".nc": "v", eraDir + "/PLEV_relative_humidity_" + myyear + ".nc": "r" } surfDict = { eraDir + "/SURF_2m_temperature_" + myyear + ".nc": "t2m", eraDir + "/SURF_2m_dewpoint_temperature_" + myyear + ".nc": "d2m", eraDir + "/SURF_geopotential_" + myyear + ".nc": "z", eraDir + "/SURF_surface_solar_radiation_downwards_" + myyear + ".nc": "ssrd", eraDir + "/SURF_surface_thermal_radiation_downwards_" + myyear + ".nc": "strd", eraDir + "/SURF_Total precipitation_" + myyear + ".nc": "tp", # removed _ eraDir + "/SURF_TOA incident solar radiation_" + myyear + ".nc": "tisr" } # read in lispoints lpin = pd.read_csv(coords, header=None) # make out path for results out = outDir if not os.path.exists(out): os.makedirs(out) # time stuff f = nc.Dataset(zp_file) nctime = f.variables['time'] dtime = pd.to_datetime( nc.num2date(nctime[:], nctime.units, calendar="standard", only_use_cftime_datetimes=False, only_use_python_datetimes=True)) if (np.array(np.where(dtime == startDT)).size == 0): sys.exit("SYSTEMEXIT:Start date not in netcdf, end of timeseries") starti = np.where( dtime == startDT)[0].item() # so can run on a single timestep print(endDT) if (np.array(np.where(dtime == endDT)).size == 0): sys.exit("SYSTEMEXIT: End date not in netcdf, end of timeseries") endi = np.where(dtime == endDT)[0].item() year = dtime.year[0] # compute timestep before we cut timeseries a = dtime[2] - dtime[1] step = a.seconds stephr = step / (60 * 60) # extract timestep dtime = dtime[starti:endi, ] print(("Running timestep " + str(startDT))) #=============================================================================== # tscale3d - 3D interpolation of pressure level fields #=============================================================================== ele = lpin.iloc[:, 2] #[:,3] lats = lpin.iloc[:, 0] #[:,2] #[s['lat'] for s in stations] lons = lpin.iloc[:, 1] + 180 #[:,1] #[s['lon'] for s in stations] convert -180-180 to 0-360 lp = hp.Bunch(ele=ele, lat=lats, lon=lons) out_xyz_dem = np.asarray([lats, lons, ele * g], order="F").transpose() # init gtob object gtob = hp.Bunch(time=dtime) for plev in plevDict: t = nc.Dataset(plev) # key is filename varname = plevDict[plev] # value of key is par shortname z = nc.Dataset(zp_file) # init grid stack xdim = out_xyz_dem.shape[0] sa_vec = np.zeros(xdim) #names=1:n for timestep in range(starti, endi): """ Return original grid temperatures and geopotential of differnet pressure levels. The function are called by inLevelInterp() to get the input ERA5 values. Args: variable: Given interpolated climate variable timestep: Time need to be interpolated. Time is in interger (e.g. 0, 1, 2) Returns: gridT: Grid temperatures of different pressure levels. Retruned temperature are formated in [level, lat, lon] gridZ: Grid geopotential of different pressure levels. Retruned temperature are formated in [level, lat, lon] gridLon: Grid longitude of pressure level variables gridLat: Grid latitude of pressure level variables Example: gridT,gridZ,gridLat,gridLon=downscaling.gridValue('Temperature',0) """ gridT = t.variables[varname][timestep, :, :, :] gridZ = z.variables['z'][timestep, :, :, :] gridLat = t[ 'latitude'][:] # coords of grid centre https://confluence.ecmwf.int/display/CKB/ERA5%3A+What+is+the+spatial+reference gridLat = gridLat[::-1] # reverse to deal with ERA5 order gridLon = t[ 'longitude'][:] # coords of grid centre https://confluence.ecmwf.int/display/CKB/ERA5%3A+What+is+the+spatial+reference gridLev = t.variables['level'][::-1] #return gridT,gridZ,gridLat,gridLon """ This is a 2D interpolatation, and returns interpolated temperatures of different pressure levels. Interpolated domain is smaller than original domain - original (ERA5) domain should be one cell larger than expected point or grid domain. Args: gridT: Grid temperatures of different pressure levels. Retruned temperature are formated in [level, lat, lon] gridZ: Grid geopotential of different pressure levels. Retruned temperature are formated in [level, lat, lon] gridLat: Grid longitude of pressure level variables gridLon: Grid latitude of pressure level variables out_xyz: Given sites, which will be interpolated. Returns: t_interp: Interpolated temperatre of different pressure levels. The returned values are fomrated in [level, lat, lon] z_interp: Interpolated geopotential of different pressure levels. The returned values are fomrated in [level, lat, lon] Examples: downscaling = downscaling(dem, geop, sa, pl) out_xyz_dem, lats, lons, shape = downscaling.demGrid() out_xyz_sur = downscaling.surGrid(lats, lons, None) #interpolate 2-meter temperature surTa = downscaling.surTa(0, out_xyz_sur) #original ERA-I values gridT,gridZ,gridLat,gridLon = downscaling.gridValue(variable,0) #interpolate temperatures and geopotential of different pressure levels. t_interp, z_interp = downscaling.inLevelInterp(gridT,gridZ, gridLat,gridLon, out_xyz_dem) """ shape = gridT.shape #create array to hold interpolation resultes t_interp = np.zeros([shape[0], len(out_xyz_dem)]) z_interp = np.zeros([ shape[0], len(out_xyz_dem) ]) # HOW MANY TIMES DO WE REALLY NEED TO COMPUTE THIS? #temperatue and elevation interpolation 2d for i in range(shape[0]): ft = RegularGridInterpolator((gridLat, gridLon), gridT[i, ::-1, :], 'linear', bounds_error=False) fz = RegularGridInterpolator((gridLat, gridLon), gridZ[i, ::-1, :], 'linear', bounds_error=False) t_interp[i, :] = ft(out_xyz_dem[:, :2]) #temperature z_interp[i, :] = fz(out_xyz_dem[:, :2]) #elevation # invert pressure levels #t_interp = t_interp[::-1,:] #z_interp = z_interp[::-1,:] """This is a 1D interpoation. The function return interpolated upper air temperature at the given sites by interpolation between different pressure levels. """ ele = out_xyz_dem[:, 2] size = np.arange(out_xyz_dem.shape[0]) n = [bisect_left(z_interp[:, i], ele[i]) for i in size] n = [x + 1 if x == 0 else x for x in n] lowN = [l - 1 for l in n] upperT = t_interp[n, size] upperZ = z_interp[n, size] dG = upperT - t_interp[lowN, size] #<0 dG /= upperZ - z_interp[lowN, size] #<0 dG *= out_xyz_dem[:, 2] - upperZ #>0 dG += upperT pl_obs = dG sa_vec = np.column_stack((sa_vec, pl_obs)) # drop init row sa_vec = sa_vec[:, 1:] # rename to variable setattr(gtob, varname, sa_vec) print("t,r,u,v done") #=============================================================================== # tscale2d - Generates 2D interpolations from coarse (ERA5) to fine (1km) grid #=============================================================================== gsob = hp.Bunch(dtime=dtime) # init grid stack xdim = shape[0] sa_vec = np.zeros((xdim)) for surf in surfDict: t = nc.Dataset(surf) # key is filename varname = surfDict[surf] # value of key is par shortname z = nc.Dataset(zp_file) # could be outside loop # init grid stack xdim = out_xyz_dem.shape[0] sa_vec = np.zeros(xdim) #names=1:n for timestep in range(starti, endi): """ 2D interpolated of surface firelds. Args: timestep: Timestep of interpolation as an interger (index) stations: pandas dataframe of input station csv file (id,lon,lat,ele) var: surface variarble eg "ssrd" Returns: t_sp: 2D interpolation of ERA surface field to stations points Example: surTa = ds.surTaPoint(0, mystations, 't2m') """ # read in data from variable 'varname' in_v = t[varname][timestep, :, :] #geopotential in_v = in_v[:: -1, :] # reverse latitude dimension to agree with ascending 'lat' lat = t.variables['latitude'][:] lat = lat[::-1] # must be ascending for RegularGridInterpolator lon = t.variables['longitude'][:] # 2d interpolation f_sa = RegularGridInterpolator((lat, lon), in_v, 'linear', bounds_error=False) out_xy = np.asarray([lats, lons]).T sa_t = f_sa(out_xy) # stack timepoint to existing sa_vec = np.column_stack((sa_vec, sa_t)) # drop init row sa_vec = sa_vec[:, 1:] # Add to gsob setattr(gsob, varname, sa_vec) print("Made a sob") #=============================================================================== # Conversions #=============================================================================== """ convert tp from m/timestep (total accumulation over timestep) to rate in mm/h Args: step: timstep in seconds (era5=3600, ensemble=10800) Note: both EDA (ensemble 3h) and HRES (1h) are accumulated over the timestep and therefore treated here the same. https://confluence.ecmwf.int/display/CKB/ERA5+data+documentation """ tphrm = gsob.tp #/step*60*60 # convert metres per timestep (in secs) -> m/hour gsob.pmmhr = tphrm * 1000 # m/hour-> mm/hour """ Convert SWin from accumulated quantities in J/m2 to instantaneous W/m2 see: https://confluence.ecmwf.int/pages/viewpage.action?pageId=104241513 Args: step: timstep in seconds (era5=3600, ensemble=10800) Note: both EDA (ensemble 3h) and HRES (1h) are accumulated over the timestep and therefore treated here the same ie step=3600s (1h) https://confluence.ecmwf.int/display/CKB/ERA5+data+documentation """ gsob.strd = gsob.strd / 3600 gsob.ssrd = gsob.ssrd / 3600 gsob.tisr = gsob.tisr / 3600 gsob.gridEle = gsob.z[:, 0] / g gtob.prate = gsob.pmmhr #*pcf # mm/hour gtob.psum = gsob.tp * 1000 * stephr #pcf mm/timestep print("conversions done") #=============================================================================== # TopoSCALE #============================================================================= #=============================================================================== # Precip #=============================================================================== ''' Args: fineEle:ele vector from station dataframe sob: contains gridEle, tp and dtime ''' # convert TP to mm/hr # lookups = { # 1:0.35, # 2:0.35, # 3:0.35, # 4:0.3, # 5:0.25, # 6:0.2, # 7:0.2, # 8:0.2, # 9:0.2, # 10:0.25, # 11:0.3, # 12:0.35 # } # # Precipitation lapse rate, varies by month (Liston and Elder, 2006). # pfis = gsob.dtime.month.map(lookups) # pfis = np.repeat(pfis.values[:,None], lp.ele.size, axis=1) # dz=(lp.ele-gsob.gridEle)/1e3 # Elevation difference in kilometers between the fine and coarse surface. # pcf=(1+pfis.T*dz[:,None])/(1-pfis.T*dz[:,None])# Precipitation correction factor. #Pf=sob.pmmhr.T*lp #=============================================================================== # Longwave #=============================================================================== """Convert to RH (should be in a function). Following MG Lawrence DOI 10.1175/BAMS-86-2-225 """ A1 = 7.625 B1 = 243.04 C1 = 610.94 tc = gsob.t2m - 273.15 tdc = gsob.d2m - 273.15 tf = gtob.t - 273.15 # fout.T c = (A1 * tc) / (B1 + tc) RHc = 100 * np.exp((tdc * A1 - tdc * c - B1 * c) / (B1 + tdc)) # Inverting eq. 8 in Lawrence. """ Calculate saturation vapor pressure at grid and "subgrid" [also through function] using the Magnus formula.""" svpf = C1 * np.exp(A1 * tf / (B1 + tf)) svpc = C1 * np.exp(A1 * tc / (B1 + tc)) """ Calculate the vapor pressure at grid (c) and subgrid (f). """ vpf = gtob.r * svpf / 1e2 # RHf vpc = RHc * svpc / 1e2 """ Use the vapor pressure and temperature to calculate clear sky # emssivity at grid and subgrid. [also function] Konzelmann et al. 1994 Ta in kelvin """ x1 = 0.43 x2 = 5.7 cef = 0.23 + x1 * (vpf / gtob.t)**( 1 / x2) #Pretty sure the use of Kelvin is correct. cec = 0.23 + x1 * (vpc / gsob.t2m)**(1 / x2) """Diagnose the all sky emissivity at grid.""" sbc = 5.67e-8 aec = gsob.strd / (sbc * gsob.t2m**4) # need to constrain to 1 as original code? """ Calculate the "cloud" emissivity at grid, assume this is the same at subgrid. """ deltae = aec - cec """ Use the former cloud emissivity to compute the all sky emissivity at subgrid. """ aef = cef + deltae gtob.lwin = aef * sbc * gtob.t**4 print("Lwin done") #=============================================================================== # make a pob - required as input to swin routine. This object is data on each # pressure level interpolated to the fine grid ie has dimensions xy (finegrid) x plev #=============================================================================== f = nc.Dataset(zp_file) lev = f.variables['level'][:] var = 't' # ds = rc.t3d( pl=plevfile, dem =demfile) # out_xyz_dem, lats, lons, shape= ds.demGrid() xdim = lev.shape[0] ydim = out_xyz_dem.shape[0] t_interp_out = np.zeros((xdim, ydim)) z_interp_out = np.zeros((xdim, ydim)) # for timestep in range(starti,endi): # gridT,gridZ,gridLat,gridLon=ds.gridValue(var,timestep) # t_interp, z_interp = ds.inLevelInterp(gridT,gridZ, gridLat,gridLon,out_xyz_dem) # t_interp_out = np.dstack((t_interp_out, t_interp)) # z_interp_out = np.dstack((z_interp_out, z_interp)) #======================= all this can be done in first instance need to gen t/z_interp_out additionally tfile = list(plevDict.keys())[list(plevDict.values()).index('t')] t = nc.Dataset(tfile) # key is filename z = nc.Dataset(zp_file) # could be outside loop for timestep in range(starti, endi): gridT = t.variables['t'][timestep, :, :, :] gridZ = z.variables['z'][timestep, :, :, :] gridLat = t['latitude'][:] gridLat = gridLat[::-1] # reverse to deal with ERA5 order gridLon = t['longitude'][:] gridLev = t.variables['level'][::-1] #return gridT,gridZ,gridLat,gridLon shape = gridT.shape #create array to hold interpolation resultes t_interp = np.zeros([shape[0], len(out_xyz_dem)]) z_interp = np.zeros([ shape[0], len(out_xyz_dem) ]) # HOW MANY TIMES DO WE REALLY NEED TO COMPUTE THIS? #temperatue and elevation interpolation 2d for i in range(shape[0]): ft = RegularGridInterpolator((gridLat, gridLon), gridT[i, ::-1, :], 'linear', bounds_error=False) fz = RegularGridInterpolator((gridLat, gridLon), gridZ[i, ::-1, :], 'linear', bounds_error=False) t_interp[i, :] = ft(out_xyz_dem[:, :2]) #temperature z_interp[i, :] = fz(out_xyz_dem[:, :2]) #elevation t_interp_out = np.dstack((t_interp_out, t_interp)) z_interp_out = np.dstack((z_interp_out, z_interp)) # invert pressure levels #t_interp = t_interp[::-1,:] #z_interp = z_interp[::-1,:] # drop init blank layer tinterp = t_interp_out[:, :, 1:] zinterp = z_interp_out[:, :, 1:] gpob = hp.Bunch(t=tinterp, z=zinterp, levels=lev) # dem = nc.Dataset(demfile) # lon = dem.variables['longitude'][:] # lat = dem.variables['latitude'][:] # demv = dem.variables['ele'][:] # demlon1 = dem.variables['longitude'][:] # demlat1 = dem.variables['latitude'][:] # demlon = np.tile(demlon1,demlat1.size) # demlat = np.repeat(demlat1,demlon1.size) # demv=np.reshape(demv,demv.size) # # why are these masked values generated? # demv =np.ma.filled(demv, fill_value=1) # tz = np.repeat(tz,demv.size) # stat = pd.DataFrame({ "ele":demv, # "lon":demlon, # "lat":demlat, # "tz":tz # }) #=============================================================================== # Compute Shortwave #=============================================================================== #def swin2D(pob,sob,tob, stat, dates): ''' main edit over standard function for points: - 3d tob,sob reduce to 2D (reshape) ''' """ toposcale surface pressure using hypsometric equation - move to own class """ g = 9.81 R = 287.05 # Gas constant for dry air. #ztemp = pob.z # geopotential height b = np.transpose(a, (2, 0, 1)) ztemp = np.transpose( gpob.z, (2, 0, 1)) # pob is originally ordered levels,stations/cells,time #Ttemp = pob.t Ttemp = np.transpose( gpob.t, (2, 0, 1)) # pob is originally ordered levels,stations/cells,time statz = np.array(lp.ele) * g #dz=ztemp.transpose()-statz[None,:,None] # transpose not needed but now consistent with CGC surface pressure equations dz = ztemp - statz # dimensions of dz : time, levels, stations # set all levels below surface to very big number so they canot be found by min newdz = dz newdz[newdz < 0] = 999999 psf = np.zeros((gsob.dtime.size, statz.shape[0])) # reshape tob.t here #gtob.tr=gtob.t.reshape(gtob.t.shape[0]*gtob.t.shape[1], gtob.t.shape[2], order='F') #tob.trT=tob.tr.T # transpose to get right order # loop through timesteps for i in range(0, gsob.dtime.size): # find overlying layer thisp = dz[i, :, :] == np.min( newdz[i, :, :], axis=0 ) # thisp is a booleen matrix of levels x stations with true indicating overlying plevel over station surface ele # flatten to 1 dimension order='Fortran' or row major thispVec = thisp.reshape(thisp.size, order='F') TtempVec = Ttemp.reshape(Ttemp.shape[0], Ttemp.shape[1] * Ttemp.shape[2], order='F') ztempVec = ztemp.reshape(ztemp.shape[0], ztemp.shape[1] * ztemp.shape[2], order='F') # booleen indexing to find temp and geopotential that correspond to lowesest overlying layer T1 = TtempVec[i, thispVec] z1 = ztempVec[i, thispVec] p1 = np.tile( gpob.levels[::-1], statz.shape[0] )[thispVec] * 1e2 #Convert to Pa. Reverse levels to ensure low ele (hig pressure) to high elel (low pressure) Tbar = np.mean( [T1, gtob.t[:, i]], axis=0 ) # temperature midway between surface (toposcale T) and loweset overlying level (T1) """ Hypsometric equation.""" #P1 is above surface is this correct? Yes! psf[i, :] = p1 * np.exp( ((z1 / g) - (statz / g)) * (g / (Tbar * R)) ) # exponent is positive ie increases pressure as surface is lower than pressure level """ Maybe follow Dubayah's approach (as in Rittger and Girotto) instead for the shortwave downscaling, other than terrain effects. """ """ Height of the "grid" (coarse scale)""" Zc = gsob.z.T #.reshape(gsob.z.shape[0]*gsob.z.shape[1], gsob.z.shape[2]).T # reshape and transpose to remove dimension and make broadcastable """ toa """ SWtoa = gsob.tisr #.reshape(gsob.tisr.shape[0]*gsob.tisr.shape[1], gsob.tisr.shape[2]).T """ Downwelling shortwave flux of the "grid" using nearest neighbor.""" SWc = gsob.ssrd #.reshape(sob.ssrd.shape[0]*sob.ssrd.shape[1], sob.ssrd.shape[2]).T """Calculate the clearness index.""" kt = SWc / SWtoa #kt[is.na(kt)==T]<-0 # make sure 0/0 =0 #kt[is.infinite(kt)==T]<-0 # make sure 0/0 =0 kt[kt < 0] = 0 kt[kt > 1] = 0.8 #upper limit of kt kt = kt """ Calculate the diffuse fraction following the regression of Ruiz-Arias 2010 """ kd = 0.952 - 1.041 * np.exp(-1 * np.exp(2.3 - 4.702 * kt)) """ Use this to calculate the downwelling diffuse and direct shortwave radiation at grid. """ SWcdiff = kd * SWc SWcdir = (1 - kd) * SWc SWcdiff = SWcdiff SWcdir = SWcdir """ Use the above with the sky-view fraction to calculate the downwelling diffuse shortwave radiation at subgrid. """ SWfdiff = SWcdiff SWfdiff = np.nan_to_num(SWfdiff) # convert nans (night) to 0 """ Direct shortwave routine, modified from Joel. Get surface pressure at "grid" (coarse scale). Can remove this part once surface pressure field is downloaded, or just check for existance. """ ztemp = np.transpose(gpob.z, (0, 2, 1)) Ttemp = np.transpose(gpob.t, (0, 2, 1)) dz = ztemp - Zc # dimensions of dz : levels, time, stations # set all levels below surface to very big number so they canot be found by min newdz = dz newdz[newdz < 0] = 999999 psc = np.zeros((gsob.dtime.size, statz.shape[0])) for i in range(0, gsob.dtime.size): #thisp.append(np.argmin(dz[:,i][dz[:,i]>0])) # find overlying layer thisp = dz[:, i, :] == np.min( newdz[:, i, :], axis=0 ) # thisp is a booleen matrix of levels x stations with true indicating overlying plevel over station surface ele !! time index in middle this time!!! z0 = Zc[i, :] T0 = gsob.t2m.T[i, :] # flatten to 1 dimension order='Fortran' or row major thispVec = thisp.reshape(thisp.size, order='F') TtempVec = Ttemp.reshape( Ttemp.shape[1], Ttemp.shape[0] * Ttemp.shape[2], order='F' ) # !! order of permutations is different from pressure at finegrid routine (time is middle dimension) ztempVec = ztemp.reshape( ztemp.shape[1], ztemp.shape[0] * ztemp.shape[2], order='F' ) # !! order of permutations is different from pressure at finegrid routine (time is middle dimension) # booleen indexing to find temp and geopotential that correspond to lowesest overlying layer T1 = TtempVec[i, thispVec] z1 = ztempVec[i, thispVec] p1 = np.tile(gpob.levels[::-1], statz.shape[0])[thispVec] * 1e2 #Convert to Pa. Tbar = np.mean([T0, T1], axis=0) """ Hypsometric equation.""" psc[i, :] = p1 * np.exp(((z1 / g) - (z0 / g)) * (g / (Tbar * R))) """compute julian dates""" jd = sg.to_jd(gsob.dtime) """ Calculates a unit vector in the direction of the sun from the observer position. """ svx, svy, svz = sg.sunvectorMD(jd, lp.lat, lp.lon, tz, lp.ele.size, gsob.dtime.size) sp = sg.sunposMD(svx, svy, svz) # Cosine of the zenith angle. #sp.zen=sp.zen*(sp.zen>0) # Sun might be below the horizon. muz = np.cos(sp.zen) muz = muz # NB! psc must be in Pa (NOT hPA!). # Calculate the "broadband" absorption coefficient. Elevation correction ka = (g * muz / (psc)) * np.log(SWtoa.T / SWcdir.T) ka = np.nan_to_num(ka) # Now you can (finally) find the direct component at subgrid. SWfdir = SWtoa.T * np.exp(-ka * psf / (g * muz)) SWfdirCor = SWfdir #*dprod gtob.swin = SWfdiff + SWfdirCor.T gtob.psf = psf print("Swin done") #gtob.swin = swin2D(gpob,gsob,gtob, stat, dtime) #gtob.swin =gtob.swin.reshape(gtob.swin.shape[0], gsob.ssrd.shape[0], gsob.ssrd.shape[1]) ntime = len(gsob.dtime) stephr = a.seconds / 60 / 60 rtime = np.array(list(range(len(gsob.dtime)))) * stephr #=============================================================================== # write results #=============================================================================== ## conversuions T = np.single(gtob.t - 273.15) gtob.r[gtob.r > 100] = 100 RH = np.single(gtob.r) ws = np.single(np.sqrt(gtob.u**2 + gtob.v**2)) prate = np.single(gtob.prate) #=========================================================================== # Calculate absolute humidity kg/kg #=========================================================================== # Tk=273.15+20 # RH=80. # ah = 13.82g/m3 pws = calc_Pws(gtob.t) pw = calc_Pw(pws, RH) ah_gm3 = calc_AH(pw, gtob.t) # ah in g/m3 #AH_kgkg = ah_gm3_To_ah_kgkg(ah_gm3,gtob.psf,gtob.t ) SH = rh2sh(pw, gtob.psf) # Dictionary to loop over varDict = { "t": T, "ws": ws, "shum": SH.transpose(), "swin": gtob.swin, "lwin": gtob.lwin, "prate": prate, "P": gtob.psf.transpose() } for var in varDict: #open f = nc.Dataset(outDir + "/" + var + "_" + str(startIndex + 1) + "_" + str(year) + ".nc", 'w', format='NETCDF4') # Implement cf H.2.1 http://cfconventions.org/Data/cf-conventions/cf-conventions-1.7/build/cf-conventions.html#idp9763584 # #make dimensions # f.createDimension('time', ntime) # f.createDimension('lon', len(lp.lon)) # f.createDimension('lat', len(lp.lat)) # #make dimension variables # mytime = f.createVariable('time', 'i', ('time',)) # longitude = f.createVariable('lon', 'f4',('lon',)) # latitude = f.createVariable('lat', 'f4',('lat',)) # myvar = f.createVariable(var, 'f4',('time','lon')) # #assign dimensions # mytime[:] = rtime # longitude = lp.lon # latitude = lp.lat # myvar[:] = varDict[var].T # #metadata # from time import ctime # mytime=ctime() # f.history = 'Created by toposcale on '+mytime # mytime.units = 'hours since '+str(gsob.dtime[0]) # f.close() #make dimensions f.createDimension('time', ntime) f.createDimension('station', len(lp.ele)) #make dimension variables mytime = f.createVariable('time', 'i', ('time', )) station = f.createVariable('station', 'i', ('station', )) #make variables myvar = f.createVariable(var, 'f4', ('time', 'station')) longitude = f.createVariable('longitude', 'f4', ('station')) latitude = f.createVariable('latitude', 'f4', ('station')) #assign dimensions mytime[:] = rtime longitude[:] = np.array(lp.lon) latitude[:] = np.array(lp.lat) station[:] = range(len(lp.ele)) myvar[:] = varDict[var].T #metadata from time import ctime mycomptime = ctime() f.history = 'Created by tscale-cci on ' + mycomptime mytime.units = 'hours since ' + str(gsob.dtime[0]) f.close() print("Toposcale complete!")
def swin2D( pob, sob, tob, lp, dtime ): # does not work yet (booleen indexing of 2d arraya fauiles as np.min returns single value when we need 161 timesize = len(dtime) statsize = len(lp.ele) """ toposcale surface pressure using hypsometric equation - move to own class """ g = 9.81 R = 287.05 # Gas constant for dry air. #ztemp = pob.z # geopotential height b = np.transpose(a, (2, 0, 1)) ztemp = np.transpose(pob.z, (2, 0, 1)) #Ttemp = pob.t Ttemp = np.transpose(pob.t, (2, 0, 1)) statz = np.array(lp.ele) * g #dz=ztemp.transpose()-statz[None,:,None] # transpose not needed but now consistent with CGC surface pressure equations dz = ztemp - statz # dimensions of dz : time, levels, stations # set all levels below surface to very big number so they canot be found by min newdz = dz newdz[newdz < 0] = 999999 psf = np.zeros((dtime.size, statz.shape[0])) # loop through timesteps for i in range(0, dtime.size): # find overlying layer thisp = dz[i, :, :] == np.min( newdz[i, :, :], axis=0 ) # thisp is a booleen matrix of levels x stations with true indicating overlying plevel over station surface ele # flatten to 1 dimension order='Fortran' or row major thispVec = thisp.reshape(thisp.size, order='F') TtempVec = Ttemp.reshape(Ttemp.shape[0], Ttemp.shape[1] * Ttemp.shape[2], order='F') ztempVec = ztemp.reshape(ztemp.shape[0], ztemp.shape[1] * ztemp.shape[2], order='F') # booleen indexing to find temp and geopotential that correspond to lowesest overlying layer T1 = TtempVec[i, thispVec] z1 = ztempVec[i, thispVec] p1 = np.tile( pob.levels[::-1], statz.shape[0] )[thispVec] * 1e2 #Convert to Pa. Reverse levels to ensure low ele (hig pressure) to high elel (low pressure) Tbar = np.mean( [T1, tob.t[i, :]], axis=0 ) # temperature midway between surface (toposcale T) and loweset overlying level (T1) """ Hypsometric equation.""" #P1 is above surface is this correct? Yes! psf[i, :] = ( p1 * np.exp((z1 - statz) * (g / (Tbar * R))) ) # exponent is positive ie increases pressure as surface is lower than pressure level #psf=np.array(psf).squeeze() ## Specific humidity routine. # mrvf=0.622.*vpf./(psf-vpf); #Mixing ratio for water vapor at subgrid. # qf=mrvf./(1+mrvf); # Specific humidity at subgrid [kg/kg]. # fout.q(:,:,n)=qf; """ Maybe follow Dubayah's approach (as in Rittger and Girotto) instead for the shortwave downscaling, other than terrain effects. """ """ Height of the "grid" (coarse scale)""" Zc = sob.z """ toa """ SWtoa = sob.tisr """ Downwelling shortwave flux of the "grid" using nearest neighbor.""" SWc = sob.ssrd """Calculate the clearness index.""" kt = SWc / SWtoa #kt[is.na(kt)==T]<-0 # make sure 0/0 =0 #kt[is.infinite(kt)==T]<-0 # make sure 0/0 =0 kt[kt < 0] = 0 kt[kt > 1] = 0.8 #upper limit of kt kt = kt """ Calculate the diffuse fraction following the regression of Ruiz-Arias 2010 """ kd = 0.952 - 1.041 * np.exp(-1 * np.exp(2.3 - 4.702 * kt)) kd = kd """ Use this to calculate the downwelling diffuse and direct shortwave radiation at grid. """ SWcdiff = kd * SWc SWcdir = (1 - kd) * SWc SWcdiff = SWcdiff SWcdir = SWcdir """ Use the above with the sky-view fraction to calculate the downwelling diffuse shortwave radiation at subgrid. """ SWfdiff = np.array(lp.svf) * SWcdiff SWfdiff = np.nan_to_num(SWfdiff) # convert nans (night) to 0 """ Direct shortwave routine, modified from Joel. Get surface pressure at "grid" (coarse scale). Can remove this part once surface pressure field is downloaded, or just check for existance. """ #ztemp = pob.z #Ttemp = pob.t #dz=ztemp.transpose()-sob.z ztemp = np.transpose(pob.z, (0, 2, 1)) Ttemp = np.transpose(pob.t, (0, 2, 1)) dz = ztemp - sob.z # dimensions of dz : levels, time, stations # set all levels below surface to very big number so they canot be found by min newdz = dz newdz[newdz < 0] = 999999 psc = np.zeros((dtime.size, statz.shape[0])) for i in range(0, dtime.size): #thisp.append(np.argmin(dz[:,i][dz[:,i]>0])) # find overlying layer thisp = dz[:, i, :] == np.min( newdz[:, i, :], axis=0 ) # thisp is a booleen matrix of levels x stations with true indicating overlying plevel over station surface ele !! time index in middle this time!!! z0 = sob.z[i, :] T0 = sob.t2m[i, :] # flatten to 1 dimension order='Fortran' or row major thispVec = thisp.reshape(thisp.size, order='F') TtempVec = Ttemp.reshape( Ttemp.shape[1], Ttemp.shape[0] * Ttemp.shape[2], order='F' ) # !! order of permutations is different from pressure at finegrid routine (time is middle dimension) ztempVec = ztemp.reshape( ztemp.shape[1], ztemp.shape[0] * ztemp.shape[2], order='F' ) # !! order of permutations is different from pressure at finegrid routine (time is middle dimension) # booleen indexing to find temp and geopotential that correspond to lowesest overlying layer T1 = TtempVec[i, thispVec] z1 = ztempVec[i, thispVec] p1 = np.tile(pob.levels[::-1], statz.shape[0])[thispVec] * 1e2 #Convert to Pa. Tbar = np.mean([T0, T1], axis=0) """ Hypsometric equation.""" psc[i, :] = (p1 * np.exp((z1 - z0) * (g / (Tbar * R)))) """compute julian dtime""" jd = sg.to_jd(dtime) """ Calculates a unit vector in the direction of the sun from the observer position. """ #sunv=sg.sunvector(jd=jd, latitude=stat.lat, longitude=stat.lon, timezone=stat.tz) svx, svy, svz = sg.sunvectorMD(jd, lp.lat, lp.lon, lp.tz, statsize, timesize) """ Computes azimuth , zenith and sun elevation for each timestamp !!! NOW THIS NEEDS ACCEPT SEPARATE VECTORS!! """ sp = sg.sunposMD(svx, svy, svz) # Cosine of the zenith angle. #sp.zen=sp.zen*(sp.zen>0) # Sun might be below the horizon. muz = np.cos(sp.zen) muz = muz # NB! psc must be in Pa (NOT hPA!). #if np.max(psc<1.5e3): # Obviously not in Pa #psc=psc*1e2 # Calculate the "broadband" absorption coefficient. Elevation correction # from Kris ka = (g * muz / (psc)) * np.log(SWtoa / SWcdir) #ka.set_fill_value(0) #ka = ka.filled() ka = np.nan_to_num(ka) # Note this equation is obtained by inverting Beer's law, i.e. use #I_0=I_inf x exp[(-ka/mu) int_z0**inf rho dz] # Along with hydrostatic equation to convert to pressure coordinates then # solve for ka using p(z=inf)=0. # Now you can (finally) find the direct component at subgrid. SWfdir = SWtoa * np.exp(-ka * psf / (g * muz)) """ Then perform the terrain correction. [Corripio 2003 / rpackage insol port].""" """compute mean horizon elevation - why negative hor.el possible??? """ horel = (((np.arccos(np.sqrt(lp.svf)) * 180) / np.pi) * 2) - lp.slp horel[horel < 0] = 0 """ normal vector - Calculates a unit vector normal to a surface defined by slope inclination and slope orientation. """ nv = sg.normalvector(slope=lp.slp, aspect=lp.asp) """ Method 1: Computes the intensity according to the position of the sun (sunv) and dotproduct normal vector to slope. From corripio r package """ """ need to reconstruct sunv matrix for multipoint case here""" sunv = np.array((svx, svy, svz)) #dotprod=np.dot(sunv ,np.transpose(nv))\ dotprod = np.tensordot(sunv, nv, axes=([0], [1])) dprod = dotprod[:, :, 0] dprod[dprod < 0] = 0 #negative indicates selfshading #dprod = dprod """Method 2: Illumination angles. Dozier""" #SEE tscale.py #saz=sp.azi # a=np.array(stat.asp) # b =np.tile(a,timesize) # asp=b.reshape(timesize,statsize) # a=np.array(stat.slp) # b =np.tile(a,timesize) # slp=b.reshape(timesize,statsize) # cosis=muz*np.cos(slp)+np.sin(sp.zen)*np.sin(slp)*np.cos(sp.azi-asp)# cosine of illumination angle at subgrid. # cosic=muz # cosine of illumination angle at grid (slope=0). """ SUN ELEVATION below hor.el set to 0 - binary mask """ a = np.array(horel) b = np.tile(a, timesize) horel2 = b.reshape(timesize, statsize) selMask = sp.sel selMask[selMask < horel2] = 0 selMask[selMask > 0] = 1 selMask = selMask """ derive incident radiation on slope accounting for self shading and cast shadow and solar geometry BOTH formulations seem to be broken """ #SWfdirCor=selMask*(cosis/cosic)*SWfdir SWfdirCor = selMask * dprod * SWfdir SWfglob = SWfdiff + SWfdirCor return SWfglob, psf """
def swin(self, pob, sob, tob, stat, dates): """ toposcale surface pressure using hypsometric equation - move to own class EDITS Feb 20 2020: -See https://www.evernote.com/Home.action?login=true#n=78e92684-4d64-4802-ab6c-2cb90b277e44&s=s500&ses=1&sh=5&sds=5&x=& - EDITS Jul 11 2019: - removed elevation scaling as this degrade results - at least in WFJ test- Kris? - reimplemnt original additative ele method - better than beers, or at least less damaging - removed mask, why is this causing problems? - dotprod method (corripio) does not seem to work - I dont think it ever did as we used SWTopo ==FALSE - use Dozier cos corrction method (as in paper right) - So ... we have ele, illumination angle, self shading and svf correction. We do not have horizon correction (partly in self shading of course) """ ## Specific humidity routine. # mrvf=0.622.*vpf./(psf-vpf); #Mixing ratio for water vapor at subgrid. # qf=mrvf./(1+mrvf); # Specific humidity at subgrid [kg/kg]. # fout.q(:,:,n)=qf; """ Maybe follow Dubayah's approach (as in Rittger and Girotto) instead for the shortwave downscaling, other than terrain effects. """ """ Height of the "grid" (coarse scale)""" Zc = sob.z """ toa """ SWtoa = sob.tisr """ Downwelling shortwave flux of the "grid" using nearest neighbor.""" SWc = sob.ssrd """Calculate the clearness index.""" kt = SWc / SWtoa #kt[is.na(kt)==T]<-0 # make sure 0/0 =0 #kt[is.infinite(kt)==T]<-0 # make sure 0/0 =0 kt[kt < 0] = 0 kt[kt > 1] = 0.8 #upper limit of kt self.kt = kt """ Calculate the diffuse fraction following the regression of Ruiz-Arias 2010 """ kd = 0.952 - 1.041 * np.exp(-1 * np.exp(2.3 - 4.702 * kt)) self.kd = kd """ Use this to calculate the downwelling diffuse and direct shortwave radiation at grid. """ SWcdiff = kd * SWc SWcdir = (1 - kd) * SWc self.SWcdiff = SWcdiff self.SWcdir = SWcdir """ Use the above with the sky-view fraction to calculate the downwelling diffuse shortwave radiation at subgrid. """ self.SWfdiff = SWcdiff #* stat.svf self.SWfdiff.set_fill_value(0) self.SWfdiff = self.SWfdiff.filled() """ Direct shortwave routine, modified from Joel. Get surface pressure at "grid" (coarse scale). Can remove this part once surface pressure field is downloaded, or just check for existance. """ #T1=Ttemp(thisp) #z1=ztemp(thisp) #p1=pob.levels(thisp)*1e2 #Convert to Pa. #Tbar=mean([T0 T1]) """compute julian dates""" jd = sg.to_jd(dates) """ Calculates a unit vector in the direction of the sun from the observer position. """ sunv = sg.sunvector(jd=jd, latitude=stat.lat, longitude=stat.lon, timezone=stat.tz) """ Computes azimuth , zenith and sun elevation for each timestamp """ sp = sg.sunpos(sunv) self.sp = sp # Cosine of the zenith angle. sp.zen = sp.zen #sp.zen=sp.zen*(sp.zen>0) # Sun might be below the horizon. muz = np.cos(sp.zen) self.muz = muz # NB! psc must be in Pa (NOT hPA!). #if np.max(psc<1.5e3): # Obviously not in Pa #psc=psc*1e2 # Compute air pressure at fine grid ztemp = pob.z Ttemp = pob.t statz = stat.ele * self.g dz = ztemp.transpose( ) - statz # transpose not needed but now consistent with CGC surface pressure equations # compute air pressure at fine grid (could remove if download air pressure variable) self.psf = [] # loop through timesteps for i in range(0, dates.size): # # find overlying layer thisp = dz[:, i] == np.min(dz[:, i][dz[:, i] > 0]) # booleen indexing T1 = Ttemp[i, thisp] z1 = ztemp[i, thisp] p1 = pob.levels[thisp] * 1e2 #Convert to Pa. Tbar = np.mean([T1, tob.t[i]], axis=0) """ Hypsometric equation.""" self.psf.append(p1 * np.exp( (z1 - statz) * (self.g / (Tbar * self.R)))) self.psf = np.array(self.psf).squeeze() # Method 1 BEERS LAW if (BEERS == 'TRUE'): # compute air pressure at coarse grid dz = ztemp.transpose() - sob.z self.psc = [] for i in range(0, dz.shape[1]): #thisp.append(np.argmin(dz[:,i][dz[:,i]>0])) thisp = dz[:, i] == np.min(dz[:, i][dz[:, i] > 0]) z0 = sob.z[i] T0 = sob.t2m[i] T1 = Ttemp[i, thisp] z1 = ztemp[i, thisp] p1 = pob.levels[thisp] * 1e2 #Convert to Pa. Tbar = np.mean([T0, T1], axis=0) """ Hypsometric equation.""" self.psc.append(p1 * np.exp( (z1 - z0) * (self.g / (Tbar * self.R)))) self.psc = np.array(self.psc).squeeze() # Calculate the "broadband" absorption coefficient. Elevation correction # from Kris ka = (self.g * muz / (self.psc)) * np.log(SWtoa / SWcdir) #ka.set_fill_value(0) #ka = ka.filled() # set inf (from SWtoa/SWcdir at nigh, zero division) to 0 (night) ka[ka == -np.inf] = 0 ka[ka == np.inf] = 0 # Note this equation is obtained by inverting Beer's law, i.e. use #I_0=I_inf x exp[(-ka/mu) int_z0**inf rho dz] # Along with hydrostatic equation to convert to pressure coordinates then # solve for ka using p(z=inf)=0. # Method 1 Beers law # Now you can (finally) find the direct component at subgrid. self.SWfdir = SWtoa * np.exp(-ka * self.psf / (self.g * muz)) #self.SWfdir=SWcdir # Method 2 ORIGINAL ELEVATION Correction # https://hal.archives-ouvertes.fr/hal-00470155/document """ele diff in km""" coarseZ = Zc[0] / 9.9 dz = (stat.ele - coarseZ) / 1000 # fine - coase in knm s = SWtoa b = SWcdir zen = sp.zen thetaz = (np.pi / 180) * zen #radians m = 1 / np.cos(thetaz) k = -np.log(b / s) / m k.set_fill_value(0) k = k.filled() #k[is.na(k)==T]<-0 #correct b=0/s=0 problem t = np.exp(1)**(-k * dz * np.cos(thetaz)) t[t > 1] < -1.1 #to constrain to reasonable values t[t < 0.8] < -0.9 #to constrain to reasonable values db = (1 - t) * SWcdir self.SWfdir = SWcdir + db #additative correction #====================================== """ Then perform the terrain correction. [Corripio 2003 / rpackage insol port].""" """compute mean horizon elevation - why negative hor.el possible??? Have tested seesm OK """ # In [485]: (((np.arccos(np.sqrt(0.9))*180.)/np.pi)*2)-20 # Out[485]: 16.869897645844034 # In [486]: (((np.arccos(np.sqrt(1))*180.)/np.pi)*2)-0 # Out[486]: 0.0 # In [487]: (((np.arccos(np.sqrt(0.9))*180.)/np.pi)*2)-0 # Out[487]: 36.869897645844034 # In [488]: (((np.arccos(np.sqrt(0.9))*180.)/np.pi)*2)-10 # Out[488]: 26.869897645844034 # In [489]: (((np.arccos(np.sqrt(0.95))*180.)/np.pi)*2)-10 # Out[489]: 15.841932763167158 horel = (((np.arccos(np.sqrt(stat.svf)) * 180) / np.pi) * 2) - stat.slp if horel < 0: horel = 0 self.meanhorel = horel """ normal vector - Calculates a unit vector normal to a surface defined by slope inclination and slope orientation. """ nv = sg.normalvector(slope=stat.slp, aspect=stat.asp) """ Method 1: Computes the intensity according to the position of the sun (sunv) and dotproduct normal vector to slope. From corripio r package THIS IS GOOD # consider october 31 29018 at 46/9 UTC=0 dt= datetime.datetime(2018, 10, 31, 12, 0) In [471]: sg.to_jd(dt) Out[471]: 2458423.0 sunv=sg.sunvector(jd=2458423 , latitude=46, longitude=9, timezone=0) sp=sg.sunpos(sunv) - sun is in the south: In [456]: sp.azi Out[456]: array([192.82694139]) - at quite low ele In [455]: sp.sel Out[455]: array([19.94907576]) # FLAT CASE In [449]: nv = sg.normalvector(slope=0, aspect=0) In [450]: np.dot(sunv ,np.transpose(nv)) Out[450]: array([[0.34118481]]) # SOOUTH SLOPE CASE = enhanced rad wrt. flat case nv = sg.normalvector(slope=30, aspect=180) In [446]: np.dot(sunv ,np.transpose(nv)) Out[446]: array([[0.75374406]]) # NORTH SLOPE CASE = self shaded In [447]: nv = sg.normalvector(slope=30, aspect=0) In [448]: np.dot(sunv ,np.transpose(nv)) Out[448]: array([[-0.16279463]]) """ dotprod = np.dot(sunv, np.transpose(nv)) dprod = dotprod.squeeze() dprod[dprod < 0] = 0 #negative indicates selfshading self.dprod = dprod """Method 2: Illumination angles. Dozier and self shading""" # THIS IS WRONG # eg consider south facting 30 degree slope on 31 oct 2018 at midday at 46,9 utc=0 # In [420]: sp.azi # Out[420]: array([192.82694139]) # In [421]: stat.slp # Out[421]: 30 # In [422]: stat.asp # Out[422]: 180 # In [423]: sp.azi # Out[423]: array([192.82694139]) # In [424]: sp.sel # Out[424]: array([19.94907576]) # In [425]: ^I^I saz=sp.azi # ...: ^I^I cosis=muz*np.cos(stat.slp)+np.sin(sp.zen)*np.sin(stat.slp)*np.cos(sp.azi-stat.asp)# cosine of illumination angle at subgrid. # ...: ^I^I cosic=muz # cosine of illumination angle at grid (slope=0). # ...: ^I^I cosi = (cosis/cosic) # ...: # In [427]: cosi # Out[427]: array([-1.14169945]) # negative cosi shows sun below horizon! # saz=sp.azi # cosis=muz*np.cos(stat.slp)+np.sin(sp.zen)*np.sin(stat.slp)*np.cos(sp.azi-stat.asp)# cosine of illumination angle at subgrid. # cosic=muz # cosine of illumination angle at grid (slope=0). # cosi = (cosis/cosic) # #If ratio of illumination angle subgrid/ illum angle grid is negative the point is selfshaded # cosi[cosi<0]=0 #WRONG!!! """ CAST SHADOWS: SUN ELEVATION below hor.el set to 0 - binary mask """ selMask = sp.sel selMask[selMask < horel] = 0 selMask[selMask > 0] = 1 self.selMask = selMask """ derive incident radiation on slope accounting for self shading and cast shadow and solar geometry BOTH formulations seem to be broken """ #self.SWfdirCor=selMask*(cosis/cosic)*self.SWfdir # this is really wrong! #self.SWfdirCor=(cosis/cosic)*self.SWfdir self.SWfdirCor = dprod * self.SWfdir * selMask # this is bad WHYYY? #self.SWfdirCor=dprod*self.SWfdir self.SWfglob = self.SWfdiff + self.SWfdirCor #self.SWfglob = self.SWfdiff+ self.SWfdir """
def swin(self, pob, sob, tob, stat, dates): """ toposcale surface pressure using hypsometric equation - move to own class EDITS Jul 11 2019: - removed elevation scaling as this degrade results - at least in WFJ test- Kris? - reimplemnt original additative ele method - better than beers, or at least less damaging - removed mask, why is this causing problems? - dotprod method (corripio) does not seem to work - I dont think it ever did as we used SWTopo ==FALSE - use Dozier cos corrction method (as in paper right) - So ... we have ele, illumination angle, self shading and svf correction. We do not have horizon correction (partly in self shading of course) """ ztemp = pob.z Ttemp = pob.t statz = stat.ele * self.g dz1 = ztemp - statz # transpose not needed but now consistent with CGC surface pressure equations dz = dz1.transpose() self.psf = [] # loop through timesteps for i in range(0, dates.size): # # find overlying layer thisp = dz[:, i] == np.min(dz[:, i][dz[:, i] > 0]) # booleen indexing T1 = Ttemp[i, thisp] z1 = ztemp[i, thisp] p1 = pob.levels[thisp] * 1e2 #Convert to Pa. Tbar = np.mean([T1, tob.t[i]], axis=0) """ Hypsometric equation.""" self.psf.append(p1 * np.exp( (z1 - statz) * (self.g / (Tbar * self.R)))) self.psf = np.array(self.psf).squeeze() ## Specific humidity routine. # mrvf=0.622.*vpf./(psf-vpf); #Mixing ratio for water vapor at subgrid. # qf=mrvf./(1+mrvf); # Specific humidity at subgrid [kg/kg]. # fout.q(:,:,n)=qf; """ Maybe follow Dubayah's approach (as in Rittger and Girotto) instead for the shortwave downscaling, other than terrain effects. """ """ Height of the "grid" (coarse scale)""" Zc = sob.z """ toa """ SWtoa = sob.tisr """ Downwelling shortwave flux of the "grid" using nearest neighbor.""" SWc = sob.ssrd """Calculate the clearness index.""" kt = SWc / SWtoa #kt[is.na(kt)==T]<-0 # make sure 0/0 =0 #kt[is.infinite(kt)==T]<-0 # make sure 0/0 =0 kt[kt < 0] = 0 kt[kt > 1] = 0.8 #upper limit of kt self.kt = kt """ Calculate the diffuse fraction following the regression of Ruiz-Arias 2010 """ kd = 0.952 - 1.041 * np.exp(-1 * np.exp(2.3 - 4.702 * kt)) self.kd = kd """ Use this to calculate the downwelling diffuse and direct shortwave radiation at grid. """ SWcdiff = kd * SWc SWcdir = (1 - kd) * SWc self.SWcdiff = SWcdiff self.SWcdir = SWcdir """ Use the above with the sky-view fraction to calculate the downwelling diffuse shortwave radiation at subgrid. """ self.SWfdiff = stat.svf * SWcdiff self.SWfdiff.set_fill_value(0) self.SWfdiff = self.SWfdiff.filled() """ Direct shortwave routine, modified from Joel. Get surface pressure at "grid" (coarse scale). Can remove this part once surface pressure field is downloaded, or just check for existance. """ ztemp = pob.z Ttemp = pob.t dz = ztemp.transpose() - sob.z self.psc = [] for i in range(0, dz.shape[1]): #thisp.append(np.argmin(dz[:,i][dz[:,i]>0])) thisp = dz[:, i] == np.min(dz[:, i][dz[:, i] > 0]) z0 = sob.z[i] T0 = sob.t2m[i] T1 = Ttemp[i, thisp] z1 = ztemp[i, thisp] p1 = pob.levels[thisp] * 1e2 #Convert to Pa. Tbar = np.mean([T0, T1], axis=0) """ Hypsometric equation.""" self.psc.append(p1 * np.exp( (z1 - z0) * (self.g / (Tbar * self.R)))) self.psc = np.array(self.psc).squeeze() #T1=Ttemp(thisp) #z1=ztemp(thisp) #p1=pob.levels(thisp)*1e2 #Convert to Pa. #Tbar=mean([T0 T1]) """compute julian dates""" jd = sg.to_jd(dates) """ Calculates a unit vector in the direction of the sun from the observer position. """ sunv = sg.sunvector(jd=jd, latitude=stat.lat, longitude=stat.lon, timezone=stat.tz) """ Computes azimuth , zenith and sun elevation for each timestamp """ sp = sg.sunpos(sunv) self.sp = sp # Cosine of the zenith angle. sp.zen = sp.zen #sp.zen=sp.zen*(sp.zen>0) # Sun might be below the horizon. muz = np.cos(sp.zen) self.muz = muz # NB! psc must be in Pa (NOT hPA!). #if np.max(psc<1.5e3): # Obviously not in Pa #psc=psc*1e2 # Calculate the "broadband" absorption coefficient. Elevation correction # from Kris ka = (self.g * muz / (self.psc)) * np.log(SWtoa / SWcdir) #ka.set_fill_value(0) #ka = ka.filled() # set inf (from SWtoa/SWcdir at nigh, zero division) to 0 (night) ka[ka == -np.inf] = 0 ka[ka == np.inf] = 0 # Note this equation is obtained by inverting Beer's law, i.e. use #I_0=I_inf x exp[(-ka/mu) int_z0**inf rho dz] # Along with hydrostatic equation to convert to pressure coordinates then # solve for ka using p(z=inf)=0. # Method 1 Beers law # Now you can (finally) find the direct component at subgrid. self.SWfdir = SWtoa * np.exp(-ka * self.psf / (self.g * muz)) #self.SWfdir=SWcdir # Method 2 ORIGINAL ELEVATION Correction """ele diff in km""" coarseZ = Zc[0] / 9.9 dz = (stat.ele - coarseZ) / 1000 # fine - coase in knm s = SWtoa b = SWcdir zen = sp.zen thetaz = (np.pi / 180) * zen #radians m = 1 / np.cos(thetaz) k = -np.log(b / s) / m k.set_fill_value(0) k = k.filled() #k[is.na(k)==T]<-0 #correct b=0/s=0 problem t = np.exp(1)**(-k * dz * np.cos(thetaz)) t[t > 1] < -1.1 #to constrain to reasonable values t[t < 0.8] < -0.9 #to constrain to reasonable values db = (1 - t) * SWcdir #self.SWfdir=SWcdir+db #additative correction #====================================== """ Then perform the terrain correction. [Corripio 2003 / rpackage insol port].""" """compute mean horizon elevation - why negative hor.el possible??? """ horel = (((np.arccos(np.sqrt(stat.svf)) * 180) / np.pi) * 2) - stat.slp if horel < 0: horel = 0 self.meanhorel = horel """ normal vector - Calculates a unit vector normal to a surface defined by slope inclination and slope orientation. """ nv = sg.normalvector(slope=stat.slp, aspect=stat.asp) """ Method 1: Computes the intensity according to the position of the sun (sunv) and dotproduct normal vector to slope. From corripio r package REMOVE THIS """ dotprod = np.dot(sunv, np.transpose(nv)) dprod = dotprod.squeeze() dprod[dprod < 0] = 0 #negative indicates selfshading self.dprod = dprod """Method 2: Illumination angles. Dozier and self shading""" saz = sp.azi cosis = muz * np.cos( stat.slp) + np.sin(sp.zen) * np.sin(stat.slp) * np.cos( sp.azi - stat.asp) # cosine of illumination angle at subgrid. cosic = muz # cosine of illumination angle at grid (slope=0). cosi = (cosis / cosic) #If ratio of illumination angle subgrid/ illum angle grid is negative the point is selfshaded cosi[cosi < 0] = 0 """ CAST SHADOWS: SUN ELEVATION below hor.el set to 0 - binary mask """ selMask = sp.sel selMask[selMask < horel] = 0 selMask[selMask > 0] = 1 self.selMask = selMask """ derive incident radiation on slope accounting for self shading and cast shadow and solar geometry BOTH formulations seem to be broken """ #self.SWfdirCor=selMask*(cosis/cosic)*self.SWfdir # this is really wrong! self.SWfdirCor = (cosis / cosic) * self.SWfdir self.SWfdirCor = selMask * dprod * self.SWfdir # this is bad #self.SWfdirCor=dprod*self.SWfdir self.SWfglob = self.SWfdiff + self.SWfdirCor #self.SWfglob = self.SWfdiff+ self.SWfdir """