Example #1
0
def ocean_stokes(LANDMASK,
                 LMAX,
                 MMAX=None,
                 LOVE=None,
                 VARNAME='LSMASK',
                 SIMPLIFY=False):
    """
    Converts data from spherical harmonic coefficients to a spatial field

    Arguments
    ---------
    LANDMASK: netCDF4 land mask file
    LMAX: maximum spherical harmonic degree

    Keyword arguments
    -----------------
    MMAX: maximum spherical harmonic order of the output harmonics
    LOVE: input load Love numbers up to degree LMAX (hl,kl,ll)
    VARNAME: variable name for mask in netCDF4 file
    SIMPLIFY: simplify land mask by removing isolated points

    Returns
    -------
    clm: cosine spherical harmonic coefficients
    slm: sine spherical harmonic coefficients
    l: spherical harmonic degree to LMAX
    m: spherical harmonic order to MMAX
    """
    #-- maximum spherical harmonic order
    MMAX = np.copy(LMAX) if MMAX is None else MMAX
    #-- Read Land-Sea Mask of specified input file
    #-- 0=Ocean, 1=Land, 2=Lake, 3=Small Island, 4=Ice Shelf
    #-- Open the land-sea NetCDF file for reading
    landsea = spatial().from_netCDF4(LANDMASK, date=False, varname=VARNAME)
    #-- create land function
    nth, nphi = landsea.shape
    land_function = np.zeros((nth, nphi), dtype=np.float64)
    #-- combine land and island levels for land function
    indx, indy = np.nonzero((landsea.data >= 1) & (landsea.data <= 3))
    land_function[indx, indy] = 1.0
    #-- remove isolated points if specified
    if SIMPLIFY:
        land_function -= find_isolated_points(land_function)
    #-- ocean function reciprocal of land function
    ocean_function = 1.0 - land_function
    #-- convert to spherical harmonics (1 cm w.e.)
    ocean_Ylms = gen_stokes(ocean_function.T,
                            landsea.lon,
                            landsea.lat,
                            UNITS=1,
                            LMIN=0,
                            LMAX=LMAX,
                            MMAX=MMAX,
                            LOVE=LOVE)
    #-- return the spherical harmonic coefficients
    return ocean_Ylms
def combine_harmonics(INPUT_FILE,
                      OUTPUT_FILE,
                      LMAX=None,
                      MMAX=None,
                      LOVE_NUMBERS=0,
                      REFERENCE=None,
                      RAD=None,
                      DESTRIPE=False,
                      UNITS=None,
                      DDEG=None,
                      INTERVAL=None,
                      BOUNDS=None,
                      REDISTRIBUTE=False,
                      LSMASK=None,
                      MEAN_FILE=None,
                      DATAFORM=None,
                      VERBOSE=False,
                      MODE=0o775):

    #-- verify that output directory exists
    DIRECTORY = os.path.abspath(os.path.dirname(OUTPUT_FILE))
    if not os.access(DIRECTORY, os.F_OK):
        os.makedirs(DIRECTORY, MODE, exist_ok=True)

    #-- read input spherical harmonic coefficients from file in DATAFORM
    if (DATAFORM == 'ascii'):
        input_Ylms = harmonics().from_ascii(INPUT_FILE)
    elif (DATAFORM == 'netCDF4'):
        #-- read input netCDF4 file (.nc)
        input_Ylms = harmonics().from_netCDF4(INPUT_FILE)
    elif (DATAFORM == 'HDF5'):
        #-- read input HDF5 file (.H5)
        input_Ylms = harmonics().from_HDF5(INPUT_FILE)
    #-- reform harmonic dimensions to be l,m,t
    #-- truncate to degree and order LMAX, MMAX
    input_Ylms = input_Ylms.truncate(lmax=LMAX, mmax=MMAX).expand_dims()
    #-- remove mean file from input Ylms
    if MEAN_FILE and (DATAFORM == 'ascii'):
        mean_Ylms = harmonics().from_ascii(MEAN_FILE, date=False)
        input_Ylms.subtract(mean_Ylms)
    elif MEAN_FILE and (DATAFORM == 'netCDF4'):
        #-- read input netCDF4 file (.nc)
        mean_Ylms = harmonics().from_netCDF4(MEAN_FILE, date=False)
        input_Ylms.subtract(mean_Ylms)
    elif MEAN_FILE and (DATAFORM == 'HDF5'):
        #-- read input HDF5 file (.H5)
        mean_Ylms = harmonics().from_HDF5(MEAN_FILE, date=False)
        input_Ylms.subtract(mean_Ylms)

    #-- read arrays of kl, hl, and ll Love Numbers
    hl, kl, ll = load_love_numbers(LMAX,
                                   LOVE_NUMBERS=LOVE_NUMBERS,
                                   REFERENCE=REFERENCE)

    #-- distribute total mass uniformly over the ocean
    if REDISTRIBUTE:
        #-- read Land-Sea Mask and convert to spherical harmonics
        ocean_Ylms = ocean_stokes(LSMASK, LMAX, MMAX=MMAX, LOVE=(hl, kl, ll))
        #-- calculate ratio between total mass and a uniformly distributed
        #-- layer of water over the ocean
        ratio = input_Ylms.clm[0, 0, :] / ocean_Ylms['clm'][0, 0]
        #-- for each spherical harmonic
        for m in range(0, MMAX + 1):  #-- MMAX+1 to include MMAX
            for l in range(m, LMAX + 1):  #-- LMAX+1 to include LMAX
                #-- remove the ratio*ocean Ylms from Ylms
                #-- note: x -= y is equivalent to x = x - y
                input_Ylms.clm[l, m, :] -= ratio * ocean_Ylms['clm'][l, m]
                input_Ylms.slm[l, m, :] -= ratio * ocean_Ylms['slm'][l, m]

    #-- if using a decorrelation filter (Isabella's destriping Routine)
    if DESTRIPE:
        input_Ylms = input_Ylms.destripe()

    #-- Gaussian smoothing
    if (RAD != 0):
        wt = 2.0 * np.pi * gauss_weights(RAD, LMAX)
    else:
        wt = np.ones((LMAX + 1))

    #-- Output spatial data
    grid = spatial()
    grid.time = np.copy(input_Ylms.time)
    grid.month = np.copy(input_Ylms.month)

    #-- Output Degree Spacing
    if (len(DDEG) == 1):
        #-- dlon == dlat
        dlon = DDEG
        dlat = DDEG
    else:
        #-- dlon != dlat
        dlon, dlat = DDEG

    #-- Output Degree Interval
    if (INTERVAL == 1):
        #-- (0:360,90:-90)
        nlon = np.int((360.0 / dlon) + 1.0)
        nlat = np.int((180.0 / dlat) + 1.0)
        grid.lon = dlon * np.arange(0, nlon)
        grid.lat = 90.0 - dlat * np.arange(0, nlat)
    elif (INTERVAL == 2):
        #-- (Degree spacing)/2
        grid.lon = np.arange(dlon / 2.0, 360 + dlon / 2.0, dlon)
        grid.lat = np.arange(90.0 - dlat / 2.0, -90.0 - dlat / 2.0, -dlat)
        nlon = len(grid.lon)
        nlat = len(grid.lat)
    elif (INTERVAL == 3):
        #-- non-global grid set with BOUNDS parameter
        minlon, maxlon, minlat, maxlat = BOUNDS.copy()
        grid.lon = np.arange(minlon + dlon / 2.0, maxlon + dlon / 2.0, dlon)
        grid.lat = np.arange(maxlat - dlat / 2.0, minlat - dlat / 2.0, -dlat)
        nlon = len(grid.lon)
        nlat = len(grid.lat)

    #-- Setting units factor for output
    #-- dfactor computes the degree dependent coefficients
    if (UNITS == 1):
        #-- 1: cmwe, centimeters water equivalent
        dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).cmwe
    elif (UNITS == 2):
        #-- 2: mmGH, mm geoid height
        dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).mmGH
    elif (UNITS == 3):
        #-- 3: mmCU, mm elastic crustal deformation
        dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).mmCU
    elif (UNITS == 4):
        #-- 4: micGal, microGal gravity perturbations
        dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).microGal
    elif (UNITS == 5):
        #-- 5: Pa, equivalent surface pressure in Pascals
        dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).Pa
    else:
        raise ValueError(('UNITS is invalid:\n1: cmwe\n2: mmGH\n3: mmCU '
                          '(elastic)\n4:microGal\n5: Pa'))

    #-- Computing plms for converting to spatial domain
    theta = (90.0 - grid.lat) * np.pi / 180.0
    PLM, dPLM = plm_holmes(LMAX, np.cos(theta))

    #-- output spatial grid
    nt = len(input_Ylms.time)
    grid.data = np.zeros((nlat, nlon, nt))
    #-- converting harmonics to truncated, smoothed coefficients in output units
    for t in range(nt):
        #-- spherical harmonics for time t
        Ylms = input_Ylms.index(t)
        Ylms.convolve(dfactor * wt)
        #-- convert spherical harmonics to output spatial grid
        grid.data[:, :, t] = harmonic_summation(Ylms.clm,
                                                Ylms.slm,
                                                grid.lon,
                                                grid.lat,
                                                LMAX=LMAX,
                                                PLM=PLM).T

    #-- if verbose output: print input and output file names
    if VERBOSE:
        print('{0}:'.format(os.path.basename(sys.argv[0])))
        print('{0} -->\n\t{1}\n'.format(INPUT_FILE, OUTPUT_FILE))
    #-- outputting data to file
    output_data(grid.squeeze(),
                FILENAME=OUTPUT_FILE,
                DATAFORM=DATAFORM,
                UNITS=UNITS)
    #-- change output permissions level to MODE
    os.chmod(OUTPUT_FILE, MODE)
def regress_grace_maps(parameters,
                       ORDER=None,
                       CYCLES=None,
                       VERBOSE=False,
                       MODE=0o775):
    #-- convert parameters into variables
    #-- Data processing center
    PROC = parameters['PROC']
    #-- Data Release
    DREL = parameters['DREL']
    #-- GRACE/GRACE-FO dataset
    DSET = parameters['DSET']
    #-- GRACE/GRACE-FO months
    START_MON = np.int(parameters['START'])
    END_MON = np.int(parameters['END'])
    MISSING = np.array(parameters['MISSING'].split(','), dtype=np.int)
    months = sorted(set(np.arange(START_MON, END_MON + 1)) - set(MISSING))
    nmon = len(months)
    #-- maximum degree and order
    LMAX = np.int(parameters['LMAX'])
    if (parameters['MMAX'].title() == 'None'):
        MMAX = np.copy(LMAX)
    else:
        MMAX = np.int(parameters['MMAX'])
    #-- Glacial Isostatic Adjustment file to read
    GIA = parameters['GIA'] if (parameters['GIA'].title() != 'None') else None
    GIA_FILE = os.path.expanduser(parameters['GIA_FILE'])
    #-- remove a set of spherical harmonics from the GRACE data
    REDISTRIBUTE_REMOVED = parameters['REDISTRIBUTE_REMOVED'] in ('Y', 'y')
    #-- smoothing radius
    RAD = np.int(parameters['RAD'])
    #-- destriped coefficients
    DESTRIPE = parameters['DESTRIPE'] in ('Y', 'y')
    #-- output spatial units
    UNITS = np.int(parameters['UNITS'])
    #-- output degree spacing
    #-- can enter dlon and dlat as [dlon,dlat] or a single value
    DDEG = np.squeeze(np.array(parameters['DDEG'].split(','), dtype='f'))
    #-- output degree interval (0:360, 90:-90) or (degree spacing/2)
    INTERVAL = np.int(parameters['INTERVAL'])
    #-- input/output data format (ascii, netCDF4, HDF5)
    DATAFORM = parameters['DATAFORM']
    #-- output directory and base filename
    DIRECTORY = os.path.expanduser(parameters['DIRECTORY'])
    FILENAME = parameters['FILENAME']
    #-- output filename suffix
    suffix = dict(ascii='txt', netCDF4='nc', HDF5='H5')[DATAFORM]

    #-- flag for spherical harmonic order
    order_str = 'M{0:d}'.format(MMAX) if (MMAX != LMAX) else ''
    #-- Calculating the Gaussian smoothing for radius RAD
    gw_str = '_r{0:0.0f}km'.format(RAD) if (RAD != 0) else ''
    #-- destriped GRACE/GRACE-FO coefficients
    ds_str = '_FL' if DESTRIPE else ''
    #-- distributing removed mass uniformly over ocean
    ocean_str = '_OCN' if REDISTRIBUTE_REMOVED else ''
    #-- input and output spatial units
    unit_list = ['cmwe', 'mmGH', 'mmCU', u'\u03BCGal', 'mbar']
    unit_name = [
        'Equivalent Water Thickness', 'Geoid Height', 'Elastic Crustal Uplift',
        'Gravitational Undulation', 'Equivalent Surface Pressure'
    ]

    #-- input file format
    input_format = '{0}{1}_L{2:d}{3}{4}{5}_{6:03d}.{7}'
    #-- output file format
    output_format = '{0}{1}_L{2:d}{3}{4}{5}_{6}{7}_{8:03d}-{9:03d}.{10}'

    #-- Output Degree Spacing
    dlon, dlat = (DDEG, DDEG) if (np.ndim(DDEG) == 0) else (DDEG[0], DDEG[1])
    #-- Output Degree Interval
    if (INTERVAL == 1):
        #-- (-180:180,90:-90)
        nlon = np.int((360.0 / dlon) + 1.0)
        nlat = np.int((180.0 / dlat) + 1.0)
    elif (INTERVAL == 2):
        #-- (Degree spacing)/2
        nlon = np.int(360.0 / dlon)
        nlat = np.int(180.0 / dlat)

    #-- Setting output parameters for each fit type
    coef_str = ['x{0:d}'.format(o) for o in range(ORDER + 1)]
    unit_suffix = [
        ' yr^{0:d}'.format(-o) if o else '' for o in range(ORDER + 1)
    ]
    if (ORDER == 0):  #-- Mean
        unit_longname = ['Mean']
    elif (ORDER == 1):  #-- Trend
        unit_longname = ['Constant', 'Trend']
    elif (ORDER == 2):  #-- Quadratic
        unit_longname = ['Constant', 'Linear', 'Quadratic']
    #-- filename strings for cyclical terms
    cyclic_str = {}
    cyclic_str['SEMI'] = ['SS', 'SC']
    cyclic_str['ANN'] = ['AS', 'AC']
    cyclic_str['S2'] = ['S2S', 'S2C']
    #-- unit longnames for cyclical terms
    cyclic_longname = {}
    cyclic_longname['SEMI'] = ['Semi-Annual Sine', 'Semi-Annual Cosine']
    cyclic_longname['ANN'] = ['Annual Sine', 'Annual Cosine']
    cyclic_longname['S2'] = ['S2 Tidal Alias Sine', 'S2 Tidal Alias Cosine']
    amp_str = []
    for i, c in enumerate(CYCLES):
        if (c == 0.5):
            flag = 'SEMI'
        elif (c == 1.0):
            flag = 'ANN'
        elif (c == (161.0 / 365.25)):
            flag = 'S2'
        coef_str.extend(cyclic_str[flag])
        unit_longname.extend(cyclic_longname[flag])
        unit_suffix.extend(['', ''])
        amp_str.append(flag)

    #-- input data spatial object
    spatial_list = []
    for t, grace_month in enumerate(months):
        #-- input GRACE/GRACE-FO spatial file
        fi = input_format.format(FILENAME, unit_list[UNITS - 1], LMAX,
                                 order_str, gw_str, ds_str, grace_month,
                                 suffix)
        #-- read GRACE/GRACE-FO spatial file
        if (DATAFORM == 'ascii'):
            dinput = spatial(spacing=[dlon, dlat], nlon=nlon,
                             nlat=nlat).from_ascii(os.path.join(DIRECTORY, fi))
        elif (DATAFORM == 'netCDF4'):
            #-- netcdf (.nc)
            dinput = spatial().from_netCDF4(os.path.join(DIRECTORY, fi))
        elif (DATAFORM == 'HDF5'):
            #-- HDF5 (.H5)
            dinput = spatial().from_HDF5(os.path.join(DIRECTORY, fi))
        #-- append to spatial list
        dinput.month[:] = grace_month
        nlat, nlon = dinput.shape
        spatial_list.append(dinput)

    #-- concatenate list to single spatial object
    grid = spatial().from_list(spatial_list)
    spatial_list = None

    #-- Fitting seasonal components
    ncomp = len(coef_str)
    ncycles = 2 * len(CYCLES)

    #-- Allocating memory for output variables
    out = dinput.zeros_like()
    out.data = np.zeros((nlat, nlon, ncomp))
    out.error = np.zeros((nlat, nlon, ncomp))
    out.mask = np.ones((nlat, nlon, ncomp), dtype=np.bool)
    #-- Fit Significance
    FS = {}
    #-- SSE: Sum of Squares Error
    #-- AIC: Akaike information criterion
    #-- BIC: Bayesian information criterion
    #-- R2Adj: Adjusted Coefficient of Determination
    for key in ['SSE', 'AIC', 'BIC', 'R2Adj']:
        FS[key] = dinput.zeros_like()

    #-- valid values for ocean function
    #-- calculate the regression coefficients and fit significance
    for i in range(nlat):
        for j in range(nlon):
            #-- Calculating the regression coefficients
            tsbeta = tsregress(grid.time,
                               grid.data[i, j, :],
                               ORDER=ORDER,
                               CYCLES=CYCLES,
                               CONF=0.95)
            #-- save regression components
            for k in range(0, ncomp):
                out.data[i, j, k] = tsbeta['beta'][k]
                out.error[i, j, k] = tsbeta['error'][k]
                out.mask[i, j, k] = False
            #-- Fit significance terms
            #-- Degrees of Freedom
            nu = tsbeta['DOF']
            #-- Converting Mean Square Error to Sum of Squares Error
            FS['SSE'].data[i, j] = tsbeta['MSE'] * nu
            FS['AIC'].data[i, j] = tsbeta['AIC']
            FS['BIC'].data[i, j] = tsbeta['BIC']
            FS['R2Adj'].data[i, j] = tsbeta['R2Adj']

    #-- list of output files
    output_files = []
    #-- Output spatial files
    for i in range(0, ncomp):
        #-- output spatial file name
        f1 = output_format.format(FILENAME, unit_list[UNITS - 1], LMAX,
                                  order_str, gw_str, ds_str, coef_str[i], '',
                                  START_MON, END_MON, suffix)
        f2 = output_format.format(FILENAME, unit_list[UNITS - 1], LMAX,
                                  order_str, gw_str, ds_str, coef_str[i],
                                  '_ERROR', START_MON, END_MON, suffix)
        #-- full attributes
        UNITS_TITLE = '{0}{1}'.format(unit_list[UNITS - 1], unit_suffix[i])
        LONGNAME = unit_name[UNITS - 1]
        FILE_TITLE = 'GRACE/GRACE-FO_Spatial_Data_{0}'.format(unit_longname[i])
        #-- output regression fit to file
        output = out.index(i, date=False)
        output_data(output,
                    FILENAME=os.path.join(DIRECTORY, f1),
                    DATAFORM=DATAFORM,
                    UNITS=UNITS_TITLE,
                    LONGNAME=LONGNAME,
                    TITLE=FILE_TITLE,
                    VERBOSE=VERBOSE,
                    MODE=MODE)
        output_data(output,
                    FILENAME=os.path.join(DIRECTORY, f2),
                    DATAFORM=DATAFORM,
                    UNITS=UNITS_TITLE,
                    LONGNAME=LONGNAME,
                    TITLE=FILE_TITLE,
                    KEY='error',
                    VERBOSE=VERBOSE,
                    MODE=MODE)
        #-- add output files to list object
        output_files.append(os.path.join(DIRECTORY, f1))
        output_files.append(os.path.join(DIRECTORY, f2))

    #-- if fitting coefficients with cyclical components
    if (ncycles > 0):
        #-- output spatial titles for amplitudes
        amp_title = {
            'ANN': 'Annual Amplitude',
            'SEMI': 'Semi-Annual Amplitude',
            'S2': 'S2 Tidal Alias Amplitude'
        }
        ph_title = {
            'ANN': 'Annual Phase',
            'SEMI': 'Semi-Annual Phase',
            'S2': 'S2 Tidal Alias Phase'
        }

        #-- output amplitude and phase of cyclical components
        for i, flag in enumerate(amp_str):
            #-- Indice pointing to the cyclical components
            j = 1 + ORDER + 2 * i
            #-- Allocating memory for output amplitude and phase
            amp = dinput.zeros_like()
            ph = dinput.zeros_like()
            #-- calculating amplitude and phase of spatial field
            amp.data, ph.data = tsamplitude(out.data[:, :, j], out.data[:, :,
                                                                        j + 1])
            #-- convert phase from -180:180 to 0:360
            ii, jj = np.nonzero(ph.data < 0)
            ph.data[ii, jj] += 360.0
            #-- Amplitude Error
            comp1 = out.error[:, :, j] * out.data[:, :, j] / amp.data
            comp2 = out.error[:, :, j + 1] * out.data[:, :, j + 1] / amp.data
            amp.error = np.sqrt(comp1**2 + comp2**2)
            #-- Phase Error (degrees)
            comp1 = out.error[:, :, j] * out.data[:, :, j + 1] / (amp.data**2)
            comp2 = out.error[:, :, j + 1] * out.data[:, :, j] / (amp.data**2)
            ph.error = (180.0 / np.pi) * np.sqrt(comp1**2 + comp2**2)

            #-- output file names for amplitude, phase and errors
            f3 = output_format.format(FILENAME, unit_list[UNITS - 1], LMAX,
                                      order_str, gw_str, ds_str, flag, '',
                                      START_MON, END_MON, suffix)
            f4 = output_format.format(FILENAME, unit_list[UNITS - 1], LMAX,
                                      order_str, gw_str, ds_str, flag,
                                      '_PHASE', START_MON, END_MON, suffix)
            #-- output spatial error file name
            f5 = output_format.format(FILENAME, unit_list[UNITS - 1], LMAX,
                                      order_str, gw_str, ds_str, flag,
                                      '_ERROR', START_MON, END_MON, suffix)
            f6 = output_format.format(FILENAME, unit_list[UNITS - 1], LMAX,
                                      order_str, gw_str, ds_str, flag,
                                      '_PHASE_ERROR', START_MON, END_MON,
                                      suffix)
            #-- full attributes
            AMP_UNITS = unit_list[UNITS - 1]
            PH_UNITS = 'degrees'
            LONGNAME = unit_name[UNITS - 1]
            AMP_TITLE = 'GRACE/GRACE-FO_Spatial_Data_{0}'.format(
                amp_title[flag])
            PH_TITLE = 'GRACE/GRACE-FO_Spatial_Data_{0}'.format(ph_title[flag])
            #-- Output seasonal amplitude and phase to files
            output_data(amp,
                        FILENAME=os.path.join(DIRECTORY, f3),
                        DATAFORM=DATAFORM,
                        UNITS=AMP_UNITS,
                        LONGNAME=LONGNAME,
                        TITLE=AMP_TITLE,
                        VERBOSE=VERBOSE,
                        MODE=MODE)
            output_data(ph,
                        FILENAME=os.path.join(DIRECTORY, f4),
                        DATAFORM=DATAFORM,
                        UNITS=PH_UNITS,
                        LONGNAME='Phase',
                        TITLE=PH_TITLE,
                        VERBOSE=VERBOSE,
                        MODE=MODE)
            #-- Output seasonal amplitude and phase error to files
            output_data(amp,
                        FILENAME=os.path.join(DIRECTORY, f5),
                        DATAFORM=DATAFORM,
                        UNITS=AMP_UNITS,
                        LONGNAME=LONGNAME,
                        TITLE=AMP_TITLE,
                        KEY='error',
                        VERBOSE=VERBOSE,
                        MODE=MODE)
            output_data(ph,
                        FILENAME=os.path.join(DIRECTORY, f6),
                        DATAFORM=DATAFORM,
                        UNITS=PH_UNITS,
                        LONGNAME='Phase',
                        TITLE=PH_TITLE,
                        KEY='error',
                        VERBOSE=VERBOSE,
                        MODE=MODE)
            #-- add output files to list object
            output_files.append(os.path.join(DIRECTORY, f3))
            output_files.append(os.path.join(DIRECTORY, f4))
            output_files.append(os.path.join(DIRECTORY, f5))
            output_files.append(os.path.join(DIRECTORY, f6))

    #-- Output fit significance
    signif_longname = {
        'SSE': 'Sum of Squares Error',
        'AIC': 'Akaike information criterion',
        'BIC': 'Bayesian information criterion',
        'R2Adj': 'Adjusted Coefficient of Determination'
    }
    #-- for each fit significance term
    for key, fs in FS.items():
        #-- output file names for fit significance
        signif_str = '{0}_'.format(key)
        f7 = output_format.format(FILENAME, unit_list[UNITS - 1], LMAX,
                                  order_str, gw_str, ds_str, signif_str,
                                  coef_str[ORDER], START_MON, END_MON, suffix)
        #-- full attributes
        LONGNAME = signif_longname[key]
        #-- output fit significance to file
        output_data(fs,
                    FILENAME=os.path.join(DIRECTORY, f7),
                    DATAFORM=DATAFORM,
                    UNITS=key,
                    LONGNAME=LONGNAME,
                    TITLE=nu,
                    VERBOSE=VERBOSE,
                    MODE=MODE)
        #-- add output files to list object
        output_files.append(os.path.join(DIRECTORY, f7))

    #-- return the list of output files
    return output_files
Example #4
0
def convert_harmonics(INPUT_FILE,
                      OUTPUT_FILE,
                      LMAX=None,
                      MMAX=None,
                      UNITS=None,
                      LOVE_NUMBERS=0,
                      REFERENCE=None,
                      DDEG=None,
                      INTERVAL=None,
                      MISSING=False,
                      FILL_VALUE=None,
                      HEADER=None,
                      DATAFORM=None,
                      VERBOSE=False,
                      MODE=0o775):

    #-- verify that output directory exists
    DIRECTORY = os.path.abspath(os.path.dirname(OUTPUT_FILE))
    if not os.access(DIRECTORY, os.F_OK):
        os.makedirs(DIRECTORY, MODE, exist_ok=True)

    #-- Grid spacing
    dlon, dlat = (DDEG, DDEG) if (np.ndim(DDEG) == 0) else (DDEG[0], DDEG[1])
    #-- Grid dimensions
    if (INTERVAL == 1):  #-- (0:360, 90:-90)
        nlon = np.int((360.0 / dlon) + 1.0)
        nlat = np.int((180.0 / dlat) + 1.0)
    elif (INTERVAL == 2):  #-- degree spacing/2
        nlon = np.int((360.0 / dlon))
        nlat = np.int((180.0 / dlat))

    #-- read spatial file in data format
    #-- expand dimensions
    if (DATAFORM == 'ascii'):
        #-- ascii (.txt)
        input_spatial = spatial(spacing=[dlon, dlat], nlat=nlat,
                                nlon=nlon).from_ascii(
                                    INPUT_FILE, header=HEADER).expand_dims()
    elif (DATAFORM == 'netCDF4'):
        #-- netcdf (.nc)
        input_spatial = spatial().from_netCDF4(INPUT_FILE).expand_dims()
    elif (DATAFORM == 'HDF5'):
        #-- HDF5 (.H5)
        input_spatial = spatial().from_HDF5(INPUT_FILE).expand_dims()
    #-- convert missing values to zero
    input_spatial.replace_invalid(0.0)
    #-- input data shape
    nlat, nlon, nt = input_spatial.shape

    #-- read arrays of kl, hl, and ll Love Numbers
    LOVE = load_love_numbers(LMAX,
                             LOVE_NUMBERS=LOVE_NUMBERS,
                             REFERENCE=REFERENCE)

    #-- calculate associated Legendre polynomials
    th = (90.0 - input_spatial.lat) * np.pi / 180.0
    PLM, dPLM = plm_holmes(LMAX, np.cos(th))
    #-- date count array
    counter = np.arange(nt)

    #-- allocate for output spherical harmonics
    Ylms = harmonics(lmax=LMAX, mmax=MMAX)
    Ylms.time = input_spatial.time.copy()
    Ylms.month = np.array(12.0 * (Ylms.time - 2002.0), dtype='i') + 1
    Ylms.clm = np.zeros((LMAX + 1, LMAX + 1, nt))
    Ylms.slm = np.zeros((LMAX + 1, LMAX + 1, nt))
    for t in range(nt):
        #-- convert spatial field to spherical harmonics
        output_Ylms = gen_stokes(input_spatial.data[:, :, t].T,
                                 input_spatial.lon,
                                 input_spatial.lat,
                                 UNITS=UNITS,
                                 LMIN=0,
                                 LMAX=LMAX,
                                 MMAX=MMAX,
                                 PLM=PLM,
                                 LOVE=LOVE)
        Ylms.clm[:, :, t] = output_Ylms['clm'][:, :].copy()
        Ylms.slm[:, :, t] = output_Ylms['slm'][:, :].copy()

    #-- if verbose output: print input and output file names
    if VERBOSE:
        print('{0}:'.format(os.path.basename(sys.argv[0])))
        print('{0} -->\n\t{1}'.format(INPUT_FILE, OUTPUT_FILE))
    #-- outputting data to file
    if (DATAFORM == 'ascii'):
        #-- ascii (.txt)
        Ylms.to_ascii(OUTPUT_FILE)
    elif (DATAFORM == 'netCDF4'):
        #-- netCDF4 (.nc)
        Ylms.to_netCDF4(OUTPUT_FILE)
    elif (DATAFORM == 'HDF5'):
        #-- HDF5 (.H5)
        Ylms.to_HDF5(OUTPUT_FILE)
    #-- change output permissions level to MODE
    os.chmod(OUTPUT_FILE, MODE)
def convert_harmonics(INPUT_FILE,
                      OUTPUT_FILE,
                      LMAX=None,
                      MMAX=None,
                      UNITS=None,
                      LOVE_NUMBERS=0,
                      REFERENCE=None,
                      DDEG=None,
                      INTERVAL=None,
                      FILL_VALUE=None,
                      HEADER=None,
                      DATAFORM=None,
                      MODE=0o775):

    #-- verify that output directory exists
    DIRECTORY = os.path.abspath(os.path.dirname(OUTPUT_FILE))
    if not os.access(DIRECTORY, os.F_OK):
        os.makedirs(DIRECTORY, MODE, exist_ok=True)

    #-- Grid spacing
    dlon, dlat = (DDEG, DDEG) if (np.ndim(DDEG) == 0) else (DDEG[0], DDEG[1])
    #-- Grid dimensions
    if (INTERVAL == 1):  #-- (0:360, 90:-90)
        nlon = np.int64((360.0 / dlon) + 1.0)
        nlat = np.int64((180.0 / dlat) + 1.0)
    elif (INTERVAL == 2):  #-- degree spacing/2
        nlon = np.int64((360.0 / dlon))
        nlat = np.int64((180.0 / dlat))

    #-- read spatial file in data format
    #-- expand dimensions
    if (DATAFORM == 'ascii'):
        #-- ascii (.txt)
        input_spatial = spatial(spacing=[dlon, dlat],
                                nlat=nlat,
                                nlon=nlon,
                                fill_value=FILL_VALUE).from_ascii(
                                    INPUT_FILE, header=HEADER).expand_dims()
    elif (DATAFORM == 'netCDF4'):
        #-- netcdf (.nc)
        input_spatial = spatial().from_netCDF4(INPUT_FILE).expand_dims()
    elif (DATAFORM == 'HDF5'):
        #-- HDF5 (.H5)
        input_spatial = spatial().from_HDF5(INPUT_FILE).expand_dims()
    #-- convert missing values to zero
    input_spatial.replace_invalid(0.0)
    #-- input data shape
    nlat, nlon, nt = input_spatial.shape

    #-- read arrays of kl, hl, and ll Love Numbers
    LOVE = load_love_numbers(LMAX,
                             LOVE_NUMBERS=LOVE_NUMBERS,
                             REFERENCE=REFERENCE)

    #-- upper bound of spherical harmonic orders (default = LMAX)
    if MMAX is None:
        MMAX = np.copy(LMAX)

    #-- calculate associated Legendre polynomials
    th = (90.0 - input_spatial.lat) * np.pi / 180.0
    PLM, dPLM = plm_holmes(LMAX, np.cos(th))

    #-- create list of harmonics objects
    Ylms_list = []
    for i, t in enumerate(input_spatial.time):
        #-- convert spatial field to spherical harmonics
        output_Ylms = gen_stokes(input_spatial.data[:, :, i].T,
                                 input_spatial.lon,
                                 input_spatial.lat,
                                 UNITS=UNITS,
                                 LMIN=0,
                                 LMAX=LMAX,
                                 MMAX=MMAX,
                                 PLM=PLM,
                                 LOVE=LOVE)
        output_Ylms.time = np.copy(t)
        output_Ylms.month = calendar_to_grace(t)
        #-- append to list
        Ylms_list.append(output_Ylms)
    #-- convert Ylms list for output spherical harmonics
    Ylms = harmonics().from_list(Ylms_list)
    Ylms_list = None

    #-- outputting data to file
    if (DATAFORM == 'ascii'):
        #-- ascii (.txt)
        Ylms.to_ascii(OUTPUT_FILE)
    elif (DATAFORM == 'netCDF4'):
        #-- netCDF4 (.nc)
        Ylms.to_netCDF4(OUTPUT_FILE)
    elif (DATAFORM == 'HDF5'):
        #-- HDF5 (.H5)
        Ylms.to_HDF5(OUTPUT_FILE)
    #-- change output permissions level to MODE
    os.chmod(OUTPUT_FILE, MODE)
def grace_spatial_maps(base_dir,
                       PROC,
                       DREL,
                       DSET,
                       LMAX,
                       RAD,
                       START=None,
                       END=None,
                       MISSING=None,
                       LMIN=None,
                       MMAX=None,
                       LOVE_NUMBERS=0,
                       REFERENCE=None,
                       DESTRIPE=False,
                       UNITS=None,
                       DDEG=None,
                       INTERVAL=None,
                       BOUNDS=None,
                       GIA=None,
                       GIA_FILE=None,
                       ATM=False,
                       POLE_TIDE=False,
                       DEG1=None,
                       DEG1_FILE=None,
                       MODEL_DEG1=False,
                       SLR_C20=None,
                       SLR_21=None,
                       SLR_22=None,
                       SLR_C30=None,
                       SLR_C50=None,
                       DATAFORM=None,
                       MEAN_FILE=None,
                       MEANFORM=None,
                       REMOVE_FILES=None,
                       REMOVE_FORMAT=None,
                       REDISTRIBUTE_REMOVED=False,
                       LANDMASK=None,
                       OUTPUT_DIRECTORY=None,
                       FILE_PREFIX=None,
                       VERBOSE=False,
                       MODE=0o775):

    #-- recursively create output directory if not currently existing
    if not os.access(OUTPUT_DIRECTORY, os.F_OK):
        os.makedirs(OUTPUT_DIRECTORY, mode=MODE, exist_ok=True)

    #-- list object of output files for file logs (full path)
    output_files = []

    #-- file information
    suffix = dict(ascii='txt', netCDF4='nc', HDF5='H5')

    #-- read arrays of kl, hl, and ll Love Numbers
    hl, kl, ll = load_love_numbers(LMAX,
                                   LOVE_NUMBERS=LOVE_NUMBERS,
                                   REFERENCE=REFERENCE)

    #-- Calculating the Gaussian smoothing for radius RAD
    if (RAD != 0):
        wt = 2.0 * np.pi * gauss_weights(RAD, LMAX)
        gw_str = '_r{0:0.0f}km'.format(RAD)
    else:
        #-- else = 1
        wt = np.ones((LMAX + 1))
        gw_str = ''

    #-- flag for spherical harmonic order
    MMAX = np.copy(LMAX) if not MMAX else MMAX
    order_str = 'M{0:d}'.format(MMAX) if (MMAX != LMAX) else ''

    #-- reading GRACE months for input date range
    #-- replacing low-degree harmonics with SLR values if specified
    #-- include degree 1 (geocenter) harmonics if specified
    #-- correcting for Pole-Tide and Atmospheric Jumps if specified
    Ylms = grace_input_months(base_dir,
                              PROC,
                              DREL,
                              DSET,
                              LMAX,
                              START,
                              END,
                              MISSING,
                              SLR_C20,
                              DEG1,
                              MMAX=MMAX,
                              SLR_21=SLR_21,
                              SLR_22=SLR_22,
                              SLR_C30=SLR_C30,
                              SLR_C50=SLR_C50,
                              DEG1_FILE=DEG1_FILE,
                              MODEL_DEG1=MODEL_DEG1,
                              ATM=ATM,
                              POLE_TIDE=POLE_TIDE)
    #-- convert to harmonics object and remove mean if specified
    GRACE_Ylms = harmonics().from_dict(Ylms)
    GRACE_Ylms.directory = Ylms['directory']
    #-- use a mean file for the static field to remove
    if MEAN_FILE:
        #-- read data form for input mean file (ascii, netCDF4, HDF5, gfc)
        mean_Ylms = harmonics().from_file(MEAN_FILE,
                                          format=MEANFORM,
                                          date=False)
        #-- remove the input mean
        GRACE_Ylms.subtract(mean_Ylms)
    else:
        GRACE_Ylms.mean(apply=True)
    #-- date information of GRACE/GRACE-FO coefficients
    nfiles = len(GRACE_Ylms.time)

    #-- filter GRACE/GRACE-FO coefficients
    if DESTRIPE:
        #-- destriping GRACE/GRACE-FO coefficients
        ds_str = '_FL'
        GRACE_Ylms = GRACE_Ylms.destripe()
    else:
        #-- using standard GRACE/GRACE-FO harmonics
        ds_str = ''

    #-- input GIA spherical harmonic datafiles
    GIA_Ylms_rate = read_GIA_model(GIA_FILE, GIA=GIA, LMAX=LMAX, MMAX=MMAX)
    gia_str = '_{0}'.format(GIA_Ylms_rate['title']) if GIA else ''
    #-- calculate the monthly mass change from GIA
    GIA_Ylms = GRACE_Ylms.zeros_like()
    GIA_Ylms.time[:] = np.copy(GRACE_Ylms.time)
    GIA_Ylms.month[:] = np.copy(GRACE_Ylms.month)
    #-- monthly GIA calculated by gia_rate*time elapsed
    #-- finding change in GIA each month
    for t in range(nfiles):
        GIA_Ylms.clm[:, :,
                     t] = GIA_Ylms_rate['clm'] * (GIA_Ylms.time[t] - 2003.3)
        GIA_Ylms.slm[:, :,
                     t] = GIA_Ylms_rate['slm'] * (GIA_Ylms.time[t] - 2003.3)

    #-- default file prefix
    if not FILE_PREFIX:
        fargs = (PROC, DREL, DSET, Ylms['title'], gia_str)
        FILE_PREFIX = '{0}_{1}_{2}{3}{4}_'.format(*fargs)

    #-- Read Ocean function and convert to Ylms for redistribution
    if REDISTRIBUTE_REMOVED:
        #-- read Land-Sea Mask and convert to spherical harmonics
        ocean_Ylms = ocean_stokes(LANDMASK, LMAX, MMAX=MMAX, LOVE=(hl, kl, ll))
        ocean_str = '_OCN'
    else:
        ocean_str = ''

    #-- input spherical harmonic datafiles to be removed from the GRACE data
    #-- Remove sets of Ylms from the GRACE data before returning
    remove_Ylms = GRACE_Ylms.zeros_like()
    remove_Ylms.time[:] = np.copy(GRACE_Ylms.time)
    remove_Ylms.month[:] = np.copy(GRACE_Ylms.month)
    if REMOVE_FILES:
        #-- extend list if a single format was entered for all files
        if len(REMOVE_FORMAT) < len(REMOVE_FILES):
            REMOVE_FORMAT = REMOVE_FORMAT * len(REMOVE_FILES)
        #-- for each file to be removed
        for REMOVE_FILE, REMOVEFORM in zip(REMOVE_FILES, REMOVE_FORMAT):
            if REMOVEFORM in ('ascii', 'netCDF4', 'HDF5'):
                #-- ascii (.txt)
                #-- netCDF4 (.nc)
                #-- HDF5 (.H5)
                Ylms = harmonics().from_file(REMOVE_FILE, format=REMOVEFORM)
            elif REMOVEFORM in ('index-ascii', 'index-netCDF4', 'index-HDF5'):
                #-- read from index file
                _, removeform = REMOVEFORM.split('-')
                #-- index containing files in data format
                Ylms = harmonics().from_index(REMOVE_FILE, format=removeform)
            #-- reduce to GRACE/GRACE-FO months and truncate to degree and order
            Ylms = Ylms.subset(GRACE_Ylms.month).truncate(lmax=LMAX, mmax=MMAX)
            #-- distribute removed Ylms uniformly over the ocean
            if REDISTRIBUTE_REMOVED:
                #-- calculate ratio between total removed mass and
                #-- a uniformly distributed cm of water over the ocean
                ratio = Ylms.clm[0, 0, :] / ocean_Ylms.clm[0, 0]
                #-- for each spherical harmonic
                for m in range(0, MMAX + 1):  #-- MMAX+1 to include MMAX
                    for l in range(m, LMAX + 1):  #-- LMAX+1 to include LMAX
                        #-- remove the ratio*ocean Ylms from Ylms
                        #-- note: x -= y is equivalent to x = x - y
                        Ylms.clm[l, m, :] -= ratio * ocean_Ylms.clm[l, m]
                        Ylms.slm[l, m, :] -= ratio * ocean_Ylms.slm[l, m]
            #-- filter removed coefficients
            if DESTRIPE:
                Ylms = Ylms.destripe()
            #-- add data for month t and INDEX_FILE to the total
            #-- remove_clm and remove_slm matrices
            #-- redistributing the mass over the ocean if specified
            remove_Ylms.add(Ylms)

    #-- Output spatial data object
    grid = spatial()
    #-- Output Degree Spacing
    dlon, dlat = (DDEG[0], DDEG[0]) if (len(DDEG) == 1) else (DDEG[0], DDEG[1])
    #-- Output Degree Interval
    if (INTERVAL == 1):
        #-- (-180:180,90:-90)
        nlon = np.int64((360.0 / dlon) + 1.0)
        nlat = np.int64((180.0 / dlat) + 1.0)
        grid.lon = -180 + dlon * np.arange(0, nlon)
        grid.lat = 90.0 - dlat * np.arange(0, nlat)
    elif (INTERVAL == 2):
        #-- (Degree spacing)/2
        grid.lon = np.arange(-180 + dlon / 2.0, 180 + dlon / 2.0, dlon)
        grid.lat = np.arange(90.0 - dlat / 2.0, -90.0 - dlat / 2.0, -dlat)
        nlon = len(grid.lon)
        nlat = len(grid.lat)
    elif (INTERVAL == 3):
        #-- non-global grid set with BOUNDS parameter
        minlon, maxlon, minlat, maxlat = BOUNDS.copy()
        grid.lon = np.arange(minlon + dlon / 2.0, maxlon + dlon / 2.0, dlon)
        grid.lat = np.arange(maxlat - dlat / 2.0, minlat - dlat / 2.0, -dlat)
        nlon = len(grid.lon)
        nlat = len(grid.lat)

    #-- Computing plms for converting to spatial domain
    theta = (90.0 - grid.lat) * np.pi / 180.0
    PLM, dPLM = plm_holmes(LMAX, np.cos(theta))

    #-- Earth Parameters
    #-- output spatial units
    unit_list = ['cmwe', 'mmGH', 'mmCU', u'\u03BCGal', 'mbar']
    unit_name = [
        'Equivalent Water Thickness', 'Geoid Height', 'Elastic Crustal Uplift',
        'Gravitational Undulation', 'Equivalent Surface Pressure'
    ]
    #-- Setting units factor for output
    #-- dfactor computes the degree dependent coefficients
    if (UNITS == 1):
        #-- 1: cmwe, centimeters water equivalent
        dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).cmwe
    elif (UNITS == 2):
        #-- 2: mmGH, mm geoid height
        dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).mmGH
    elif (UNITS == 3):
        #-- 3: mmCU, mm elastic crustal deformation
        dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).mmCU
    elif (UNITS == 4):
        #-- 4: micGal, microGal gravity perturbations
        dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).microGal
    elif (UNITS == 5):
        #-- 5: mbar, millibars equivalent surface pressure
        dfactor = units(lmax=LMAX).harmonic(hl, kl, ll).mbar
    else:
        raise ValueError('Invalid units code {0:d}'.format(UNITS))

    #-- output file format
    file_format = '{0}{1}_L{2:d}{3}{4}{5}_{6:03d}.{7}'
    #-- converting harmonics to truncated, smoothed coefficients in units
    #-- combining harmonics to calculate output spatial fields
    for i, grace_month in enumerate(GRACE_Ylms.month):
        #-- GRACE/GRACE-FO harmonics for time t
        Ylms = GRACE_Ylms.index(i)
        #-- Remove GIA rate for time
        Ylms.subtract(GIA_Ylms.index(i))
        #-- Remove monthly files to be removed
        Ylms.subtract(remove_Ylms.index(i))
        #-- smooth harmonics and convert to output units
        Ylms.convolve(dfactor * wt)
        #-- convert spherical harmonics to output spatial grid
        grid.data = harmonic_summation(Ylms.clm,
                                       Ylms.slm,
                                       grid.lon,
                                       grid.lat,
                                       LMIN=LMIN,
                                       LMAX=LMAX,
                                       MMAX=MMAX,
                                       PLM=PLM).T
        #-- copy time variables for month
        grid.time = np.copy(Ylms.time)
        grid.month = np.copy(Ylms.month)

        #-- output monthly files to ascii, netCDF4 or HDF5
        args = (FILE_PREFIX, unit_list[UNITS - 1], LMAX, order_str, gw_str,
                ds_str, grace_month, suffix[DATAFORM])
        FILE = os.path.join(OUTPUT_DIRECTORY, file_format.format(*args))
        if (DATAFORM == 'ascii'):
            #-- ascii (.txt)
            grid.to_ascii(FILE, date=True, verbose=VERBOSE)
        elif (DATAFORM == 'netCDF4'):
            #-- netCDF4
            grid.to_netCDF4(FILE,
                            date=True,
                            verbose=VERBOSE,
                            units=unit_list[UNITS - 1],
                            longname=unit_name[UNITS - 1],
                            title='GRACE/GRACE-FO Spatial Data')
        elif (DATAFORM == 'HDF5'):
            #-- HDF5
            grid.to_HDF5(FILE,
                         date=True,
                         verbose=VERBOSE,
                         units=unit_list[UNITS - 1],
                         longname=unit_name[UNITS - 1],
                         title='GRACE/GRACE-FO Spatial Data')
        #-- set the permissions mode of the output files
        os.chmod(FILE, MODE)
        #-- add file to list
        output_files.append(FILE)

    #-- return the list of output files
    return output_files
def calc_sensitivity_kernel(LMAX,
                            RAD,
                            LMIN=None,
                            MMAX=None,
                            LOVE_NUMBERS=0,
                            REFERENCE=None,
                            DATAFORM=None,
                            MASCON_FILE=None,
                            REDISTRIBUTE_MASCONS=False,
                            FIT_METHOD=0,
                            LANDMASK=None,
                            DDEG=None,
                            INTERVAL=None,
                            OUTPUT_DIRECTORY=None,
                            MODE=0o775):

    #-- file information
    suffix = dict(ascii='txt', netCDF4='nc', HDF5='H5')
    #-- file parser for reading index files
    #-- removes commented lines (can comment out files in the index)
    #-- removes empty lines (if there are extra empty lines)
    parser = re.compile(r'^(?!\#|\%|$)', re.VERBOSE)

    #-- Create output Directory if not currently existing
    if (not os.access(OUTPUT_DIRECTORY, os.F_OK)):
        os.mkdir(OUTPUT_DIRECTORY)

    #-- list object of output files for file logs (full path)
    output_files = []

    #-- read arrays of kl, hl, and ll Love Numbers
    hl, kl, ll = load_love_numbers(LMAX,
                                   LOVE_NUMBERS=LOVE_NUMBERS,
                                   REFERENCE=REFERENCE)

    #-- Earth Parameters
    factors = units(lmax=LMAX).harmonic(hl, kl, ll)
    #-- Average Density of the Earth [g/cm^3]
    rho_e = factors.rho_e
    #-- Average Radius of the Earth [cm]
    rad_e = factors.rad_e

    #-- input/output string for both LMAX==MMAX and LMAX != MMAX cases
    MMAX = np.copy(LMAX) if not MMAX else MMAX
    order_str = 'M{0:d}'.format(MMAX) if (MMAX != LMAX) else ''

    #-- Calculating the Gaussian smoothing for radius RAD
    if (RAD != 0):
        wt = 2.0 * np.pi * gauss_weights(RAD, LMAX)
        gw_str = '_r{0:0.0f}km'.format(RAD)
    else:
        #-- else = 1
        wt = np.ones((LMAX + 1))
        gw_str = ''

    #-- Read Ocean function and convert to Ylms for redistribution
    if REDISTRIBUTE_MASCONS:
        #-- read Land-Sea Mask and convert to spherical harmonics
        ocean_Ylms = ocean_stokes(LANDMASK, LMAX, MMAX=MMAX, LOVE=(hl, kl, ll))
        ocean_str = '_OCN'
    else:
        #-- not distributing uniformly over ocean
        ocean_str = ''

    #-- input mascon spherical harmonic datafiles
    with open(MASCON_FILE, 'r') as f:
        mascon_files = [l for l in f.read().splitlines() if parser.match(l)]
    #-- number of mascons
    n_mas = len(mascon_files)
    #-- spatial area of the mascon
    total_area = np.zeros((n_mas))
    #-- name of each mascon
    mascon_name = []
    #-- for each valid file in the index (iterate over mascons)
    mascon_list = []
    for k, fi in enumerate(mascon_files):
        #-- read mascon spherical harmonics
        Ylms = harmonics().from_file(os.path.expanduser(fi),
                                     format=DATAFORM,
                                     date=False)
        #-- Calculating the total mass of each mascon (1 cmwe uniform)
        total_area[k] = 4.0 * np.pi * (rad_e**3) * rho_e * Ylms.clm[0, 0] / 3.0
        #-- distribute mascon mass uniformly over the ocean
        if REDISTRIBUTE_MASCONS:
            #-- calculate ratio between total mascon mass and
            #-- a uniformly distributed cm of water over the ocean
            ratio = Ylms.clm[0, 0] / ocean_Ylms.clm[0, 0]
            #-- for each spherical harmonic
            for m in range(0, MMAX + 1):  #-- MMAX+1 to include MMAX
                for l in range(m, LMAX + 1):  #-- LMAX+1 to include LMAX
                    #-- remove ratio*ocean Ylms from mascon Ylms
                    #-- note: x -= y is equivalent to x = x - y
                    Ylms.clm[l, m] -= ratio * ocean_Ylms.clm[l, m]
                    Ylms.slm[l, m] -= ratio * ocean_Ylms.slm[l, m]
        #-- truncate mascon spherical harmonics to d/o LMAX/MMAX and add to list
        mascon_list.append(Ylms.truncate(lmax=LMAX, mmax=MMAX))
        #-- mascon base is the file without directory or suffix
        mascon_base = os.path.basename(mascon_files[k])
        mascon_base = os.path.splitext(mascon_base)[0]
        #-- if lower case, will capitalize
        mascon_base = mascon_base.upper()
        #-- if mascon name contains degree and order info, remove
        mascon_name.append(mascon_base.replace('_L{0:d}'.format(LMAX), ''))
    #-- create single harmonics object from list
    mascon_Ylms = harmonics().from_list(mascon_list, date=False)

    #-- Output spatial data object
    grid = spatial()
    #-- Output Degree Spacing
    dlon, dlat = (DDEG[0], DDEG[0]) if (len(DDEG) == 1) else (DDEG[0], DDEG[1])
    #-- Output Degree Interval
    if (INTERVAL == 1):
        #-- (-180:180,90:-90)
        n_lon = np.int64((360.0 / dlon) + 1.0)
        n_lat = np.int64((180.0 / dlat) + 1.0)
        grid.lon = -180 + dlon * np.arange(0, n_lon)
        grid.lat = 90.0 - dlat * np.arange(0, n_lat)
    elif (INTERVAL == 2):
        #-- (Degree spacing)/2
        grid.lon = np.arange(-180 + dlon / 2.0, 180 + dlon / 2.0, dlon)
        grid.lat = np.arange(90.0 - dlat / 2.0, -90.0 - dlat / 2.0, -dlat)
        n_lon = len(grid.lon)
        n_lat = len(grid.lat)

    #-- Computing plms for converting to spatial domain
    theta = (90.0 - grid.lat) * np.pi / 180.0
    PLM, dPLM = plm_holmes(LMAX, np.cos(theta))

    #-- Calculating the number of cos and sin harmonics between LMIN and LMAX
    #-- taking into account MMAX (if MMAX == LMAX then LMAX-MMAX=0)
    n_harm = np.int64(LMAX**2 - LMIN**2 + 2 * LMAX + 1 - (LMAX - MMAX)**2 -
                      (LMAX - MMAX))

    #-- Initialing harmonics for least squares fitting
    #-- mascon kernel
    M_lm = np.zeros((n_harm, n_mas))
    #-- mascon kernel converted to output unit
    MA_lm = np.zeros((n_harm, n_mas))
    #-- sensitivity kernel
    A_lm = np.zeros((n_harm, n_mas))
    #-- Initializing conversion factors
    #-- factor for converting to smoothed coefficients of mass
    fact = np.zeros((n_harm))
    #-- factor for converting back into geoid coefficients
    fact_inv = np.zeros((n_harm))
    #-- smoothing factor
    wt_lm = np.zeros((n_harm))

    #-- ii is a counter variable for building the mascon column array
    ii = 0
    #-- Creating column array of clm/slm coefficients
    #-- Order is [C00...C6060,S11...S6060]
    #-- Calculating factor to convert geoid spherical harmonic coefficients
    #-- to coefficients of mass (Wahr, 1998)
    coeff = rho_e * rad_e / 3.0
    coeff_inv = 0.75 / (np.pi * rho_e * rad_e**3)
    #-- Switching between Cosine and Sine Stokes
    for cs, csharm in enumerate(['clm', 'slm']):
        #-- copy cosine and sin harmonics
        mascon_harm = getattr(mascon_Ylms, csharm)
        #-- for each spherical harmonic degree
        #-- +1 to include LMAX
        for l in range(LMIN, LMAX + 1):
            #-- for each spherical harmonic order
            #-- Sine Stokes for (m=0) = 0
            mm = np.min([MMAX, l])
            #-- +1 to include l or MMAX (whichever is smaller)
            for m in range(cs, mm + 1):
                #-- Mascon Spherical Harmonics
                M_lm[ii, :] = np.copy(mascon_harm[l, m, :])
                #-- degree dependent factor to convert to mass
                fact[ii] = (2.0 * l + 1.0) / (1.0 + kl[l])
                #-- degree dependent factor to convert from mass
                fact_inv[ii] = coeff_inv * (1.0 + kl[l]) / (2.0 * l + 1.0)
                #-- degree dependent smoothing
                wt_lm[ii] = np.copy(wt[l])
                #-- add 1 to counter
                ii += 1

    #-- Converting mascon coefficients to fit method
    if (FIT_METHOD == 1):
        #-- Fitting Sensitivity Kernel as mass coefficients
        #-- converting M_lm to mass coefficients of the kernel
        for i in range(n_harm):
            MA_lm[i, :] = M_lm[i, :] * wt_lm[i] * fact[i]
        fit_factor = wt_lm * fact
        inv_fit_factor = np.copy(fact_inv)
    else:
        #-- Fitting Sensitivity Kernel as geoid coefficients
        for i in range(n_harm):
            MA_lm[:, :] = M_lm[i, :] * wt_lm[i]
        fit_factor = wt_lm * np.ones((n_harm))
        inv_fit_factor = np.ones((n_harm))

    #-- Fitting the sensitivity kernel from the input kernel
    for i in range(n_harm):
        #-- setting kern_i equal to 1 for d/o
        kern_i = np.zeros((n_harm))
        #-- converting to mass coefficients if specified
        kern_i[i] = 1.0 * fit_factor[i]
        #-- spherical harmonics solution for the
        #-- mascon sensitivity kernels
        #-- Least Squares Solutions: Inv(X'.X).(X'.Y)
        kern_lm = np.linalg.lstsq(MA_lm, kern_i, rcond=-1)[0]
        for k in range(n_mas):
            A_lm[i, k] = kern_lm[k] * total_area[k]

    #-- for each mascon
    for k in range(n_mas):
        #-- reshaping harmonics of sensitivity kernel to LMAX+1,MMAX+1
        #-- calculating the spatial sensitivity kernel of each mascon
        #-- kernel calculated as outlined in Tiwari (2009) and Jacobs (2012)
        #-- Initializing output sensitivity kernel (both spatial and Ylms)
        kern_Ylms = harmonics(lmax=LMAX, mmax=MMAX)
        kern_Ylms.clm = np.zeros((LMAX + 1, MMAX + 1))
        kern_Ylms.slm = np.zeros((LMAX + 1, MMAX + 1))
        kern_Ylms.time = total_area[k]
        #-- counter variable for deconstructing the mascon column arrays
        ii = 0
        #-- Switching between Cosine and Sine Stokes
        for cs, csharm in enumerate(['clm', 'slm']):
            #-- for each spherical harmonic degree
            #-- +1 to include LMAX
            for l in range(LMIN, LMAX + 1):
                #-- for each spherical harmonic order
                #-- Sine Stokes for (m=0) = 0
                mm = np.min([MMAX, l])
                #-- +1 to include l or MMAX (whichever is smaller)
                for m in range(cs, mm + 1):
                    #-- inv_fit_factor: normalize from mass harmonics
                    temp = getattr(kern_Ylms, csharm)
                    temp[l, m] = inv_fit_factor[ii] * A_lm[ii, k]
                    #-- add 1 to counter
                    ii += 1

        #-- convert spherical harmonics to output spatial grid
        grid.data = harmonic_summation(kern_Ylms.clm,
                                       kern_Ylms.slm,
                                       grid.lon,
                                       grid.lat,
                                       LMAX=LMAX,
                                       MMAX=MMAX,
                                       PLM=PLM).T
        grid.time = total_area[k]

        #-- output names for sensitivity kernel Ylm and spatial files
        #-- for both LMAX==MMAX and LMAX != MMAX cases
        args = (mascon_name[k], ocean_str, LMAX, order_str, gw_str,
                suffix[DATAFORM])
        FILE1 = '{0}_SKERNEL_CLM{1}_L{2:d}{3}{4}.{5}'.format(*args)
        FILE2 = '{0}_SKERNEL{1}_L{2:d}{3}{4}.{5}'.format(*args)
        #-- output sensitivity kernel to file
        if (DATAFORM == 'ascii'):
            #-- ascii (.txt)
            kern_Ylms.to_ascii(os.path.join(OUTPUT_DIRECTORY, FILE1),
                               date=False)
            grid.to_ascii(os.path.join(OUTPUT_DIRECTORY, FILE2),
                          date=False,
                          units='unitless',
                          longname='Sensitivity_Kernel')
        elif (DATAFORM == 'netCDF4'):
            #-- netCDF4 (.nc)
            kern_Ylms.to_netCDF4(os.path.join(OUTPUT_DIRECTORY, FILE1),
                                 date=False)
            grid.to_netCDF4(os.path.join(OUTPUT_DIRECTORY, FILE2),
                            date=False,
                            units='unitless',
                            longname='Sensitivity_Kernel')
        elif (DATAFORM == 'HDF5'):
            #-- netcdf (.H5)
            kern_Ylms.to_HDF5(os.path.join(OUTPUT_DIRECTORY, FILE1),
                              date=False)
            grid.to_HDF5(os.path.join(OUTPUT_DIRECTORY, FILE2),
                         date=False,
                         units='unitless',
                         longname='Sensitivity_Kernel')
        #-- change the permissions mode
        os.chmod(os.path.join(OUTPUT_DIRECTORY, FILE1), MODE)
        os.chmod(os.path.join(OUTPUT_DIRECTORY, FILE2), MODE)
        #-- add output files to list object
        output_files.append(os.path.join(OUTPUT_DIRECTORY, FILE1))
        output_files.append(os.path.join(OUTPUT_DIRECTORY, FILE2))

    #-- return the list of output files
    return output_files
Example #8
0
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