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)
Exemple #2
0
def grace_mean_harmonics(base_dir, parameters, MODE=0o775):
    #-- Data processing center
    PROC = parameters['PROC']
    #-- Data Release
    DREL = parameters['DREL']
    #-- GRACE dataset
    DSET = parameters['DSET']
    #-- pole tide corrections from Wahr et al (2015)
    POLE_TIDE = parameters['POLE_TIDE'] in ('Y', 'y')
    #-- ATM corrections
    ATM = parameters['ATM'] in ('Y', 'y')

    #-- maximum degree and order
    LMAX = np.int(parameters['LMAX'])
    #-- maximum spherical harmonic order
    if (parameters['MMAX'].title() == 'None'):
        MMAX = np.copy(LMAX)
    else:
        MMAX = np.int(parameters['MMAX'])

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

    #-- 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)
    #-- SLR C2,0 and C3,0
    SLR_C20 = parameters['SLR_C20']
    SLR_C30 = parameters['SLR_C30']
    #-- Degree 1 correction
    DEG1 = parameters['DEG1']

    #-- data formats for output: ascii, netCDF4, HDF5
    DATAFORM = parameters['DATAFORM']
    suffix = dict(ascii='txt', netCDF4='nc', HDF5='H5')

    #-- reading GRACE months for input range with grace_input_months.py
    #-- replacing C20 and C30 with SLR values and updating Degree 1 if specified
    #-- correcting for Pole Tide Drift and Atmospheric Jumps if specified
    input_Ylms = grace_input_months(base_dir,
                                    PROC,
                                    DREL,
                                    DSET,
                                    LMAX,
                                    START_MON,
                                    END_MON,
                                    MISSING,
                                    SLR_C20,
                                    DEG1,
                                    MMAX=MMAX,
                                    MODEL_DEG1=False,
                                    SLR_C30=SLR_C30,
                                    POLE_TIDE=POLE_TIDE,
                                    ATM=ATM)
    grace_Ylms = harmonics().from_dict(input_Ylms)
    #-- output directory
    if (parameters['DIRECTORY'].title() == 'None'):
        #-- if not entering via parameter file use directory for product
        DIRECTORY = os.path.expanduser(input_Ylms['directory'])
    else:
        DIRECTORY = os.path.expanduser(parameters['DIRECTORY'])
    #-- descriptor string for processing parameters
    grace_str = input_Ylms['title']
    #-- calculate mean Ylms
    mean_Ylms = grace_Ylms.mean()
    mean_Ylms.time = np.mean(grace_Ylms.time)
    mean_Ylms.month = np.mean(grace_Ylms.month)

    #-- default output filename if not entering via parameter file
    if (parameters['FILENAME'].title() == 'None'):
        file_format = '{0}_{1}_{2}_MEAN_CLM{3}_L{4:d}{5}_{6:03d}-{7:03d}.{8}'
        FILENAME = file_format.format(PROC, DREL, DSET, grace_str, LMAX,
                                      order_str, START_MON, END_MON,
                                      suffix[DATAFORM])
    else:
        FILENAME = parameters['FILENAME']

    #-- output spherical harmonics for the static field
    if (DATAFORM == 'ascii'):
        #-- output mean field to ascii
        mean_Ylms.to_ascii(os.path.join(DIRECTORY, FILENAME))
    elif (DATAFORM == 'netCDF4'):
        #-- output mean field to netCDF4
        mean_Ylms.to_netCDF4(os.path.join(DIRECTORY, FILENAME))
    elif (DATAFORM == 'HDF5'):
        #-- output mean field to HDF5
        mean_Ylms.to_HDF5(os.path.join(DIRECTORY, FILENAME))
    #-- change the permissions mode
    os.chmod(os.path.join(DIRECTORY, FILENAME), MODE)

    #-- return the output file
    return os.path.join(DIRECTORY, FILENAME)
def grace_mean_harmonics(base_dir, PROC, DREL, DSET, LMAX,
    START=None,
    END=None,
    MISSING=None,
    MMAX=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,
    MEAN_FILE=None,
    MEANFORM=None,
    VERBOSE=False,
    MODE=0o775):

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

    #-- data formats for output: ascii, netCDF4, HDF5, gfc
    suffix = dict(ascii='txt',netCDF4='nc',HDF5='H5',gfc='gfc')[MEANFORM]

    #-- 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 Drift and Atmospheric Jumps if specified
    input_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)
    grace_Ylms = harmonics().from_dict(input_Ylms)
    #-- descriptor string for processing parameters
    grace_str = input_Ylms['title']
    #-- calculate mean Ylms
    mean_Ylms = mean().from_harmonics(grace_Ylms.mean())
    mean_Ylms.time = np.mean(grace_Ylms.time)
    mean_Ylms.month = np.mean(grace_Ylms.month)
    #-- number of months
    nt = grace_Ylms.shape[-1]
    #-- calculate RMS of harmonic errors
    mean_Ylms.eclm = np.sqrt(np.sum(input_Ylms['eclm']**2,axis=2)/nt)
    mean_Ylms.eslm = np.sqrt(np.sum(input_Ylms['eslm']**2,axis=2)/nt)
    #-- product information
    mean_Ylms.center = PROC
    mean_Ylms.release = DREL
    mean_Ylms.product = DSET

    #-- default output filename if not entering via parameter file
    if not MEAN_FILE:
        DIRECTORY = os.path.expanduser(input_Ylms['directory'])
        args = (PROC,DREL,DSET,grace_str,LMAX,order_str,START,END,suffix)
        file_format = '{0}_{1}_{2}_MEAN_CLM{3}_L{4:d}{5}_{6:03d}-{7:03d}.{8}'
        MEAN_FILE = os.path.join(DIRECTORY,file_format.format(*args))
    else:
        DIRECTORY = os.path.dirname(MEAN_FILE)
    #-- recursively create output directory if non-existent
    if not os.access(DIRECTORY, os.F_OK):
        os.makedirs(DIRECTORY, MODE)

    #-- output spherical harmonics for the static field
    if (MEANFORM == 'gfc'):
        #-- output mean field to gfc format
        mean_Ylms.to_gfc(MEAN_FILE, verbose=VERBOSE)
    else:
        #-- output mean field to specified file format
        mean_Ylms.to_file(MEAN_FILE, format=MEANFORM, verbose=VERBOSE)
    #-- change the permissions mode
    os.chmod(MEAN_FILE, MODE)

    #-- return the output file
    return MEAN_FILE
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)
Exemple #5
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 least_squares_mascons(parameters,
                          LOVE_NUMBERS=0,
                          REFERENCE=None,
                          MODE=0o775):
    #-- convert parameters to variables
    #-- index file of data files (containing path)
    #-- path.expanduser = tilde expansion of path
    INDEX_FILE = os.path.expanduser(parameters['INDEX_FILE'])
    DATAFORM = parameters['DATAFORM']
    #-- spherical harmonic degree range
    LMIN = np.int(parameters['LMIN'])
    LMAX = np.int(parameters['LMAX'])
    #-- maximum spherical harmonic order
    if (parameters['MMAX'].title() == 'None'):
        MMAX = np.copy(LMAX)
    else:
        MMAX = np.int(parameters['MMAX'])
    #-- gaussian smoothing radius
    RAD = np.float(parameters['RAD'])
    #-- filter coefficients for stripe effects
    DESTRIPE = parameters['DESTRIPE'] in ('Y', 'y')
    #-- index of mascons spherical harmonics
    MASCON_INDEX = os.path.expanduser(parameters['MASCON_INDEX'])
    #-- mascon distribution over the ocean
    MASCON_OCEAN = parameters['MASCON_OCEAN'] in ('Y', 'y')
    #-- destribute total mass of spherical harmonics over the ocean
    REDISTRIBUTE = parameters['REDISTRIBUTE'] in ('Y', 'y')
    #-- calculating with data errors
    DATA_ERROR = parameters['DATA_ERROR'] in ('Y', 'y')
    #-- input data have dates
    DATE = parameters['DATE'] in ('Y', 'y')
    #-- output directory
    DIRECTORY = os.path.expanduser(parameters['DIRECTORY'])
    #-- 1: fit mass, 2: fit geoid
    FIT_METHOD = np.int(parameters['FIT_METHOD'])

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

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

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

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

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

    #-- output string for both LMAX==MMAX and LMAX != MMAX cases
    order_str = 'M{0:d}'.format(MMAX) if (MMAX != LMAX) else ''
    #-- output string for destriped harmonics
    ds_str = '_FL' if DESTRIPE else ''

    #-- Read Ocean function and convert to Ylms for redistribution
    if (MASCON_OCEAN | REDISTRIBUTE):
        #-- read Land-Sea Mask and convert to spherical harmonics
        LANDMASK = os.path.expanduser(parameters['LANDMASK'])
        ocean_Ylms = ocean_stokes(LANDMASK, LMAX, MMAX=MMAX, LOVE=(hl, kl, ll))
        ocean_str = '_OCN'
    else:
        #-- not distributing uniformly over ocean
        ocean_str = ''

    #-- input spherical harmonic datafile index
    #-- correspond file names with GRACE month
    #-- this allows additional months to be in the index
    data_Ylms = harmonics().from_index(INDEX_FILE, DATAFORM, date=DATE)
    #-- number of files within the index
    n_files = data_Ylms.shape[-1]
    #-- truncate to degree and order
    data_Ylms = data_Ylms.truncate(lmax=LMAX, mmax=MMAX)
    #-- Total mass in the simulated input data in gigatonnes
    rmass = 4.0 * np.pi * (rad_e**
                           3.0) * rho_e * data_Ylms.clm[0, 0, :] / 3.0 / 1e15
    #-- distribute Ylms uniformly over the ocean
    if REDISTRIBUTE:
        #-- calculate ratio between total removed mass and
        #-- a uniformly distributed cm of water over the ocean
        ratio = data_Ylms.clm[0, 0, :] / ocean_Ylms['clm'][0, 0]
        #-- for each spherical harmonic
        for m in range(0, MMAX + 1):  #-- MMAX+1 to include MMAX
            for l in range(m, LMAX + 1):  #-- LMAX+1 to include LMAX
                #-- remove the ratio*ocean Ylms from Ylms
                #-- note: x -= y is equivalent to x = x - y
                data_Ylms.clm[l, m, :] -= ratio * ocean_Ylms['clm'][l, m]
                data_Ylms.slm[l, m, :] -= ratio * ocean_Ylms['slm'][l, m]
    #-- filter data coefficients
    if DESTRIPE:
        data_Ylms = data_Ylms.destripe()

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

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

    #-- Initialing harmonics for least squares fitting
    #-- mascon kernel
    M_lm = np.zeros((n_harm, n_mas))
    #-- mascon kernel converted to output unit
    MA_lm = np.zeros((n_harm, n_mas))
    #-- corrected clm and slm
    Y_lm = np.zeros((n_harm, n_files))
    #-- sensitivity kernel
    A_lm = np.zeros((n_harm, n_mas))
    #-- Initializing output Mascon time-series
    mascon = np.zeros((n_mas, n_files))
    #-- Initializing conversion factors
    #-- factor for converting to coefficients of mass
    fact = np.zeros((n_harm))
    #-- smoothing factor
    wt_lm = np.zeros((n_harm))

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

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

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

    #-- output formatting string if containing date variables
    if DATE:
        formatting_string = '{0:03d} {1:12.4f} {2:16.10f} {3:16.5f} {4:16.10f}'
    else:
        formatting_string = '{0:16.10f} {1:16.5f} {2:16.10f}'

    #-- for each mascon
    for k in range(n_mas):
        #-- output filename format:
        #-- mascon name, LMAX, Gaussian smoothing radii, filter flag
        file_out = '{0}{1}_L{2:d}{3}{4}{5}{6}.txt'.format(
            parameters['FILENAME'], mascon_name[k], LMAX, order_str, gw_str,
            ds_str, ocean_str)
        #-- add output files to list object
        output_files.append(os.path.join(DIRECTORY, file_out))

        #-- Output mascon datafiles
        #-- Will output each mascon mass series
        #-- if with dates:
        #-- month, date, mascon mass, mascon area
        #-- else:
        #-- mascon mass, mascon area
        #-- open output mascon time-series file
        fid = open(os.path.join(DIRECTORY, file_out), 'w')
        #-- for each date
        for f in range(n_files):
            #-- Summing over all spherical harmonics for mascon k, and time t
            #-- multiplies by the degree dependent factor to convert
            #-- the harmonics into mass coefficients
            #-- Converting mascon mass time-series from g to gigatonnes
            if DATA_ERROR:
                #-- if input data are errors (i.e. estimated dealiasing errors)
                mascon[k, f] = np.sqrt(np.sum(
                    (A_lm[:, k] * Y_lm[:, f])**2)) / 1e15
            else:
                mascon[k, f] = np.sum(A_lm[:, k] * Y_lm[:, f]) / 1e15
            #-- output to file
            if DATE:
                #-- if files contain date information
                args = (data_Ylms.month[f], data_Ylms.time[f], mascon[k, f],
                        area_tot[k] / 1e10, rmass[f])
                print(formatting_string.format(*args), file=fid)
            else:
                #-- just print the time-series and mascon areas
                args = (mascon[k, f], area_tot[k] / 1e10, rmass[f])
                print(formatting_string.format(*args), file=fid)
        #-- close the output file
        fid.close()
        #-- change the permissions mode of the output file
        os.chmod(os.path.join(DIRECTORY, file_out), MODE)
        #-- add output files to list object
        output_files.append(os.path.join(DIRECTORY, file_out))

    #-- return the list of output files
    return output_files
Exemple #7
0
def dealiasing_monthly_mean(base_dir,
                            PROC=None,
                            DREL=None,
                            DSET=None,
                            LMAX=None,
                            DATAFORM=None,
                            CLOBBER=False,
                            VERBOSE=False,
                            MODE=0o775):

    #-- create logger
    loglevels = [logging.CRITICAL, logging.INFO, logging.DEBUG]
    logging.basicConfig(level=loglevels[VERBOSE])

    #-- output data suffix
    suffix = dict(ascii='txt', netCDF4='nc', HDF5='H5')
    #-- aod1b data products
    aod1b_products = dict(GAA='atm', GAB='ocn', GAC='glo', GAD='oba')
    #-- compile regular expressions operator for the clm/slm headers
    #-- for the specific AOD1b product
    hx = re.compile(r'^DATA.*SET.*{0}'.format(aod1b_products[DSET]),
                    re.VERBOSE)
    #-- compile regular expression operator to find numerical instances
    #-- will extract the data from the file
    regex_pattern = r'[-+]?(?:(?:\d*\.\d+)|(?:\d+\.?))(?:[Ee][+-]?\d+)?'
    rx = re.compile(regex_pattern, re.VERBOSE)

    #-- set number of hours in a file
    #-- set the ocean model for a given release
    if DREL in ('RL01', 'RL02', 'RL03', 'RL04', 'RL05'):
        #-- for 00, 06, 12 and 18
        n_time = 4
        ATMOSPHERE = 'ECMWF'
        OCEAN_MODEL = 'OMCT'
        default_center = 'EIGEN'
        default_lmax = 100
    elif DREL in ('RL06', ):
        #-- for 00, 03, 06, 09, 12, 15, 18 and 21
        n_time = 8
        ATMOSPHERE = 'ECMWF'
        OCEAN_MODEL = 'MPIOM'
        default_center = 'GFZOP'
        default_lmax = 180
    else:
        raise ValueError('Invalid data release')
    #-- Maximum spherical harmonic degree (LMAX)
    LMAX = default_lmax if not LMAX else LMAX
    #-- Calculating the number of cos and sin harmonics up to d/o of file
    n_harm = (default_lmax**2 + 3 * default_lmax) // 2 + 1

    #-- AOD1B data products
    product = {}
    product['atm'] = 'Atmospheric loading from {0}'.format(ATMOSPHERE)
    product['ocn'] = 'Oceanic loading from {0}'.format(OCEAN_MODEL)
    product['glo'] = 'Global atmospheric and oceanic loading'
    product['oba'] = 'Ocean bottom pressure from {0}'.format(OCEAN_MODEL)

    #-- GRACE AOD1B directory for data release
    aod1b_dir = os.path.join(base_dir, 'AOD1B', DREL)
    #-- GRACE data directory for data release and processing center
    grace_dir = os.path.join(base_dir, PROC, DREL)
    #-- recursively create output directory if not currently existing
    if not os.access(os.path.join(grace_dir, DSET), os.F_OK):
        os.makedirs(os.path.join(grace_dir, DSET), MODE)
    #-- file formatting string if outputting to SHM format
    shm = '{0}-2_{1:4.0f}{2:03.0f}-{3:4.0f}{4:03.0f}_{5}_{6}_{7}_{8}00.gz'
    #-- center name if outputting to SHM format
    if (PROC == 'CSR'):
        CENTER = 'UTCSR'
    elif (PROC == 'GFZ'):
        CENTER = default_center
    elif (PROC == 'JPL'):
        CENTER = 'JPLEM'
    else:
        CENTER = default_center

    #-- read input DATE file from GSM data product
    grace_datefile = '{0}_{1}_DATES.txt'.format(PROC, DREL)
    date_input = np.loadtxt(os.path.join(grace_dir, 'GSM', grace_datefile),
                            skiprows=1)
    grace_month = date_input[:, 1].astype(np.int64)
    start_yr = date_input[:, 2]
    start_day = date_input[:, 3].astype(np.int64)
    end_yr = date_input[:, 4]
    end_day = date_input[:, 5].astype(np.int64)
    #-- output date file reduced to months with complete AOD
    f_out = open(os.path.join(grace_dir, DSET, grace_datefile), 'w')
    #-- date file header information
    args = ('Mid-date', 'Month', 'Start_Day', 'End_Day', 'Total_Days')
    print('{0} {1:>10} {2:>11} {3:>10} {4:>13}'.format(*args), file=f_out)

    #-- for each GRACE/GRACE-FO month
    for t, gm in enumerate(grace_month):
        #-- check if GRACE/GRACE-FO month crosses years
        if (start_yr[t] != end_yr[t]):
            #-- check if start_yr is a Leap Year or Standard Year
            dpy = gravity_toolkit.time.calendar_days(start_yr[t]).sum()
            #-- list of Julian Days to read from both start and end year
            julian_days_to_read = []
            #-- add days to read from start and end years
            julian_days_to_read.extend([
                calc_julian_day(start_yr[t], D)
                for D in range(start_day[t], dpy + 1)
            ])
            julian_days_to_read.extend([
                calc_julian_day(end_yr[t], D)
                for D in range(1, end_day[t] + 1)
            ])
        else:
            #-- Julian Days to read going from start_day to end_day
            julian_days_to_read = [
                calc_julian_day(start_yr[t], D)
                for D in range(start_day[t], end_day[t] + 1)
            ]

        #-- output filename for GRACE/GRACE-FO month
        if (DATAFORM == 'SHM'):
            MISSION = 'GRAC' if (gm <= 186) else 'GRFO'
            FILE = shm.format(DSET.upper(), start_yr[t], start_day[t],
                              end_yr[t], end_day[t], MISSION, CENTER, 'BC01',
                              DREL[2:])
        else:
            args = (PROC, DREL, DSET.upper(), LMAX, gm, suffix[DATAFORM])
            FILE = '{0}_{1}_{2}_CLM_L{3:d}_{4:03d}.{5}'.format(*args)

        #-- calendar dates to read
        JD = np.array(julian_days_to_read)
        Y, M, D, h, m, s = gravity_toolkit.time.convert_julian(JD,
                                                               ASTYPE='i',
                                                               FORMAT='tuple')
        #-- find unique year and month pairs to read
        rx1 = '|'.join(['{0:d}-{1:02d}'.format(*p) for p in set(zip(Y, M))])
        rx2 = '|'.join(
            ['{0:0d}-{1:02d}-{2:02d}'.format(*p) for p in set(zip(Y, M, D))])
        #-- compile regular expressions operators for finding tar files
        tx = re.compile(r'AOD1B_({0})_\d+.(tar.gz|tgz)$'.format(rx1),
                        re.VERBOSE)
        #-- finding all of the tar files in the AOD1b directory
        input_tar_files = [tf for tf in os.listdir(aod1b_dir) if tx.match(tf)]
        #-- compile regular expressions operators for file dates
        #-- will extract year and month and calendar day from the ascii file
        fx = re.compile(r'AOD1B_({0})_X_\d+.asc(.gz)?$'.format(rx2),
                        re.VERBOSE)

        #-- check the last modified times of the tar file members
        input_mtime = np.zeros_like(julian_days_to_read, dtype=np.int64)
        input_file_check = np.zeros_like(julian_days_to_read, dtype=bool)
        c = 0
        #-- for each tar file
        for fi in sorted(input_tar_files):
            #-- open the AOD1B monthly tar file
            tar = tarfile.open(name=os.path.join(aod1b_dir, fi), mode='r:gz')
            #-- for each ascii file within the tar file that matches fx
            monthly_members = [m for m in tar.getmembers() if fx.match(m.name)]
            for member in monthly_members:
                #-- check last modification time of input tar file members
                input_mtime[c] = member.mtime
                input_file_check[c] = True
                c += 1

        #-- check if all files exist
        COMPLETE = input_file_check.all()
        #-- if output file exists: check if input tar file is newer
        TEST = False
        OVERWRITE = 'clobber'
        if os.access(os.path.join(grace_dir, DSET, FILE), os.F_OK):
            #-- check last modification time of input and output files
            output_mtime = os.stat(os.path.join(grace_dir, DSET,
                                                FILE)).st_mtime
            #-- if input tar file is newer: overwrite the output file
            if (input_mtime > output_mtime).any():
                TEST = True
                OVERWRITE = 'overwrite'
        else:
            TEST = True
            OVERWRITE = 'new'

        #-- print GRACE/GRACE-FO dates if there is a complete month of AOD
        if COMPLETE:
            #-- print GRACE/GRACE-FO dates to file
            print(('{0:13.8f} {1:03d} {2:8.0f} {3:03d} {4:8.0f} {5:03d} '
                   '{6:8.0f}').format(date_input[t, 0], gm, start_yr[t],
                                      start_day[t], end_yr[t], end_day[t],
                                      date_input[t, 6]),
                  file=f_out)

        #-- if there are new files, files to be rewritten or clobbered
        if COMPLETE and (TEST or CLOBBER):
            #-- if verbose: output information about the output file
            logging.info('{0} ({1})'.format(FILE, OVERWRITE))
            #-- allocate for the mean output harmonics
            Ylms = harmonics(lmax=LMAX, mmax=LMAX)
            nt = len(julian_days_to_read) * n_time
            Ylms.clm = np.zeros((LMAX + 1, LMAX + 1, nt))
            Ylms.slm = np.zeros((LMAX + 1, LMAX + 1, nt))
            Ylms.time = np.zeros((nt))
            count = 0
            #-- for each tar file
            for fi in sorted(input_tar_files):
                #-- open the AOD1B monthly tar file
                tar = tarfile.open(name=os.path.join(aod1b_dir, fi),
                                   mode='r:gz')
                #-- for each ascii file within the tar file that matches fx
                monthly_members = [
                    m for m in tar.getmembers() if fx.match(m.name)
                ]
                for member in monthly_members:
                    #-- extract member name
                    YMD, SFX = fx.findall(member.name).pop()
                    #-- open datafile for day
                    if (SFX == '.gz'):
                        fid = gzip.GzipFile(fileobj=tar.extractfile(member))
                    else:
                        fid = tar.extractfile(member)
                    #-- create counters for hour in dataset
                    hours = np.zeros((n_time))
                    c = 0
                    #-- while loop ends when dataset is read
                    while (c < n_time):
                        #-- read line
                        file_contents = fid.readline().decode('ISO-8859-1')
                        #-- find file header for data product
                        if bool(hx.search(file_contents)):
                            #-- extract hour from header and convert to float
                            HH, = re.findall(r'(\d+):\d+:\d+', file_contents)
                            hours[c] = np.int64(HH)
                            #-- read each line of spherical harmonics
                            for k in range(0, n_harm):
                                file_contents = fid.readline().decode(
                                    'ISO-8859-1')
                                #-- find numerical instances in the data line
                                line_contents = rx.findall(file_contents)
                                #-- spherical harmonic degree and order
                                l1 = np.int64(line_contents[0])
                                m1 = np.int64(line_contents[1])
                                #-- spherical harmonic data saved to output Ylms
                                if (l1 <= LMAX) & (m1 <= LMAX):
                                    Ylms.clm[l1, m1,
                                             c] += np.float64(line_contents[2])
                                    Ylms.slm[l1, m1,
                                             c] += np.float64(line_contents[3])
                            #-- add 1 to hour counter
                            c += 1
                    #-- close the input file for day
                    fid.close()
                    #-- year fraction of the particular date and times
                    YEAR = np.repeat(Y[count // n_time], n_time).astype('f')
                    MONTH = np.repeat(M[count // n_time], n_time).astype('f')
                    DAY = np.repeat(D[count // n_time], n_time).astype('f')
                    Ylms.time[count:count+n_time] = \
                        gravity_toolkit.time.convert_calendar_decimal(YEAR,
                        MONTH, day=DAY, hour=hours)
                    #-- add to day counter
                    count += n_time

            #-- calculate mean harmonics for GRACE/GRACE-FO month
            #-- convert from harmonics object to dealiasing object
            mean_Ylms = dealiasing().from_harmonics(Ylms.mean())
            mean_Ylms.time = np.mean(Ylms.time)
            mean_Ylms.month = np.int64(gm)
            #-- product information
            mean_Ylms.center = PROC
            mean_Ylms.release = DREL
            mean_Ylms.product = DSET
            #-- start and end time for month
            start_time = gravity_toolkit.time.convert_julian(np.min(JD))
            mean_Ylms.start_time = [
                '{0:4.0f}'.format(start_time['year']),
                '{0:02.0f}'.format(start_time['month']),
                '{0:02.0f}'.format(start_time['day'])
            ]
            end_time = gravity_toolkit.time.convert_julian(np.max(JD))
            mean_Ylms.end_time = [
                '{0:4.0f}'.format(end_time['year']),
                '{0:02.0f}'.format(end_time['month']),
                '{0:02.0f}'.format(end_time['day'])
            ]
            #-- output mean Ylms to file
            if (DATAFORM == 'ascii'):
                #-- ascii (.txt)
                mean_Ylms.to_ascii(os.path.join(grace_dir, DSET, FILE))
            elif (DATAFORM == 'netCDF4'):
                #-- netcdf (.nc)
                mean_Ylms.to_netCDF4(os.path.join(grace_dir, DSET, FILE))
            elif (DATAFORM == 'HDF5'):
                #-- HDF5 (.H5)
                mean_Ylms.to_HDF5(os.path.join(grace_dir, DSET, FILE))
            elif (DATAFORM == 'SHM'):
                mean_Ylms.to_SHM(os.path.join(grace_dir, DSET, FILE))
            #-- set the permissions mode of the output file
            os.chmod(os.path.join(grace_dir, DSET, FILE), MODE)

        elif not COMPLETE:
            logging.info('File {0} not output (incomplete)'.format(FILE))

    #-- if outputting as spherical harmonic model files
    if (DATAFORM == 'SHM'):
        #-- Create an index file for each GRACE product
        grace_files = [
            fi for fi in os.listdir(os.path.join(grace_dir, DSET))
            if re.match(r'{0}-2(.*?)\.gz'.format(DSET), fi)
        ]
        #-- outputting GRACE filenames to index
        with open(os.path.join(grace_dir, DSET, 'index.txt'), 'w') as fid:
            for fi in sorted(grace_files):
                print(fi, file=fid)
        #-- change permissions of index file
        os.chmod(os.path.join(grace_dir, DSET, 'index.txt'), MODE)

    #-- print completion flag
    logging.info('Complete: {0}/{1}/{2}'.format(PROC, DREL, DSET))
    #-- close the output date file
    f_out.close()
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 mascon_reconstruct(DSET,
                       LMAX,
                       RAD,
                       START=None,
                       END=None,
                       MMAX=None,
                       DESTRIPE=False,
                       LOVE_NUMBERS=0,
                       REFERENCE=None,
                       GIA=None,
                       GIA_FILE=None,
                       ATM=False,
                       DATAFORM=None,
                       MASCON_FILE=None,
                       REDISTRIBUTE_MASCONS=False,
                       RECONSTRUCT_FILE=None,
                       LANDMASK=None,
                       OUTPUT_DIRECTORY=None,
                       MODE=0o775):

    #-- for datasets not GSM: will add a label for the dataset
    dset_str = '' if (DSET == 'GSM') else '_{0}'.format(DSET)
    #-- atmospheric ECMWF "jump" flag (if ATM)
    atm_str = '_wATM' if ATM else ''
    #-- Gaussian smoothing string for radius RAD
    gw_str = '_r{0:0.0f}km'.format(RAD) if (RAD != 0) else ''
    #-- input GIA spherical harmonic datafiles
    GIA_Ylms_rate = read_GIA_model(GIA_FILE, GIA=GIA, LMAX=LMAX, MMAX=MMAX)
    gia_str = '_{0}'.format(GIA_Ylms_rate['title']) if GIA else ''
    #-- output string for both LMAX==MMAX and LMAX != MMAX cases
    MMAX = np.copy(LMAX) if not MMAX else MMAX
    order_str = 'M{0:d}'.format(MMAX) if (MMAX != LMAX) else ''
    #-- filter grace coefficients flag
    ds_str = '_FL' if DESTRIPE else ''
    #-- output filename suffix
    suffix = dict(ascii='txt', netCDF4='nc', HDF5='H5')
    #-- file parser for reading index files
    #-- removes commented lines (can comment out files in the index)
    #-- removes empty lines (if there are extra empty lines)
    parser = re.compile(r'^(?!\#|\%|$)', re.VERBOSE)

    #-- create initial reconstruct index for calc_mascon.py
    fid = open(RECONSTRUCT_FILE, 'w')
    #-- output file format
    file_format = '{0}{1}{2}{3}{4}_L{5:d}{6}{7}{8}_{9:03d}-{10:03d}.{11}'

    #-- read load love numbers
    hl, kl, ll = load_love_numbers(LMAX,
                                   LOVE_NUMBERS=LOVE_NUMBERS,
                                   REFERENCE=REFERENCE)
    #-- Earth Parameters
    factors = units(lmax=LMAX).harmonic(hl, kl, ll)
    #-- Average Density of the Earth [g/cm^3]
    rho_e = factors.rho_e
    #-- Average Radius of the Earth [cm]
    rad_e = factors.rad_e
    #-- Read Ocean function and convert to Ylms for redistribution
    if REDISTRIBUTE_MASCONS:
        #-- read Land-Sea Mask and convert to spherical harmonics
        ocean_Ylms = ocean_stokes(LANDMASK, LMAX, MMAX=MMAX, LOVE=(hl, kl, ll))
        ocean_str = '_OCN'
    else:
        #-- not distributing uniformly over ocean
        ocean_str = ''

    #-- input mascon spherical harmonic datafiles
    with open(MASCON_FILE, 'r') as f:
        mascon_files = [l for l in f.read().splitlines() if parser.match(l)]
    for k, fi in enumerate(mascon_files):
        #-- read mascon spherical harmonics
        if (DATAFORM == 'ascii'):
            #-- ascii (.txt)
            Ylms = harmonics().from_ascii(os.path.expanduser(fi), date=False)
        elif (DATAFORM == 'netCDF4'):
            #-- netcdf (.nc)
            Ylms = harmonics().from_netCDF4(os.path.expanduser(fi), date=False)
        elif (DATAFORM == 'HDF5'):
            #-- HDF5 (.H5)
            Ylms = harmonics().from_HDF5(os.path.expanduser(fi), date=False)
        #-- Calculating the total mass of each mascon (1 cmwe uniform)
        total_area = 4.0 * np.pi * (rad_e**3) * rho_e * Ylms.clm[0, 0] / 3.0
        #-- distribute mascon mass uniformly over the ocean
        if REDISTRIBUTE_MASCONS:
            #-- calculate ratio between total mascon mass and
            #-- a uniformly distributed cm of water over the ocean
            ratio = Ylms.clm[0, 0] / ocean_Ylms.clm[0, 0]
            #-- for each spherical harmonic
            for m in range(0, MMAX + 1):  #-- MMAX+1 to include MMAX
                for l in range(m, LMAX + 1):  #-- LMAX+1 to include LMAX
                    #-- remove ratio*ocean Ylms from mascon Ylms
                    #-- note: x -= y is equivalent to x = x - y
                    Ylms.clm[l, m] -= ratio * ocean_Ylms.clm[l, m]
                    Ylms.slm[l, m] -= ratio * ocean_Ylms.slm[l, m]
        #-- truncate mascon spherical harmonics to d/o LMAX/MMAX
        Ylms = Ylms.truncate(lmax=LMAX, mmax=MMAX)
        #-- mascon base is the file without directory or suffix
        mascon_base = os.path.basename(fi)
        mascon_base = os.path.splitext(mascon_base)[0]
        #-- if lower case, will capitalize
        mascon_base = mascon_base.upper()
        #-- if mascon name contains degree and order info, remove
        mascon_name = mascon_base.replace('_L{0:d}'.format(LMAX), '')

        #-- input filename format (for both LMAX==MMAX and LMAX != MMAX cases):
        #-- mascon name, GRACE dataset, GIA model, LMAX, (MMAX,)
        #-- Gaussian smoothing, filter flag, remove reconstructed fields flag
        #-- output GRACE error file
        args = (mascon_name, dset_str, gia_str.upper(), atm_str, ocean_str,
                LMAX, order_str, gw_str, ds_str)
        file_input = '{0}{1}{2}{3}{4}_L{5:d}{6}{7}{8}.txt'.format(*args)
        mascon_data_input = np.loadtxt(
            os.path.join(OUTPUT_DIRECTORY, file_input))

        #-- convert mascon time-series from Gt to cmwe
        mascon_sigma = 1e15 * mascon_data_input[:, 2] / total_area
        #-- mascon time-series Ylms
        mascon_Ylms = Ylms.scale(mascon_sigma)
        mascon_Ylms.time = mascon_data_input[:, 1].copy()
        mascon_Ylms.month = mascon_data_input[:, 0].astype(np.int64)

        #-- output to file: no ascii option
        args = (mascon_name, dset_str, gia_str.upper(), atm_str, ocean_str,
                LMAX, order_str, gw_str, ds_str, START, END, suffix[DATAFORM])
        FILE = file_format.format(*args)
        #-- output harmonics to file
        if (DATAFORM == 'netCDF4'):
            #-- netcdf (.nc)
            mascon_Ylms.to_netCDF4(os.path.join(OUTPUT_DIRECTORY, FILE))
        elif (DATAFORM == 'HDF5'):
            #-- HDF5 (.H5)
            mascon_Ylms.to_HDF5(os.path.join(OUTPUT_DIRECTORY, FILE))
        #-- print file name to index
        print(tilde_compress(os.path.join(OUTPUT_DIRECTORY, FILE)), file=fid)
        #-- change the permissions mode
        os.chmod(os.path.join(OUTPUT_DIRECTORY, FILE), MODE)
    #-- close the reconstruct index
    fid.close()
    #-- change the permissions mode of the index file
    os.chmod(RECONSTRUCT_FILE, MODE)
def 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
Exemple #11
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 mascon_reconstruct(parameters, LOVE_NUMBERS=0, REFERENCE=None, MODE=0o775):
    #-- convert parameters into variables
    #-- Data processing center
    PROC = parameters['PROC']
    #-- Data Release
    DREL = parameters['DREL']
    #-- GRACE dataset
    DSET = parameters['DSET']
    #-- Date Range
    START_MON = np.int(parameters['START'])
    END_MON = np.int(parameters['END'])
    #-- spherical harmonic parameters
    LMAX = np.int(parameters['LMAX'])
    #-- maximum spherical harmonic order
    if (parameters['MMAX'].title() == 'None'):
        MMAX = np.copy(LMAX)
    else:
        MMAX = np.int(parameters['MMAX'])
    #-- gaussian smoothing radius
    RAD = np.float(parameters['RAD'])
    #-- filtered coefficients for stripe effects
    DESTRIPE = parameters['DESTRIPE'] in ('Y', 'y')
    #-- ECMWF jump corrections
    ATM = parameters['ATM'] in ('Y', 'y')
    #-- Glacial Isostatic Adjustment file to read
    GIA = parameters['GIA'] if (parameters['GIA'].title() != 'None') else None
    GIA_FILE = os.path.expanduser(parameters['GIA_FILE'])
    #-- input/output data format (ascii, netCDF4, HDF5)
    DATAFORM = parameters['DATAFORM']
    #-- index of mascons spherical harmonics
    #-- path.expanduser = tilde expansion of path
    MASCON_INDEX = os.path.expanduser(parameters['MASCON_INDEX'])
    #-- mascon distribution over the ocean
    MASCON_OCEAN = parameters['MASCON_OCEAN'] in ('Y', 'y')

    #-- for datasets not GSM: will add a label for the dataset
    dset_str = '' if (DSET == 'GSM') else '_{0}'.format(DSET)
    #-- atmospheric ECMWF "jump" flag (if ATM)
    atm_str = '_wATM' if ATM else ''
    #-- Gaussian smoothing string for radius RAD
    gw_str = '_r{0:0.0f}km'.format(RAD) if (RAD != 0) else ''
    #-- input GIA spherical harmonic datafiles
    GIA_Ylms_rate = read_GIA_model(GIA_FILE, GIA=GIA, LMAX=LMAX, MMAX=MMAX)
    gia_str = '_{0}'.format(GIA_Ylms_rate['title']) if GIA else ''
    #-- output string for both LMAX==MMAX and LMAX != MMAX cases
    order_str = 'M{0:d}'.format(MMAX) if (MMAX != LMAX) else ''
    #-- filter grace coefficients flag
    ds_str = '_FL' if DESTRIPE else ''
    #-- output filename suffix
    suffix = dict(ascii='txt', netCDF4='nc', HDF5='H5')

    #-- GRACE output filename prefix
    #-- mascon directory for GRACE product, processing and date range
    DIRECTORY = os.path.expanduser(parameters['DIRECTORY'])

    #-- create initial reconstruct index for calc_mascon.py
    fid = open(os.path.expanduser(parameters['RECONSTRUCT_INDEX']), 'w')
    HOME = os.path.expanduser('~')
    #-- output file format
    file_format = '{0}{1}{2}{3}{4}_L{5:d}{6}{7}{8}_{9:03d}-{10:03d}.{11}'

    #-- read load love numbers
    hl, kl, ll = load_love_numbers(LMAX,
                                   LOVE_NUMBERS=LOVE_NUMBERS,
                                   REFERENCE=REFERENCE)
    #-- Earth Parameters
    factors = units(lmax=LMAX).harmonic(hl, kl, ll)
    #-- Average Density of the Earth [g/cm^3]
    rho_e = factors.rho_e
    #-- Average Radius of the Earth [cm]
    rad_e = factors.rad_e
    #-- Read Ocean function and convert to Ylms for redistribution
    if MASCON_OCEAN:
        #-- read Land-Sea Mask and convert to spherical harmonics
        LANDMASK = os.path.expanduser(parameters['LANDMASK'])
        ocean_Ylms = ocean_stokes(LANDMASK, LMAX, MMAX=MMAX, LOVE=(hl, kl, ll))
        ocean_str = '_OCN'
    else:
        #-- not distributing uniformly over ocean
        ocean_str = ''

    #-- input mascon spherical harmonic datafiles
    with open(MASCON_INDEX, 'r') as f:
        mascon_files = f.read().splitlines()
    for k, fi in enumerate(mascon_files):
        #-- read mascon spherical harmonics
        if (DATAFORM == 'ascii'):
            #-- ascii (.txt)
            Ylms = harmonics().from_ascii(os.path.expanduser(fi), date=False)
        elif (DATAFORM == 'netCDF4'):
            #-- netcdf (.nc)
            Ylms = harmonics().from_netCDF4(os.path.expanduser(fi), date=False)
        elif (DATAFORM == 'HDF5'):
            #-- HDF5 (.H5)
            Ylms = harmonics().from_HDF5(os.path.expanduser(fi), date=False)
        #-- Calculating the total mass of each mascon (1 cmwe uniform)
        total_area = 4.0 * np.pi * (rad_e**3) * rho_e * Ylms.clm[0, 0] / 3.0
        #-- distribute MASCON mass uniformly over the ocean
        if MASCON_OCEAN:
            #-- calculate ratio between total mascon mass and
            #-- a uniformly distributed cm of water over the ocean
            ratio = Ylms.clm[0, 0] / ocean_Ylms['clm'][0, 0]
            #-- for each spherical harmonic
            for m in range(0, MMAX + 1):  #-- MMAX+1 to include MMAX
                for l in range(m, LMAX + 1):  #-- LMAX+1 to include LMAX
                    #-- remove ratio*ocean Ylms from mascon Ylms
                    #-- note: x -= y is equivalent to x = x - y
                    Ylms.clm[l, m] -= ratio * ocean_Ylms['clm'][l, m]
                    Ylms.slm[l, m] -= ratio * ocean_Ylms['slm'][l, m]
        #-- truncate mascon spherical harmonics to d/o LMAX/MMAX
        Ylms = Ylms.truncate(lmax=LMAX, mmax=MMAX)
        #-- mascon base is the file without directory or suffix
        mascon_base = os.path.basename(fi)
        mascon_base = os.path.splitext(mascon_base)[0]
        #-- if lower case, will capitalize
        mascon_base = mascon_base.upper()
        #-- if mascon name contains degree and order info, remove
        mascon_name = mascon_base.replace('_L{0:d}'.format(LMAX), '')

        #-- input filename format (for both LMAX==MMAX and LMAX != MMAX cases):
        #-- mascon name, GRACE dataset, GIA model, LMAX, (MMAX,)
        #-- Gaussian smoothing, filter flag, remove reconstructed fields flag
        #-- output GRACE error file
        args = (mascon_name, dset_str, gia_str.upper(), atm_str, ocean_str,
                LMAX, order_str, gw_str, ds_str)
        file_input = '{0}{1}{2}{3}{4}_L{5:d}{6}{7}{8}.txt'.format(*args)
        mascon_data_input = np.loadtxt(os.path.join(DIRECTORY, file_input))
        nmon = np.shape(mascon_data_input)[0]

        #-- convert mascon time-series from Gt to cmwe
        mascon_sigma = 1e15 * mascon_data_input[:, 2] / total_area
        #-- mascon time-series Ylms
        mascon_Ylms = Ylms.scale(mascon_sigma)
        mascon_Ylms.time = mascon_data_input[:, 1].copy()
        mascon_Ylms.month = mascon_data_input[:, 0].astype(np.int)

        #-- output to file: no ascii option
        args = (mascon_name, dset_str, gia_str.upper(), atm_str, ocean_str,
                LMAX, order_str, gw_str, ds_str, START_MON, END_MON,
                suffix[DATAFORM])
        FILE = file_format.format(*args)
        #-- output harmonics to file
        if (DATAFORM == 'netCDF4'):
            #-- netcdf (.nc)
            mascon_Ylms.to_netCDF4(os.path.join(DIRECTORY, FILE))
        elif (DATAFORM == 'HDF5'):
            #-- HDF5 (.H5)
            mascon_Ylms.to_HDF5(os.path.join(DIRECTORY, FILE))
        #-- print file name to index
        print(os.path.join(DIRECTORY, FILE).replace(HOME, '~'), file=fid)
        #-- change the permissions mode
        os.chmod(os.path.join(DIRECTORY, FILE), MODE)
    #-- close the reconstruct index
    fid.close()
    #-- change the permissions mode of the index file
    os.chmod(os.path.expanduser(parameters['RECONSTRUCT_INDEX']), MODE)
def 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
Exemple #14
0
def calc_mascon(base_dir,
                parameters,
                LOVE_NUMBERS=0,
                REFERENCE=None,
                MODE=0o775):
    #-- convert parameters to variables
    #-- Data processing center
    PROC = parameters['PROC']
    #-- Data Release
    DREL = parameters['DREL']
    #-- GRACE dataset
    DSET = parameters['DSET']
    #-- Date Range and missing months
    start_mon = np.int(parameters['START'])
    end_mon = np.int(parameters['END'])
    missing = np.array(parameters['MISSING'].split(','), dtype=np.int)
    #-- spherical harmonic degree range
    LMIN = np.int(parameters['LMIN'])
    LMAX = np.int(parameters['LMAX'])
    #-- maximum spherical harmonic order
    if (parameters['MMAX'].title() == 'None'):
        MMAX = np.copy(LMAX)
    else:
        MMAX = np.int(parameters['MMAX'])
    #-- degree 1 coefficients
    #-- None: No degree 1
    #-- Tellus: GRACE/GRACE-FO TN-13 from PO.DAAC
    #--     https://grace.jpl.nasa.gov/data/get-data/geocenter/
    #-- SLR: satellite laser ranging from CSR
    #--     ftp://ftp.csr.utexas.edu/pub/slr/geocenter/
    #-- SLF: Sutterley and Velicogna, Remote Sensing (2019)
    #--     https://www.mdpi.com/2072-4292/11/18/2108
    DEG1 = parameters['DEG1']
    #-- replace C20, C30 with coefficients from SLR
    SLR_C20 = parameters['SLR_C20']
    SLR_C30 = parameters['SLR_C30']
    #-- ECMWF jump corrections
    ATM = parameters['ATM'] in ('Y', 'y')
    #-- Pole-Tide from Wahr et al. (2015)
    POLE_TIDE = parameters['POLE_TIDE'] in ('Y', 'y')
    #-- Glacial Isostatic Adjustment file to read
    GIA = parameters['GIA'] if (parameters['GIA'].title() != 'None') else None
    GIA_FILE = os.path.expanduser(parameters['GIA_FILE'])
    #-- remove a set of spherical harmonics from the GRACE data
    REMOVE_INDEX = parameters['REMOVE_INDEX']
    REDISTRIBUTE_REMOVED = parameters['REDISTRIBUTE_REMOVED'] in ('Y', 'y')
    #-- remove reconstructed fields
    RECONSTRUCT = parameters['RECONSTRUCT'] in ('Y', 'y')
    #-- gaussian smoothing radius
    RAD = np.float(parameters['RAD'])
    #-- filter coefficients for stripe effects
    DESTRIPE = parameters['DESTRIPE'] in ('Y', 'y')
    #-- input/output data format (ascii, netCDF4, HDF5)
    DATAFORM = parameters['DATAFORM']
    #-- index of mascons spherical harmonics
    #-- path.expanduser = tilde expansion of path
    MASCON_INDEX = os.path.expanduser(parameters['MASCON_INDEX'])
    #-- output directory for mascon time series files
    DIRECTORY = os.path.expanduser(parameters['DIRECTORY'])
    #-- 1: fit mass, 2: fit geoid
    FIT_METHOD = np.int(parameters['FIT_METHOD'])
    #-- mascon redistribution
    MASCON_OCEAN = parameters['MASCON_OCEAN'] in ('Y', 'y')

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

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

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

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

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

    #-- for datasets not GSM: will add a label for the dataset
    dset_str = '' if (DSET == 'GSM') else '_{0}'.format(DSET)
    #-- atmospheric ECMWF "jump" flag (if ATM)
    atm_str = '_wATM' if ATM else ''
    #-- output string for both LMAX==MMAX and LMAX != MMAX cases
    order_str = 'M{0:d}'.format(MMAX) if (MMAX != LMAX) else ''

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

    #-- Read Ocean function and convert to Ylms for redistribution
    if (MASCON_OCEAN | REDISTRIBUTE_REMOVED):
        #-- read Land-Sea Mask and convert to spherical harmonics
        LSMASK = os.path.expanduser(parameters['LANDMASK'])
        ocean_Ylms = ocean_stokes(LSMASK, LMAX, MMAX=MMAX, LOVE=(hl, kl, ll))
        ocean_str = '_OCN'
    else:
        #-- not distributing uniformly over ocean
        ocean_str = ''

    #-- input GRACE/GRACE-FO spherical harmonic datafiles
    #-- reading GRACE months for input range with grace_input_months.py
    #-- replacing SLR and Degree 1 if specified
    #-- correcting for Pole-Tide and Atmospheric Jumps if specified
    Ylms = grace_input_months(base_dir,
                              PROC,
                              DREL,
                              DSET,
                              LMAX,
                              start_mon,
                              end_mon,
                              missing,
                              SLR_C20,
                              DEG1,
                              MMAX=MMAX,
                              SLR_C30=SLR_C30,
                              MODEL_DEG1=True,
                              ATM=ATM,
                              POLE_TIDE=POLE_TIDE)
    #-- full path to directory for specific GRACE/GRACE-FO product
    grace_dir = Ylms['directory']
    #-- create harmonics object from GRACE/GRACE-FO data
    GRACE_Ylms = harmonics().from_dict(Ylms)
    #-- use a mean file for the static field to remove
    if (parameters['MEAN_FILE'].title() == 'None'):
        GRACE_Ylms.mean(apply=True)
    else:
        #-- read data form for input mean file (ascii, netCDF4, HDF5, gfc)
        if (parameters['MEANFORM'] == 'ascii'):
            mean_Ylms = harmonics().from_ascii(parameters['MEAN_FILE'],
                                               date=False)
        elif (parameters['MEANFORM'] == 'netCDF4'):
            mean_Ylms = harmonics().from_netCDF4(parameters['MEAN_FILE'],
                                                 date=False)
        elif (parameters['MEANFORM'] == 'HDF5'):
            mean_Ylms = harmonics().from_HDF5(parameters['MEAN_FILE'],
                                              date=False)
        elif (parameters['MEANFORM'] == 'gfc'):
            mean_Ylms = harmonics().from_gfc(parameters['MEAN_FILE'])
        #-- remove the input mean
        GRACE_Ylms.subtract(mean_Ylms)
    #-- date information of GRACE/GRACE-FO coefficients
    n_files = len(GRACE_Ylms.time)

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

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

    #-- input spherical harmonic datafiles to be removed from the GRACE data
    #-- Remove sets of Ylms from the GRACE data before returning
    remove_Ylms = GRACE_Ylms.zeros_like()
    remove_Ylms.time[:] = np.copy(GRACE_Ylms.time)
    remove_Ylms.month[:] = np.copy(GRACE_Ylms.month)
    if (parameters['REMOVE_INDEX'].title() != 'None'):
        #-- for each file index separated by commas
        for REMOVE_INDEX in parameters['REMOVE_INDEX'].split(','):
            Ylms = harmonics().from_index(REMOVE_INDEX, DATAFORM)
            #-- reduce to GRACE/GRACE-FO months and truncate to degree and order
            Ylms = Ylms.subset(GRACE_Ylms.month).truncate(lmax=LMAX, mmax=MMAX)
            #-- distribute removed Ylms uniformly over the ocean
            if REDISTRIBUTE_REMOVED:
                #-- calculate ratio between total removed mass and
                #-- a uniformly distributed cm of water over the ocean
                ratio = Ylms.clm[0, 0, :] / ocean_Ylms['clm'][0, 0]
                #-- for each spherical harmonic
                for m in range(0, MMAX + 1):  #-- MMAX+1 to include MMAX
                    for l in range(m, LMAX + 1):  #-- LMAX+1 to include LMAX
                        #-- remove the ratio*ocean Ylms from Ylms
                        #-- note: x -= y is equivalent to x = x - y
                        Ylms.clm[l, m, :] -= ratio * ocean_Ylms['clm'][l, m]
                        Ylms.slm[l, m, :] -= ratio * ocean_Ylms['slm'][l, m]
            #-- filter removed coefficients
            if DESTRIPE:
                Ylms = Ylms.destripe()
            #-- add data for month t and INDEX_FILE to the total
            #-- remove_clm and remove_slm matrices
            #-- redistributing the mass over the ocean if specified
            remove_Ylms.add(Ylms)

    #-- input reconstructed spherical harmonic datafiles
    construct_Ylms = GRACE_Ylms.zeros_like()
    construct_Ylms.time[:] = np.copy(GRACE_Ylms.time)
    construct_Ylms.month[:] = np.copy(GRACE_Ylms.month)
    if RECONSTRUCT:
        #-- input index for reconstructed spherical harmonic datafiles
        RECONSTRUCT_INDEX = os.path.expanduser(parameters['RECONSTRUCT_INDEX'])
        with open(RECONSTRUCT_INDEX, 'r') as f:
            reconstruct_files = f.read().splitlines()
        #-- remove commented lines (can comment out files in the index)
        reconstruct_files = [
            f for f in reconstruct_files if not re.match('#', f)
        ]
        #-- for each valid file in the index (iterate over mascons)
        for construct_file in reconstruct_files:
            #-- read reconstructed spherical harmonics
            if (DATAFORM == 'ascii'):
                #-- ascii (.txt)
                Ylms = harmonics().from_ascii(construct_file)
            elif (DATAFORM == 'netCDF4'):
                #-- netcdf (.nc)
                Ylms = harmonics().from_netCDF4(construct_file)
            elif (DATAFORM == 'HDF5'):
                #-- HDF5 (.H5)
                Ylms = harmonics().from_HDF5(construct_file)
            #-- truncate clm and slm matrices to LMAX/MMAX
            #-- add harmonics object to total
            construct_Ylms.add(Ylms.truncate(lmax=LMAX, mmax=MMAX))
        #-- filter reconstructed coefficients
        if DESTRIPE:
            construct_Ylms = construct_Ylms.destripe()
        #-- set flag for removing reconstructed coefficients
        construct_str = '_LEAKAGE'
    else:
        #-- set flag for not removing the reconstructed coefficients
        construct_str = ''

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

    #-- calculating GRACE/GRACE-FO error (Wahr et al. 2006)
    #-- output GRACE error file (for both LMAX==MMAX and LMAX != MMAX cases)
    args = (PROC, DREL, DSET, LMAX, order_str, ds_str, atm_str,
            GRACE_Ylms.month[0], GRACE_Ylms.month[-1], suffix[DATAFORM])
    delta_format = '{0}_{1}_{2}_DELTA_CLM_L{3:d}{4}{5}{6}_{7:03d}-{8:03d}.{9}'
    DELTA_FILE = delta_format.format(*args)
    #-- check full path of the GRACE directory for delta file
    #-- if file was previously calculated: will read file
    #-- else: will calculate the GRACE/GRACE-FO error
    if (not os.access(os.path.join(grace_dir, DELTA_FILE), os.F_OK)):
        #-- add output delta file to list object
        output_files.append(os.path.join(grace_dir, DELTA_FILE))

        #-- Delta coefficients of GRACE time series (Error components)
        delta_Ylms = harmonics(lmax=LMAX, mmax=MMAX)
        delta_Ylms.clm = np.zeros((LMAX + 1, MMAX + 1))
        delta_Ylms.slm = np.zeros((LMAX + 1, MMAX + 1))
        #-- Smoothing Half-Width (CNES is a 10-day solution)
        #-- All other solutions are monthly solutions (HFWTH for annual = 6)
        if ((PROC == 'CNES') and (DREL in ('RL01', 'RL02'))):
            HFWTH = 19
        else:
            HFWTH = 6
        #-- Equal to the noise of the smoothed time-series
        #-- for each spherical harmonic order
        for m in range(0, MMAX + 1):  #-- MMAX+1 to include MMAX
            #-- for each spherical harmonic degree
            for l in range(m, LMAX + 1):  #-- LMAX+1 to include LMAX
                #-- Delta coefficients of GRACE time series
                for cs, csharm in enumerate(['clm', 'slm']):
                    #-- calculate GRACE Error (Noise of smoothed time-series)
                    #-- With Annual and Semi-Annual Terms
                    val1 = getattr(GRACE_Ylms, csharm)
                    smth = tssmooth(GRACE_Ylms.time,
                                    val1[l, m, :],
                                    HFWTH=HFWTH)
                    #-- number of smoothed points
                    nsmth = len(smth['data'])
                    tsmth = np.mean(smth['time'])
                    #-- GRACE delta Ylms
                    #-- variance of data-(smoothed+annual+semi)
                    val2 = getattr(delta_Ylms, csharm)
                    val2[l, m] = np.sqrt(np.sum(smth['noise']**2) / nsmth)

        #-- save GRACE/GRACE-FO delta harmonics to file
        delta_Ylms.time = np.copy(tsmth)
        delta_Ylms.month = np.int(nsmth)
        if (DATAFORM == 'ascii'):
            #-- ascii (.txt)
            delta_Ylms.to_ascii(os.path.join(grace_dir, DELTA_FILE))
        elif (DATAFORM == 'netCDF4'):
            #-- netcdf (.nc)
            delta_Ylms.to_netCDF4(os.path.join(grace_dir, DELTA_FILE))
        elif (DATAFORM == 'HDF5'):
            #-- HDF5 (.H5)
            delta_Ylms.to_HDF5(os.path.join(grace_dir, DELTA_FILE))
    else:
        #-- read GRACE/GRACE-FO delta harmonics from file
        if (DATAFORM == 'ascii'):
            #-- ascii (.txt)
            delta_Ylms = harmonics().from_ascii(
                os.path.join(grace_dir, DELTA_FILE))
        elif (DATAFORM == 'netCDF4'):
            #-- netcdf (.nc)
            delta_Ylms = harmonics().from_netCDF4(
                os.path.join(grace_dir, DELTA_FILE))
        elif (DATAFORM == 'HDF5'):
            #-- HDF5 (.H5)
            delta_Ylms = harmonics().from_HDF5(
                os.path.join(grace_dir, DELTA_FILE))
        #-- truncate GRACE/GRACE-FO delta clm and slm to d/o LMAX/MMAX
        delta_Ylms = delta_Ylms.truncate(lmax=LMAX, mmax=MMAX)
        tsmth = np.squeeze(delta_Ylms.time)
        nsmth = np.int(delta_Ylms.month)

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

    #-- Initialing harmonics for least squares fitting
    #-- mascon kernel
    M_lm = np.zeros((n_harm, n_mas))
    #-- mascon kernel converted to output unit
    MA_lm = np.zeros((n_harm, n_mas))
    #-- corrected clm and slm
    Y_lm = np.zeros((n_harm, n_files))
    #-- sensitivity kernel
    A_lm = np.zeros((n_harm, n_mas))
    #-- Satellite error harmonics
    delta_lm = np.zeros((n_harm))
    #-- Initializing output Mascon time-series
    mascon = np.zeros((n_mas, n_files))
    #-- Mascon satellite error component
    M_delta = np.zeros((n_mas))
    #-- Initializing conversion factors
    #-- factor for converting to coefficients of mass
    fact = np.zeros((n_harm))
    #-- smoothing factor
    wt_lm = np.zeros((n_harm))

    #-- ii is a counter variable for building the mascon column array
    ii = 0
    #-- Creating column array of clm/slm coefficients
    #-- Order is [C00...C6060,S11...S6060]
    #-- Calculating factor to convert geoid spherical harmonic coefficients
    #-- to coefficients of mass (Wahr, 1998)
    coeff = rho_e * rad_e / 3.0
    #-- Switching between Cosine and Sine Stokes
    for cs, csharm in enumerate(['clm', 'slm']):
        #-- copy cosine and sin harmonics
        mascon_harm = getattr(mascon_Ylms, csharm)
        grace_harm = getattr(GRACE_Ylms, csharm)
        GIA_harm = getattr(GIA_Ylms, csharm)
        remove_harm = getattr(remove_Ylms, csharm)
        construct_harm = getattr(construct_Ylms, csharm)
        delta_harm = getattr(delta_Ylms, csharm)
        #-- for each spherical harmonic degree
        #-- +1 to include LMAX
        for l in range(LMIN, LMAX + 1):
            #-- for each spherical harmonic order
            #-- Sine Stokes for (m=0) = 0
            mm = np.min([MMAX, l])
            #-- +1 to include l or MMAX (whichever is smaller)
            for m in range(cs, mm + 1):
                #-- Mascon Spherical Harmonics
                M_lm[ii, :] = np.copy(mascon_harm[l, m, :])
                #-- GRACE Spherical Harmonics
                #-- Correcting GRACE Harmonics for GIA and Removed Terms
                Y_lm[ii,:] = grace_harm[l,m,:] - GIA_harm[l,m,:] - \
                    remove_harm[l,m,:] - construct_harm[l,m,:]
                #-- GRACE delta spherical harmonics
                delta_lm[ii] = np.copy(delta_harm[l, m])
                #-- degree dependent factor to convert to mass
                fact[ii] = (2.0 * l + 1.0) / (1.0 + kl[l])
                #-- degree dependent smoothing
                wt_lm[ii] = np.copy(wt[l])
                #-- add 1 to counter
                ii += 1

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

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

    #-- for each mascon
    for k in range(n_mas):
        #-- Multiply the Satellite error (noise of a smoothed time-series
        #-- with annual and semi-annual components) by the sensitivity kernel
        #-- Converting to Gigatonnes
        M_delta[k] = np.sqrt(np.sum((delta_lm * A_lm[:, k])**2)) / 1e15

        #-- output filename format (for both LMAX==MMAX and LMAX != MMAX cases):
        #-- mascon name, GRACE dataset, GIA model, LMAX, (MMAX,)
        #-- Gaussian smoothing, filter flag, remove reconstructed fields flag
        #-- output GRACE error file
        file_out = '{0}{1}{2}{3}{4}_L{5:d}{6}{7}{8}{9}.txt'.format(
            mascon_name[k], dset_str, gia_str.upper(), atm_str, ocean_str,
            LMAX, order_str, gw_str, ds_str, construct_str)

        #-- Output mascon datafiles
        #-- Will output each mascon time series
        #-- month, date, mascon mass [Gt], satellite error [Gt], mascon area [km^2]
        #-- open output mascon time-series file
        fid = open(os.path.join(DIRECTORY, file_out), 'w')
        #-- for each date
        formatting_string = '{0:03d} {1:12.4f} {2:16.10f} {3:16.10f} {4:16.5f}'
        for t, mon in enumerate(GRACE_Ylms.month):
            #-- Summing over all spherical harmonics for mascon k, and time t
            #-- multiplies by the degree dependent factor to convert
            #-- the harmonics into mass coefficients
            #-- Converting mascon mass time-series from g to gigatonnes
            mascon[k, t] = np.sum(A_lm[:, k] * Y_lm[:, t]) / 1e15
            #-- output to file
            args = (mon, GRACE_Ylms.time[t], mascon[k, t], M_delta[k],
                    total_area[k] / 1e10)
            print(formatting_string.format(*args), file=fid)
        #-- close the output file
        fid.close()
        #-- change the permissions mode
        os.chmod(os.path.join(DIRECTORY, file_out), MODE)
        #-- add output files to list object
        output_files.append(os.path.join(DIRECTORY, file_out))

    #-- return the list of output files
    return output_files