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 mascon_reconstruct(DSET, LMAX, RAD, START=None, END=None, MMAX=None, DESTRIPE=False, LOVE_NUMBERS=0, REFERENCE=None, GIA=None, GIA_FILE=None, ATM=False, DATAFORM=None, MASCON_FILE=None, REDISTRIBUTE_MASCONS=False, RECONSTRUCT_FILE=None, LANDMASK=None, OUTPUT_DIRECTORY=None, MODE=0o775): #-- for datasets not GSM: will add a label for the dataset dset_str = '' if (DSET == 'GSM') else '_{0}'.format(DSET) #-- atmospheric ECMWF "jump" flag (if ATM) atm_str = '_wATM' if ATM else '' #-- Gaussian smoothing string for radius RAD gw_str = '_r{0:0.0f}km'.format(RAD) if (RAD != 0) else '' #-- 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 '' #-- 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 '' #-- filter grace coefficients flag ds_str = '_FL' if DESTRIPE else '' #-- output filename suffix 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 initial reconstruct index for calc_mascon.py fid = open(RECONSTRUCT_FILE, 'w') #-- output file format file_format = '{0}{1}{2}{3}{4}_L{5:d}{6}{7}{8}_{9:03d}-{10:03d}.{11}' #-- read load 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 #-- 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)] for k, fi in enumerate(mascon_files): #-- read mascon spherical harmonics if (DATAFORM == 'ascii'): #-- ascii (.txt) Ylms = harmonics().from_ascii(os.path.expanduser(fi), date=False) elif (DATAFORM == 'netCDF4'): #-- netcdf (.nc) Ylms = harmonics().from_netCDF4(os.path.expanduser(fi), date=False) elif (DATAFORM == 'HDF5'): #-- HDF5 (.H5) Ylms = harmonics().from_HDF5(os.path.expanduser(fi), date=False) #-- Calculating the total mass of each mascon (1 cmwe uniform) total_area = 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 Ylms = Ylms.truncate(lmax=LMAX, mmax=MMAX) #-- mascon base is the file without directory or suffix mascon_base = os.path.basename(fi) 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 = mascon_base.replace('_L{0:d}'.format(LMAX), '') #-- input filename format (for both LMAX==MMAX and LMAX != MMAX cases): #-- mascon name, GRACE dataset, GIA model, LMAX, (MMAX,) #-- Gaussian smoothing, filter flag, remove reconstructed fields flag #-- output GRACE error file args = (mascon_name, dset_str, gia_str.upper(), atm_str, ocean_str, LMAX, order_str, gw_str, ds_str) file_input = '{0}{1}{2}{3}{4}_L{5:d}{6}{7}{8}.txt'.format(*args) mascon_data_input = np.loadtxt( os.path.join(OUTPUT_DIRECTORY, file_input)) #-- convert mascon time-series from Gt to cmwe mascon_sigma = 1e15 * mascon_data_input[:, 2] / total_area #-- mascon time-series Ylms mascon_Ylms = Ylms.scale(mascon_sigma) mascon_Ylms.time = mascon_data_input[:, 1].copy() mascon_Ylms.month = mascon_data_input[:, 0].astype(np.int64) #-- output to file: no ascii option args = (mascon_name, dset_str, gia_str.upper(), atm_str, ocean_str, LMAX, order_str, gw_str, ds_str, START, END, suffix[DATAFORM]) FILE = file_format.format(*args) #-- output harmonics to file if (DATAFORM == 'netCDF4'): #-- netcdf (.nc) mascon_Ylms.to_netCDF4(os.path.join(OUTPUT_DIRECTORY, FILE)) elif (DATAFORM == 'HDF5'): #-- HDF5 (.H5) mascon_Ylms.to_HDF5(os.path.join(OUTPUT_DIRECTORY, FILE)) #-- print file name to index print(tilde_compress(os.path.join(OUTPUT_DIRECTORY, FILE)), file=fid) #-- change the permissions mode os.chmod(os.path.join(OUTPUT_DIRECTORY, FILE), MODE) #-- close the reconstruct index fid.close() #-- change the permissions mode of the index file os.chmod(RECONSTRUCT_FILE, MODE)
def least_squares_mascons(parameters, LOVE_NUMBERS=0, REFERENCE=None, MODE=0o775): #-- convert parameters to variables #-- index file of data files (containing path) #-- path.expanduser = tilde expansion of path INDEX_FILE = os.path.expanduser(parameters['INDEX_FILE']) DATAFORM = parameters['DATAFORM'] #-- spherical harmonic degree range LMIN = np.int(parameters['LMIN']) LMAX = np.int(parameters['LMAX']) #-- maximum spherical harmonic order if (parameters['MMAX'].title() == 'None'): MMAX = np.copy(LMAX) else: MMAX = np.int(parameters['MMAX']) #-- gaussian smoothing radius RAD = np.float(parameters['RAD']) #-- filter coefficients for stripe effects DESTRIPE = parameters['DESTRIPE'] in ('Y', 'y') #-- index of mascons spherical harmonics MASCON_INDEX = os.path.expanduser(parameters['MASCON_INDEX']) #-- mascon distribution over the ocean MASCON_OCEAN = parameters['MASCON_OCEAN'] in ('Y', 'y') #-- destribute total mass of spherical harmonics over the ocean REDISTRIBUTE = parameters['REDISTRIBUTE'] in ('Y', 'y') #-- calculating with data errors DATA_ERROR = parameters['DATA_ERROR'] in ('Y', 'y') #-- input data have dates DATE = parameters['DATE'] in ('Y', 'y') #-- output directory DIRECTORY = os.path.expanduser(parameters['DIRECTORY']) #-- 1: fit mass, 2: fit geoid FIT_METHOD = np.int(parameters['FIT_METHOD']) #-- 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 = [] #-- read arrays of kl, hl, and ll Love Numbers hl, kl, ll = load_love_numbers(LMAX, LOVE_NUMBERS=LOVE_NUMBERS, REFERENCE='CF') #-- 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 #-- 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 = '' #-- output string for both LMAX==MMAX and LMAX != MMAX cases order_str = 'M{0:d}'.format(MMAX) if (MMAX != LMAX) else '' #-- output string for destriped harmonics ds_str = '_FL' if DESTRIPE else '' #-- Read Ocean function and convert to Ylms for redistribution if (MASCON_OCEAN | REDISTRIBUTE): #-- read Land-Sea Mask and convert to spherical harmonics LANDMASK = os.path.expanduser(parameters['LANDMASK']) ocean_Ylms = ocean_stokes(LANDMASK, LMAX, MMAX=MMAX, LOVE=(hl, kl, ll)) ocean_str = '_OCN' else: #-- not distributing uniformly over ocean ocean_str = '' #-- input spherical harmonic datafile index #-- correspond file names with GRACE month #-- this allows additional months to be in the index data_Ylms = harmonics().from_index(INDEX_FILE, DATAFORM, date=DATE) #-- number of files within the index n_files = data_Ylms.shape[-1] #-- truncate to degree and order data_Ylms = data_Ylms.truncate(lmax=LMAX, mmax=MMAX) #-- Total mass in the simulated input data in gigatonnes rmass = 4.0 * np.pi * (rad_e** 3.0) * rho_e * data_Ylms.clm[0, 0, :] / 3.0 / 1e15 #-- distribute Ylms uniformly over the ocean if REDISTRIBUTE: #-- calculate ratio between total removed mass and #-- a uniformly distributed cm of water over the ocean ratio = data_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 data_Ylms.clm[l, m, :] -= ratio * ocean_Ylms['clm'][l, m] data_Ylms.slm[l, m, :] -= ratio * ocean_Ylms['slm'][l, m] #-- filter data coefficients if DESTRIPE: data_Ylms = data_Ylms.destripe() #-- input mascon spherical harmonic datafiles with open(MASCON_INDEX, 'r') as f: mascon_files = f.read().splitlines() #-- number of mascons n_mas = len(mascon_files) #-- spatial area of the mascon area_tot = np.zeros((n_mas)) #-- name of each mascon mascon_name = [] #-- for each valid file in the index (iterate over mascons) mascon_list = [] for k in range(n_mas): #-- read mascon spherical harmonics if (DATAFORM == 'ascii'): #-- ascii (.txt) Ylms = harmonics().from_ascii(mascon_files[k], date=False) elif (DATAFORM == 'netCDF4'): #-- netcdf (.nc) Ylms = harmonics().from_netCDF4(mascon_files[k], date=False) elif (DATAFORM == 'HDF5'): #-- HDF5 (.H5) Ylms = harmonics().from_HDF5(mascon_files[k], date=False) #-- Calculating the total mass of each mascon (1 cmH2O uniform) area_tot[k] = 4.0 * np.pi * (rad_e**3) * rho_e * Ylms.clm[0, 0] / 3.0 #-- distribute MASCON mass uniformly over the ocean if MASCON_OCEAN: #-- 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) #-- 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.int(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)) #-- corrected clm and slm Y_lm = np.zeros((n_harm, n_files)) #-- sensitivity kernel A_lm = np.zeros((n_harm, n_mas)) #-- Initializing output Mascon time-series mascon = np.zeros((n_mas, n_files)) #-- Initializing conversion factors #-- factor for converting to coefficients of mass fact = 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] coeff = rho_e * rad_e / 3.0 #-- Switching between Cosine and Sine Stokes for cs, csharm in enumerate(['clm', 'slm']): #-- copy cosine and sin harmonics mascon_harm = getattr(mascon_Ylms, csharm) data_harm = getattr(data_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, :]) #-- Data Spherical Harmonics Y_lm[ii, :] = np.copy(data_harm[l, m, :]) #-- degree dependent factor to convert to mass fact[ii] = (2.0 * l + 1.0) / (1.0 + kl[l]) #-- degree dependent smoothing wt_lm[ii] = np.copy(wt[l]) #-- add 1 to counter ii += 1 #-- free up memory from data harmonics data_Ylms.clm = None data_Ylms.slm = None #-- 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, :] = wt_lm[i] * M_lm[i, :] * fact[i] fit_factor = wt_lm * fact else: #-- Fitting Sensitivity Kernel as geoid coefficients MA_lm[:, :] = np.copy(M_lm) fit_factor = wt_lm * 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] * area_tot[k] #-- output formatting string if containing date variables if DATE: formatting_string = '{0:03d} {1:12.4f} {2:16.10f} {3:16.5f} {4:16.10f}' else: formatting_string = '{0:16.10f} {1:16.5f} {2:16.10f}' #-- for each mascon for k in range(n_mas): #-- output filename format: #-- mascon name, LMAX, Gaussian smoothing radii, filter flag file_out = '{0}{1}_L{2:d}{3}{4}{5}{6}.txt'.format( parameters['FILENAME'], mascon_name[k], LMAX, order_str, gw_str, ds_str, ocean_str) #-- add output files to list object output_files.append(os.path.join(DIRECTORY, file_out)) #-- Output mascon datafiles #-- Will output each mascon mass series #-- if with dates: #-- month, date, mascon mass, mascon area #-- else: #-- mascon mass, mascon area #-- open output mascon time-series file fid = open(os.path.join(DIRECTORY, file_out), 'w') #-- for each date for f in range(n_files): #-- Summing over all spherical harmonics for mascon k, and time t #-- multiplies by the degree dependent factor to convert #-- the harmonics into mass coefficients #-- Converting mascon mass time-series from g to gigatonnes if DATA_ERROR: #-- if input data are errors (i.e. estimated dealiasing errors) mascon[k, f] = np.sqrt(np.sum( (A_lm[:, k] * Y_lm[:, f])**2)) / 1e15 else: mascon[k, f] = np.sum(A_lm[:, k] * Y_lm[:, f]) / 1e15 #-- output to file if DATE: #-- if files contain date information args = (data_Ylms.month[f], data_Ylms.time[f], mascon[k, f], area_tot[k] / 1e10, rmass[f]) print(formatting_string.format(*args), file=fid) else: #-- just print the time-series and mascon areas args = (mascon[k, f], area_tot[k] / 1e10, rmass[f]) print(formatting_string.format(*args), file=fid) #-- close the output file fid.close() #-- change the permissions mode of the output file os.chmod(os.path.join(DIRECTORY, file_out), MODE) #-- add output files to list object output_files.append(os.path.join(DIRECTORY, file_out)) #-- return the list of output files return output_files
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 mascon_reconstruct(parameters, LOVE_NUMBERS=0, REFERENCE=None, MODE=0o775): #-- convert parameters into variables #-- Data processing center PROC = parameters['PROC'] #-- Data Release DREL = parameters['DREL'] #-- GRACE dataset DSET = parameters['DSET'] #-- Date Range START_MON = np.int(parameters['START']) END_MON = np.int(parameters['END']) #-- spherical harmonic parameters LMAX = np.int(parameters['LMAX']) #-- maximum spherical harmonic order if (parameters['MMAX'].title() == 'None'): MMAX = np.copy(LMAX) else: MMAX = np.int(parameters['MMAX']) #-- gaussian smoothing radius RAD = np.float(parameters['RAD']) #-- filtered coefficients for stripe effects DESTRIPE = parameters['DESTRIPE'] in ('Y', 'y') #-- ECMWF jump corrections ATM = parameters['ATM'] in ('Y', 'y') #-- Glacial Isostatic Adjustment file to read GIA = parameters['GIA'] if (parameters['GIA'].title() != 'None') else None GIA_FILE = os.path.expanduser(parameters['GIA_FILE']) #-- input/output data format (ascii, netCDF4, HDF5) DATAFORM = parameters['DATAFORM'] #-- index of mascons spherical harmonics #-- path.expanduser = tilde expansion of path MASCON_INDEX = os.path.expanduser(parameters['MASCON_INDEX']) #-- mascon distribution over the ocean MASCON_OCEAN = parameters['MASCON_OCEAN'] in ('Y', 'y') #-- for datasets not GSM: will add a label for the dataset dset_str = '' if (DSET == 'GSM') else '_{0}'.format(DSET) #-- atmospheric ECMWF "jump" flag (if ATM) atm_str = '_wATM' if ATM else '' #-- Gaussian smoothing string for radius RAD gw_str = '_r{0:0.0f}km'.format(RAD) if (RAD != 0) else '' #-- 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 '' #-- output string for both LMAX==MMAX and LMAX != MMAX cases order_str = 'M{0:d}'.format(MMAX) if (MMAX != LMAX) else '' #-- filter grace coefficients flag ds_str = '_FL' if DESTRIPE else '' #-- output filename suffix suffix = dict(ascii='txt', netCDF4='nc', HDF5='H5') #-- GRACE output filename prefix #-- mascon directory for GRACE product, processing and date range DIRECTORY = os.path.expanduser(parameters['DIRECTORY']) #-- create initial reconstruct index for calc_mascon.py fid = open(os.path.expanduser(parameters['RECONSTRUCT_INDEX']), 'w') HOME = os.path.expanduser('~') #-- output file format file_format = '{0}{1}{2}{3}{4}_L{5:d}{6}{7}{8}_{9:03d}-{10:03d}.{11}' #-- read load 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 #-- Read Ocean function and convert to Ylms for redistribution if MASCON_OCEAN: #-- read Land-Sea Mask and convert to spherical harmonics LANDMASK = os.path.expanduser(parameters['LANDMASK']) 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_INDEX, 'r') as f: mascon_files = f.read().splitlines() for k, fi in enumerate(mascon_files): #-- read mascon spherical harmonics if (DATAFORM == 'ascii'): #-- ascii (.txt) Ylms = harmonics().from_ascii(os.path.expanduser(fi), date=False) elif (DATAFORM == 'netCDF4'): #-- netcdf (.nc) Ylms = harmonics().from_netCDF4(os.path.expanduser(fi), date=False) elif (DATAFORM == 'HDF5'): #-- HDF5 (.H5) Ylms = harmonics().from_HDF5(os.path.expanduser(fi), date=False) #-- Calculating the total mass of each mascon (1 cmwe uniform) total_area = 4.0 * np.pi * (rad_e**3) * rho_e * Ylms.clm[0, 0] / 3.0 #-- distribute MASCON mass uniformly over the ocean if MASCON_OCEAN: #-- 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 Ylms = Ylms.truncate(lmax=LMAX, mmax=MMAX) #-- mascon base is the file without directory or suffix mascon_base = os.path.basename(fi) 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 = mascon_base.replace('_L{0:d}'.format(LMAX), '') #-- input filename format (for both LMAX==MMAX and LMAX != MMAX cases): #-- mascon name, GRACE dataset, GIA model, LMAX, (MMAX,) #-- Gaussian smoothing, filter flag, remove reconstructed fields flag #-- output GRACE error file args = (mascon_name, dset_str, gia_str.upper(), atm_str, ocean_str, LMAX, order_str, gw_str, ds_str) file_input = '{0}{1}{2}{3}{4}_L{5:d}{6}{7}{8}.txt'.format(*args) mascon_data_input = np.loadtxt(os.path.join(DIRECTORY, file_input)) nmon = np.shape(mascon_data_input)[0] #-- convert mascon time-series from Gt to cmwe mascon_sigma = 1e15 * mascon_data_input[:, 2] / total_area #-- mascon time-series Ylms mascon_Ylms = Ylms.scale(mascon_sigma) mascon_Ylms.time = mascon_data_input[:, 1].copy() mascon_Ylms.month = mascon_data_input[:, 0].astype(np.int) #-- output to file: no ascii option args = (mascon_name, dset_str, gia_str.upper(), atm_str, ocean_str, LMAX, order_str, gw_str, ds_str, START_MON, END_MON, suffix[DATAFORM]) FILE = file_format.format(*args) #-- output harmonics to file if (DATAFORM == 'netCDF4'): #-- netcdf (.nc) mascon_Ylms.to_netCDF4(os.path.join(DIRECTORY, FILE)) elif (DATAFORM == 'HDF5'): #-- HDF5 (.H5) mascon_Ylms.to_HDF5(os.path.join(DIRECTORY, FILE)) #-- print file name to index print(os.path.join(DIRECTORY, FILE).replace(HOME, '~'), file=fid) #-- change the permissions mode os.chmod(os.path.join(DIRECTORY, FILE), MODE) #-- close the reconstruct index fid.close() #-- change the permissions mode of the index file os.chmod(os.path.expanduser(parameters['RECONSTRUCT_INDEX']), MODE)
def calc_mascon(base_dir, parameters, LOVE_NUMBERS=0, REFERENCE=None, MODE=0o775): #-- convert parameters to variables #-- 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) #-- spherical harmonic degree range LMIN = np.int(parameters['LMIN']) LMAX = np.int(parameters['LMAX']) #-- maximum spherical harmonic order if (parameters['MMAX'].title() == 'None'): MMAX = np.copy(LMAX) else: MMAX = np.int(parameters['MMAX']) #-- degree 1 coefficients #-- None: No degree 1 #-- Tellus: GRACE/GRACE-FO TN-13 from PO.DAAC #-- https://grace.jpl.nasa.gov/data/get-data/geocenter/ #-- SLR: satellite laser ranging from CSR #-- ftp://ftp.csr.utexas.edu/pub/slr/geocenter/ #-- SLF: Sutterley and Velicogna, Remote Sensing (2019) #-- https://www.mdpi.com/2072-4292/11/18/2108 DEG1 = parameters['DEG1'] #-- replace C20, C30 with coefficients from SLR SLR_C20 = parameters['SLR_C20'] SLR_C30 = parameters['SLR_C30'] #-- ECMWF jump corrections ATM = parameters['ATM'] in ('Y', 'y') #-- Pole-Tide from Wahr et al. (2015) POLE_TIDE = parameters['POLE_TIDE'] in ('Y', 'y') #-- 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 REMOVE_INDEX = parameters['REMOVE_INDEX'] REDISTRIBUTE_REMOVED = parameters['REDISTRIBUTE_REMOVED'] in ('Y', 'y') #-- remove reconstructed fields RECONSTRUCT = parameters['RECONSTRUCT'] in ('Y', 'y') #-- gaussian smoothing radius RAD = np.float(parameters['RAD']) #-- filter coefficients for stripe effects DESTRIPE = parameters['DESTRIPE'] in ('Y', 'y') #-- input/output data format (ascii, netCDF4, HDF5) DATAFORM = parameters['DATAFORM'] #-- index of mascons spherical harmonics #-- path.expanduser = tilde expansion of path MASCON_INDEX = os.path.expanduser(parameters['MASCON_INDEX']) #-- output directory for mascon time series files DIRECTORY = os.path.expanduser(parameters['DIRECTORY']) #-- 1: fit mass, 2: fit geoid FIT_METHOD = np.int(parameters['FIT_METHOD']) #-- mascon redistribution MASCON_OCEAN = parameters['MASCON_OCEAN'] in ('Y', 'y') #-- 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) #-- 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 #-- for datasets not GSM: will add a label for the dataset dset_str = '' if (DSET == 'GSM') else '_{0}'.format(DSET) #-- atmospheric ECMWF "jump" flag (if ATM) atm_str = '_wATM' if ATM else '' #-- output string for both LMAX==MMAX and LMAX != MMAX cases 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 (MASCON_OCEAN | REDISTRIBUTE_REMOVED): #-- read Land-Sea Mask and convert to spherical harmonics LSMASK = os.path.expanduser(parameters['LANDMASK']) ocean_Ylms = ocean_stokes(LSMASK, LMAX, MMAX=MMAX, LOVE=(hl, kl, ll)) ocean_str = '_OCN' else: #-- not distributing uniformly over ocean ocean_str = '' #-- input GRACE/GRACE-FO spherical harmonic datafiles #-- 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=True, ATM=ATM, POLE_TIDE=POLE_TIDE) #-- full path to directory for specific GRACE/GRACE-FO product grace_dir = Ylms['directory'] #-- create harmonics object from GRACE/GRACE-FO data GRACE_Ylms = harmonics().from_dict(Ylms) #-- use a mean file for the static field to remove if (parameters['MEAN_FILE'].title() == 'None'): GRACE_Ylms.mean(apply=True) 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 GRACE_Ylms.subtract(mean_Ylms) #-- date information of GRACE/GRACE-FO coefficients n_files = 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(n_files): 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) #-- 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 (parameters['REMOVE_INDEX'].title() != 'None'): #-- for each file index separated by commas for REMOVE_INDEX in parameters['REMOVE_INDEX'].split(','): Ylms = harmonics().from_index(REMOVE_INDEX, DATAFORM) #-- 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) #-- input reconstructed spherical harmonic datafiles construct_Ylms = GRACE_Ylms.zeros_like() construct_Ylms.time[:] = np.copy(GRACE_Ylms.time) construct_Ylms.month[:] = np.copy(GRACE_Ylms.month) if RECONSTRUCT: #-- input index for reconstructed spherical harmonic datafiles RECONSTRUCT_INDEX = os.path.expanduser(parameters['RECONSTRUCT_INDEX']) with open(RECONSTRUCT_INDEX, 'r') as f: reconstruct_files = f.read().splitlines() #-- remove commented lines (can comment out files in the index) reconstruct_files = [ f for f in reconstruct_files if not re.match('#', f) ] #-- for each valid file in the index (iterate over mascons) for construct_file in reconstruct_files: #-- read reconstructed spherical harmonics if (DATAFORM == 'ascii'): #-- ascii (.txt) Ylms = harmonics().from_ascii(construct_file) elif (DATAFORM == 'netCDF4'): #-- netcdf (.nc) Ylms = harmonics().from_netCDF4(construct_file) elif (DATAFORM == 'HDF5'): #-- HDF5 (.H5) Ylms = harmonics().from_HDF5(construct_file) #-- truncate clm and slm matrices to LMAX/MMAX #-- add harmonics object to total construct_Ylms.add(Ylms.truncate(lmax=LMAX, mmax=MMAX)) #-- filter reconstructed coefficients if DESTRIPE: construct_Ylms = construct_Ylms.destripe() #-- set flag for removing reconstructed coefficients construct_str = '_LEAKAGE' else: #-- set flag for not removing the reconstructed coefficients construct_str = '' #-- input mascon spherical harmonic datafiles with open(MASCON_INDEX, 'r') as f: mascon_files = f.read().splitlines() #-- 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 if (DATAFORM == 'ascii'): #-- ascii (.txt) Ylms = harmonics().from_ascii(os.path.expanduser(fi), date=False) elif (DATAFORM == 'netCDF4'): #-- netcdf (.nc) Ylms = harmonics().from_netCDF4(os.path.expanduser(fi), date=False) elif (DATAFORM == 'HDF5'): #-- HDF5 (.H5) Ylms = harmonics().from_HDF5(os.path.expanduser(fi), 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 MASCON_OCEAN: #-- 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) #-- 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 = 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(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) #-- 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.int(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)) else: #-- read GRACE/GRACE-FO delta harmonics from file 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/GRACE-FO delta clm and slm to d/o LMAX/MMAX delta_Ylms = delta_Ylms.truncate(lmax=LMAX, mmax=MMAX) tsmth = np.squeeze(delta_Ylms.time) nsmth = np.int(delta_Ylms.month) #-- 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.int(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)) #-- corrected clm and slm Y_lm = np.zeros((n_harm, n_files)) #-- sensitivity kernel A_lm = np.zeros((n_harm, n_mas)) #-- Satellite error harmonics delta_lm = np.zeros((n_harm)) #-- Initializing output Mascon time-series mascon = np.zeros((n_mas, n_files)) #-- Mascon satellite error component M_delta = np.zeros((n_mas)) #-- Initializing conversion factors #-- factor for converting to coefficients of mass fact = 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 #-- Switching between Cosine and Sine Stokes for cs, csharm in enumerate(['clm', 'slm']): #-- copy cosine and sin harmonics mascon_harm = getattr(mascon_Ylms, csharm) grace_harm = getattr(GRACE_Ylms, csharm) GIA_harm = getattr(GIA_Ylms, csharm) remove_harm = getattr(remove_Ylms, csharm) construct_harm = getattr(construct_Ylms, csharm) delta_harm = getattr(delta_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, :]) #-- GRACE Spherical Harmonics #-- Correcting GRACE Harmonics for GIA and Removed Terms Y_lm[ii,:] = grace_harm[l,m,:] - GIA_harm[l,m,:] - \ remove_harm[l,m,:] - construct_harm[l,m,:] #-- GRACE delta spherical harmonics delta_lm[ii] = np.copy(delta_harm[l, m]) #-- degree dependent factor to convert to mass fact[ii] = (2.0 * l + 1.0) / (1.0 + kl[l]) #-- 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 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)) #-- 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): #-- Multiply the Satellite error (noise of a smoothed time-series #-- with annual and semi-annual components) by the sensitivity kernel #-- Converting to Gigatonnes M_delta[k] = np.sqrt(np.sum((delta_lm * A_lm[:, k])**2)) / 1e15 #-- output filename format (for both LMAX==MMAX and LMAX != MMAX cases): #-- mascon name, GRACE dataset, GIA model, LMAX, (MMAX,) #-- Gaussian smoothing, filter flag, remove reconstructed fields flag #-- output GRACE error file file_out = '{0}{1}{2}{3}{4}_L{5:d}{6}{7}{8}{9}.txt'.format( mascon_name[k], dset_str, gia_str.upper(), atm_str, ocean_str, LMAX, order_str, gw_str, ds_str, construct_str) #-- Output mascon datafiles #-- Will output each mascon time series #-- month, date, mascon mass [Gt], satellite error [Gt], mascon area [km^2] #-- open output mascon time-series file fid = open(os.path.join(DIRECTORY, file_out), 'w') #-- for each date formatting_string = '{0:03d} {1:12.4f} {2:16.10f} {3:16.10f} {4:16.5f}' for t, mon in enumerate(GRACE_Ylms.month): #-- Summing over all spherical harmonics for mascon k, and time t #-- multiplies by the degree dependent factor to convert #-- the harmonics into mass coefficients #-- Converting mascon mass time-series from g to gigatonnes mascon[k, t] = np.sum(A_lm[:, k] * Y_lm[:, t]) / 1e15 #-- output to file args = (mon, GRACE_Ylms.time[t], mascon[k, t], M_delta[k], total_area[k] / 1e10) print(formatting_string.format(*args), file=fid) #-- close the output file fid.close() #-- change the permissions mode os.chmod(os.path.join(DIRECTORY, file_out), MODE) #-- add output files to list object output_files.append(os.path.join(DIRECTORY, file_out)) #-- return the list of output files return output_files