def ocean_stokes(LANDMASK, LMAX, MMAX=None, LOVE=None, VARNAME='LSMASK', SIMPLIFY=False): """ Converts data from spherical harmonic coefficients to a spatial field Arguments --------- LANDMASK: netCDF4 land mask file LMAX: maximum spherical harmonic degree Keyword arguments ----------------- MMAX: maximum spherical harmonic order of the output harmonics LOVE: input load Love numbers up to degree LMAX (hl,kl,ll) VARNAME: variable name for mask in netCDF4 file SIMPLIFY: simplify land mask by removing isolated points Returns ------- clm: cosine spherical harmonic coefficients slm: sine spherical harmonic coefficients l: spherical harmonic degree to LMAX m: spherical harmonic order to MMAX """ #-- maximum spherical harmonic order MMAX = np.copy(LMAX) if MMAX is None else MMAX #-- Read Land-Sea Mask of specified input file #-- 0=Ocean, 1=Land, 2=Lake, 3=Small Island, 4=Ice Shelf #-- Open the land-sea NetCDF file for reading landsea = spatial().from_netCDF4(LANDMASK, date=False, varname=VARNAME) #-- create land function nth, nphi = landsea.shape land_function = np.zeros((nth, nphi), dtype=np.float64) #-- combine land and island levels for land function indx, indy = np.nonzero((landsea.data >= 1) & (landsea.data <= 3)) land_function[indx, indy] = 1.0 #-- remove isolated points if specified if SIMPLIFY: land_function -= find_isolated_points(land_function) #-- ocean function reciprocal of land function ocean_function = 1.0 - land_function #-- convert to spherical harmonics (1 cm w.e.) ocean_Ylms = gen_stokes(ocean_function.T, landsea.lon, landsea.lat, UNITS=1, LMIN=0, LMAX=LMAX, MMAX=MMAX, LOVE=LOVE) #-- return the spherical harmonic coefficients return ocean_Ylms
def combine_harmonics(INPUT_FILE, OUTPUT_FILE, LMAX=None, MMAX=None, LOVE_NUMBERS=0, REFERENCE=None, RAD=None, DESTRIPE=False, UNITS=None, DDEG=None, INTERVAL=None, BOUNDS=None, REDISTRIBUTE=False, LSMASK=None, MEAN_FILE=None, DATAFORM=None, VERBOSE=False, MODE=0o775): #-- verify that output directory exists DIRECTORY = os.path.abspath(os.path.dirname(OUTPUT_FILE)) if not os.access(DIRECTORY, os.F_OK): os.makedirs(DIRECTORY, MODE, exist_ok=True) #-- read input spherical harmonic coefficients from file in DATAFORM if (DATAFORM == 'ascii'): input_Ylms = harmonics().from_ascii(INPUT_FILE) elif (DATAFORM == 'netCDF4'): #-- read input netCDF4 file (.nc) input_Ylms = harmonics().from_netCDF4(INPUT_FILE) elif (DATAFORM == 'HDF5'): #-- read input HDF5 file (.H5) input_Ylms = harmonics().from_HDF5(INPUT_FILE) #-- reform harmonic dimensions to be l,m,t #-- truncate to degree and order LMAX, MMAX input_Ylms = input_Ylms.truncate(lmax=LMAX, mmax=MMAX).expand_dims() #-- remove mean file from input Ylms if MEAN_FILE and (DATAFORM == 'ascii'): mean_Ylms = harmonics().from_ascii(MEAN_FILE, date=False) input_Ylms.subtract(mean_Ylms) elif MEAN_FILE and (DATAFORM == 'netCDF4'): #-- read input netCDF4 file (.nc) mean_Ylms = harmonics().from_netCDF4(MEAN_FILE, date=False) input_Ylms.subtract(mean_Ylms) elif MEAN_FILE and (DATAFORM == 'HDF5'): #-- read input HDF5 file (.H5) mean_Ylms = harmonics().from_HDF5(MEAN_FILE, date=False) input_Ylms.subtract(mean_Ylms) #-- read arrays of kl, hl, and ll Love Numbers hl, kl, ll = load_love_numbers(LMAX, LOVE_NUMBERS=LOVE_NUMBERS, REFERENCE=REFERENCE) #-- distribute total mass uniformly over the ocean if REDISTRIBUTE: #-- read Land-Sea Mask and convert to spherical harmonics ocean_Ylms = ocean_stokes(LSMASK, LMAX, MMAX=MMAX, LOVE=(hl, kl, ll)) #-- calculate ratio between total mass and a uniformly distributed #-- layer of water over the ocean ratio = input_Ylms.clm[0, 0, :] / ocean_Ylms['clm'][0, 0] #-- for each spherical harmonic for m in range(0, MMAX + 1): #-- MMAX+1 to include MMAX for l in range(m, LMAX + 1): #-- LMAX+1 to include LMAX #-- remove the ratio*ocean Ylms from Ylms #-- note: x -= y is equivalent to x = x - y input_Ylms.clm[l, m, :] -= ratio * ocean_Ylms['clm'][l, m] input_Ylms.slm[l, m, :] -= ratio * ocean_Ylms['slm'][l, m] #-- if using a decorrelation filter (Isabella's destriping Routine) if DESTRIPE: input_Ylms = input_Ylms.destripe() #-- Gaussian smoothing if (RAD != 0): wt = 2.0 * np.pi * gauss_weights(RAD, LMAX) else: wt = np.ones((LMAX + 1)) #-- Output spatial data grid = spatial() grid.time = np.copy(input_Ylms.time) grid.month = np.copy(input_Ylms.month) #-- Output Degree Spacing if (len(DDEG) == 1): #-- dlon == dlat dlon = DDEG dlat = DDEG else: #-- dlon != dlat dlon, dlat = DDEG #-- Output Degree Interval if (INTERVAL == 1): #-- (0:360,90:-90) nlon = np.int((360.0 / dlon) + 1.0) nlat = np.int((180.0 / dlat) + 1.0) grid.lon = dlon * np.arange(0, nlon) grid.lat = 90.0 - dlat * np.arange(0, nlat) elif (INTERVAL == 2): #-- (Degree spacing)/2 grid.lon = np.arange(dlon / 2.0, 360 + dlon / 2.0, dlon) grid.lat = np.arange(90.0 - dlat / 2.0, -90.0 - dlat / 2.0, -dlat) nlon = len(grid.lon) nlat = len(grid.lat) elif (INTERVAL == 3): #-- non-global grid set with BOUNDS parameter minlon, maxlon, minlat, maxlat = BOUNDS.copy() grid.lon = np.arange(minlon + dlon / 2.0, maxlon + dlon / 2.0, dlon) grid.lat = np.arange(maxlat - dlat / 2.0, minlat - dlat / 2.0, -dlat) nlon = len(grid.lon) nlat = len(grid.lat) #-- Setting units factor for output #-- dfactor computes the degree dependent coefficients if (UNITS == 1): #-- 1: cmwe, centimeters water equivalent dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).cmwe elif (UNITS == 2): #-- 2: mmGH, mm geoid height dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).mmGH elif (UNITS == 3): #-- 3: mmCU, mm elastic crustal deformation dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).mmCU elif (UNITS == 4): #-- 4: micGal, microGal gravity perturbations dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).microGal elif (UNITS == 5): #-- 5: Pa, equivalent surface pressure in Pascals dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).Pa else: raise ValueError(('UNITS is invalid:\n1: cmwe\n2: mmGH\n3: mmCU ' '(elastic)\n4:microGal\n5: Pa')) #-- Computing plms for converting to spatial domain theta = (90.0 - grid.lat) * np.pi / 180.0 PLM, dPLM = plm_holmes(LMAX, np.cos(theta)) #-- output spatial grid nt = len(input_Ylms.time) grid.data = np.zeros((nlat, nlon, nt)) #-- converting harmonics to truncated, smoothed coefficients in output units for t in range(nt): #-- spherical harmonics for time t Ylms = input_Ylms.index(t) Ylms.convolve(dfactor * wt) #-- convert spherical harmonics to output spatial grid grid.data[:, :, t] = harmonic_summation(Ylms.clm, Ylms.slm, grid.lon, grid.lat, LMAX=LMAX, PLM=PLM).T #-- if verbose output: print input and output file names if VERBOSE: print('{0}:'.format(os.path.basename(sys.argv[0]))) print('{0} -->\n\t{1}\n'.format(INPUT_FILE, OUTPUT_FILE)) #-- outputting data to file output_data(grid.squeeze(), FILENAME=OUTPUT_FILE, DATAFORM=DATAFORM, UNITS=UNITS) #-- change output permissions level to MODE os.chmod(OUTPUT_FILE, MODE)
def regress_grace_maps(parameters, ORDER=None, CYCLES=None, VERBOSE=False, MODE=0o775): #-- convert parameters into variables #-- Data processing center PROC = parameters['PROC'] #-- Data Release DREL = parameters['DREL'] #-- GRACE/GRACE-FO dataset DSET = parameters['DSET'] #-- GRACE/GRACE-FO months START_MON = np.int(parameters['START']) END_MON = np.int(parameters['END']) MISSING = np.array(parameters['MISSING'].split(','), dtype=np.int) months = sorted(set(np.arange(START_MON, END_MON + 1)) - set(MISSING)) nmon = len(months) #-- maximum degree and order LMAX = np.int(parameters['LMAX']) if (parameters['MMAX'].title() == 'None'): MMAX = np.copy(LMAX) else: MMAX = np.int(parameters['MMAX']) #-- Glacial Isostatic Adjustment file to read GIA = parameters['GIA'] if (parameters['GIA'].title() != 'None') else None GIA_FILE = os.path.expanduser(parameters['GIA_FILE']) #-- remove a set of spherical harmonics from the GRACE data REDISTRIBUTE_REMOVED = parameters['REDISTRIBUTE_REMOVED'] in ('Y', 'y') #-- smoothing radius RAD = np.int(parameters['RAD']) #-- destriped coefficients DESTRIPE = parameters['DESTRIPE'] in ('Y', 'y') #-- output spatial units UNITS = np.int(parameters['UNITS']) #-- output degree spacing #-- can enter dlon and dlat as [dlon,dlat] or a single value DDEG = np.squeeze(np.array(parameters['DDEG'].split(','), dtype='f')) #-- output degree interval (0:360, 90:-90) or (degree spacing/2) INTERVAL = np.int(parameters['INTERVAL']) #-- input/output data format (ascii, netCDF4, HDF5) DATAFORM = parameters['DATAFORM'] #-- output directory and base filename DIRECTORY = os.path.expanduser(parameters['DIRECTORY']) FILENAME = parameters['FILENAME'] #-- output filename suffix suffix = dict(ascii='txt', netCDF4='nc', HDF5='H5')[DATAFORM] #-- flag for spherical harmonic order order_str = 'M{0:d}'.format(MMAX) if (MMAX != LMAX) else '' #-- Calculating the Gaussian smoothing for radius RAD gw_str = '_r{0:0.0f}km'.format(RAD) if (RAD != 0) else '' #-- destriped GRACE/GRACE-FO coefficients ds_str = '_FL' if DESTRIPE else '' #-- distributing removed mass uniformly over ocean ocean_str = '_OCN' if REDISTRIBUTE_REMOVED else '' #-- input and output spatial units unit_list = ['cmwe', 'mmGH', 'mmCU', u'\u03BCGal', 'mbar'] unit_name = [ 'Equivalent Water Thickness', 'Geoid Height', 'Elastic Crustal Uplift', 'Gravitational Undulation', 'Equivalent Surface Pressure' ] #-- input file format input_format = '{0}{1}_L{2:d}{3}{4}{5}_{6:03d}.{7}' #-- output file format output_format = '{0}{1}_L{2:d}{3}{4}{5}_{6}{7}_{8:03d}-{9:03d}.{10}' #-- Output Degree Spacing dlon, dlat = (DDEG, DDEG) if (np.ndim(DDEG) == 0) else (DDEG[0], DDEG[1]) #-- Output Degree Interval if (INTERVAL == 1): #-- (-180:180,90:-90) nlon = np.int((360.0 / dlon) + 1.0) nlat = np.int((180.0 / dlat) + 1.0) elif (INTERVAL == 2): #-- (Degree spacing)/2 nlon = np.int(360.0 / dlon) nlat = np.int(180.0 / dlat) #-- Setting output parameters for each fit type coef_str = ['x{0:d}'.format(o) for o in range(ORDER + 1)] unit_suffix = [ ' yr^{0:d}'.format(-o) if o else '' for o in range(ORDER + 1) ] if (ORDER == 0): #-- Mean unit_longname = ['Mean'] elif (ORDER == 1): #-- Trend unit_longname = ['Constant', 'Trend'] elif (ORDER == 2): #-- Quadratic unit_longname = ['Constant', 'Linear', 'Quadratic'] #-- filename strings for cyclical terms cyclic_str = {} cyclic_str['SEMI'] = ['SS', 'SC'] cyclic_str['ANN'] = ['AS', 'AC'] cyclic_str['S2'] = ['S2S', 'S2C'] #-- unit longnames for cyclical terms cyclic_longname = {} cyclic_longname['SEMI'] = ['Semi-Annual Sine', 'Semi-Annual Cosine'] cyclic_longname['ANN'] = ['Annual Sine', 'Annual Cosine'] cyclic_longname['S2'] = ['S2 Tidal Alias Sine', 'S2 Tidal Alias Cosine'] amp_str = [] for i, c in enumerate(CYCLES): if (c == 0.5): flag = 'SEMI' elif (c == 1.0): flag = 'ANN' elif (c == (161.0 / 365.25)): flag = 'S2' coef_str.extend(cyclic_str[flag]) unit_longname.extend(cyclic_longname[flag]) unit_suffix.extend(['', '']) amp_str.append(flag) #-- input data spatial object spatial_list = [] for t, grace_month in enumerate(months): #-- input GRACE/GRACE-FO spatial file fi = input_format.format(FILENAME, unit_list[UNITS - 1], LMAX, order_str, gw_str, ds_str, grace_month, suffix) #-- read GRACE/GRACE-FO spatial file if (DATAFORM == 'ascii'): dinput = spatial(spacing=[dlon, dlat], nlon=nlon, nlat=nlat).from_ascii(os.path.join(DIRECTORY, fi)) elif (DATAFORM == 'netCDF4'): #-- netcdf (.nc) dinput = spatial().from_netCDF4(os.path.join(DIRECTORY, fi)) elif (DATAFORM == 'HDF5'): #-- HDF5 (.H5) dinput = spatial().from_HDF5(os.path.join(DIRECTORY, fi)) #-- append to spatial list dinput.month[:] = grace_month nlat, nlon = dinput.shape spatial_list.append(dinput) #-- concatenate list to single spatial object grid = spatial().from_list(spatial_list) spatial_list = None #-- Fitting seasonal components ncomp = len(coef_str) ncycles = 2 * len(CYCLES) #-- Allocating memory for output variables out = dinput.zeros_like() out.data = np.zeros((nlat, nlon, ncomp)) out.error = np.zeros((nlat, nlon, ncomp)) out.mask = np.ones((nlat, nlon, ncomp), dtype=np.bool) #-- Fit Significance FS = {} #-- SSE: Sum of Squares Error #-- AIC: Akaike information criterion #-- BIC: Bayesian information criterion #-- R2Adj: Adjusted Coefficient of Determination for key in ['SSE', 'AIC', 'BIC', 'R2Adj']: FS[key] = dinput.zeros_like() #-- valid values for ocean function #-- calculate the regression coefficients and fit significance for i in range(nlat): for j in range(nlon): #-- Calculating the regression coefficients tsbeta = tsregress(grid.time, grid.data[i, j, :], ORDER=ORDER, CYCLES=CYCLES, CONF=0.95) #-- save regression components for k in range(0, ncomp): out.data[i, j, k] = tsbeta['beta'][k] out.error[i, j, k] = tsbeta['error'][k] out.mask[i, j, k] = False #-- Fit significance terms #-- Degrees of Freedom nu = tsbeta['DOF'] #-- Converting Mean Square Error to Sum of Squares Error FS['SSE'].data[i, j] = tsbeta['MSE'] * nu FS['AIC'].data[i, j] = tsbeta['AIC'] FS['BIC'].data[i, j] = tsbeta['BIC'] FS['R2Adj'].data[i, j] = tsbeta['R2Adj'] #-- list of output files output_files = [] #-- Output spatial files for i in range(0, ncomp): #-- output spatial file name f1 = output_format.format(FILENAME, unit_list[UNITS - 1], LMAX, order_str, gw_str, ds_str, coef_str[i], '', START_MON, END_MON, suffix) f2 = output_format.format(FILENAME, unit_list[UNITS - 1], LMAX, order_str, gw_str, ds_str, coef_str[i], '_ERROR', START_MON, END_MON, suffix) #-- full attributes UNITS_TITLE = '{0}{1}'.format(unit_list[UNITS - 1], unit_suffix[i]) LONGNAME = unit_name[UNITS - 1] FILE_TITLE = 'GRACE/GRACE-FO_Spatial_Data_{0}'.format(unit_longname[i]) #-- output regression fit to file output = out.index(i, date=False) output_data(output, FILENAME=os.path.join(DIRECTORY, f1), DATAFORM=DATAFORM, UNITS=UNITS_TITLE, LONGNAME=LONGNAME, TITLE=FILE_TITLE, VERBOSE=VERBOSE, MODE=MODE) output_data(output, FILENAME=os.path.join(DIRECTORY, f2), DATAFORM=DATAFORM, UNITS=UNITS_TITLE, LONGNAME=LONGNAME, TITLE=FILE_TITLE, KEY='error', VERBOSE=VERBOSE, MODE=MODE) #-- add output files to list object output_files.append(os.path.join(DIRECTORY, f1)) output_files.append(os.path.join(DIRECTORY, f2)) #-- if fitting coefficients with cyclical components if (ncycles > 0): #-- output spatial titles for amplitudes amp_title = { 'ANN': 'Annual Amplitude', 'SEMI': 'Semi-Annual Amplitude', 'S2': 'S2 Tidal Alias Amplitude' } ph_title = { 'ANN': 'Annual Phase', 'SEMI': 'Semi-Annual Phase', 'S2': 'S2 Tidal Alias Phase' } #-- output amplitude and phase of cyclical components for i, flag in enumerate(amp_str): #-- Indice pointing to the cyclical components j = 1 + ORDER + 2 * i #-- Allocating memory for output amplitude and phase amp = dinput.zeros_like() ph = dinput.zeros_like() #-- calculating amplitude and phase of spatial field amp.data, ph.data = tsamplitude(out.data[:, :, j], out.data[:, :, j + 1]) #-- convert phase from -180:180 to 0:360 ii, jj = np.nonzero(ph.data < 0) ph.data[ii, jj] += 360.0 #-- Amplitude Error comp1 = out.error[:, :, j] * out.data[:, :, j] / amp.data comp2 = out.error[:, :, j + 1] * out.data[:, :, j + 1] / amp.data amp.error = np.sqrt(comp1**2 + comp2**2) #-- Phase Error (degrees) comp1 = out.error[:, :, j] * out.data[:, :, j + 1] / (amp.data**2) comp2 = out.error[:, :, j + 1] * out.data[:, :, j] / (amp.data**2) ph.error = (180.0 / np.pi) * np.sqrt(comp1**2 + comp2**2) #-- output file names for amplitude, phase and errors f3 = output_format.format(FILENAME, unit_list[UNITS - 1], LMAX, order_str, gw_str, ds_str, flag, '', START_MON, END_MON, suffix) f4 = output_format.format(FILENAME, unit_list[UNITS - 1], LMAX, order_str, gw_str, ds_str, flag, '_PHASE', START_MON, END_MON, suffix) #-- output spatial error file name f5 = output_format.format(FILENAME, unit_list[UNITS - 1], LMAX, order_str, gw_str, ds_str, flag, '_ERROR', START_MON, END_MON, suffix) f6 = output_format.format(FILENAME, unit_list[UNITS - 1], LMAX, order_str, gw_str, ds_str, flag, '_PHASE_ERROR', START_MON, END_MON, suffix) #-- full attributes AMP_UNITS = unit_list[UNITS - 1] PH_UNITS = 'degrees' LONGNAME = unit_name[UNITS - 1] AMP_TITLE = 'GRACE/GRACE-FO_Spatial_Data_{0}'.format( amp_title[flag]) PH_TITLE = 'GRACE/GRACE-FO_Spatial_Data_{0}'.format(ph_title[flag]) #-- Output seasonal amplitude and phase to files output_data(amp, FILENAME=os.path.join(DIRECTORY, f3), DATAFORM=DATAFORM, UNITS=AMP_UNITS, LONGNAME=LONGNAME, TITLE=AMP_TITLE, VERBOSE=VERBOSE, MODE=MODE) output_data(ph, FILENAME=os.path.join(DIRECTORY, f4), DATAFORM=DATAFORM, UNITS=PH_UNITS, LONGNAME='Phase', TITLE=PH_TITLE, VERBOSE=VERBOSE, MODE=MODE) #-- Output seasonal amplitude and phase error to files output_data(amp, FILENAME=os.path.join(DIRECTORY, f5), DATAFORM=DATAFORM, UNITS=AMP_UNITS, LONGNAME=LONGNAME, TITLE=AMP_TITLE, KEY='error', VERBOSE=VERBOSE, MODE=MODE) output_data(ph, FILENAME=os.path.join(DIRECTORY, f6), DATAFORM=DATAFORM, UNITS=PH_UNITS, LONGNAME='Phase', TITLE=PH_TITLE, KEY='error', VERBOSE=VERBOSE, MODE=MODE) #-- add output files to list object output_files.append(os.path.join(DIRECTORY, f3)) output_files.append(os.path.join(DIRECTORY, f4)) output_files.append(os.path.join(DIRECTORY, f5)) output_files.append(os.path.join(DIRECTORY, f6)) #-- Output fit significance signif_longname = { 'SSE': 'Sum of Squares Error', 'AIC': 'Akaike information criterion', 'BIC': 'Bayesian information criterion', 'R2Adj': 'Adjusted Coefficient of Determination' } #-- for each fit significance term for key, fs in FS.items(): #-- output file names for fit significance signif_str = '{0}_'.format(key) f7 = output_format.format(FILENAME, unit_list[UNITS - 1], LMAX, order_str, gw_str, ds_str, signif_str, coef_str[ORDER], START_MON, END_MON, suffix) #-- full attributes LONGNAME = signif_longname[key] #-- output fit significance to file output_data(fs, FILENAME=os.path.join(DIRECTORY, f7), DATAFORM=DATAFORM, UNITS=key, LONGNAME=LONGNAME, TITLE=nu, VERBOSE=VERBOSE, MODE=MODE) #-- add output files to list object output_files.append(os.path.join(DIRECTORY, f7)) #-- return the list of output files return output_files
def convert_harmonics(INPUT_FILE, OUTPUT_FILE, LMAX=None, MMAX=None, UNITS=None, LOVE_NUMBERS=0, REFERENCE=None, DDEG=None, INTERVAL=None, MISSING=False, FILL_VALUE=None, HEADER=None, DATAFORM=None, VERBOSE=False, MODE=0o775): #-- verify that output directory exists DIRECTORY = os.path.abspath(os.path.dirname(OUTPUT_FILE)) if not os.access(DIRECTORY, os.F_OK): os.makedirs(DIRECTORY, MODE, exist_ok=True) #-- Grid spacing dlon, dlat = (DDEG, DDEG) if (np.ndim(DDEG) == 0) else (DDEG[0], DDEG[1]) #-- Grid dimensions if (INTERVAL == 1): #-- (0:360, 90:-90) nlon = np.int((360.0 / dlon) + 1.0) nlat = np.int((180.0 / dlat) + 1.0) elif (INTERVAL == 2): #-- degree spacing/2 nlon = np.int((360.0 / dlon)) nlat = np.int((180.0 / dlat)) #-- read spatial file in data format #-- expand dimensions if (DATAFORM == 'ascii'): #-- ascii (.txt) input_spatial = spatial(spacing=[dlon, dlat], nlat=nlat, nlon=nlon).from_ascii( INPUT_FILE, header=HEADER).expand_dims() elif (DATAFORM == 'netCDF4'): #-- netcdf (.nc) input_spatial = spatial().from_netCDF4(INPUT_FILE).expand_dims() elif (DATAFORM == 'HDF5'): #-- HDF5 (.H5) input_spatial = spatial().from_HDF5(INPUT_FILE).expand_dims() #-- convert missing values to zero input_spatial.replace_invalid(0.0) #-- input data shape nlat, nlon, nt = input_spatial.shape #-- read arrays of kl, hl, and ll Love Numbers LOVE = load_love_numbers(LMAX, LOVE_NUMBERS=LOVE_NUMBERS, REFERENCE=REFERENCE) #-- calculate associated Legendre polynomials th = (90.0 - input_spatial.lat) * np.pi / 180.0 PLM, dPLM = plm_holmes(LMAX, np.cos(th)) #-- date count array counter = np.arange(nt) #-- allocate for output spherical harmonics Ylms = harmonics(lmax=LMAX, mmax=MMAX) Ylms.time = input_spatial.time.copy() Ylms.month = np.array(12.0 * (Ylms.time - 2002.0), dtype='i') + 1 Ylms.clm = np.zeros((LMAX + 1, LMAX + 1, nt)) Ylms.slm = np.zeros((LMAX + 1, LMAX + 1, nt)) for t in range(nt): #-- convert spatial field to spherical harmonics output_Ylms = gen_stokes(input_spatial.data[:, :, t].T, input_spatial.lon, input_spatial.lat, UNITS=UNITS, LMIN=0, LMAX=LMAX, MMAX=MMAX, PLM=PLM, LOVE=LOVE) Ylms.clm[:, :, t] = output_Ylms['clm'][:, :].copy() Ylms.slm[:, :, t] = output_Ylms['slm'][:, :].copy() #-- if verbose output: print input and output file names if VERBOSE: print('{0}:'.format(os.path.basename(sys.argv[0]))) print('{0} -->\n\t{1}'.format(INPUT_FILE, OUTPUT_FILE)) #-- outputting data to file if (DATAFORM == 'ascii'): #-- ascii (.txt) Ylms.to_ascii(OUTPUT_FILE) elif (DATAFORM == 'netCDF4'): #-- netCDF4 (.nc) Ylms.to_netCDF4(OUTPUT_FILE) elif (DATAFORM == 'HDF5'): #-- HDF5 (.H5) Ylms.to_HDF5(OUTPUT_FILE) #-- change output permissions level to MODE os.chmod(OUTPUT_FILE, MODE)
def convert_harmonics(INPUT_FILE, OUTPUT_FILE, LMAX=None, MMAX=None, UNITS=None, LOVE_NUMBERS=0, REFERENCE=None, DDEG=None, INTERVAL=None, FILL_VALUE=None, HEADER=None, DATAFORM=None, MODE=0o775): #-- verify that output directory exists DIRECTORY = os.path.abspath(os.path.dirname(OUTPUT_FILE)) if not os.access(DIRECTORY, os.F_OK): os.makedirs(DIRECTORY, MODE, exist_ok=True) #-- Grid spacing dlon, dlat = (DDEG, DDEG) if (np.ndim(DDEG) == 0) else (DDEG[0], DDEG[1]) #-- Grid dimensions if (INTERVAL == 1): #-- (0:360, 90:-90) nlon = np.int64((360.0 / dlon) + 1.0) nlat = np.int64((180.0 / dlat) + 1.0) elif (INTERVAL == 2): #-- degree spacing/2 nlon = np.int64((360.0 / dlon)) nlat = np.int64((180.0 / dlat)) #-- read spatial file in data format #-- expand dimensions if (DATAFORM == 'ascii'): #-- ascii (.txt) input_spatial = spatial(spacing=[dlon, dlat], nlat=nlat, nlon=nlon, fill_value=FILL_VALUE).from_ascii( INPUT_FILE, header=HEADER).expand_dims() elif (DATAFORM == 'netCDF4'): #-- netcdf (.nc) input_spatial = spatial().from_netCDF4(INPUT_FILE).expand_dims() elif (DATAFORM == 'HDF5'): #-- HDF5 (.H5) input_spatial = spatial().from_HDF5(INPUT_FILE).expand_dims() #-- convert missing values to zero input_spatial.replace_invalid(0.0) #-- input data shape nlat, nlon, nt = input_spatial.shape #-- read arrays of kl, hl, and ll Love Numbers LOVE = load_love_numbers(LMAX, LOVE_NUMBERS=LOVE_NUMBERS, REFERENCE=REFERENCE) #-- upper bound of spherical harmonic orders (default = LMAX) if MMAX is None: MMAX = np.copy(LMAX) #-- calculate associated Legendre polynomials th = (90.0 - input_spatial.lat) * np.pi / 180.0 PLM, dPLM = plm_holmes(LMAX, np.cos(th)) #-- create list of harmonics objects Ylms_list = [] for i, t in enumerate(input_spatial.time): #-- convert spatial field to spherical harmonics output_Ylms = gen_stokes(input_spatial.data[:, :, i].T, input_spatial.lon, input_spatial.lat, UNITS=UNITS, LMIN=0, LMAX=LMAX, MMAX=MMAX, PLM=PLM, LOVE=LOVE) output_Ylms.time = np.copy(t) output_Ylms.month = calendar_to_grace(t) #-- append to list Ylms_list.append(output_Ylms) #-- convert Ylms list for output spherical harmonics Ylms = harmonics().from_list(Ylms_list) Ylms_list = None #-- outputting data to file if (DATAFORM == 'ascii'): #-- ascii (.txt) Ylms.to_ascii(OUTPUT_FILE) elif (DATAFORM == 'netCDF4'): #-- netCDF4 (.nc) Ylms.to_netCDF4(OUTPUT_FILE) elif (DATAFORM == 'HDF5'): #-- HDF5 (.H5) Ylms.to_HDF5(OUTPUT_FILE) #-- change output permissions level to MODE os.chmod(OUTPUT_FILE, MODE)
def grace_spatial_maps(base_dir, PROC, DREL, DSET, LMAX, RAD, START=None, END=None, MISSING=None, LMIN=None, MMAX=None, LOVE_NUMBERS=0, REFERENCE=None, DESTRIPE=False, UNITS=None, DDEG=None, INTERVAL=None, BOUNDS=None, GIA=None, GIA_FILE=None, ATM=False, POLE_TIDE=False, DEG1=None, DEG1_FILE=None, MODEL_DEG1=False, SLR_C20=None, SLR_21=None, SLR_22=None, SLR_C30=None, SLR_C50=None, DATAFORM=None, MEAN_FILE=None, MEANFORM=None, REMOVE_FILES=None, REMOVE_FORMAT=None, REDISTRIBUTE_REMOVED=False, LANDMASK=None, OUTPUT_DIRECTORY=None, FILE_PREFIX=None, VERBOSE=False, MODE=0o775): #-- recursively create output directory if not currently existing if not os.access(OUTPUT_DIRECTORY, os.F_OK): os.makedirs(OUTPUT_DIRECTORY, mode=MODE, exist_ok=True) #-- list object of output files for file logs (full path) output_files = [] #-- file information suffix = dict(ascii='txt', netCDF4='nc', HDF5='H5') #-- read arrays of kl, hl, and ll Love Numbers hl, kl, ll = load_love_numbers(LMAX, LOVE_NUMBERS=LOVE_NUMBERS, REFERENCE=REFERENCE) #-- Calculating the Gaussian smoothing for radius RAD if (RAD != 0): wt = 2.0 * np.pi * gauss_weights(RAD, LMAX) gw_str = '_r{0:0.0f}km'.format(RAD) else: #-- else = 1 wt = np.ones((LMAX + 1)) gw_str = '' #-- flag for spherical harmonic order MMAX = np.copy(LMAX) if not MMAX else MMAX order_str = 'M{0:d}'.format(MMAX) if (MMAX != LMAX) else '' #-- reading GRACE months for input date range #-- replacing low-degree harmonics with SLR values if specified #-- include degree 1 (geocenter) harmonics if specified #-- correcting for Pole-Tide and Atmospheric Jumps if specified Ylms = grace_input_months(base_dir, PROC, DREL, DSET, LMAX, START, END, MISSING, SLR_C20, DEG1, MMAX=MMAX, SLR_21=SLR_21, SLR_22=SLR_22, SLR_C30=SLR_C30, SLR_C50=SLR_C50, DEG1_FILE=DEG1_FILE, MODEL_DEG1=MODEL_DEG1, ATM=ATM, POLE_TIDE=POLE_TIDE) #-- convert to harmonics object and remove mean if specified GRACE_Ylms = harmonics().from_dict(Ylms) GRACE_Ylms.directory = Ylms['directory'] #-- use a mean file for the static field to remove if MEAN_FILE: #-- read data form for input mean file (ascii, netCDF4, HDF5, gfc) mean_Ylms = harmonics().from_file(MEAN_FILE, format=MEANFORM, date=False) #-- remove the input mean GRACE_Ylms.subtract(mean_Ylms) else: GRACE_Ylms.mean(apply=True) #-- date information of GRACE/GRACE-FO coefficients nfiles = len(GRACE_Ylms.time) #-- filter GRACE/GRACE-FO coefficients if DESTRIPE: #-- destriping GRACE/GRACE-FO coefficients ds_str = '_FL' GRACE_Ylms = GRACE_Ylms.destripe() else: #-- using standard GRACE/GRACE-FO harmonics ds_str = '' #-- input GIA spherical harmonic datafiles GIA_Ylms_rate = read_GIA_model(GIA_FILE, GIA=GIA, LMAX=LMAX, MMAX=MMAX) gia_str = '_{0}'.format(GIA_Ylms_rate['title']) if GIA else '' #-- calculate the monthly mass change from GIA GIA_Ylms = GRACE_Ylms.zeros_like() GIA_Ylms.time[:] = np.copy(GRACE_Ylms.time) GIA_Ylms.month[:] = np.copy(GRACE_Ylms.month) #-- monthly GIA calculated by gia_rate*time elapsed #-- finding change in GIA each month for t in range(nfiles): GIA_Ylms.clm[:, :, t] = GIA_Ylms_rate['clm'] * (GIA_Ylms.time[t] - 2003.3) GIA_Ylms.slm[:, :, t] = GIA_Ylms_rate['slm'] * (GIA_Ylms.time[t] - 2003.3) #-- default file prefix if not FILE_PREFIX: fargs = (PROC, DREL, DSET, Ylms['title'], gia_str) FILE_PREFIX = '{0}_{1}_{2}{3}{4}_'.format(*fargs) #-- Read Ocean function and convert to Ylms for redistribution if REDISTRIBUTE_REMOVED: #-- read Land-Sea Mask and convert to spherical harmonics ocean_Ylms = ocean_stokes(LANDMASK, LMAX, MMAX=MMAX, LOVE=(hl, kl, ll)) ocean_str = '_OCN' else: ocean_str = '' #-- input spherical harmonic datafiles to be removed from the GRACE data #-- Remove sets of Ylms from the GRACE data before returning remove_Ylms = GRACE_Ylms.zeros_like() remove_Ylms.time[:] = np.copy(GRACE_Ylms.time) remove_Ylms.month[:] = np.copy(GRACE_Ylms.month) if REMOVE_FILES: #-- extend list if a single format was entered for all files if len(REMOVE_FORMAT) < len(REMOVE_FILES): REMOVE_FORMAT = REMOVE_FORMAT * len(REMOVE_FILES) #-- for each file to be removed for REMOVE_FILE, REMOVEFORM in zip(REMOVE_FILES, REMOVE_FORMAT): if REMOVEFORM in ('ascii', 'netCDF4', 'HDF5'): #-- ascii (.txt) #-- netCDF4 (.nc) #-- HDF5 (.H5) Ylms = harmonics().from_file(REMOVE_FILE, format=REMOVEFORM) elif REMOVEFORM in ('index-ascii', 'index-netCDF4', 'index-HDF5'): #-- read from index file _, removeform = REMOVEFORM.split('-') #-- index containing files in data format Ylms = harmonics().from_index(REMOVE_FILE, format=removeform) #-- reduce to GRACE/GRACE-FO months and truncate to degree and order Ylms = Ylms.subset(GRACE_Ylms.month).truncate(lmax=LMAX, mmax=MMAX) #-- distribute removed Ylms uniformly over the ocean if REDISTRIBUTE_REMOVED: #-- calculate ratio between total removed mass and #-- a uniformly distributed cm of water over the ocean ratio = Ylms.clm[0, 0, :] / ocean_Ylms.clm[0, 0] #-- for each spherical harmonic for m in range(0, MMAX + 1): #-- MMAX+1 to include MMAX for l in range(m, LMAX + 1): #-- LMAX+1 to include LMAX #-- remove the ratio*ocean Ylms from Ylms #-- note: x -= y is equivalent to x = x - y Ylms.clm[l, m, :] -= ratio * ocean_Ylms.clm[l, m] Ylms.slm[l, m, :] -= ratio * ocean_Ylms.slm[l, m] #-- filter removed coefficients if DESTRIPE: Ylms = Ylms.destripe() #-- add data for month t and INDEX_FILE to the total #-- remove_clm and remove_slm matrices #-- redistributing the mass over the ocean if specified remove_Ylms.add(Ylms) #-- Output spatial data object grid = spatial() #-- Output Degree Spacing dlon, dlat = (DDEG[0], DDEG[0]) if (len(DDEG) == 1) else (DDEG[0], DDEG[1]) #-- Output Degree Interval if (INTERVAL == 1): #-- (-180:180,90:-90) nlon = np.int64((360.0 / dlon) + 1.0) nlat = np.int64((180.0 / dlat) + 1.0) grid.lon = -180 + dlon * np.arange(0, nlon) grid.lat = 90.0 - dlat * np.arange(0, nlat) elif (INTERVAL == 2): #-- (Degree spacing)/2 grid.lon = np.arange(-180 + dlon / 2.0, 180 + dlon / 2.0, dlon) grid.lat = np.arange(90.0 - dlat / 2.0, -90.0 - dlat / 2.0, -dlat) nlon = len(grid.lon) nlat = len(grid.lat) elif (INTERVAL == 3): #-- non-global grid set with BOUNDS parameter minlon, maxlon, minlat, maxlat = BOUNDS.copy() grid.lon = np.arange(minlon + dlon / 2.0, maxlon + dlon / 2.0, dlon) grid.lat = np.arange(maxlat - dlat / 2.0, minlat - dlat / 2.0, -dlat) nlon = len(grid.lon) nlat = len(grid.lat) #-- Computing plms for converting to spatial domain theta = (90.0 - grid.lat) * np.pi / 180.0 PLM, dPLM = plm_holmes(LMAX, np.cos(theta)) #-- Earth Parameters #-- output spatial units unit_list = ['cmwe', 'mmGH', 'mmCU', u'\u03BCGal', 'mbar'] unit_name = [ 'Equivalent Water Thickness', 'Geoid Height', 'Elastic Crustal Uplift', 'Gravitational Undulation', 'Equivalent Surface Pressure' ] #-- Setting units factor for output #-- dfactor computes the degree dependent coefficients if (UNITS == 1): #-- 1: cmwe, centimeters water equivalent dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).cmwe elif (UNITS == 2): #-- 2: mmGH, mm geoid height dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).mmGH elif (UNITS == 3): #-- 3: mmCU, mm elastic crustal deformation dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).mmCU elif (UNITS == 4): #-- 4: micGal, microGal gravity perturbations dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).microGal elif (UNITS == 5): #-- 5: mbar, millibars equivalent surface pressure dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).mbar else: raise ValueError('Invalid units code {0:d}'.format(UNITS)) #-- output file format file_format = '{0}{1}_L{2:d}{3}{4}{5}_{6:03d}.{7}' #-- converting harmonics to truncated, smoothed coefficients in units #-- combining harmonics to calculate output spatial fields for i, grace_month in enumerate(GRACE_Ylms.month): #-- GRACE/GRACE-FO harmonics for time t Ylms = GRACE_Ylms.index(i) #-- Remove GIA rate for time Ylms.subtract(GIA_Ylms.index(i)) #-- Remove monthly files to be removed Ylms.subtract(remove_Ylms.index(i)) #-- smooth harmonics and convert to output units Ylms.convolve(dfactor * wt) #-- convert spherical harmonics to output spatial grid grid.data = harmonic_summation(Ylms.clm, Ylms.slm, grid.lon, grid.lat, LMIN=LMIN, LMAX=LMAX, MMAX=MMAX, PLM=PLM).T #-- copy time variables for month grid.time = np.copy(Ylms.time) grid.month = np.copy(Ylms.month) #-- output monthly files to ascii, netCDF4 or HDF5 args = (FILE_PREFIX, unit_list[UNITS - 1], LMAX, order_str, gw_str, ds_str, grace_month, suffix[DATAFORM]) FILE = os.path.join(OUTPUT_DIRECTORY, file_format.format(*args)) if (DATAFORM == 'ascii'): #-- ascii (.txt) grid.to_ascii(FILE, date=True, verbose=VERBOSE) elif (DATAFORM == 'netCDF4'): #-- netCDF4 grid.to_netCDF4(FILE, date=True, verbose=VERBOSE, units=unit_list[UNITS - 1], longname=unit_name[UNITS - 1], title='GRACE/GRACE-FO Spatial Data') elif (DATAFORM == 'HDF5'): #-- HDF5 grid.to_HDF5(FILE, date=True, verbose=VERBOSE, units=unit_list[UNITS - 1], longname=unit_name[UNITS - 1], title='GRACE/GRACE-FO Spatial Data') #-- set the permissions mode of the output files os.chmod(FILE, MODE) #-- add file to list output_files.append(FILE) #-- return the list of output files return output_files
def calc_sensitivity_kernel(LMAX, RAD, LMIN=None, MMAX=None, LOVE_NUMBERS=0, REFERENCE=None, DATAFORM=None, MASCON_FILE=None, REDISTRIBUTE_MASCONS=False, FIT_METHOD=0, LANDMASK=None, DDEG=None, INTERVAL=None, OUTPUT_DIRECTORY=None, MODE=0o775): #-- file information suffix = dict(ascii='txt', netCDF4='nc', HDF5='H5') #-- file parser for reading index files #-- removes commented lines (can comment out files in the index) #-- removes empty lines (if there are extra empty lines) parser = re.compile(r'^(?!\#|\%|$)', re.VERBOSE) #-- Create output Directory if not currently existing if (not os.access(OUTPUT_DIRECTORY, os.F_OK)): os.mkdir(OUTPUT_DIRECTORY) #-- list object of output files for file logs (full path) output_files = [] #-- read arrays of kl, hl, and ll Love Numbers hl, kl, ll = load_love_numbers(LMAX, LOVE_NUMBERS=LOVE_NUMBERS, REFERENCE=REFERENCE) #-- Earth Parameters factors = units(lmax=LMAX).harmonic(hl, kl, ll) #-- Average Density of the Earth [g/cm^3] rho_e = factors.rho_e #-- Average Radius of the Earth [cm] rad_e = factors.rad_e #-- input/output string for both LMAX==MMAX and LMAX != MMAX cases MMAX = np.copy(LMAX) if not MMAX else MMAX order_str = 'M{0:d}'.format(MMAX) if (MMAX != LMAX) else '' #-- Calculating the Gaussian smoothing for radius RAD if (RAD != 0): wt = 2.0 * np.pi * gauss_weights(RAD, LMAX) gw_str = '_r{0:0.0f}km'.format(RAD) else: #-- else = 1 wt = np.ones((LMAX + 1)) gw_str = '' #-- Read Ocean function and convert to Ylms for redistribution if REDISTRIBUTE_MASCONS: #-- read Land-Sea Mask and convert to spherical harmonics ocean_Ylms = ocean_stokes(LANDMASK, LMAX, MMAX=MMAX, LOVE=(hl, kl, ll)) ocean_str = '_OCN' else: #-- not distributing uniformly over ocean ocean_str = '' #-- input mascon spherical harmonic datafiles with open(MASCON_FILE, 'r') as f: mascon_files = [l for l in f.read().splitlines() if parser.match(l)] #-- number of mascons n_mas = len(mascon_files) #-- spatial area of the mascon total_area = np.zeros((n_mas)) #-- name of each mascon mascon_name = [] #-- for each valid file in the index (iterate over mascons) mascon_list = [] for k, fi in enumerate(mascon_files): #-- read mascon spherical harmonics Ylms = harmonics().from_file(os.path.expanduser(fi), format=DATAFORM, date=False) #-- Calculating the total mass of each mascon (1 cmwe uniform) total_area[k] = 4.0 * np.pi * (rad_e**3) * rho_e * Ylms.clm[0, 0] / 3.0 #-- distribute mascon mass uniformly over the ocean if REDISTRIBUTE_MASCONS: #-- calculate ratio between total mascon mass and #-- a uniformly distributed cm of water over the ocean ratio = Ylms.clm[0, 0] / ocean_Ylms.clm[0, 0] #-- for each spherical harmonic for m in range(0, MMAX + 1): #-- MMAX+1 to include MMAX for l in range(m, LMAX + 1): #-- LMAX+1 to include LMAX #-- remove ratio*ocean Ylms from mascon Ylms #-- note: x -= y is equivalent to x = x - y Ylms.clm[l, m] -= ratio * ocean_Ylms.clm[l, m] Ylms.slm[l, m] -= ratio * ocean_Ylms.slm[l, m] #-- truncate mascon spherical harmonics to d/o LMAX/MMAX and add to list mascon_list.append(Ylms.truncate(lmax=LMAX, mmax=MMAX)) #-- mascon base is the file without directory or suffix mascon_base = os.path.basename(mascon_files[k]) mascon_base = os.path.splitext(mascon_base)[0] #-- if lower case, will capitalize mascon_base = mascon_base.upper() #-- if mascon name contains degree and order info, remove mascon_name.append(mascon_base.replace('_L{0:d}'.format(LMAX), '')) #-- create single harmonics object from list mascon_Ylms = harmonics().from_list(mascon_list, date=False) #-- Output spatial data object grid = spatial() #-- Output Degree Spacing dlon, dlat = (DDEG[0], DDEG[0]) if (len(DDEG) == 1) else (DDEG[0], DDEG[1]) #-- Output Degree Interval if (INTERVAL == 1): #-- (-180:180,90:-90) n_lon = np.int64((360.0 / dlon) + 1.0) n_lat = np.int64((180.0 / dlat) + 1.0) grid.lon = -180 + dlon * np.arange(0, n_lon) grid.lat = 90.0 - dlat * np.arange(0, n_lat) elif (INTERVAL == 2): #-- (Degree spacing)/2 grid.lon = np.arange(-180 + dlon / 2.0, 180 + dlon / 2.0, dlon) grid.lat = np.arange(90.0 - dlat / 2.0, -90.0 - dlat / 2.0, -dlat) n_lon = len(grid.lon) n_lat = len(grid.lat) #-- Computing plms for converting to spatial domain theta = (90.0 - grid.lat) * np.pi / 180.0 PLM, dPLM = plm_holmes(LMAX, np.cos(theta)) #-- Calculating the number of cos and sin harmonics between LMIN and LMAX #-- taking into account MMAX (if MMAX == LMAX then LMAX-MMAX=0) n_harm = np.int64(LMAX**2 - LMIN**2 + 2 * LMAX + 1 - (LMAX - MMAX)**2 - (LMAX - MMAX)) #-- Initialing harmonics for least squares fitting #-- mascon kernel M_lm = np.zeros((n_harm, n_mas)) #-- mascon kernel converted to output unit MA_lm = np.zeros((n_harm, n_mas)) #-- sensitivity kernel A_lm = np.zeros((n_harm, n_mas)) #-- Initializing conversion factors #-- factor for converting to smoothed coefficients of mass fact = np.zeros((n_harm)) #-- factor for converting back into geoid coefficients fact_inv = np.zeros((n_harm)) #-- smoothing factor wt_lm = np.zeros((n_harm)) #-- ii is a counter variable for building the mascon column array ii = 0 #-- Creating column array of clm/slm coefficients #-- Order is [C00...C6060,S11...S6060] #-- Calculating factor to convert geoid spherical harmonic coefficients #-- to coefficients of mass (Wahr, 1998) coeff = rho_e * rad_e / 3.0 coeff_inv = 0.75 / (np.pi * rho_e * rad_e**3) #-- Switching between Cosine and Sine Stokes for cs, csharm in enumerate(['clm', 'slm']): #-- copy cosine and sin harmonics mascon_harm = getattr(mascon_Ylms, csharm) #-- for each spherical harmonic degree #-- +1 to include LMAX for l in range(LMIN, LMAX + 1): #-- for each spherical harmonic order #-- Sine Stokes for (m=0) = 0 mm = np.min([MMAX, l]) #-- +1 to include l or MMAX (whichever is smaller) for m in range(cs, mm + 1): #-- Mascon Spherical Harmonics M_lm[ii, :] = np.copy(mascon_harm[l, m, :]) #-- degree dependent factor to convert to mass fact[ii] = (2.0 * l + 1.0) / (1.0 + kl[l]) #-- degree dependent factor to convert from mass fact_inv[ii] = coeff_inv * (1.0 + kl[l]) / (2.0 * l + 1.0) #-- degree dependent smoothing wt_lm[ii] = np.copy(wt[l]) #-- add 1 to counter ii += 1 #-- Converting mascon coefficients to fit method if (FIT_METHOD == 1): #-- Fitting Sensitivity Kernel as mass coefficients #-- converting M_lm to mass coefficients of the kernel for i in range(n_harm): MA_lm[i, :] = M_lm[i, :] * wt_lm[i] * fact[i] fit_factor = wt_lm * fact inv_fit_factor = np.copy(fact_inv) else: #-- Fitting Sensitivity Kernel as geoid coefficients for i in range(n_harm): MA_lm[:, :] = M_lm[i, :] * wt_lm[i] fit_factor = wt_lm * np.ones((n_harm)) inv_fit_factor = np.ones((n_harm)) #-- Fitting the sensitivity kernel from the input kernel for i in range(n_harm): #-- setting kern_i equal to 1 for d/o kern_i = np.zeros((n_harm)) #-- converting to mass coefficients if specified kern_i[i] = 1.0 * fit_factor[i] #-- spherical harmonics solution for the #-- mascon sensitivity kernels #-- Least Squares Solutions: Inv(X'.X).(X'.Y) kern_lm = np.linalg.lstsq(MA_lm, kern_i, rcond=-1)[0] for k in range(n_mas): A_lm[i, k] = kern_lm[k] * total_area[k] #-- for each mascon for k in range(n_mas): #-- reshaping harmonics of sensitivity kernel to LMAX+1,MMAX+1 #-- calculating the spatial sensitivity kernel of each mascon #-- kernel calculated as outlined in Tiwari (2009) and Jacobs (2012) #-- Initializing output sensitivity kernel (both spatial and Ylms) kern_Ylms = harmonics(lmax=LMAX, mmax=MMAX) kern_Ylms.clm = np.zeros((LMAX + 1, MMAX + 1)) kern_Ylms.slm = np.zeros((LMAX + 1, MMAX + 1)) kern_Ylms.time = total_area[k] #-- counter variable for deconstructing the mascon column arrays ii = 0 #-- Switching between Cosine and Sine Stokes for cs, csharm in enumerate(['clm', 'slm']): #-- for each spherical harmonic degree #-- +1 to include LMAX for l in range(LMIN, LMAX + 1): #-- for each spherical harmonic order #-- Sine Stokes for (m=0) = 0 mm = np.min([MMAX, l]) #-- +1 to include l or MMAX (whichever is smaller) for m in range(cs, mm + 1): #-- inv_fit_factor: normalize from mass harmonics temp = getattr(kern_Ylms, csharm) temp[l, m] = inv_fit_factor[ii] * A_lm[ii, k] #-- add 1 to counter ii += 1 #-- convert spherical harmonics to output spatial grid grid.data = harmonic_summation(kern_Ylms.clm, kern_Ylms.slm, grid.lon, grid.lat, LMAX=LMAX, MMAX=MMAX, PLM=PLM).T grid.time = total_area[k] #-- output names for sensitivity kernel Ylm and spatial files #-- for both LMAX==MMAX and LMAX != MMAX cases args = (mascon_name[k], ocean_str, LMAX, order_str, gw_str, suffix[DATAFORM]) FILE1 = '{0}_SKERNEL_CLM{1}_L{2:d}{3}{4}.{5}'.format(*args) FILE2 = '{0}_SKERNEL{1}_L{2:d}{3}{4}.{5}'.format(*args) #-- output sensitivity kernel to file if (DATAFORM == 'ascii'): #-- ascii (.txt) kern_Ylms.to_ascii(os.path.join(OUTPUT_DIRECTORY, FILE1), date=False) grid.to_ascii(os.path.join(OUTPUT_DIRECTORY, FILE2), date=False, units='unitless', longname='Sensitivity_Kernel') elif (DATAFORM == 'netCDF4'): #-- netCDF4 (.nc) kern_Ylms.to_netCDF4(os.path.join(OUTPUT_DIRECTORY, FILE1), date=False) grid.to_netCDF4(os.path.join(OUTPUT_DIRECTORY, FILE2), date=False, units='unitless', longname='Sensitivity_Kernel') elif (DATAFORM == 'HDF5'): #-- netcdf (.H5) kern_Ylms.to_HDF5(os.path.join(OUTPUT_DIRECTORY, FILE1), date=False) grid.to_HDF5(os.path.join(OUTPUT_DIRECTORY, FILE2), date=False, units='unitless', longname='Sensitivity_Kernel') #-- change the permissions mode os.chmod(os.path.join(OUTPUT_DIRECTORY, FILE1), MODE) os.chmod(os.path.join(OUTPUT_DIRECTORY, FILE2), MODE) #-- add output files to list object output_files.append(os.path.join(OUTPUT_DIRECTORY, FILE1)) output_files.append(os.path.join(OUTPUT_DIRECTORY, FILE2)) #-- return the list of output files return output_files
def scale_grace_maps(base_dir, PROC, DREL, DSET, LMAX, RAD, START=None, END=None, MISSING=None, LMIN=None, MMAX=None, LOVE_NUMBERS=0, REFERENCE=None, DESTRIPE=False, DDEG=None, INTERVAL=None, GIA=None, GIA_FILE=None, ATM=False, POLE_TIDE=False, DEG1=None, DEG1_FILE=None, MODEL_DEG1=False, SLR_C20=None, SLR_21=None, SLR_22=None, SLR_C30=None, SLR_C50=None, DATAFORM=None, MEAN_FILE=None, MEANFORM=None, REMOVE_FILES=None, REMOVE_FORMAT=None, REDISTRIBUTE_REMOVED=False, SCALE_FILE=None, ERROR_FILE=None, POWER_FILE=None, LANDMASK=None, OUTPUT_DIRECTORY=None, FILE_PREFIX=None, VERBOSE=False, MODE=0o775): #-- recursively create output Directory if not currently existing if not os.access(OUTPUT_DIRECTORY, os.F_OK): os.makedirs(OUTPUT_DIRECTORY, mode=MODE, exist_ok=True) #-- list object of output files for file logs (full path) output_files = [] #-- file information suffix = dict(ascii='txt', netCDF4='nc', HDF5='H5') #-- output file format file_format = '{0}{1}{2}_L{3:d}{4}{5}{6}_{7:03d}-{8:03d}.{9}' #-- read arrays of kl, hl, and ll Love Numbers hl,kl,ll = load_love_numbers(LMAX, LOVE_NUMBERS=LOVE_NUMBERS, REFERENCE=REFERENCE) #-- atmospheric ECMWF "jump" flag (if ATM) atm_str = '_wATM' if ATM else '' #-- output string for both LMAX==MMAX and LMAX != MMAX cases MMAX = np.copy(LMAX) if not MMAX else MMAX order_str = 'M{0:d}'.format(MMAX) if (MMAX != LMAX) else '' #-- output spatial units unit_str = 'cmwe' unit_name = 'Equivalent Water Thickness' #-- invalid value fill_value = -9999.0 #-- Calculating the Gaussian smoothing for radius RAD if (RAD != 0): wt = 2.0*np.pi*gauss_weights(RAD,LMAX) gw_str = '_r{0:0.0f}km'.format(RAD) else: #-- else = 1 wt = np.ones((LMAX+1)) gw_str = '' #-- Read Ocean function and convert to Ylms for redistribution if REDISTRIBUTE_REMOVED: #-- read Land-Sea Mask and convert to spherical harmonics ocean_Ylms = ocean_stokes(LANDMASK, LMAX, MMAX=MMAX, LOVE=(hl,kl,ll)) #-- Grid spacing dlon,dlat = (DDEG[0],DDEG[0]) if (len(DDEG) == 1) else (DDEG[0],DDEG[1]) #-- Grid dimensions if (INTERVAL == 1):#-- (0:360, 90:-90) nlon = np.int64((360.0/dlon)+1.0) nlat = np.int64((180.0/dlat)+1.0) elif (INTERVAL == 2):#-- degree spacing/2 nlon = np.int64((360.0/dlon)) nlat = np.int64((180.0/dlat)) #-- read data for input scale files (ascii, netCDF4, HDF5) if (DATAFORM == 'ascii'): kfactor = spatial(spacing=[dlon,dlat],nlat=nlat,nlon=nlon).from_ascii( SCALE_FILE,date=False) k_error = spatial(spacing=[dlon,dlat],nlat=nlat,nlon=nlon).from_ascii( ERROR_FILE,date=False) k_power = spatial(spacing=[dlon,dlat],nlat=nlat,nlon=nlon).from_ascii( POWER_FILE,date=False) elif (DATAFORM == 'netCDF4'): kfactor = spatial().from_netCDF4(SCALE_FILE,date=False) k_error = spatial().from_netCDF4(ERROR_FILE,date=False) k_power = spatial().from_netCDF4(POWER_FILE,date=False) elif (DATAFORM == 'HDF5'): kfactor = spatial().from_HDF5(SCALE_FILE,date=False) k_error = spatial().from_HDF5(ERROR_FILE,date=False) k_power = spatial().from_HDF5(POWER_FILE,date=False) #-- input data shape nlat,nlon = kfactor.shape #-- input GRACE/GRACE-FO spherical harmonic datafiles for date range #-- replacing low-degree harmonics with SLR values if specified #-- include degree 1 (geocenter) harmonics if specified #-- correcting for Pole-Tide and Atmospheric Jumps if specified Ylms = grace_input_months(base_dir, PROC, DREL, DSET, LMAX, START, END, MISSING, SLR_C20, DEG1, MMAX=MMAX, SLR_21=SLR_21, SLR_22=SLR_22, SLR_C30=SLR_C30, SLR_C50=SLR_C50, DEG1_FILE=DEG1_FILE, MODEL_DEG1=MODEL_DEG1, ATM=ATM, POLE_TIDE=POLE_TIDE) #-- create harmonics object from GRACE/GRACE-FO data GRACE_Ylms = harmonics().from_dict(Ylms) GRACE_Ylms.directory = Ylms['directory'] #-- use a mean file for the static field to remove if MEAN_FILE: #-- read data form for input mean file (ascii, netCDF4, HDF5, gfc) mean_Ylms = harmonics().from_file(MEAN_FILE,format=MEANFORM,date=False) #-- remove the input mean GRACE_Ylms.subtract(mean_Ylms) else: GRACE_Ylms.mean(apply=True) #-- date information of GRACE/GRACE-FO coefficients nfiles = len(GRACE_Ylms.time) #-- filter GRACE/GRACE-FO coefficients if DESTRIPE: #-- destriping GRACE/GRACE-FO coefficients ds_str = '_FL' GRACE_Ylms = GRACE_Ylms.destripe() else: #-- using standard GRACE/GRACE-FO harmonics ds_str = '' #-- input GIA spherical harmonic datafiles GIA_Ylms_rate = read_GIA_model(GIA_FILE,GIA=GIA,LMAX=LMAX,MMAX=MMAX) gia_str = '_{0}'.format(GIA_Ylms_rate['title']) if GIA else '' #-- calculate the monthly mass change from GIA GIA_Ylms = GRACE_Ylms.zeros_like() GIA_Ylms.time[:] = np.copy(GRACE_Ylms.time) GIA_Ylms.month[:] = np.copy(GRACE_Ylms.month) #-- monthly GIA calculated by gia_rate*time elapsed #-- finding change in GIA each month for t in range(nfiles): GIA_Ylms.clm[:,:,t] = GIA_Ylms_rate['clm']*(GIA_Ylms.time[t]-2003.3) GIA_Ylms.slm[:,:,t] = GIA_Ylms_rate['slm']*(GIA_Ylms.time[t]-2003.3) #-- default file prefix if not FILE_PREFIX: fargs = (PROC,DREL,DSET,Ylms['title'],gia_str) FILE_PREFIX = '{0}_{1}_{2}{3}{4}_'.format(*fargs) #-- input spherical harmonic datafiles to be removed from the GRACE data #-- Remove sets of Ylms from the GRACE data before returning remove_Ylms = GRACE_Ylms.zeros_like() remove_Ylms.time[:] = np.copy(GRACE_Ylms.time) remove_Ylms.month[:] = np.copy(GRACE_Ylms.month) if REMOVE_FILES: #-- extend list if a single format was entered for all files if len(REMOVE_FORMAT) < len(REMOVE_FILES): REMOVE_FORMAT = REMOVE_FORMAT*len(REMOVE_FILES) #-- for each file to be removed for REMOVE_FILE,REMOVEFORM in zip(REMOVE_FILES,REMOVE_FORMAT): if REMOVEFORM in ('ascii','netCDF4','HDF5'): #-- ascii (.txt) #-- netCDF4 (.nc) #-- HDF5 (.H5) Ylms = harmonics().from_file(REMOVE_FILE, format=REMOVEFORM) elif REMOVEFORM in ('index-ascii','index-netCDF4','index-HDF5'): #-- read from index file _,removeform = REMOVEFORM.split('-') #-- index containing files in data format Ylms = harmonics().from_index(REMOVE_FILE, format=removeform) #-- reduce to GRACE/GRACE-FO months and truncate to degree and order Ylms = Ylms.subset(GRACE_Ylms.month).truncate(lmax=LMAX,mmax=MMAX) #-- distribute removed Ylms uniformly over the ocean if REDISTRIBUTE_REMOVED: #-- calculate ratio between total removed mass and #-- a uniformly distributed cm of water over the ocean ratio = Ylms.clm[0,0,:]/ocean_Ylms.clm[0,0] #-- for each spherical harmonic for m in range(0,MMAX+1):#-- MMAX+1 to include MMAX for l in range(m,LMAX+1):#-- LMAX+1 to include LMAX #-- remove the ratio*ocean Ylms from Ylms #-- note: x -= y is equivalent to x = x - y Ylms.clm[l,m,:] -= ratio*ocean_Ylms.clm[l,m] Ylms.slm[l,m,:] -= ratio*ocean_Ylms.slm[l,m] #-- filter removed coefficients if DESTRIPE: Ylms = Ylms.destripe() #-- add data for month t and INDEX_FILE to the total #-- remove_clm and remove_slm matrices #-- redistributing the mass over the ocean if specified remove_Ylms.add(Ylms) #-- calculating GRACE/GRACE-FO error (Wahr et al. 2006) #-- output GRACE error file (for both LMAX==MMAX and LMAX != MMAX cases) args = (PROC,DREL,DSET,LMAX,order_str,ds_str,atm_str,GRACE_Ylms.month[0], GRACE_Ylms.month[-1], suffix[DATAFORM]) delta_format = '{0}_{1}_{2}_DELTA_CLM_L{3:d}{4}{5}{6}_{7:03d}-{8:03d}.{9}' DELTA_FILE = os.path.join(GRACE_Ylms.directory,delta_format.format(*args)) #-- check full path of the GRACE directory for delta file #-- if file was previously calculated: will read file #-- else: will calculate the GRACE/GRACE-FO error if not os.access(DELTA_FILE, os.F_OK): #-- add output delta file to list object output_files.append(DELTA_FILE) #-- Delta coefficients of GRACE time series (Error components) delta_Ylms = harmonics(lmax=LMAX,mmax=MMAX) delta_Ylms.clm = np.zeros((LMAX+1,MMAX+1)) delta_Ylms.slm = np.zeros((LMAX+1,MMAX+1)) #-- Smoothing Half-Width (CNES is a 10-day solution) #-- All other solutions are monthly solutions (HFWTH for annual = 6) if ((PROC == 'CNES') and (DREL in ('RL01','RL02'))): HFWTH = 19 else: HFWTH = 6 #-- Equal to the noise of the smoothed time-series #-- for each spherical harmonic order for m in range(0,MMAX+1):#-- MMAX+1 to include MMAX #-- for each spherical harmonic degree for l in range(m,LMAX+1):#-- LMAX+1 to include LMAX #-- Delta coefficients of GRACE time series for cs,csharm in enumerate(['clm','slm']): #-- calculate GRACE Error (Noise of smoothed time-series) #-- With Annual and Semi-Annual Terms val1 = getattr(GRACE_Ylms, csharm) smth = tssmooth(GRACE_Ylms.time, val1[l,m,:], HFWTH=HFWTH) #-- number of smoothed points nsmth = len(smth['data']) tsmth = np.mean(smth['time']) #-- GRACE delta Ylms #-- variance of data-(smoothed+annual+semi) val2 = getattr(delta_Ylms, csharm) val2[l,m] = np.sqrt(np.sum(smth['noise']**2)/nsmth) #-- save GRACE/GRACE-FO delta harmonics to file delta_Ylms.time = np.copy(tsmth) delta_Ylms.month = np.int64(nsmth) delta_Ylms.to_file(DELTA_FILE,format=DATAFORM) else: #-- read GRACE/GRACE-FO delta harmonics from file delta_Ylms = harmonics().from_file(DELTA_FILE,format=DATAFORM) #-- copy time and number of smoothed fields tsmth = np.squeeze(delta_Ylms.time) nsmth = np.int64(delta_Ylms.month) #-- Output spatial data object grid = spatial() grid.lon = np.copy(kfactor.lon) grid.lat = np.copy(kfactor.lat) grid.time = np.zeros((nfiles)) grid.month = np.zeros((nfiles),dtype=np.int64) grid.data = np.zeros((nlat,nlon,nfiles)) grid.mask = np.zeros((nlat,nlon,nfiles),dtype=bool) #-- Computing plms for converting to spatial domain phi = grid.lon[np.newaxis,:]*np.pi/180.0 theta = (90.0-grid.lat)*np.pi/180.0 PLM,dPLM = plm_holmes(LMAX,np.cos(theta)) #-- square of legendre polynomials truncated to order MMAX mm = np.arange(0,MMAX+1) PLM2 = PLM[:,mm,:]**2 #-- dfactor is the degree dependent coefficients #-- for converting to centimeters water equivalent (cmwe) dfactor = units(lmax=LMAX).harmonic(hl,kl,ll).cmwe #-- converting harmonics to truncated, smoothed coefficients in units #-- combining harmonics to calculate output spatial fields for i,gm in enumerate(GRACE_Ylms.month): #-- GRACE/GRACE-FO harmonics for time t Ylms = GRACE_Ylms.index(i) #-- Remove GIA rate for time Ylms.subtract(GIA_Ylms.index(i)) #-- Remove monthly files to be removed Ylms.subtract(remove_Ylms.index(i)) #-- smooth harmonics and convert to output units Ylms.convolve(dfactor*wt) #-- convert spherical harmonics to output spatial grid grid.data[:,:,i] = harmonic_summation(Ylms.clm, Ylms.slm, grid.lon, grid.lat, LMAX=LMAX, MMAX=MMAX, PLM=PLM).T #-- copy time variables for month grid.time[i] = np.copy(Ylms.time) grid.month[i] = np.copy(Ylms.month) #-- update spacing and dimensions grid.update_spacing() grid.update_extents() grid.update_dimensions() #-- scale output data with kfactor grid = grid.scale(kfactor.data) grid.replace_invalid(fill_value, mask=kfactor.mask) #-- output monthly files to ascii, netCDF4 or HDF5 args = (FILE_PREFIX,'',unit_str,LMAX,order_str,gw_str,ds_str, grid.month[0],grid.month[-1],suffix[DATAFORM]) FILE=os.path.join(OUTPUT_DIRECTORY,file_format.format(*args)) if (DATAFORM == 'ascii'): #-- ascii (.txt) grid.to_ascii(FILE, date=True, verbose=VERBOSE) elif (DATAFORM == 'netCDF4'): #-- netCDF4 grid.to_netCDF4(FILE, date=True, verbose=VERBOSE, units=unit_str, longname=unit_name, title='GRACE/GRACE-FO Spatial Data') elif (DATAFORM == 'HDF5'): #-- HDF5 grid.to_HDF5(FILE, date=True, verbose=VERBOSE, units=unit_str, longname=unit_name, title='GRACE/GRACE-FO Spatial Data') #-- set the permissions mode of the output files os.chmod(FILE, MODE) #-- add file to list output_files.append(FILE) #-- calculate power of scaled GRACE/GRACE-FO data scaled_power = grid.sum(power=2.0).power(0.5) #-- calculate residual leakage errors #-- scaled by ratio of GRACE and synthetic power ratio = scaled_power.scale(k_power.power(-1).data) error = k_error.scale(ratio.data) #-- output monthly error files to ascii, netCDF4 or HDF5 args = (FILE_PREFIX,'ERROR_',unit_str,LMAX,order_str,gw_str,ds_str, grid.month[0],grid.month[-1],suffix[DATAFORM]) FILE = os.path.join(OUTPUT_DIRECTORY,file_format.format(*args)) if (DATAFORM == 'ascii'): #-- ascii (.txt) error.to_ascii(FILE, date=False, verbose=VERBOSE) elif (DATAFORM == 'netCDF4'): #-- netCDF4 error.to_netCDF4(FILE, date=False, verbose=VERBOSE, units=unit_str, longname=unit_name, title='GRACE/GRACE-FO Scaling Error') elif (DATAFORM == 'HDF5'): #-- HDF5 error.to_HDF5(FILE, date=False, verbose=VERBOSE, units=unit_str, longname=unit_name, title='GRACE/GRACE-FO Scaling Error') #-- set the permissions mode of the output files os.chmod(FILE, MODE) #-- add file to list output_files.append(FILE) #-- Output spatial data object delta = spatial() delta.lon = np.copy(kfactor.lon) delta.lat = np.copy(kfactor.lat) delta.time = np.copy(tsmth) delta.month = np.copy(nsmth) delta.data = np.zeros((nlat,nlon)) delta.mask = np.zeros((nlat,nlon),dtype=bool) #-- calculate scaled spatial error #-- Calculating cos(m*phi)^2 and sin(m*phi)^2 m = delta_Ylms.m[:,np.newaxis] ccos = np.cos(np.dot(m,phi))**2 ssin = np.sin(np.dot(m,phi))**2 #-- truncate delta harmonics to spherical harmonic range Ylms = delta_Ylms.truncate(LMAX,lmin=LMIN,mmax=MMAX) #-- convolve delta harmonics with degree dependent factors #-- smooth harmonics and convert to output units Ylms = Ylms.convolve(dfactor*wt).power(2.0).scale(1.0/nsmth) #-- Calculate fourier coefficients d_cos = np.zeros((MMAX+1,nlat))#-- [m,th] d_sin = np.zeros((MMAX+1,nlat))#-- [m,th] #-- Calculating delta spatial values for k in range(0,nlat): #-- summation over all spherical harmonic degrees d_cos[:,k] = np.sum(PLM2[:,:,k]*Ylms.clm, axis=0) d_sin[:,k] = np.sum(PLM2[:,:,k]*Ylms.slm, axis=0) #-- Multiplying by c/s(phi#m) to get spatial error map delta.data[:] = np.sqrt(np.dot(ccos.T,d_cos) + np.dot(ssin.T,d_sin)).T #-- update spacing and dimensions delta.update_spacing() delta.update_extents() delta.update_dimensions() #-- scale output harmonic errors with kfactor delta = delta.scale(kfactor.data) delta.replace_invalid(fill_value, mask=kfactor.mask) #-- output monthly files to ascii, netCDF4 or HDF5 args = (FILE_PREFIX,'DELTA_',unit_str,LMAX,order_str,gw_str,ds_str, grid.month[0],grid.month[-1],suffix[DATAFORM]) FILE=os.path.join(OUTPUT_DIRECTORY,file_format.format(*args)) if (DATAFORM == 'ascii'): #-- ascii (.txt) delta.to_ascii(FILE, date=True, verbose=VERBOSE) elif (DATAFORM == 'netCDF4'): #-- netCDF4 delta.to_netCDF4(FILE, date=True, verbose=VERBOSE, units=unit_str, longname=unit_name, title='GRACE/GRACE-FO Spatial Error') elif (DATAFORM == 'HDF5'): #-- HDF5 delta.to_HDF5(FILE, date=True, verbose=VERBOSE, units=unit_str, longname=unit_name, title='GRACE/GRACE-FO Spatial Error') #-- set the permissions mode of the output files os.chmod(FILE, MODE) #-- add file to list output_files.append(FILE) #-- return the list of output files return output_files
def grace_spatial_error(base_dir, parameters, LOVE_NUMBERS=0, REFERENCE=None, VERBOSE=False, MODE=0o775): #-- Data processing center PROC = parameters['PROC'] #-- Data Release DREL = parameters['DREL'] #-- GRACE dataset DSET = parameters['DSET'] #-- Date Range and missing months start_mon = np.int(parameters['START']) end_mon = np.int(parameters['END']) missing = np.array(parameters['MISSING'].split(','), dtype=np.int) #-- minimum degree LMIN = np.int(parameters['LMIN']) #-- maximum degree and order LMAX = np.int(parameters['LMAX']) if (parameters['MMAX'].title() == 'None'): MMAX = np.copy(LMAX) else: MMAX = np.int(parameters['MMAX']) #-- SLR C2,0 and C3,0 SLR_C20 = parameters['SLR_C20'] SLR_C30 = parameters['SLR_C30'] #-- Degree 1 correction DEG1 = parameters['DEG1'] MODEL_DEG1 = parameters['MODEL_DEG1'] in ('Y', 'y') #-- ECMWF jump corrections ATM = parameters['ATM'] in ('Y', 'y') #-- Pole Tide correction from Wahr et al. (2015) POLE_TIDE = parameters['POLE_TIDE'] in ('Y', 'y') #-- smoothing radius RAD = np.int(parameters['RAD']) #-- destriped coefficients DESTRIPE = parameters['DESTRIPE'] in ('Y', 'y') #-- output spatial units UNITS = np.int(parameters['UNITS']) #-- output degree spacing #-- can enter dlon and dlat as [dlon,dlat] or a single value DDEG = np.squeeze(np.array(parameters['DDEG'].split(','), dtype='f')) #-- output degree interval (0:360, 90:-90) or (degree spacing/2) INTERVAL = np.int(parameters['INTERVAL']) #-- output data format (ascii, netCDF4, HDF5) DATAFORM = parameters['DATAFORM'] #-- output directory and base filename DIRECTORY = os.path.expanduser(parameters['DIRECTORY']) FILENAME = parameters['FILENAME'] #-- recursively create output directory if not currently existing if (not os.access(DIRECTORY, os.F_OK)): os.makedirs(DIRECTORY, mode=MODE, exist_ok=True) #-- list object of output files for file logs (full path) output_files = [] #-- file information suffix = dict(ascii='txt', netCDF4='nc', HDF5='H5') #-- read arrays of kl, hl, and ll Love Numbers hl, kl, ll = load_love_numbers(LMAX, LOVE_NUMBERS=LOVE_NUMBERS, REFERENCE=REFERENCE) #-- Calculating the Gaussian smoothing for radius RAD if (RAD != 0): wt = 2.0 * np.pi * gauss_weights(RAD, LMAX) gw_str = '_r{0:0.0f}km'.format(RAD) else: #-- else = 1 wt = np.ones((LMAX + 1)) gw_str = '' #-- flag for spherical harmonic order order_str = 'M{0:d}'.format(MMAX) if (MMAX != LMAX) else '' #-- atmospheric ECMWF "jump" flag (if ATM) atm_str = '_wATM' if ATM else '' #-- reading GRACE months for input range with grace_input_months.py #-- replacing SLR and Degree 1 if specified #-- correcting for Pole-Tide and Atmospheric Jumps if specified Ylms = grace_input_months(base_dir, PROC, DREL, DSET, LMAX, start_mon, end_mon, missing, SLR_C20, DEG1, MMAX=MMAX, SLR_C30=SLR_C30, MODEL_DEG1=MODEL_DEG1, ATM=ATM, POLE_TIDE=POLE_TIDE) #-- convert to harmonics object and remove mean if specified GRACE_Ylms = harmonics().from_dict(Ylms) grace_dir = Ylms['directory'] #-- mean parameters MEAN = parameters['MEAN'] in ('Y', 'y') #-- use a mean file for the static field to remove if (parameters['MEAN_FILE'].title() == 'None'): mean_Ylms = GRACE_Ylms.mean(apply=MEAN) else: #-- read data form for input mean file (ascii, netCDF4, HDF5, gfc) if (parameters['MEANFORM'] == 'ascii'): mean_Ylms = harmonics().from_ascii(parameters['MEAN_FILE'], date=False) elif (parameters['MEANFORM'] == 'netCDF4'): mean_Ylms = harmonics().from_netCDF4(parameters['MEAN_FILE'], date=False) elif (parameters['MEANFORM'] == 'HDF5'): mean_Ylms = harmonics().from_HDF5(parameters['MEAN_FILE'], date=False) elif (parameters['MEANFORM'] == 'gfc'): mean_Ylms = harmonics().from_gfc(parameters['MEAN_FILE']) #-- remove the input mean if MEAN: GRACE_Ylms.subtract(mean_Ylms) #-- date information of GRACE/GRACE-FO coefficients nfiles = len(GRACE_Ylms.time) #-- filter GRACE/GRACE-FO coefficients if DESTRIPE: #-- destriping GRACE/GRACE-FO coefficients ds_str = '_FL' GRACE_Ylms = GRACE_Ylms.destripe() else: #-- using standard GRACE/GRACE-FO harmonics ds_str = '' #-- calculating GRACE error (Wahr et al 2006) #-- output GRACE error file (for both LMAX==MMAX and LMAX != MMAX cases) args = (PROC, DREL, DSET, LMAX, order_str, ds_str, atm_str, GRACE_Ylms.month[0], GRACE_Ylms.month[-1], suffix[DATAFORM]) delta_format = '{0}_{1}_{2}_DELTA_CLM_L{3:d}{4}{5}{6}_{7:03d}-{8:03d}.{9}' DELTA_FILE = delta_format.format(*args) #-- full path of the GRACE directory #-- if file was previously calculated, will read file #-- else will calculate the GRACE error if (not os.access(os.path.join(grace_dir, DELTA_FILE), os.F_OK)): #-- add output delta file to list object output_files.append(os.path.join(grace_dir, DELTA_FILE)) #-- Delta coefficients of GRACE time series (Error components) delta_Ylms = harmonics(lmax=LMAX, mmax=MMAX) delta_Ylms.clm = np.zeros((LMAX + 1, MMAX + 1)) delta_Ylms.slm = np.zeros((LMAX + 1, MMAX + 1)) #-- Smoothing Half-Width (CNES is a 10-day solution) #-- 365/10/2 = 18.25 (next highest is 19) #-- All other solutions are monthly solutions (HFWTH for annual = 6) if ((PROC == 'CNES') and (DREL in ('RL01', 'RL02'))): HFWTH = 19 else: HFWTH = 6 #-- Equal to the noise of the smoothed time-series #-- for each spherical harmonic order for m in range(0, MMAX + 1): #-- MMAX+1 to include MMAX #-- for each spherical harmonic degree for l in range(m, LMAX + 1): #-- LMAX+1 to include LMAX #-- Delta coefficients of GRACE time series for cs, csharm in enumerate(['clm', 'slm']): #-- Constrained GRACE Error (Noise of smoothed time-series) #-- With Annual and Semi-Annual Terms val1 = getattr(GRACE_Ylms, csharm) smth = tssmooth(GRACE_Ylms.time, val1[l, m, :], HFWTH=HFWTH) #-- number of smoothed points nsmth = len(smth['data']) #-- GRACE delta Ylms #-- variance of data-(smoothed+annual+semi) val2 = getattr(delta_Ylms, csharm) val2[l, m] = np.sqrt(np.sum(smth['noise']**2) / nsmth) #-- save GRACE DELTA to file delta_Ylms.time = np.copy(nsmth) delta_Ylms.month = np.copy(nsmth) if (DATAFORM == 'ascii'): #-- ascii (.txt) delta_Ylms.to_ascii(os.path.join(grace_dir, DELTA_FILE)) elif (DATAFORM == 'netCDF4'): #-- netcdf (.nc) delta_Ylms.to_netCDF4(os.path.join(grace_dir, DELTA_FILE)) elif (DATAFORM == 'HDF5'): #-- HDF5 (.H5) delta_Ylms.to_HDF5(os.path.join(grace_dir, DELTA_FILE)) #-- set the permissions mode of the output harmonics file os.chmod(os.path.join(grace_dir, DELTA_FILE), MODE) #-- append delta harmonics file to output files list output_files.append(os.path.join(grace_dir, DELTA_FILE)) else: #-- read GRACE DELTA spherical harmonics datafile if (DATAFORM == 'ascii'): #-- ascii (.txt) delta_Ylms = harmonics().from_ascii( os.path.join(grace_dir, DELTA_FILE)) elif (DATAFORM == 'netCDF4'): #-- netcdf (.nc) delta_Ylms = harmonics().from_netCDF4( os.path.join(grace_dir, DELTA_FILE)) elif (DATAFORM == 'HDF5'): #-- HDF5 (.H5) delta_Ylms = harmonics().from_HDF5( os.path.join(grace_dir, DELTA_FILE)) #-- truncate grace delta clm and slm to d/o LMAX/MMAX delta_Ylms = delta_Ylms.truncate(lmax=LMAX, mmax=MMAX) nsmth = np.int(delta_Ylms.time) #-- Output spatial data object delta = spatial() #-- Output Degree Spacing dlon, dlat = (DDEG, DDEG) if (np.ndim(DDEG) == 0) else (DDEG[0], DDEG[1]) #-- Output Degree Interval if (INTERVAL == 1): #-- (-180:180,90:-90) nlon = np.int((360.0 / dlon) + 1.0) nlat = np.int((180.0 / dlat) + 1.0) delta.lon = -180 + dlon * np.arange(0, nlon) delta.lat = 90.0 - dlat * np.arange(0, nlat) elif (INTERVAL == 2): #-- (Degree spacing)/2 delta.lon = np.arange(-180 + dlon / 2.0, 180 + dlon / 2.0, dlon) delta.lat = np.arange(90.0 - dlat / 2.0, -90.0 - dlat / 2.0, -dlat) nlon = len(delta.lon) nlat = len(delta.lat) #-- Earth Parameters factors = units(lmax=LMAX).harmonic(hl, kl, ll) #-- output spatial units unit_list = ['cmwe', 'mmGH', 'mmCU', u'\u03BCGal', 'mbar'] unit_name = [ 'Equivalent Water Thickness', 'Geoid Height', 'Elastic Crustal Uplift', 'Gravitational Undulation', 'Equivalent Surface Pressure' ] #-- dfactor is the degree dependent coefficients #-- for specific spherical harmonic output units if (UNITS == 1): #-- 1: cmwe, centimeters water equivalent dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).cmwe elif (UNITS == 2): #-- 2: mmGH, millimeters geoid height dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).mmGH elif (UNITS == 3): #-- 3: mmCU, millimeters elastic crustal deformation dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).mmCU elif (UNITS == 4): #-- 4: micGal, microGal gravity perturbations dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).microGal elif (UNITS == 5): #-- 5: mbar, millibar equivalent surface pressure dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).mbar #-- Computing plms for converting to spatial domain phi = delta.lon[np.newaxis, :] * np.pi / 180.0 theta = (90.0 - delta.lat) * np.pi / 180.0 PLM, dPLM = plm_holmes(LMAX, np.cos(theta)) #-- square of legendre polynomials truncated to order MMAX mm = np.arange(0, MMAX + 1) PLM2 = PLM[:, mm, :]**2 #-- Calculating cos(m*phi)^2 and sin(m*phi)^2 m = delta_Ylms.m[:, np.newaxis] ccos = np.cos(np.dot(m, phi))**2 ssin = np.sin(np.dot(m, phi))**2 #-- truncate delta harmonics to spherical harmonic range Ylms = delta_Ylms.truncate(LMAX, lmin=LMIN, mmax=MMAX) #-- convolve delta harmonics with degree dependent factors #-- smooth harmonics and convert to output units Ylms = Ylms.convolve(dfactor * wt).power(2.0).scale(1.0 / nsmth) #-- Calculate fourier coefficients d_cos = np.zeros((MMAX + 1, nlat)) #-- [m,th] d_sin = np.zeros((MMAX + 1, nlat)) #-- [m,th] #-- Calculating delta spatial values for k in range(0, nlat): #-- summation over all spherical harmonic degrees d_cos[:, k] = np.sum(PLM2[:, :, k] * Ylms.clm, axis=0) d_sin[:, k] = np.sum(PLM2[:, :, k] * Ylms.slm, axis=0) #-- Multiplying by c/s(phi#m) to get spatial maps (lon,lat) delta.data = np.sqrt(np.dot(ccos.T, d_cos) + np.dot(ssin.T, d_sin)).T #-- output file format file_format = '{0}{1}_L{2:d}{3}{4}{5}_ERR_{6:03d}-{7:03d}.{8}' #-- output error file to ascii, netCDF4 or HDF5 args = (FILENAME, unit_list[UNITS - 1], LMAX, order_str, gw_str, ds_str, GRACE_Ylms.month[0], GRACE_Ylms.month[-1], suffix[DATAFORM]) FILE = os.path.join(DIRECTORY, file_format.format(*args)) if (DATAFORM == 'ascii'): #-- ascii (.txt) delta.to_ascii(FILE, date=False, verbose=VERBOSE) elif (DATAFORM == 'netCDF4'): #-- netCDF4 delta.to_netCDF4(FILE, date=False, verbose=VERBOSE, units=unit_list[UNITS - 1], longname=unit_name[UNITS - 1], title='GRACE/GRACE-FO Spatial Error') elif (DATAFORM == 'HDF5'): #-- HDF5 delta.to_HDF5(FILE, date=False, verbose=VERBOSE, units=unit_list[UNITS - 1], longname=unit_name[UNITS - 1], title='GRACE/GRACE-FO Spatial Error') #-- set the permissions mode of the output files os.chmod(FILE, MODE) #-- add file to list output_files.append(FILE) #-- return the list of output files return output_files