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