Source code for gravity_toolkit.gen_spherical_cap

#!/usr/bin/env python
u"""
gen_spherical_cap.py
Written by Tyler Sutterley (06/2023)
Calculates gravitational spherical harmonic coefficients for a spherical cap

Creating a spherical cap with generating angle alpha is a 2 step process:
    1) obtain harmonics when cap is located at the north pole
    2) rotate the cap to an arbitrary latitude and longitude

CALLING SEQUENCE:
    Ylms = gen_spherical_cap(data, lon, lat, LMAX=LMAX, RAD_CAP=RAD_CAP)

INPUTS:
    data: data magnitude
    lon: longitude of spherical cap center
    lat: latitude of spherical cap center

OUTPUTS:
    clm: cosine spherical harmonic coefficients (geodesy normalization)
    slm: sine spherical harmonic coefficients (geodesy normalization)
    l: spherical harmonic degree to LMAX
    m: spherical harmonic order to MMAX

OPTIONS:
    LMAX: Upper bound of Spherical Harmonic Degrees
    MMAX: Upper bound of Spherical Harmonic Orders
    RAD_CAP: spherical cap radius in degrees
    RAD_KM: spherical cap radius in kilometers
    AREA: spherical cap area in cm^2
    UNITS: input data units
        1: cm of water thickness (default)
        2: gigatonnes of mass
        3: kg/m^2
        list: custom unit conversion factor
    PLM: input Legendre polynomials
    LOVE: input load Love numbers up to degree LMAX (hl,kl,ll)

PYTHON DEPENDENCIES:
    numpy: Scientific Computing Tools For Python (https://numpy.org)

PROGRAM DEPENDENCIES:
    associated_legendre.py: Computes fully-normalized associated
        Legendre polynomials
    legendre_polynomials.py: Computes fully normalized Legendre polynomials
    units.py: class for converting spherical harmonic data to specific units
    harmonics.py: spherical harmonic data class for processing GRACE/GRACE-FO
    destripe_harmonics.py: calculates the decorrelation (destriping) filter
        and filters the GRACE/GRACE-FO coefficients for striping errors

REFERENCES:
    Holmes and Featherstone, Journal of Geodesy, 76, 279-299, 2002
        https://doi.org/10.1007/s00190-002-0216-2
    I. M. Longman, Journal of Geophysical Research, 67(2), 1962
        https://doi.org/10.1029/JZ067i002p00845
    W. E. Farrell, Reviews of Geophysics and Space Physics, 10(3), 1972
        https://doi.org/10.1029/RG010i003p00761
    H. N. Pollack, Journal of Geophysical Research, 78(11), 1973
        https://doi.org/10.1029/JB078i011p01760
    T. Jacob et al., Journal of Geodesy, 86, 337-358, 2012
        https://doi.org/10.1007/s00190-011-0522-7

UPDATE HISTORY:
    Updated 06/2023: modified custom units case to not convert to cmwe
    Updated 03/2023: simplified unit degree factors using units class
        improve typing for variables in docstrings
    Updated 02/2023: set custom units as top option in if/else statements
    Updated 01/2023: refactored associated legendre polynomials
    Updated 11/2022: use f-strings for formatting verbose or ascii output
    Updated 04/2022: updated docstrings to numpy documentation format
    Updated 11/2021: added UNITS list option for converting from custom units
    Updated 07/2020: added function docstrings
    Updated 05/2020: vectorize calculation over degrees to improve compute time
    Updated 04/2020: reading load love numbers outside of this function
        include degrees and orders in output dictionary for harmonics class
        use units class for Earth parameters
    Updated 04/2018: Using Holmes and Featherstone relation for Plms
    Updated 03/2018: simplified love number extrapolation if LMAX > 696
    Updated 07/2017: outputs of legendre_polynomials.py include derivatives now
    Updated 08/2015: added error handling for common exceptions
    Updated 05/2015: added parameter MMAX for MMAX != LMAX
        added parameter RAD_KM for the cap radius in kilometers (alternative)
        added parameter UNITS to be similar to gen_stokes program and
        for reconstructing the spatial fields of recovered mascon time series
    Updated 03/2014: using Legendre polynomials code to generate the zonal harmonics
        rather than using the first array of plm_mohlenkamp.
        added some updates to comments
    Updated 10/2013: major reorganization for computational efficiency
    Updated 10/2013: added option for entering cap radius instead of AREA
    Updated 05/2013: added option to precompute plms
    Updated 05/2013: adapted for python (changed UNGRID and PTMASS options to Y/N)
    Updated 06/2012: major revision to code organizzation
    Written 04/2012
"""
import numpy as np
import gravity_toolkit.units
import gravity_toolkit.harmonics
from gravity_toolkit.associated_legendre import plm_holmes
from gravity_toolkit.legendre_polynomials import legendre_polynomials

[docs] def gen_spherical_cap(data, lon, lat, LMAX=60, MMAX=None, AREA=0, RAD_CAP=0, RAD_KM=0, UNITS=1, PLM=None, LOVE=None): r""" Calculates spherical harmonic coefficients for a spherical cap :cite:p:`Holmes:2002ff,Longman:1962ev,Farrell:1972cm,Pollack:1973gi,Jacob:2012eo` Parameters ---------- data: np.ndarray data magnitude lon: np.ndarray longitude of spherical cap center lat: np.ndarray latitude of spherical cap center LMAX: int, default 60 Upper bound of Spherical Harmonic Degrees MMAX: int or NoneType, default None Upper bound of Spherical Harmonic Orders AREA: Float, default 0 Area of spherical cap (cm\ :sup:`2`) UNITS: int, default 1 Input data units - ``1``: cm water equivalent thickness (cm w.e., g/cm\ :sup:`2`) - ``2``: gigatonnes of mass (Gt) - ``3``: mm water equivalent thickness (mm w.e., kg/m\ :sup:`2`) - list: custom unit conversion factor PLM: np.ndarray, default 0 Input Legendre polynomials LOVE: tuple or NoneType, default None Input load Love numbers up to degree LMAX (``hl``, ``kl``, ``ll``) Returns ------- clm: np.ndarray cosine spherical harmonic coefficients slm: np.ndarray sine spherical harmonic coefficients l: np.ndarray spherical harmonic degree to LMAX m: np.ndarray spherical harmonic order to MMAX """ # upper bound of spherical harmonic orders (default = LMAX) if MMAX is None: MMAX = np.copy(LMAX) # convert lon and lat to radians phi = lon*np.pi/180.0# Longitude in radians th = (90.0 - lat)*np.pi/180.0# Colatitude in radians # Earth Parameters factors = gravity_toolkit.units(lmax=LMAX) # Converting input area into an equivalent spherical cap radius # Following Jacob et al. (2012) Equation 4 and 5 # alpha is the vertical semi-angle subtending a cone at the # center of the earth if (RAD_CAP != 0): # if given spherical cap radius in degrees # converting to radians alpha = RAD_CAP*np.pi/180.0 elif (AREA != 0): # if given spherical cap area in cm^2 # radius in centimeters radius_cm = np.sqrt(AREA/np.pi) # Calculating angular radius of spherical cap alpha = (radius_cm/factors.rad_e) elif (RAD_KM != 0): # if given spherical cap radius in kilometers # Calculating angular radius of spherical cap alpha = (1e5*RAD_KM)/factors.rad_e else: raise ValueError('Input RAD_CAP, AREA or RAD_KM of spherical cap') # Calculate factor to convert from input units if isinstance(UNITS, (list, np.ndarray)): # custom units unit_conv = 1.0 dfactor = np.copy(UNITS) elif (UNITS == 1): # Input data is in cm water equivalent (cmwe) unit_conv = 1.0 # degree dependent factors to convert from coefficients # of mass into normalized geoid coefficients dfactor = 4.0*np.pi*factors.spatial(*LOVE).cmwe/(1.0 + 2.0*factors.l) elif (UNITS == 2): # Input data is in gigatonnes (Gt) # calculate spherical cap area from angular radius area = np.pi*(alpha*factors.rad_e)**2 # the 1.e15 converts from gigatons/cm^2 to cm of water # 1 g/cm^3 = 1000 kg/m^3 = density water # 1 Gt = 1 Pg = 1.e15 g unit_conv = 1.e15/area # degree dependent factors to convert from coefficients # of mass into normalized geoid coefficients dfactor = 4.0*np.pi*factors.spatial(*LOVE).cmwe/(1.0 + 2.0*factors.l) elif (UNITS == 3): # Input data is in kg/m^2 # 1 kg = 1000 g # 1 m^2 = 100*100 cm^2 = 1e4 cm^2 unit_conv = 0.1 # degree dependent factors to convert from coefficients # of mass into normalized geoid coefficients dfactor = 4.0*np.pi*factors.spatial(*LOVE).cmwe/(1.0 + 2.0*factors.l) else: raise ValueError(f'Unknown units {UNITS}') # Calculating plms of the spherical caps # From Longman et al. (1962) # pl_alpha = F(alpha) from Jacob 2011 # pl_alpha is purely zonal and depends only on the size of the cap # allocating for constructed array pl_alpha = np.zeros((LMAX+1)) # l=0 is a special case (P(-1) = 1, P(1) = cos(alpha)) pl_alpha[0] = (1.0 - np.cos(alpha))/2.0 # for all other degrees: calculate the legendre polynomials up to LMAX+1 pl_matrix,_ = legendre_polynomials(LMAX+1,np.cos(alpha)) for l in range(1, LMAX+1):# LMAX+1 to include LMAX # from Longman (1962) and Jacob et al (2012) # unnormalizing Legendre polynomials # sqrt(2*l - 1) == sqrt(2*(l-1) + 1) # sqrt(2*l + 3) == sqrt(2*(l+1) + 1) pl_lower = pl_matrix[l-1]/np.sqrt(2.0*l-1.0) pl_upper = pl_matrix[l+1]/np.sqrt(2.0*l+3.0) pl_alpha[l] = (pl_lower - pl_upper)/2.0 # Calculating Legendre Polynomials # added option to precompute plms to improve computational speed # this would be the plm for the center of the spherical cap # used to rotate the spherical cap to point lat/lon if PLM is None: plmout,_ = plm_holmes(LMAX, np.cos(th)) # truncate precomputed plms to order plmout = np.squeeze(plmout[:,:MMAX+1,:]) else: # truncate precomputed plms to degree and order plmout = PLM[:LMAX+1,:MMAX+1] # calculate array of m values ranging from 0 to MMAX (harmonic orders) # MMAX+1 as there are MMAX+1 elements between 0 and MMAX m = np.arange(MMAX+1) # Multiplying by the units conversion factor (unit_conv) to # convert from the input units into cmwe # Multiplying point mass data (converted to cmwe) with sin/cos of m*phis # data normally is 1 for a uniform 1cm water equivalent layer # but can be a mass point if reconstructing a spherical harmonic field # NOTE: NOT a matrix multiplication as data (and phi) is a single point dcos = unit_conv*data*np.cos(m*phi) dsin = unit_conv*data*np.sin(m*phi) # Multiplying by plm_alpha (F_l from Jacob 2012) plm = np.zeros((LMAX+1, MMAX+1)) # Initializing preliminary spherical harmonic matrices yclm = np.zeros((LMAX+1, MMAX+1)) yslm = np.zeros((LMAX+1, MMAX+1)) # Initializing output spherical harmonic matrices Ylms = gravity_toolkit.harmonics(lmax=LMAX, mmax=MMAX) Ylms.clm = np.zeros((LMAX+1, MMAX+1)) Ylms.slm = np.zeros((LMAX+1, MMAX+1)) for m in range(0,MMAX+1):# MMAX+1 to include MMAX l = np.arange(m,LMAX+1)# LMAX+1 to include LMAX # rotate spherical cap to be centered at lat/lon plm[l,m] = plmout[l,m]*pl_alpha[l] # multiplying clm by cos(m*phi) and slm by sin(m*phi) # to get a field of spherical harmonics yclm[l,m] = plm[l,m]*dcos[m] yslm[l,m] = plm[l,m]*dsin[m] # multiplying by factors to convert to geoid coefficients Ylms.clm[l,m] = dfactor[l]*yclm[l,m] Ylms.slm[l,m] = dfactor[l]*yslm[l,m] # return the output spherical harmonics object return Ylms