def gen_disc_load(data, lon, lat, area, LMAX=60, MMAX=None, PLM=None, LOVE=None): """ Calculates spherical harmonic coefficients for a uniform disc load Arguments --------- data: data magnitude in gigatonnes lon: longitude of disc center lat: latitude of disc center area: area of disc in km^2 Keyword arguments ----------------- LMAX: Upper bound of Spherical Harmonic Degrees MMAX: Upper bound of Spherical Harmonic Orders PLM: input Legendre polynomials LOVE: input load Love numbers up to degree LMAX (hl,kl,ll) Returns ------- clm: cosine spherical harmonic coefficients slm: sine spherical harmonic coefficients l: spherical harmonic degree to LMAX m: spherical harmonic order to MMAX """ #-- upper bound of spherical harmonic orders (default = LMAX) if MMAX is None: MMAX = np.copy(LMAX) #-- Earth Parameters factors = units(lmax=LMAX) rho_e = factors.rho_e #-- Average Density of the Earth [g/cm^3] rad_e = factors.rad_e #-- Average Radius of the Earth [cm] #-- convert lon and lat to radians phi = lon * np.pi / 180.0 #-- Longitude in radians th = (90.0 - lat) * np.pi / 180.0 #-- Colatitude in radians #-- convert input area into cm^2 and then divide by area of a half sphere #-- alpha will be 1 - the ratio of the input area with the half sphere alpha = (1.0 - 1e10 * area / (2.0 * np.pi * rad_e**2)) #-- Input data is in gigatonnes (Gt) #-- 1e15 converts from Gt to grams, 1e10 converts from km^2 to cm^2 unit_conv = 1e15 / (1e10 * area) #-- Coefficient for calculating Stokes coefficients for a disc load #-- From Jacob et al (2012), Farrell (1972) and Longman (1962) coeff = 3.0 / (rad_e * rho_e) #-- extract arrays of kl, hl, and ll Love Numbers hl, kl, ll = LOVE #-- calculate array of l values ranging from 0 to LMAX (harmonic degrees) #-- LMAX+1 as there are LMAX+1 elements between 0 and LMAX l = np.arange(LMAX + 1) #-- calculate SH degree dependent factors to convert from coefficients #-- of mass into normalized geoid coefficients #-- NOTE: these are not the normal factors for converting to geoid due #-- to the square of the denominator #-- kl[l] is the Load Love Number of degree l dfactor = (1.0 + kl[l]) / ((1.0 + 2.0 * l)**2) #-- Calculating plms of the disc #-- allocating for constructed array pl_alpha = np.zeros((LMAX + 1)) #-- l=0 is a special case (P(-1) = 1, P(1) = cos(alpha)) pl_alpha[0] = (1.0 - alpha) / 2.0 #-- for all other degrees: calculate the legendre polynomials up to LMAX+1 pl_matrix, dpl_matrix = legendre_polynomials(LMAX + 1, alpha) for l in range(1, LMAX + 1): #-- LMAX+1 to include LMAX #-- from Longman (1962) and Jacob et al (2012) #-- unnormalizing Legendre polynomials #-- sqrt(2*l - 1) == sqrt(2*(l-1) + 1) #-- sqrt(2*l + 3) == sqrt(2*(l+1) + 1) pl_lower = pl_matrix[l - 1] / np.sqrt(2.0 * l - 1.0) pl_upper = pl_matrix[l + 1] / np.sqrt(2.0 * l + 3.0) pl_alpha[l] = (pl_lower - pl_upper) / 2.0 #-- Calculate Legendre Polynomials using Holmes and Featherstone relation #-- this would be the plm for the center of the disc load #-- used to rotate the disc load to point lat/lon if PLM is None: plmout, dplm = plm_holmes(LMAX, np.cos(th)) #-- truncate precomputed plms to order plmout = np.squeeze(plmout[:, :MMAX + 1, :]) else: #-- truncate precomputed plms to degree and order plmout = PLM[:LMAX + 1, :MMAX + 1] #-- calculate array of m values ranging from 0 to MMAX (harmonic orders) #-- MMAX+1 as there are MMAX+1 elements between 0 and MMAX m = np.arange(MMAX + 1) #-- Multiplying by the units conversion factor (unit_conv) to #-- convert from the input units into cmH2O equivalent #-- Multiplying point mass data (converted to cmH2O) with sin/cos of m*phis #-- data normally is 1 for a uniform 1cm water equivalent layer #-- but can be a mass point if reconstructing a spherical harmonic field #-- NOTE: NOT a matrix multiplication as data (and phi) is a single point dcos = unit_conv * data * np.cos(m * phi) dsin = unit_conv * data * np.sin(m * phi) #-- Multiplying by plm_alpha (F_l from Jacob 2012) plm = np.zeros((LMAX + 1, MMAX + 1)) #-- Initializing preliminary spherical harmonic matrices yclm = np.zeros((LMAX + 1, MMAX + 1)) yslm = np.zeros((LMAX + 1, MMAX + 1)) #-- Initializing output spherical harmonic matrices Ylms = {} Ylms['l'] = np.arange(LMAX + 1) Ylms['m'] = np.arange(MMAX + 1) Ylms['clm'] = np.zeros((LMAX + 1, MMAX + 1)) Ylms['slm'] = np.zeros((LMAX + 1, MMAX + 1)) for m in range(0, MMAX + 1): #-- MMAX+1 to include MMAX l = np.arange(m, LMAX + 1) #-- LMAX+1 to include LMAX #-- rotate disc load to be centered at lat/lon plm[l, m] = plmout[l, m] * pl_alpha[l] #-- multiplying clm by cos(m*phi) and slm by sin(m*phi) #-- to get a field of spherical harmonics yclm[l, m] = plm[l, m] * dcos[m] yslm[l, m] = plm[l, m] * dsin[m] #-- multiplying by coefficients to convert to geoid coefficients Ylms['clm'][l, m] = coeff * dfactor[l] * yclm[l, m] Ylms['slm'][l, m] = coeff * dfactor[l] * yslm[l, m] #-- return the output spherical harmonics return Ylms
def combine_harmonics(INPUT_FILE, OUTPUT_FILE, LMAX=None, MMAX=None, LOVE_NUMBERS=0, REFERENCE=None, RAD=None, DESTRIPE=False, UNITS=None, DDEG=None, INTERVAL=None, BOUNDS=None, REDISTRIBUTE=False, LSMASK=None, MEAN_FILE=None, DATAFORM=None, VERBOSE=False, MODE=0o775): #-- verify that output directory exists DIRECTORY = os.path.abspath(os.path.dirname(OUTPUT_FILE)) if not os.access(DIRECTORY, os.F_OK): os.makedirs(DIRECTORY, MODE, exist_ok=True) #-- read input spherical harmonic coefficients from file in DATAFORM if (DATAFORM == 'ascii'): input_Ylms = harmonics().from_ascii(INPUT_FILE) elif (DATAFORM == 'netCDF4'): #-- read input netCDF4 file (.nc) input_Ylms = harmonics().from_netCDF4(INPUT_FILE) elif (DATAFORM == 'HDF5'): #-- read input HDF5 file (.H5) input_Ylms = harmonics().from_HDF5(INPUT_FILE) #-- reform harmonic dimensions to be l,m,t #-- truncate to degree and order LMAX, MMAX input_Ylms = input_Ylms.truncate(lmax=LMAX, mmax=MMAX).expand_dims() #-- remove mean file from input Ylms if MEAN_FILE and (DATAFORM == 'ascii'): mean_Ylms = harmonics().from_ascii(MEAN_FILE, date=False) input_Ylms.subtract(mean_Ylms) elif MEAN_FILE and (DATAFORM == 'netCDF4'): #-- read input netCDF4 file (.nc) mean_Ylms = harmonics().from_netCDF4(MEAN_FILE, date=False) input_Ylms.subtract(mean_Ylms) elif MEAN_FILE and (DATAFORM == 'HDF5'): #-- read input HDF5 file (.H5) mean_Ylms = harmonics().from_HDF5(MEAN_FILE, date=False) input_Ylms.subtract(mean_Ylms) #-- read arrays of kl, hl, and ll Love Numbers hl, kl, ll = load_love_numbers(LMAX, LOVE_NUMBERS=LOVE_NUMBERS, REFERENCE=REFERENCE) #-- distribute total mass uniformly over the ocean if REDISTRIBUTE: #-- read Land-Sea Mask and convert to spherical harmonics ocean_Ylms = ocean_stokes(LSMASK, LMAX, MMAX=MMAX, LOVE=(hl, kl, ll)) #-- calculate ratio between total mass and a uniformly distributed #-- layer of water over the ocean ratio = input_Ylms.clm[0, 0, :] / ocean_Ylms['clm'][0, 0] #-- for each spherical harmonic for m in range(0, MMAX + 1): #-- MMAX+1 to include MMAX for l in range(m, LMAX + 1): #-- LMAX+1 to include LMAX #-- remove the ratio*ocean Ylms from Ylms #-- note: x -= y is equivalent to x = x - y input_Ylms.clm[l, m, :] -= ratio * ocean_Ylms['clm'][l, m] input_Ylms.slm[l, m, :] -= ratio * ocean_Ylms['slm'][l, m] #-- if using a decorrelation filter (Isabella's destriping Routine) if DESTRIPE: input_Ylms = input_Ylms.destripe() #-- Gaussian smoothing if (RAD != 0): wt = 2.0 * np.pi * gauss_weights(RAD, LMAX) else: wt = np.ones((LMAX + 1)) #-- Output spatial data grid = spatial() grid.time = np.copy(input_Ylms.time) grid.month = np.copy(input_Ylms.month) #-- Output Degree Spacing if (len(DDEG) == 1): #-- dlon == dlat dlon = DDEG dlat = DDEG else: #-- dlon != dlat dlon, dlat = DDEG #-- Output Degree Interval if (INTERVAL == 1): #-- (0:360,90:-90) nlon = np.int((360.0 / dlon) + 1.0) nlat = np.int((180.0 / dlat) + 1.0) grid.lon = dlon * np.arange(0, nlon) grid.lat = 90.0 - dlat * np.arange(0, nlat) elif (INTERVAL == 2): #-- (Degree spacing)/2 grid.lon = np.arange(dlon / 2.0, 360 + dlon / 2.0, dlon) grid.lat = np.arange(90.0 - dlat / 2.0, -90.0 - dlat / 2.0, -dlat) nlon = len(grid.lon) nlat = len(grid.lat) elif (INTERVAL == 3): #-- non-global grid set with BOUNDS parameter minlon, maxlon, minlat, maxlat = BOUNDS.copy() grid.lon = np.arange(minlon + dlon / 2.0, maxlon + dlon / 2.0, dlon) grid.lat = np.arange(maxlat - dlat / 2.0, minlat - dlat / 2.0, -dlat) nlon = len(grid.lon) nlat = len(grid.lat) #-- Setting units factor for output #-- dfactor computes the degree dependent coefficients if (UNITS == 1): #-- 1: cmwe, centimeters water equivalent dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).cmwe elif (UNITS == 2): #-- 2: mmGH, mm geoid height dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).mmGH elif (UNITS == 3): #-- 3: mmCU, mm elastic crustal deformation dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).mmCU elif (UNITS == 4): #-- 4: micGal, microGal gravity perturbations dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).microGal elif (UNITS == 5): #-- 5: Pa, equivalent surface pressure in Pascals dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).Pa else: raise ValueError(('UNITS is invalid:\n1: cmwe\n2: mmGH\n3: mmCU ' '(elastic)\n4:microGal\n5: Pa')) #-- Computing plms for converting to spatial domain theta = (90.0 - grid.lat) * np.pi / 180.0 PLM, dPLM = plm_holmes(LMAX, np.cos(theta)) #-- output spatial grid nt = len(input_Ylms.time) grid.data = np.zeros((nlat, nlon, nt)) #-- converting harmonics to truncated, smoothed coefficients in output units for t in range(nt): #-- spherical harmonics for time t Ylms = input_Ylms.index(t) Ylms.convolve(dfactor * wt) #-- convert spherical harmonics to output spatial grid grid.data[:, :, t] = harmonic_summation(Ylms.clm, Ylms.slm, grid.lon, grid.lat, LMAX=LMAX, PLM=PLM).T #-- if verbose output: print input and output file names if VERBOSE: print('{0}:'.format(os.path.basename(sys.argv[0]))) print('{0} -->\n\t{1}\n'.format(INPUT_FILE, OUTPUT_FILE)) #-- outputting data to file output_data(grid.squeeze(), FILENAME=OUTPUT_FILE, DATAFORM=DATAFORM, UNITS=UNITS) #-- change output permissions level to MODE os.chmod(OUTPUT_FILE, MODE)
def gen_stokes(data, lon, lat, LMIN=0, LMAX=60, MMAX=None, UNITS=1, PLM=None, LOVE=None): """ Converts data from the spatial domain to spherical harmonic coefficients Arguments --------- data: data matrix lon: longitude array lat: latitude array Keyword arguments ----------------- LMIN: Lower bound of Spherical Harmonic Degrees LMAX: Upper bound of Spherical Harmonic Degrees MMAX: Upper bound of Spherical Harmonic Orders UNITS: input data units 1: cm of water thickness 2: Gigatonnes of mass 3: kg/m^2 list: custom degree-dependent unit conversion factor PLM: input Legendre polynomials LOVE: input load Love numbers up to degree LMAX (hl,kl,ll) Returns ------- clm: cosine spherical harmonic coefficients slm: sine spherical harmonic coefficients l: spherical harmonic degree to LMAX m: spherical harmonic order to MMAX """ #-- converting LMIN and LMAX to integer LMIN = np.int64(LMIN) LMAX = np.int64(LMAX) #-- upper bound of spherical harmonic orders (default = LMAX) MMAX = np.copy(LMAX) if (MMAX is None) else MMAX #-- grid dimensions nlat = np.int64(len(lat)) #-- grid step dlon = np.abs(lon[1] - lon[0]) dlat = np.abs(lat[1] - lat[0]) #-- longitude degree spacing in radians dphi = dlon * np.pi / 180.0 #-- colatitude degree spacing in radians dth = dlat * np.pi / 180.0 #-- reformatting longitudes to range 0:360 (if previously -180:180) lon = np.squeeze(lon.copy()) if np.any(lon < 0): lon_ind, = np.nonzero(lon < 0) lon[lon_ind] += 360.0 #-- Longitude in radians phi = lon[np.newaxis, :] * np.pi / 180.0 #-- Colatitude in radians th = (90.0 - np.squeeze(lat.copy())) * np.pi / 180.0 #-- reforming data to lonXlat if input latXlon sz = np.shape(data) data = data.T if (sz[0] == nlat) else np.copy(data) #-- SH Degree dependent factors to convert into fully normalized SH's #-- use splat operator to extract arrays of kl, hl, and ll Love Numbers factors = gravity_toolkit.units(lmax=LMAX).spatial(*LOVE) #-- extract degree dependent factor for specific units #-- calculate integration factors for theta and phi #-- Multiplying sin(th) with differentials of theta and phi #-- to calculate the integration factor at each latitude int_fact = np.zeros((nlat)) if (UNITS == 1): #-- Default Parameter: Input in cm w.e. (g/cm^2) dfactor = factors.cmwe int_fact[:] = np.sin(th) * dphi * dth elif (UNITS == 2): #-- Input in gigatonnes (Gt) dfactor = factors.cmwe #-- rad_e: Average Radius of the Earth [cm] int_fact[:] = 1e15 / (factors.rad_e**2) elif (UNITS == 3): #-- Input in kg/m^2 (mm w.e.) dfactor = factors.mmwe int_fact[:] = np.sin(th) * dphi * dth elif isinstance(UNITS, (list, np.ndarray)): #-- custom units dfactor = np.copy(UNITS) int_fact[:] = np.sin(th) * dphi * dth else: raise ValueError('Unknown units {0}'.format(UNITS)) #-- Calculating cos/sin of phi arrays #-- output [m,phi] m = np.arange(MMAX + 1) ccos = np.cos(np.dot(m[:, np.newaxis], phi)) ssin = np.sin(np.dot(m[:, np.newaxis], phi)) #-- Calculating fully-normalized Legendre Polynomials #-- Output is plm[l,m,th] plm = np.zeros((LMAX + 1, MMAX + 1, nlat)) #-- added option to precompute plms to improve computational speed if PLM is None: #-- if plms are not pre-computed: calculate Legendre polynomials PLM, dPLM = plm_holmes(LMAX, np.cos(th)) #-- Multiplying by integration factors [sin(theta)*dtheta*dphi] #-- truncate legendre polynomials to spherical harmonic order MMAX for j in range(0, nlat): plm[:, m, j] = PLM[:, m, j] * int_fact[j] #-- Initializing preliminary spherical harmonic matrices yclm = np.zeros((LMAX + 1, MMAX + 1)) yslm = np.zeros((LMAX + 1, MMAX + 1)) #-- Initializing output spherical harmonic matrices Ylms = gravity_toolkit.harmonics(lmax=LMAX, mmax=MMAX) Ylms.clm = np.zeros((LMAX + 1, MMAX + 1)) Ylms.slm = np.zeros((LMAX + 1, MMAX + 1)) #-- Multiplying gridded data with sin/cos of m#phis #-- This will sum through all phis in the dot product #-- output [m,theta] dcos = np.dot(ccos, data) dsin = np.dot(ssin, data) for l in range(LMIN, LMAX + 1): #-- equivalent to LMIN:LMAX mm = np.min([MMAX, l]) #-- truncate to MMAX if specified (if l > MMAX) m = np.arange(0, mm + 1) #-- mm+1 elements between 0 and mm #-- Summing product of plms and data over all latitudes #-- axis=1 signifies the direction of the summation yclm[l, m] = np.sum(plm[l, m, :] * dcos[m, :], axis=1) yslm[l, m] = np.sum(plm[l, m, :] * dsin[m, :], axis=1) #-- Multiplying by factors to convert to fully normalized coefficients Ylms.clm[l, m] = dfactor[l] * yclm[l, m] Ylms.slm[l, m] = dfactor[l] * yslm[l, m] #-- return the output spherical harmonics object return Ylms
def gen_pressure_stokes(PG, R, lon, lat, LMAX=60, MMAX=None, PLM=None, LOVE=None): """ Converts pressure fields from the spatial domain to spherical harmonic coefficients Arguments --------- PG: pressure/gravity ratio R: radius lon: longitude array lat: latitude array Keyword arguments ----------------- LMAX: Upper bound of Spherical Harmonic Degrees MMAX: Upper bound of Spherical Harmonic Orders PLM: input Legendre polynomials LOVE: input load Love numbers up to degree LMAX (hl,kl,ll) Returns ------- clm: cosine spherical harmonic coefficients slm: sine spherical harmonic coefficients l: spherical harmonic degree to LMAX m: spherical harmonic order to MMAX """ #-- converting LMAX to integer LMAX = np.int(LMAX) #-- upper bound of spherical harmonic orders (default = LMAX) MMAX = np.copy(LMAX) if not MMAX else MMAX #-- grid dimensions nlat = np.int(len(lat)) #-- grid step dlon = np.abs(lon[1] - lon[0]) dlat = np.abs(lat[1] - lat[0]) #-- longitude degree spacing in radians dphi = dlon * np.pi / 180.0 #-- colatitude degree spacing in radians dth = dlat * np.pi / 180.0 #-- reformatting longitudes to range 0:360 (if previously -180:180) lon = np.squeeze(lon.copy()) if np.any(lon < 0): lon_ind, = np.nonzero(lon < 0) lon[lon_ind] += 360.0 #-- Longitude in radians phi = lon[np.newaxis, :] * np.pi / 180.0 #-- Colatitude in radians th = (90.0 - np.squeeze(lat.copy())) * np.pi / 180.0 #-- For gridded data: dmat = original data matrix sz = np.shape(PG) #-- reforming data to lonXlat if input latXlon PG = np.transpose(PG) if (sz[0] == nlat) else PG R = np.transpose(R) if (sz[0] == nlat) else R #-- Coefficient for calculating Stokes coefficients from pressure field #-- extract arrays of kl, hl, and ll Love Numbers factors = units(lmax=LMAX).spatial(*LOVE) #-- Earth Parameters #-- Average Radius of the Earth [m] rad_e = factors.rad_e / 100.0 #-- SH Degree dependent factors with indirect loading components dfactor = factors.mmwe #-- Calculating cos/sin of phi arrays #-- output [m,phi] m = np.arange(MMAX + 1) ccos = np.cos(np.dot(m[:, np.newaxis], phi)) ssin = np.sin(np.dot(m[:, np.newaxis], phi)) #-- Calculates fully-normalized Legendre Polynomials with plm_holmes.py #-- Output is plm[l,m,th] plm = np.zeros((LMAX + 1, MMAX + 1, nlat)) #-- added option to precompute plms to improve computational speed if PLM is None: #-- if plms are not pre-computed: calculate Legendre polynomials PLM, dPLM = plm_holmes(LMAX, np.cos(th)) #-- Multiplying by integration factors [sin(theta)*dtheta*dphi] #-- truncate legendre polynomials to spherical harmonic order MMAX m = np.arange(MMAX + 1) for j in range(0, nlat): plm[:, m, j] = PLM[:, m, j] * np.sin(th[j]) * dphi * dth #-- Initializing preliminary spherical harmonic matrices yclm = np.zeros((LMAX + 1, MMAX + 1)) yslm = np.zeros((LMAX + 1, MMAX + 1)) #-- Initializing output spherical harmonic matrices clm = np.zeros((LMAX + 1, MMAX + 1)) slm = np.zeros((LMAX + 1, MMAX + 1)) for l in range(0, LMAX + 1): #-- equivalent to 0:LMAX mm = np.min([MMAX, l]) #-- truncate to MMAX if specified (if l > MMAX) m = np.arange(0, mm + 1) #-- mm+1 elements between 0 and mm #-- Multiplying gridded data with sin/cos of m#phis #-- This will sum through all phis in the dot product #-- output [m,theta] pfactor = PG * (R / rad_e)**(l + 2) dcos = np.dot(ccos, pfactor) dsin = np.dot(ssin, pfactor) #-- Summing product of plms and data over all latitudes #-- axis=1 signifies the direction of the summation (colatitude (th)) #-- ycos and ysin are the SH coefficients before normalizing yclm[l, m] = np.sum(plm[l, m, :] * dcos[m, :], axis=1) yslm[l, m] = np.sum(plm[l, m, :] * dsin[m, :], axis=1) #-- Multiplying by factors to normalize clm[l, m] = dfactor[l] * yclm[l, m] slm[l, m] = dfactor[l] * yslm[l, m] #-- return the harmonics return { 'clm': clm, 'slm': slm, 'l': np.arange(LMAX + 1), 'm': np.arange(MMAX + 1) }
def convert_harmonics(INPUT_FILE, OUTPUT_FILE, LMAX=None, MMAX=None, UNITS=None, LOVE_NUMBERS=0, REFERENCE=None, DDEG=None, INTERVAL=None, MISSING=False, FILL_VALUE=None, HEADER=None, DATAFORM=None, VERBOSE=False, MODE=0o775): #-- verify that output directory exists DIRECTORY = os.path.abspath(os.path.dirname(OUTPUT_FILE)) if not os.access(DIRECTORY, os.F_OK): os.makedirs(DIRECTORY, MODE, exist_ok=True) #-- Grid spacing dlon, dlat = (DDEG, DDEG) if (np.ndim(DDEG) == 0) else (DDEG[0], DDEG[1]) #-- Grid dimensions if (INTERVAL == 1): #-- (0:360, 90:-90) nlon = np.int((360.0 / dlon) + 1.0) nlat = np.int((180.0 / dlat) + 1.0) elif (INTERVAL == 2): #-- degree spacing/2 nlon = np.int((360.0 / dlon)) nlat = np.int((180.0 / dlat)) #-- read spatial file in data format #-- expand dimensions if (DATAFORM == 'ascii'): #-- ascii (.txt) input_spatial = spatial(spacing=[dlon, dlat], nlat=nlat, nlon=nlon).from_ascii( INPUT_FILE, header=HEADER).expand_dims() elif (DATAFORM == 'netCDF4'): #-- netcdf (.nc) input_spatial = spatial().from_netCDF4(INPUT_FILE).expand_dims() elif (DATAFORM == 'HDF5'): #-- HDF5 (.H5) input_spatial = spatial().from_HDF5(INPUT_FILE).expand_dims() #-- convert missing values to zero input_spatial.replace_invalid(0.0) #-- input data shape nlat, nlon, nt = input_spatial.shape #-- read arrays of kl, hl, and ll Love Numbers LOVE = load_love_numbers(LMAX, LOVE_NUMBERS=LOVE_NUMBERS, REFERENCE=REFERENCE) #-- calculate associated Legendre polynomials th = (90.0 - input_spatial.lat) * np.pi / 180.0 PLM, dPLM = plm_holmes(LMAX, np.cos(th)) #-- date count array counter = np.arange(nt) #-- allocate for output spherical harmonics Ylms = harmonics(lmax=LMAX, mmax=MMAX) Ylms.time = input_spatial.time.copy() Ylms.month = np.array(12.0 * (Ylms.time - 2002.0), dtype='i') + 1 Ylms.clm = np.zeros((LMAX + 1, LMAX + 1, nt)) Ylms.slm = np.zeros((LMAX + 1, LMAX + 1, nt)) for t in range(nt): #-- convert spatial field to spherical harmonics output_Ylms = gen_stokes(input_spatial.data[:, :, t].T, input_spatial.lon, input_spatial.lat, UNITS=UNITS, LMIN=0, LMAX=LMAX, MMAX=MMAX, PLM=PLM, LOVE=LOVE) Ylms.clm[:, :, t] = output_Ylms['clm'][:, :].copy() Ylms.slm[:, :, t] = output_Ylms['slm'][:, :].copy() #-- if verbose output: print input and output file names if VERBOSE: print('{0}:'.format(os.path.basename(sys.argv[0]))) print('{0} -->\n\t{1}'.format(INPUT_FILE, OUTPUT_FILE)) #-- outputting data to file if (DATAFORM == 'ascii'): #-- ascii (.txt) Ylms.to_ascii(OUTPUT_FILE) elif (DATAFORM == 'netCDF4'): #-- netCDF4 (.nc) Ylms.to_netCDF4(OUTPUT_FILE) elif (DATAFORM == 'HDF5'): #-- HDF5 (.H5) Ylms.to_HDF5(OUTPUT_FILE) #-- change output permissions level to MODE os.chmod(OUTPUT_FILE, MODE)
def gen_spherical_cap(data, lon, lat, LMAX=60, MMAX=None, AREA=0, RAD_CAP=0, RAD_KM=0, UNITS=1, PLM=None, LOVE=None): """ Calculates spherical harmonic coefficients for a spherical cap Arguments --------- data: data magnitude lon: longitude of spherical cap center lat: latitude of spherical cap center Keyword arguments ----------------- LMAX: Upper bound of Spherical Harmonic Degrees MMAX: Upper bound of Spherical Harmonic Orders AREA: spherical cap area in cm^2 UNITS: input data units 1: cm of water thickness (default) 2: gigatonnes of mass 3: kg/m^2 list: custom unit conversion factor PLM: input Legendre polynomials LOVE: input load Love numbers up to degree LMAX (hl,kl,ll) Returns ------- clm: cosine spherical harmonic coefficients slm: sine spherical harmonic coefficients l: spherical harmonic degree to LMAX m: spherical harmonic order to MMAX """ #-- upper bound of spherical harmonic orders (default = LMAX) if MMAX is None: MMAX = np.copy(LMAX) #-- Earth Parameters factors = gravity_toolkit.units(lmax=LMAX) rho_e = factors.rho_e#-- Average Density of the Earth [g/cm^3] rad_e = factors.rad_e#-- Average Radius of the Earth [cm] #-- convert lon and lat to radians phi = lon*np.pi/180.0#-- Longitude in radians th = (90.0 - lat)*np.pi/180.0#-- Colatitude in radians #-- Converting input area into an equivalent spherical cap radius #-- Following Jacob et al. (2012) Equation 4 and 5 #-- alpha is the vertical semi-angle subtending a cone at the #-- center of the earth if (RAD_CAP != 0): #-- if given spherical cap radius in degrees #-- converting to radians alpha = RAD_CAP*np.pi/180.0 elif (AREA != 0): #-- if given spherical cap area in cm^2 #-- radius in centimeters radius_cm = np.sqrt(AREA/np.pi) #-- Calculating angular radius of spherical cap alpha = (radius_cm/rad_e) elif (RAD_KM != 0): #-- if given spherical cap radius in kilometers #-- Calculating angular radius of spherical cap alpha = (1e5*RAD_KM)/rad_e else: raise ValueError('Input RAD_CAP, AREA or RAD_KM of spherical cap') #-- Calculate factor to convert from input units into cmH2O equivalent #-- Default input is for inputs already in cmH2O (unit_conv = 1) if (UNITS == 1): #-- Input data is in cm water equivalent (cmH2O) unit_conv = 1.0 elif (UNITS == 2): #-- Input data is in gigatonnes (Gt) #-- calculate spherical cap area from angular radius area = np.pi*(alpha*rad_e)**2 #-- the 1.e15 converts from gigatons/cm^2 to cm of water #-- 1 g/cm^3 = 1000 kg/m^3 = density water #-- 1 Gt = 1 Pg = 1.e15 g unit_conv = 1.e15/area elif (UNITS == 3): #-- Input data is in kg/m^2 #-- 1 kg = 1000 g #-- 1 m^2 = 100*100 cm^2 = 1e4 cm^2 unit_conv = 0.1 elif isinstance(UNITS,(list,np.ndarray)): #-- custom units unit_conv = np.copy(UNITS) else: raise ValueError('Unknown units {0}'.format(UNITS)) #-- Coefficient for calculating Stokes coefficients for a spherical cap #-- From Jacob et al (2012), Farrell (1972) and Longman (1962) coeff = 3.0/(rad_e*rho_e) #-- extract arrays of kl, hl, and ll Love Numbers hl,kl,ll = LOVE #-- calculate array of l values ranging from 0 to LMAX (harmonic degrees) #-- LMAX+1 as there are LMAX+1 elements between 0 and LMAX l = np.arange(LMAX+1) #-- calculate SH degree dependent factors to convert from coefficients #-- of mass into normalized geoid coefficients #-- NOTE: these are not the normal factors for converting to geoid due #-- to the square of the denominator #-- kl[l] is the Load Love Number of degree l dfactor = (1.0 + kl[l])/((1.0 + 2.0*l)**2) #-- Calculating plms of the spherical caps #-- From Longman et al. (1962) #-- pl_alpha = F(alpha) from Jacob 2011 #-- pl_alpha is purely zonal and depends only on the size of the cap #-- allocating for constructed array pl_alpha = np.zeros((LMAX+1)) #-- l=0 is a special case (P(-1) = 1, P(1) = cos(alpha)) pl_alpha[0] = (1.0 - np.cos(alpha))/2.0 #-- for all other degrees: calculate the legendre polynomials up to LMAX+1 pl_matrix,_ = legendre_polynomials(LMAX+1,np.cos(alpha)) for l in range(1, LMAX+1):#-- LMAX+1 to include LMAX #-- from Longman (1962) and Jacob et al (2012) #-- unnormalizing Legendre polynomials #-- sqrt(2*l - 1) == sqrt(2*(l-1) + 1) #-- sqrt(2*l + 3) == sqrt(2*(l+1) + 1) pl_lower = pl_matrix[l-1]/np.sqrt(2.0*l-1.0) pl_upper = pl_matrix[l+1]/np.sqrt(2.0*l+3.0) pl_alpha[l] = (pl_lower - pl_upper)/2.0 #-- Calculating Legendre Polynomials #-- added option to precompute plms to improve computational speed #-- this would be the plm for the center of the spherical cap #-- used to rotate the spherical cap to point lat/lon if PLM is None: plmout,dplm = plm_holmes(LMAX,np.cos(th)) #-- truncate precomputed plms to order plmout = np.squeeze(plmout[:,:MMAX+1,:]) else: #-- truncate precomputed plms to degree and order plmout = PLM[:LMAX+1,:MMAX+1] #-- calculate array of m values ranging from 0 to MMAX (harmonic orders) #-- MMAX+1 as there are MMAX+1 elements between 0 and MMAX m = np.arange(MMAX+1) #-- Multiplying by the units conversion factor (unit_conv) to #-- convert from the input units into cmH2O equivalent #-- Multiplying point mass data (converted to cmH2O) with sin/cos of m*phis #-- data normally is 1 for a uniform 1cm water equivalent layer #-- but can be a mass point if reconstructing a spherical harmonic field #-- NOTE: NOT a matrix multiplication as data (and phi) is a single point dcos = unit_conv*data*np.cos(m*phi) dsin = unit_conv*data*np.sin(m*phi) #-- Multiplying by plm_alpha (F_l from Jacob 2012) plm = np.zeros((LMAX+1,MMAX+1)) #-- Initializing preliminary spherical harmonic matrices yclm = np.zeros((LMAX+1,MMAX+1)) yslm = np.zeros((LMAX+1,MMAX+1)) #-- Initializing output spherical harmonic matrices Ylms = gravity_toolkit.harmonics(lmax=LMAX, mmax=MMAX) Ylms.clm = np.zeros((LMAX+1,MMAX+1)) Ylms.slm = np.zeros((LMAX+1,MMAX+1)) for m in range(0,MMAX+1):#-- MMAX+1 to include MMAX l = np.arange(m,LMAX+1)#-- LMAX+1 to include LMAX #-- rotate spherical cap to be centered at lat/lon plm[l,m] = plmout[l,m]*pl_alpha[l] #-- multiplying clm by cos(m*phi) and slm by sin(m*phi) #-- to get a field of spherical harmonics yclm[l,m] = plm[l,m]*dcos[m] yslm[l,m] = plm[l,m]*dsin[m] #-- multiplying by coefficients to convert to geoid coefficients Ylms.clm[l,m] = coeff*dfactor[l]*yclm[l,m] Ylms.slm[l,m] = coeff*dfactor[l]*yslm[l,m] #-- return the output spherical harmonics object return Ylms
def convert_harmonics(INPUT_FILE, OUTPUT_FILE, LMAX=None, MMAX=None, UNITS=None, LOVE_NUMBERS=0, REFERENCE=None, DDEG=None, INTERVAL=None, FILL_VALUE=None, HEADER=None, DATAFORM=None, MODE=0o775): #-- verify that output directory exists DIRECTORY = os.path.abspath(os.path.dirname(OUTPUT_FILE)) if not os.access(DIRECTORY, os.F_OK): os.makedirs(DIRECTORY, MODE, exist_ok=True) #-- Grid spacing dlon, dlat = (DDEG, DDEG) if (np.ndim(DDEG) == 0) else (DDEG[0], DDEG[1]) #-- Grid dimensions if (INTERVAL == 1): #-- (0:360, 90:-90) nlon = np.int64((360.0 / dlon) + 1.0) nlat = np.int64((180.0 / dlat) + 1.0) elif (INTERVAL == 2): #-- degree spacing/2 nlon = np.int64((360.0 / dlon)) nlat = np.int64((180.0 / dlat)) #-- read spatial file in data format #-- expand dimensions if (DATAFORM == 'ascii'): #-- ascii (.txt) input_spatial = spatial(spacing=[dlon, dlat], nlat=nlat, nlon=nlon, fill_value=FILL_VALUE).from_ascii( INPUT_FILE, header=HEADER).expand_dims() elif (DATAFORM == 'netCDF4'): #-- netcdf (.nc) input_spatial = spatial().from_netCDF4(INPUT_FILE).expand_dims() elif (DATAFORM == 'HDF5'): #-- HDF5 (.H5) input_spatial = spatial().from_HDF5(INPUT_FILE).expand_dims() #-- convert missing values to zero input_spatial.replace_invalid(0.0) #-- input data shape nlat, nlon, nt = input_spatial.shape #-- read arrays of kl, hl, and ll Love Numbers LOVE = load_love_numbers(LMAX, LOVE_NUMBERS=LOVE_NUMBERS, REFERENCE=REFERENCE) #-- upper bound of spherical harmonic orders (default = LMAX) if MMAX is None: MMAX = np.copy(LMAX) #-- calculate associated Legendre polynomials th = (90.0 - input_spatial.lat) * np.pi / 180.0 PLM, dPLM = plm_holmes(LMAX, np.cos(th)) #-- create list of harmonics objects Ylms_list = [] for i, t in enumerate(input_spatial.time): #-- convert spatial field to spherical harmonics output_Ylms = gen_stokes(input_spatial.data[:, :, i].T, input_spatial.lon, input_spatial.lat, UNITS=UNITS, LMIN=0, LMAX=LMAX, MMAX=MMAX, PLM=PLM, LOVE=LOVE) output_Ylms.time = np.copy(t) output_Ylms.month = calendar_to_grace(t) #-- append to list Ylms_list.append(output_Ylms) #-- convert Ylms list for output spherical harmonics Ylms = harmonics().from_list(Ylms_list) Ylms_list = None #-- outputting data to file if (DATAFORM == 'ascii'): #-- ascii (.txt) Ylms.to_ascii(OUTPUT_FILE) elif (DATAFORM == 'netCDF4'): #-- netCDF4 (.nc) Ylms.to_netCDF4(OUTPUT_FILE) elif (DATAFORM == 'HDF5'): #-- HDF5 (.H5) Ylms.to_HDF5(OUTPUT_FILE) #-- change output permissions level to MODE os.chmod(OUTPUT_FILE, MODE)
def harmonic_summation(clm1, slm1, lon, lat, LMIN=0, LMAX=0, MMAX=None, PLM=None): """ Converts data from spherical harmonic coefficients to a spatial field Arguments --------- clm1: cosine spherical harmonic coefficients in output units slm1: sine spherical harmonic coefficients in output units lon: longitude array lat: latitude array Keyword arguments ----------------- LMIN: Lower bound of Spherical Harmonic Degrees LMAX: Upper bound of Spherical Harmonic Degrees MMAX: Upper bound of Spherical Harmonic Orders PLM: Fully-normalized associated Legendre polynomials Returns ------- spatial: spatial field """ #-- if LMAX is not specified, will use the size of the input harmonics if (LMAX == 0): LMAX = np.shape(clm1)[0] - 1 #-- upper bound of spherical harmonic orders (default = LMAX) if MMAX is None: MMAX = np.copy(LMAX) #-- Longitude in radians phi = (np.squeeze(lon) * np.pi / 180.0)[np.newaxis, :] #-- Colatitude in radians th = (90.0 - np.squeeze(lat)) * np.pi / 180.0 thmax = len(th) #-- Calculate fourier coefficients from legendre coefficients d_cos = np.zeros((MMAX + 1, thmax)) #-- [m,th] d_sin = np.zeros((MMAX + 1, thmax)) #-- [m,th] if PLM is None: #-- if plms are not pre-computed: calculate Legendre polynomials PLM, dPLM = plm_holmes(LMAX, np.cos(th)) #-- Truncating harmonics to degree and order LMAX #-- removing coefficients below LMIN and above MMAX mm = np.arange(0, MMAX + 1) clm = np.zeros((LMAX + 1, MMAX + 1)) slm = np.zeros((LMAX + 1, MMAX + 1)) clm[LMIN:LMAX + 1, mm] = clm1[LMIN:LMAX + 1, mm] slm[LMIN:LMAX + 1, mm] = slm1[LMIN:LMAX + 1, mm] for k in range(0, thmax): #-- summation over all spherical harmonic degrees d_cos[:, k] = np.sum(PLM[:, mm, k] * clm[:, mm], axis=0) d_sin[:, k] = np.sum(PLM[:, mm, k] * slm[:, mm], axis=0) #-- Final signal recovery from fourier coefficients m = np.arange(0, MMAX + 1)[:, np.newaxis] #-- Calculating cos(m*phi) and sin(m*phi) ccos = np.cos(np.dot(m, phi)) ssin = np.sin(np.dot(m, phi)) #-- summation of cosine and sine harmonics s = np.dot(np.transpose(ccos), d_cos) + np.dot(np.transpose(ssin), d_sin) #-- return output data return s
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 integration(data, lon, lat, LMAX=60, MMAX=None, PLM=0, **kwargs): """ Converts data from the spatial domain to spherical harmonic coefficients Arguments --------- data: data magnitude lon: longitude array lat: latitude array Keyword arguments ----------------- LMAX: Upper bound of Spherical Harmonic Degrees MMAX: Upper bound of Spherical Harmonic Orders PLM: input Legendre polynomials Returns ------- clm: cosine spherical harmonic coefficients slm: sine spherical harmonic coefficients l: spherical harmonic degree to LMAX m: spherical harmonic order to MMAX """ #-- dimensions of the longitude and latitude arrays nlon = np.int64(len(lon)) nlat = np.int64(len(lat)) #-- grid step dlon = np.abs(lon[1]-lon[0]) dlat = np.abs(lat[1]-lat[0]) #-- longitude degree spacing in radians dphi = dlon*np.pi/180.0 #-- colatitude degree spacing in radians dth = dlat*np.pi/180.0 #-- reformatting longitudes to range 0:360 (if previously -180:180) if np.count_nonzero(lon < 0): lon[lon < 0] += 360.0 #-- calculate longitude and colatitude arrays in radians phi = np.reshape(lon,(1,nlon))*np.pi/180.0#-- reshape to 1xnlon th = (90.0 - np.squeeze(lat))*np.pi/180.0#-- remove singleton dimensions #-- Calculating cos/sin of phi arrays (output [m,phi]) #-- LMAX+1 as there are LMAX+1 elements between 0 and LMAX m = np.arange(MMAX+1)[:, np.newaxis] ccos = np.cos(np.dot(m,phi)) ssin = np.sin(np.dot(m,phi)) #-- Multiplying sin(th) with differentials of theta and phi #-- to calculate the integration factor at each latitude int_fact = np.sin(th)*dphi*dth coeff = 1.0/(4.0*np.pi) #-- Calculate polynomials using Holmes and Featherstone (2002) relation plm = np.zeros((LMAX+1,MMAX+1,nlat)) if (np.ndim(PLM) == 0): plmout,dplm = plm_holmes(LMAX,np.cos(th)) else: #-- use precomputed plms to improve computational speed #-- or to use a different recursion relation for polynomials plmout = PLM #-- Multiply plms by integration factors [sin(theta)*dtheta*dphi] #-- truncate plms to maximum spherical harmonic order if MMAX < LMAX m = np.arange(MMAX+1) for j in range(0,nlat): plm[:,m,j] = plmout[:,m,j]*int_fact[j] #-- Initializing preliminary spherical harmonic matrices yclm = np.zeros((LMAX+1,MMAX+1)) yslm = np.zeros((LMAX+1,MMAX+1)) #-- Initializing output spherical harmonic matrices Ylms = gravity_toolkit.harmonics(lmax=LMAX, mmax=MMAX) Ylms.clm = np.zeros((LMAX+1,MMAX+1)) Ylms.slm = np.zeros((LMAX+1,MMAX+1)) #-- Multiplying gridded data with sin/cos of m#phis (output [m,theta]) #-- This will sum through all phis in the dot product dcos = np.dot(ccos,data) dsin = np.dot(ssin,data) for l in range(0,LMAX+1): mm = np.min([MMAX,l])#-- truncate to MMAX if specified (if l > MMAX) m = np.arange(0,mm+1)#-- mm+1 elements between 0 and mm #-- Summing product of plms and data over all latitudes yclm[l,m] = np.sum(plm[l,m,:]*dcos[m,:], axis=1) yslm[l,m] = np.sum(plm[l,m,:]*dsin[m,:], axis=1) #-- convert to output normalization (4-pi normalized harmonics) Ylms.clm[l,m] = coeff*yclm[l,m] Ylms.slm[l,m] = coeff*yslm[l,m] #-- return the output spherical harmonics object return Ylms
def scale_grace_maps(base_dir, PROC, DREL, DSET, LMAX, RAD, START=None, END=None, MISSING=None, LMIN=None, MMAX=None, LOVE_NUMBERS=0, REFERENCE=None, DESTRIPE=False, DDEG=None, INTERVAL=None, GIA=None, GIA_FILE=None, ATM=False, POLE_TIDE=False, DEG1=None, DEG1_FILE=None, MODEL_DEG1=False, SLR_C20=None, SLR_21=None, SLR_22=None, SLR_C30=None, SLR_C50=None, DATAFORM=None, MEAN_FILE=None, MEANFORM=None, REMOVE_FILES=None, REMOVE_FORMAT=None, REDISTRIBUTE_REMOVED=False, SCALE_FILE=None, ERROR_FILE=None, POWER_FILE=None, LANDMASK=None, OUTPUT_DIRECTORY=None, FILE_PREFIX=None, VERBOSE=False, MODE=0o775): #-- recursively create output Directory if not currently existing if not os.access(OUTPUT_DIRECTORY, os.F_OK): os.makedirs(OUTPUT_DIRECTORY, mode=MODE, exist_ok=True) #-- list object of output files for file logs (full path) output_files = [] #-- file information suffix = dict(ascii='txt', netCDF4='nc', HDF5='H5') #-- output file format file_format = '{0}{1}{2}_L{3:d}{4}{5}{6}_{7:03d}-{8:03d}.{9}' #-- read arrays of kl, hl, and ll Love Numbers hl,kl,ll = load_love_numbers(LMAX, LOVE_NUMBERS=LOVE_NUMBERS, REFERENCE=REFERENCE) #-- atmospheric ECMWF "jump" flag (if ATM) atm_str = '_wATM' if ATM else '' #-- output string for both LMAX==MMAX and LMAX != MMAX cases MMAX = np.copy(LMAX) if not MMAX else MMAX order_str = 'M{0:d}'.format(MMAX) if (MMAX != LMAX) else '' #-- output spatial units unit_str = 'cmwe' unit_name = 'Equivalent Water Thickness' #-- invalid value fill_value = -9999.0 #-- Calculating the Gaussian smoothing for radius RAD if (RAD != 0): wt = 2.0*np.pi*gauss_weights(RAD,LMAX) gw_str = '_r{0:0.0f}km'.format(RAD) else: #-- else = 1 wt = np.ones((LMAX+1)) gw_str = '' #-- Read Ocean function and convert to Ylms for redistribution if REDISTRIBUTE_REMOVED: #-- read Land-Sea Mask and convert to spherical harmonics ocean_Ylms = ocean_stokes(LANDMASK, LMAX, MMAX=MMAX, LOVE=(hl,kl,ll)) #-- Grid spacing dlon,dlat = (DDEG[0],DDEG[0]) if (len(DDEG) == 1) else (DDEG[0],DDEG[1]) #-- Grid dimensions if (INTERVAL == 1):#-- (0:360, 90:-90) nlon = np.int64((360.0/dlon)+1.0) nlat = np.int64((180.0/dlat)+1.0) elif (INTERVAL == 2):#-- degree spacing/2 nlon = np.int64((360.0/dlon)) nlat = np.int64((180.0/dlat)) #-- read data for input scale files (ascii, netCDF4, HDF5) if (DATAFORM == 'ascii'): kfactor = spatial(spacing=[dlon,dlat],nlat=nlat,nlon=nlon).from_ascii( SCALE_FILE,date=False) k_error = spatial(spacing=[dlon,dlat],nlat=nlat,nlon=nlon).from_ascii( ERROR_FILE,date=False) k_power = spatial(spacing=[dlon,dlat],nlat=nlat,nlon=nlon).from_ascii( POWER_FILE,date=False) elif (DATAFORM == 'netCDF4'): kfactor = spatial().from_netCDF4(SCALE_FILE,date=False) k_error = spatial().from_netCDF4(ERROR_FILE,date=False) k_power = spatial().from_netCDF4(POWER_FILE,date=False) elif (DATAFORM == 'HDF5'): kfactor = spatial().from_HDF5(SCALE_FILE,date=False) k_error = spatial().from_HDF5(ERROR_FILE,date=False) k_power = spatial().from_HDF5(POWER_FILE,date=False) #-- input data shape nlat,nlon = kfactor.shape #-- input GRACE/GRACE-FO spherical harmonic datafiles for date range #-- replacing low-degree harmonics with SLR values if specified #-- include degree 1 (geocenter) harmonics if specified #-- correcting for Pole-Tide and Atmospheric Jumps if specified Ylms = grace_input_months(base_dir, PROC, DREL, DSET, LMAX, START, END, MISSING, SLR_C20, DEG1, MMAX=MMAX, SLR_21=SLR_21, SLR_22=SLR_22, SLR_C30=SLR_C30, SLR_C50=SLR_C50, DEG1_FILE=DEG1_FILE, MODEL_DEG1=MODEL_DEG1, ATM=ATM, POLE_TIDE=POLE_TIDE) #-- create harmonics object from GRACE/GRACE-FO data GRACE_Ylms = harmonics().from_dict(Ylms) GRACE_Ylms.directory = Ylms['directory'] #-- use a mean file for the static field to remove if MEAN_FILE: #-- read data form for input mean file (ascii, netCDF4, HDF5, gfc) mean_Ylms = harmonics().from_file(MEAN_FILE,format=MEANFORM,date=False) #-- remove the input mean GRACE_Ylms.subtract(mean_Ylms) else: GRACE_Ylms.mean(apply=True) #-- date information of GRACE/GRACE-FO coefficients nfiles = len(GRACE_Ylms.time) #-- filter GRACE/GRACE-FO coefficients if DESTRIPE: #-- destriping GRACE/GRACE-FO coefficients ds_str = '_FL' GRACE_Ylms = GRACE_Ylms.destripe() else: #-- using standard GRACE/GRACE-FO harmonics ds_str = '' #-- input GIA spherical harmonic datafiles GIA_Ylms_rate = read_GIA_model(GIA_FILE,GIA=GIA,LMAX=LMAX,MMAX=MMAX) gia_str = '_{0}'.format(GIA_Ylms_rate['title']) if GIA else '' #-- calculate the monthly mass change from GIA GIA_Ylms = GRACE_Ylms.zeros_like() GIA_Ylms.time[:] = np.copy(GRACE_Ylms.time) GIA_Ylms.month[:] = np.copy(GRACE_Ylms.month) #-- monthly GIA calculated by gia_rate*time elapsed #-- finding change in GIA each month for t in range(nfiles): GIA_Ylms.clm[:,:,t] = GIA_Ylms_rate['clm']*(GIA_Ylms.time[t]-2003.3) GIA_Ylms.slm[:,:,t] = GIA_Ylms_rate['slm']*(GIA_Ylms.time[t]-2003.3) #-- default file prefix if not FILE_PREFIX: fargs = (PROC,DREL,DSET,Ylms['title'],gia_str) FILE_PREFIX = '{0}_{1}_{2}{3}{4}_'.format(*fargs) #-- input spherical harmonic datafiles to be removed from the GRACE data #-- Remove sets of Ylms from the GRACE data before returning remove_Ylms = GRACE_Ylms.zeros_like() remove_Ylms.time[:] = np.copy(GRACE_Ylms.time) remove_Ylms.month[:] = np.copy(GRACE_Ylms.month) if REMOVE_FILES: #-- extend list if a single format was entered for all files if len(REMOVE_FORMAT) < len(REMOVE_FILES): REMOVE_FORMAT = REMOVE_FORMAT*len(REMOVE_FILES) #-- for each file to be removed for REMOVE_FILE,REMOVEFORM in zip(REMOVE_FILES,REMOVE_FORMAT): if REMOVEFORM in ('ascii','netCDF4','HDF5'): #-- ascii (.txt) #-- netCDF4 (.nc) #-- HDF5 (.H5) Ylms = harmonics().from_file(REMOVE_FILE, format=REMOVEFORM) elif REMOVEFORM in ('index-ascii','index-netCDF4','index-HDF5'): #-- read from index file _,removeform = REMOVEFORM.split('-') #-- index containing files in data format Ylms = harmonics().from_index(REMOVE_FILE, format=removeform) #-- reduce to GRACE/GRACE-FO months and truncate to degree and order Ylms = Ylms.subset(GRACE_Ylms.month).truncate(lmax=LMAX,mmax=MMAX) #-- distribute removed Ylms uniformly over the ocean if REDISTRIBUTE_REMOVED: #-- calculate ratio between total removed mass and #-- a uniformly distributed cm of water over the ocean ratio = Ylms.clm[0,0,:]/ocean_Ylms.clm[0,0] #-- for each spherical harmonic for m in range(0,MMAX+1):#-- MMAX+1 to include MMAX for l in range(m,LMAX+1):#-- LMAX+1 to include LMAX #-- remove the ratio*ocean Ylms from Ylms #-- note: x -= y is equivalent to x = x - y Ylms.clm[l,m,:] -= ratio*ocean_Ylms.clm[l,m] Ylms.slm[l,m,:] -= ratio*ocean_Ylms.slm[l,m] #-- filter removed coefficients if DESTRIPE: Ylms = Ylms.destripe() #-- add data for month t and INDEX_FILE to the total #-- remove_clm and remove_slm matrices #-- redistributing the mass over the ocean if specified remove_Ylms.add(Ylms) #-- calculating GRACE/GRACE-FO error (Wahr et al. 2006) #-- output GRACE error file (for both LMAX==MMAX and LMAX != MMAX cases) args = (PROC,DREL,DSET,LMAX,order_str,ds_str,atm_str,GRACE_Ylms.month[0], GRACE_Ylms.month[-1], suffix[DATAFORM]) delta_format = '{0}_{1}_{2}_DELTA_CLM_L{3:d}{4}{5}{6}_{7:03d}-{8:03d}.{9}' DELTA_FILE = os.path.join(GRACE_Ylms.directory,delta_format.format(*args)) #-- check full path of the GRACE directory for delta file #-- if file was previously calculated: will read file #-- else: will calculate the GRACE/GRACE-FO error if not os.access(DELTA_FILE, os.F_OK): #-- add output delta file to list object output_files.append(DELTA_FILE) #-- Delta coefficients of GRACE time series (Error components) delta_Ylms = harmonics(lmax=LMAX,mmax=MMAX) delta_Ylms.clm = np.zeros((LMAX+1,MMAX+1)) delta_Ylms.slm = np.zeros((LMAX+1,MMAX+1)) #-- Smoothing Half-Width (CNES is a 10-day solution) #-- All other solutions are monthly solutions (HFWTH for annual = 6) if ((PROC == 'CNES') and (DREL in ('RL01','RL02'))): HFWTH = 19 else: HFWTH = 6 #-- Equal to the noise of the smoothed time-series #-- for each spherical harmonic order for m in range(0,MMAX+1):#-- MMAX+1 to include MMAX #-- for each spherical harmonic degree for l in range(m,LMAX+1):#-- LMAX+1 to include LMAX #-- Delta coefficients of GRACE time series for cs,csharm in enumerate(['clm','slm']): #-- calculate GRACE Error (Noise of smoothed time-series) #-- With Annual and Semi-Annual Terms val1 = getattr(GRACE_Ylms, csharm) smth = tssmooth(GRACE_Ylms.time, val1[l,m,:], HFWTH=HFWTH) #-- number of smoothed points nsmth = len(smth['data']) tsmth = np.mean(smth['time']) #-- GRACE delta Ylms #-- variance of data-(smoothed+annual+semi) val2 = getattr(delta_Ylms, csharm) val2[l,m] = np.sqrt(np.sum(smth['noise']**2)/nsmth) #-- save GRACE/GRACE-FO delta harmonics to file delta_Ylms.time = np.copy(tsmth) delta_Ylms.month = np.int64(nsmth) delta_Ylms.to_file(DELTA_FILE,format=DATAFORM) else: #-- read GRACE/GRACE-FO delta harmonics from file delta_Ylms = harmonics().from_file(DELTA_FILE,format=DATAFORM) #-- copy time and number of smoothed fields tsmth = np.squeeze(delta_Ylms.time) nsmth = np.int64(delta_Ylms.month) #-- Output spatial data object grid = spatial() grid.lon = np.copy(kfactor.lon) grid.lat = np.copy(kfactor.lat) grid.time = np.zeros((nfiles)) grid.month = np.zeros((nfiles),dtype=np.int64) grid.data = np.zeros((nlat,nlon,nfiles)) grid.mask = np.zeros((nlat,nlon,nfiles),dtype=bool) #-- Computing plms for converting to spatial domain phi = grid.lon[np.newaxis,:]*np.pi/180.0 theta = (90.0-grid.lat)*np.pi/180.0 PLM,dPLM = plm_holmes(LMAX,np.cos(theta)) #-- square of legendre polynomials truncated to order MMAX mm = np.arange(0,MMAX+1) PLM2 = PLM[:,mm,:]**2 #-- dfactor is the degree dependent coefficients #-- for converting to centimeters water equivalent (cmwe) dfactor = units(lmax=LMAX).harmonic(hl,kl,ll).cmwe #-- converting harmonics to truncated, smoothed coefficients in units #-- combining harmonics to calculate output spatial fields for i,gm in enumerate(GRACE_Ylms.month): #-- GRACE/GRACE-FO harmonics for time t Ylms = GRACE_Ylms.index(i) #-- Remove GIA rate for time Ylms.subtract(GIA_Ylms.index(i)) #-- Remove monthly files to be removed Ylms.subtract(remove_Ylms.index(i)) #-- smooth harmonics and convert to output units Ylms.convolve(dfactor*wt) #-- convert spherical harmonics to output spatial grid grid.data[:,:,i] = harmonic_summation(Ylms.clm, Ylms.slm, grid.lon, grid.lat, LMAX=LMAX, MMAX=MMAX, PLM=PLM).T #-- copy time variables for month grid.time[i] = np.copy(Ylms.time) grid.month[i] = np.copy(Ylms.month) #-- update spacing and dimensions grid.update_spacing() grid.update_extents() grid.update_dimensions() #-- scale output data with kfactor grid = grid.scale(kfactor.data) grid.replace_invalid(fill_value, mask=kfactor.mask) #-- output monthly files to ascii, netCDF4 or HDF5 args = (FILE_PREFIX,'',unit_str,LMAX,order_str,gw_str,ds_str, grid.month[0],grid.month[-1],suffix[DATAFORM]) FILE=os.path.join(OUTPUT_DIRECTORY,file_format.format(*args)) if (DATAFORM == 'ascii'): #-- ascii (.txt) grid.to_ascii(FILE, date=True, verbose=VERBOSE) elif (DATAFORM == 'netCDF4'): #-- netCDF4 grid.to_netCDF4(FILE, date=True, verbose=VERBOSE, units=unit_str, longname=unit_name, title='GRACE/GRACE-FO Spatial Data') elif (DATAFORM == 'HDF5'): #-- HDF5 grid.to_HDF5(FILE, date=True, verbose=VERBOSE, units=unit_str, longname=unit_name, title='GRACE/GRACE-FO Spatial Data') #-- set the permissions mode of the output files os.chmod(FILE, MODE) #-- add file to list output_files.append(FILE) #-- calculate power of scaled GRACE/GRACE-FO data scaled_power = grid.sum(power=2.0).power(0.5) #-- calculate residual leakage errors #-- scaled by ratio of GRACE and synthetic power ratio = scaled_power.scale(k_power.power(-1).data) error = k_error.scale(ratio.data) #-- output monthly error files to ascii, netCDF4 or HDF5 args = (FILE_PREFIX,'ERROR_',unit_str,LMAX,order_str,gw_str,ds_str, grid.month[0],grid.month[-1],suffix[DATAFORM]) FILE = os.path.join(OUTPUT_DIRECTORY,file_format.format(*args)) if (DATAFORM == 'ascii'): #-- ascii (.txt) error.to_ascii(FILE, date=False, verbose=VERBOSE) elif (DATAFORM == 'netCDF4'): #-- netCDF4 error.to_netCDF4(FILE, date=False, verbose=VERBOSE, units=unit_str, longname=unit_name, title='GRACE/GRACE-FO Scaling Error') elif (DATAFORM == 'HDF5'): #-- HDF5 error.to_HDF5(FILE, date=False, verbose=VERBOSE, units=unit_str, longname=unit_name, title='GRACE/GRACE-FO Scaling Error') #-- set the permissions mode of the output files os.chmod(FILE, MODE) #-- add file to list output_files.append(FILE) #-- Output spatial data object delta = spatial() delta.lon = np.copy(kfactor.lon) delta.lat = np.copy(kfactor.lat) delta.time = np.copy(tsmth) delta.month = np.copy(nsmth) delta.data = np.zeros((nlat,nlon)) delta.mask = np.zeros((nlat,nlon),dtype=bool) #-- calculate scaled spatial error #-- Calculating cos(m*phi)^2 and sin(m*phi)^2 m = delta_Ylms.m[:,np.newaxis] ccos = np.cos(np.dot(m,phi))**2 ssin = np.sin(np.dot(m,phi))**2 #-- truncate delta harmonics to spherical harmonic range Ylms = delta_Ylms.truncate(LMAX,lmin=LMIN,mmax=MMAX) #-- convolve delta harmonics with degree dependent factors #-- smooth harmonics and convert to output units Ylms = Ylms.convolve(dfactor*wt).power(2.0).scale(1.0/nsmth) #-- Calculate fourier coefficients d_cos = np.zeros((MMAX+1,nlat))#-- [m,th] d_sin = np.zeros((MMAX+1,nlat))#-- [m,th] #-- Calculating delta spatial values for k in range(0,nlat): #-- summation over all spherical harmonic degrees d_cos[:,k] = np.sum(PLM2[:,:,k]*Ylms.clm, axis=0) d_sin[:,k] = np.sum(PLM2[:,:,k]*Ylms.slm, axis=0) #-- Multiplying by c/s(phi#m) to get spatial error map delta.data[:] = np.sqrt(np.dot(ccos.T,d_cos) + np.dot(ssin.T,d_sin)).T #-- update spacing and dimensions delta.update_spacing() delta.update_extents() delta.update_dimensions() #-- scale output harmonic errors with kfactor delta = delta.scale(kfactor.data) delta.replace_invalid(fill_value, mask=kfactor.mask) #-- output monthly files to ascii, netCDF4 or HDF5 args = (FILE_PREFIX,'DELTA_',unit_str,LMAX,order_str,gw_str,ds_str, grid.month[0],grid.month[-1],suffix[DATAFORM]) FILE=os.path.join(OUTPUT_DIRECTORY,file_format.format(*args)) if (DATAFORM == 'ascii'): #-- ascii (.txt) delta.to_ascii(FILE, date=True, verbose=VERBOSE) elif (DATAFORM == 'netCDF4'): #-- netCDF4 delta.to_netCDF4(FILE, date=True, verbose=VERBOSE, units=unit_str, longname=unit_name, title='GRACE/GRACE-FO Spatial Error') elif (DATAFORM == 'HDF5'): #-- HDF5 delta.to_HDF5(FILE, date=True, verbose=VERBOSE, units=unit_str, longname=unit_name, title='GRACE/GRACE-FO Spatial Error') #-- set the permissions mode of the output files os.chmod(FILE, MODE) #-- add file to list output_files.append(FILE) #-- return the list of output files return output_files
def grace_spatial_error(base_dir, parameters, LOVE_NUMBERS=0, REFERENCE=None, VERBOSE=False, MODE=0o775): #-- Data processing center PROC = parameters['PROC'] #-- Data Release DREL = parameters['DREL'] #-- GRACE dataset DSET = parameters['DSET'] #-- Date Range and missing months start_mon = np.int(parameters['START']) end_mon = np.int(parameters['END']) missing = np.array(parameters['MISSING'].split(','), dtype=np.int) #-- minimum degree LMIN = np.int(parameters['LMIN']) #-- maximum degree and order LMAX = np.int(parameters['LMAX']) if (parameters['MMAX'].title() == 'None'): MMAX = np.copy(LMAX) else: MMAX = np.int(parameters['MMAX']) #-- SLR C2,0 and C3,0 SLR_C20 = parameters['SLR_C20'] SLR_C30 = parameters['SLR_C30'] #-- Degree 1 correction DEG1 = parameters['DEG1'] MODEL_DEG1 = parameters['MODEL_DEG1'] in ('Y', 'y') #-- ECMWF jump corrections ATM = parameters['ATM'] in ('Y', 'y') #-- Pole Tide correction from Wahr et al. (2015) POLE_TIDE = parameters['POLE_TIDE'] in ('Y', 'y') #-- smoothing radius RAD = np.int(parameters['RAD']) #-- destriped coefficients DESTRIPE = parameters['DESTRIPE'] in ('Y', 'y') #-- output spatial units UNITS = np.int(parameters['UNITS']) #-- output degree spacing #-- can enter dlon and dlat as [dlon,dlat] or a single value DDEG = np.squeeze(np.array(parameters['DDEG'].split(','), dtype='f')) #-- output degree interval (0:360, 90:-90) or (degree spacing/2) INTERVAL = np.int(parameters['INTERVAL']) #-- output data format (ascii, netCDF4, HDF5) DATAFORM = parameters['DATAFORM'] #-- output directory and base filename DIRECTORY = os.path.expanduser(parameters['DIRECTORY']) FILENAME = parameters['FILENAME'] #-- recursively create output directory if not currently existing if (not os.access(DIRECTORY, os.F_OK)): os.makedirs(DIRECTORY, mode=MODE, exist_ok=True) #-- list object of output files for file logs (full path) output_files = [] #-- file information suffix = dict(ascii='txt', netCDF4='nc', HDF5='H5') #-- read arrays of kl, hl, and ll Love Numbers hl, kl, ll = load_love_numbers(LMAX, LOVE_NUMBERS=LOVE_NUMBERS, REFERENCE=REFERENCE) #-- Calculating the Gaussian smoothing for radius RAD if (RAD != 0): wt = 2.0 * np.pi * gauss_weights(RAD, LMAX) gw_str = '_r{0:0.0f}km'.format(RAD) else: #-- else = 1 wt = np.ones((LMAX + 1)) gw_str = '' #-- flag for spherical harmonic order order_str = 'M{0:d}'.format(MMAX) if (MMAX != LMAX) else '' #-- atmospheric ECMWF "jump" flag (if ATM) atm_str = '_wATM' if ATM else '' #-- reading GRACE months for input range with grace_input_months.py #-- replacing SLR and Degree 1 if specified #-- correcting for Pole-Tide and Atmospheric Jumps if specified Ylms = grace_input_months(base_dir, PROC, DREL, DSET, LMAX, start_mon, end_mon, missing, SLR_C20, DEG1, MMAX=MMAX, SLR_C30=SLR_C30, MODEL_DEG1=MODEL_DEG1, ATM=ATM, POLE_TIDE=POLE_TIDE) #-- convert to harmonics object and remove mean if specified GRACE_Ylms = harmonics().from_dict(Ylms) grace_dir = Ylms['directory'] #-- mean parameters MEAN = parameters['MEAN'] in ('Y', 'y') #-- use a mean file for the static field to remove if (parameters['MEAN_FILE'].title() == 'None'): mean_Ylms = GRACE_Ylms.mean(apply=MEAN) else: #-- read data form for input mean file (ascii, netCDF4, HDF5, gfc) if (parameters['MEANFORM'] == 'ascii'): mean_Ylms = harmonics().from_ascii(parameters['MEAN_FILE'], date=False) elif (parameters['MEANFORM'] == 'netCDF4'): mean_Ylms = harmonics().from_netCDF4(parameters['MEAN_FILE'], date=False) elif (parameters['MEANFORM'] == 'HDF5'): mean_Ylms = harmonics().from_HDF5(parameters['MEAN_FILE'], date=False) elif (parameters['MEANFORM'] == 'gfc'): mean_Ylms = harmonics().from_gfc(parameters['MEAN_FILE']) #-- remove the input mean if MEAN: GRACE_Ylms.subtract(mean_Ylms) #-- date information of GRACE/GRACE-FO coefficients nfiles = len(GRACE_Ylms.time) #-- filter GRACE/GRACE-FO coefficients if DESTRIPE: #-- destriping GRACE/GRACE-FO coefficients ds_str = '_FL' GRACE_Ylms = GRACE_Ylms.destripe() else: #-- using standard GRACE/GRACE-FO harmonics ds_str = '' #-- calculating GRACE error (Wahr et al 2006) #-- output GRACE error file (for both LMAX==MMAX and LMAX != MMAX cases) args = (PROC, DREL, DSET, LMAX, order_str, ds_str, atm_str, GRACE_Ylms.month[0], GRACE_Ylms.month[-1], suffix[DATAFORM]) delta_format = '{0}_{1}_{2}_DELTA_CLM_L{3:d}{4}{5}{6}_{7:03d}-{8:03d}.{9}' DELTA_FILE = delta_format.format(*args) #-- full path of the GRACE directory #-- if file was previously calculated, will read file #-- else will calculate the GRACE error if (not os.access(os.path.join(grace_dir, DELTA_FILE), os.F_OK)): #-- add output delta file to list object output_files.append(os.path.join(grace_dir, DELTA_FILE)) #-- Delta coefficients of GRACE time series (Error components) delta_Ylms = harmonics(lmax=LMAX, mmax=MMAX) delta_Ylms.clm = np.zeros((LMAX + 1, MMAX + 1)) delta_Ylms.slm = np.zeros((LMAX + 1, MMAX + 1)) #-- Smoothing Half-Width (CNES is a 10-day solution) #-- 365/10/2 = 18.25 (next highest is 19) #-- All other solutions are monthly solutions (HFWTH for annual = 6) if ((PROC == 'CNES') and (DREL in ('RL01', 'RL02'))): HFWTH = 19 else: HFWTH = 6 #-- Equal to the noise of the smoothed time-series #-- for each spherical harmonic order for m in range(0, MMAX + 1): #-- MMAX+1 to include MMAX #-- for each spherical harmonic degree for l in range(m, LMAX + 1): #-- LMAX+1 to include LMAX #-- Delta coefficients of GRACE time series for cs, csharm in enumerate(['clm', 'slm']): #-- Constrained GRACE Error (Noise of smoothed time-series) #-- With Annual and Semi-Annual Terms val1 = getattr(GRACE_Ylms, csharm) smth = tssmooth(GRACE_Ylms.time, val1[l, m, :], HFWTH=HFWTH) #-- number of smoothed points nsmth = len(smth['data']) #-- GRACE delta Ylms #-- variance of data-(smoothed+annual+semi) val2 = getattr(delta_Ylms, csharm) val2[l, m] = np.sqrt(np.sum(smth['noise']**2) / nsmth) #-- save GRACE DELTA to file delta_Ylms.time = np.copy(nsmth) delta_Ylms.month = np.copy(nsmth) if (DATAFORM == 'ascii'): #-- ascii (.txt) delta_Ylms.to_ascii(os.path.join(grace_dir, DELTA_FILE)) elif (DATAFORM == 'netCDF4'): #-- netcdf (.nc) delta_Ylms.to_netCDF4(os.path.join(grace_dir, DELTA_FILE)) elif (DATAFORM == 'HDF5'): #-- HDF5 (.H5) delta_Ylms.to_HDF5(os.path.join(grace_dir, DELTA_FILE)) #-- set the permissions mode of the output harmonics file os.chmod(os.path.join(grace_dir, DELTA_FILE), MODE) #-- append delta harmonics file to output files list output_files.append(os.path.join(grace_dir, DELTA_FILE)) else: #-- read GRACE DELTA spherical harmonics datafile if (DATAFORM == 'ascii'): #-- ascii (.txt) delta_Ylms = harmonics().from_ascii( os.path.join(grace_dir, DELTA_FILE)) elif (DATAFORM == 'netCDF4'): #-- netcdf (.nc) delta_Ylms = harmonics().from_netCDF4( os.path.join(grace_dir, DELTA_FILE)) elif (DATAFORM == 'HDF5'): #-- HDF5 (.H5) delta_Ylms = harmonics().from_HDF5( os.path.join(grace_dir, DELTA_FILE)) #-- truncate grace delta clm and slm to d/o LMAX/MMAX delta_Ylms = delta_Ylms.truncate(lmax=LMAX, mmax=MMAX) nsmth = np.int(delta_Ylms.time) #-- Output spatial data object delta = spatial() #-- Output Degree Spacing dlon, dlat = (DDEG, DDEG) if (np.ndim(DDEG) == 0) else (DDEG[0], DDEG[1]) #-- Output Degree Interval if (INTERVAL == 1): #-- (-180:180,90:-90) nlon = np.int((360.0 / dlon) + 1.0) nlat = np.int((180.0 / dlat) + 1.0) delta.lon = -180 + dlon * np.arange(0, nlon) delta.lat = 90.0 - dlat * np.arange(0, nlat) elif (INTERVAL == 2): #-- (Degree spacing)/2 delta.lon = np.arange(-180 + dlon / 2.0, 180 + dlon / 2.0, dlon) delta.lat = np.arange(90.0 - dlat / 2.0, -90.0 - dlat / 2.0, -dlat) nlon = len(delta.lon) nlat = len(delta.lat) #-- Earth Parameters factors = units(lmax=LMAX).harmonic(hl, kl, ll) #-- output spatial units unit_list = ['cmwe', 'mmGH', 'mmCU', u'\u03BCGal', 'mbar'] unit_name = [ 'Equivalent Water Thickness', 'Geoid Height', 'Elastic Crustal Uplift', 'Gravitational Undulation', 'Equivalent Surface Pressure' ] #-- dfactor is the degree dependent coefficients #-- for specific spherical harmonic output units if (UNITS == 1): #-- 1: cmwe, centimeters water equivalent dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).cmwe elif (UNITS == 2): #-- 2: mmGH, millimeters geoid height dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).mmGH elif (UNITS == 3): #-- 3: mmCU, millimeters elastic crustal deformation dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).mmCU elif (UNITS == 4): #-- 4: micGal, microGal gravity perturbations dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).microGal elif (UNITS == 5): #-- 5: mbar, millibar equivalent surface pressure dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).mbar #-- Computing plms for converting to spatial domain phi = delta.lon[np.newaxis, :] * np.pi / 180.0 theta = (90.0 - delta.lat) * np.pi / 180.0 PLM, dPLM = plm_holmes(LMAX, np.cos(theta)) #-- square of legendre polynomials truncated to order MMAX mm = np.arange(0, MMAX + 1) PLM2 = PLM[:, mm, :]**2 #-- Calculating cos(m*phi)^2 and sin(m*phi)^2 m = delta_Ylms.m[:, np.newaxis] ccos = np.cos(np.dot(m, phi))**2 ssin = np.sin(np.dot(m, phi))**2 #-- truncate delta harmonics to spherical harmonic range Ylms = delta_Ylms.truncate(LMAX, lmin=LMIN, mmax=MMAX) #-- convolve delta harmonics with degree dependent factors #-- smooth harmonics and convert to output units Ylms = Ylms.convolve(dfactor * wt).power(2.0).scale(1.0 / nsmth) #-- Calculate fourier coefficients d_cos = np.zeros((MMAX + 1, nlat)) #-- [m,th] d_sin = np.zeros((MMAX + 1, nlat)) #-- [m,th] #-- Calculating delta spatial values for k in range(0, nlat): #-- summation over all spherical harmonic degrees d_cos[:, k] = np.sum(PLM2[:, :, k] * Ylms.clm, axis=0) d_sin[:, k] = np.sum(PLM2[:, :, k] * Ylms.slm, axis=0) #-- Multiplying by c/s(phi#m) to get spatial maps (lon,lat) delta.data = np.sqrt(np.dot(ccos.T, d_cos) + np.dot(ssin.T, d_sin)).T #-- output file format file_format = '{0}{1}_L{2:d}{3}{4}{5}_ERR_{6:03d}-{7:03d}.{8}' #-- output error file to ascii, netCDF4 or HDF5 args = (FILENAME, unit_list[UNITS - 1], LMAX, order_str, gw_str, ds_str, GRACE_Ylms.month[0], GRACE_Ylms.month[-1], suffix[DATAFORM]) FILE = os.path.join(DIRECTORY, file_format.format(*args)) if (DATAFORM == 'ascii'): #-- ascii (.txt) delta.to_ascii(FILE, date=False, verbose=VERBOSE) elif (DATAFORM == 'netCDF4'): #-- netCDF4 delta.to_netCDF4(FILE, date=False, verbose=VERBOSE, units=unit_list[UNITS - 1], longname=unit_name[UNITS - 1], title='GRACE/GRACE-FO Spatial Error') elif (DATAFORM == 'HDF5'): #-- HDF5 delta.to_HDF5(FILE, date=False, verbose=VERBOSE, units=unit_list[UNITS - 1], longname=unit_name[UNITS - 1], title='GRACE/GRACE-FO Spatial Error') #-- set the permissions mode of the output files os.chmod(FILE, MODE) #-- add file to list output_files.append(FILE) #-- return the list of output files return output_files