From 0f8f3a25ee75a3e8bc9b630732e74d004d2c241b Mon Sep 17 00:00:00 2001 From: Tyler Sutterley Date: Tue, 7 Jul 2026 16:58:30 -0700 Subject: [PATCH 1/5] refactor: use `np.einsum` for spherical harmonic summations refactor: use `np.radians` to convert from degrees to radians refactor: use `np.hypot` to calculate the sum of two squares feat: add dunder (magic) methods for mathematical operations --- .github/workflows/python-request.yml | 2 +- access/itsg_graz_grace_sync.py | 5 +- dealiasing/dealiasing_global_uplift.py | 2 +- .../api_reference/clenshaw_summation.rst | 2 + .../api_reference/sea_level_equation.rst | 2 + .../notebooks/GRACE-Geostrophic-Maps.ipynb | 6 +- .../notebooks/GRACE-Spatial-Error.ipynb | 4 +- doc/source/notebooks/GRACE-Spatial-Maps.ipynb | 2 +- geocenter/calc_degree_one.py | 261 +++++++++++------- geocenter/delta_degree_one.py | 113 +++----- geocenter/geocenter_monte_carlo.py | 2 +- geocenter/geocenter_spatial_maps.py | 8 +- geocenter/kernel_degree_one.py | 88 +++--- geocenter/model_degree_one.py | 121 ++++---- geocenter/monte_carlo_degree_one.py | 223 +++++++++------ gravity_toolkit/SLR/C20.py | 4 +- gravity_toolkit/SLR/C30.py | 2 +- gravity_toolkit/SLR/C50.py | 2 +- gravity_toolkit/clenshaw_summation.py | 112 ++++---- gravity_toolkit/gen_disc_load.py | 35 +-- gravity_toolkit/gen_harmonics.py | 198 ++++++------- gravity_toolkit/gen_point_load.py | 32 ++- gravity_toolkit/gen_spherical_cap.py | 37 +-- gravity_toolkit/gen_stokes.py | 64 ++--- gravity_toolkit/grace_input_months.py | 14 +- gravity_toolkit/harmonic_gradients.py | 146 ++++------ gravity_toolkit/harmonic_summation.py | 74 +++-- gravity_toolkit/harmonics.py | 68 ++++- gravity_toolkit/mascons.py | 39 ++- gravity_toolkit/sea_level_equation.py | 162 +++++------ gravity_toolkit/spatial.py | 64 ++++- gravity_toolkit/time_series/amplitude.py | 7 +- gravity_toolkit/time_series/piecewise.py | 11 +- gravity_toolkit/time_series/smooth.py | 19 +- gravity_toolkit/tools.py | 7 +- mapping/plot_AIS_GrIS_maps.py | 4 +- mapping/plot_AIS_grid_3maps.py | 4 +- mapping/plot_AIS_grid_4maps.py | 6 +- mapping/plot_AIS_grid_maps.py | 4 +- mapping/plot_AIS_grid_movie.py | 4 +- mapping/plot_AIS_regional_maps.py | 4 +- mapping/plot_AIS_regional_movie.py | 4 +- mapping/plot_GrIS_grid_3maps.py | 4 +- mapping/plot_GrIS_grid_5maps.py | 4 +- mapping/plot_GrIS_grid_maps.py | 4 +- mapping/plot_GrIS_grid_movie.py | 4 +- mapping/plot_QML_grid_3maps.py | 4 +- mapping/plot_global_grid_3maps.py | 4 +- mapping/plot_global_grid_4maps.py | 4 +- mapping/plot_global_grid_5maps.py | 4 +- mapping/plot_global_grid_9maps.py | 4 +- mapping/plot_global_grid_maps.py | 4 +- mapping/plot_global_grid_movie.py | 4 +- scripts/calc_sensitivity_kernel.py | 2 +- scripts/combine_HEX_spherical_caps.py | 2 +- scripts/combine_harmonics.py | 2 +- scripts/combine_sea_level_data.py | 2 +- scripts/convert_harmonics.py | 2 +- scripts/grace_spatial_error.py | 6 +- scripts/grace_spatial_maps.py | 2 +- scripts/piecewise_grace_maps.py | 12 +- scripts/plot_SLR_azimuthal.py | 2 +- scripts/regional_spherical_caps.py | 4 +- scripts/regress_grace_maps.py | 10 +- scripts/run_sea_level_equation.py | 2 +- scripts/scale_grace_maps.py | 6 +- scripts/sea_level_error.py | 2 +- scripts/sea_level_regress.py | 12 +- scripts/sea_level_stokes.py | 2 +- test/test_download_and_read.py | 4 +- test/test_harmonics.py | 7 +- test/test_masks.py | 6 +- test/test_sea_level.py | 55 ++++ utilities/quick_mascon_regress.py | 7 +- 74 files changed, 1146 insertions(+), 1015 deletions(-) create mode 100644 test/test_sea_level.py diff --git a/.github/workflows/python-request.yml b/.github/workflows/python-request.yml index fd0c89d6..cecb5289 100644 --- a/.github/workflows/python-request.yml +++ b/.github/workflows/python-request.yml @@ -32,7 +32,7 @@ jobs: with: lfs: true - name: Set up pixi environment - uses: prefix-dev/setup-pixi@v0.9.1 + uses: prefix-dev/setup-pixi@v0.9.6 - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names diff --git a/access/itsg_graz_grace_sync.py b/access/itsg_graz_grace_sync.py index c765a4cd..fcf0d1be 100755 --- a/access/itsg_graz_grace_sync.py +++ b/access/itsg_graz_grace_sync.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" itsg_graz_grace_sync.py -Written by Tyler Sutterley (05/2023) +Written by Tyler Sutterley (07/2026) Syncs GRACE/GRACE-FO and auxiliary data from the ITSG GRAZ server CALLING SEQUENCE: @@ -39,6 +39,7 @@ utilities.py: download and management utilities for syncing files UPDATE HISTORY: + Updated 07/2026: ITSG GRACE server moved from outgoing to pub Updated 05/2023: use pathlib to define and operate on paths Updated 12/2022: single implicit import of gravity toolkit Updated 11/2022: use f-strings for formatting verbose or ascii output @@ -82,7 +83,7 @@ def itsg_graz_grace_sync(DIRECTORY, RELEASE=None, LMAX=None, TIMEOUT=0, logging.basicConfig(level=logging.INFO) # ITSG GRAZ server - HOST = ['http://ftp.tugraz.at','outgoing','ITSG','GRACE'] + HOST = ['http://ftp.tugraz.at','pub','ITSG','GRACE'] # open connection with ITSG GRAZ server at remote directory release_directory = f'ITSG-{RELEASE}' # regular expression operators for ITSG data and models diff --git a/dealiasing/dealiasing_global_uplift.py b/dealiasing/dealiasing_global_uplift.py index fd6fa239..a139c3d8 100644 --- a/dealiasing/dealiasing_global_uplift.py +++ b/dealiasing/dealiasing_global_uplift.py @@ -239,7 +239,7 @@ def dealiasing_global_uplift(base_dir, attributes['time']['standard_name'] = 'time' # Computing plms for converting to spatial domain - theta = (90.0 - grid.lat)*np.pi/180.0 + theta = np.radians(90.0 - grid.lat) PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(theta)) # for each tar file diff --git a/doc/source/api_reference/clenshaw_summation.rst b/doc/source/api_reference/clenshaw_summation.rst index e6ff5c99..92ab7897 100644 --- a/doc/source/api_reference/clenshaw_summation.rst +++ b/doc/source/api_reference/clenshaw_summation.rst @@ -18,3 +18,5 @@ Calling Sequence .. __: https://github.com/tsutterley/gravity-toolkit/blob/main/gravity_toolkit/clenshaw_summation.py .. autofunction:: gravity_toolkit.clenshaw_summation + +.. autofunction:: gravity_toolkit.clenshaw_summation._clenshaw diff --git a/doc/source/api_reference/sea_level_equation.rst b/doc/source/api_reference/sea_level_equation.rst index 0be5ac99..c5d86961 100644 --- a/doc/source/api_reference/sea_level_equation.rst +++ b/doc/source/api_reference/sea_level_equation.rst @@ -20,3 +20,5 @@ Calling Sequence .. __: https://github.com/tsutterley/gravity-toolkit/blob/main/gravity_toolkit/sea_level_equation.py .. autofunction:: gravity_toolkit.sea_level_equation + +.. autofunction:: gravity_toolkit.sea_level_equation._clenshaw diff --git a/doc/source/notebooks/GRACE-Geostrophic-Maps.ipynb b/doc/source/notebooks/GRACE-Geostrophic-Maps.ipynb index a147a48d..631bb230 100644 --- a/doc/source/notebooks/GRACE-Geostrophic-Maps.ipynb +++ b/doc/source/notebooks/GRACE-Geostrophic-Maps.ipynb @@ -339,7 +339,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -365,7 +365,7 @@ "grid.lat = np.copy(landsea.lat)\n", "\n", "# Computing plms for converting to spatial domain\n", - "theta = (90.0 - grid.lat)*np.pi/180.0\n", + "theta = np.radians(90.0 - grid.lat)\n", "PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(theta))\n", "RAD = widgets.gaussian.value\n", "\n", @@ -444,7 +444,7 @@ "grid.data = np.zeros((nlat, nlon, 2,nt))\n", "grid.mask = np.ones((nlat, nlon, 2,nt), dtype=bool)\n", "# mask equatorial regions due to hydrostrophic inaccuracies\n", - "valid, = np.nonzero((np.abs(grid.lat) > 10))\n", + "valid, = np.flatnonzero((np.abs(grid.lat) > 10))\n", "grid.mask[valid,:,:,:] = False\n", "# set land values from land-sea mask to invalid\n", "indy,indx = np.nonzero(np.logical_not(landsea.mask))\n", diff --git a/doc/source/notebooks/GRACE-Spatial-Error.ipynb b/doc/source/notebooks/GRACE-Spatial-Error.ipynb index 1b765a04..b2cf733e 100644 --- a/doc/source/notebooks/GRACE-Spatial-Error.ipynb +++ b/doc/source/notebooks/GRACE-Spatial-Error.ipynb @@ -288,13 +288,13 @@ " nlat = len(grid.lat)\n", "\n", "# Computing plms for converting to spatial domain\n", - "theta = (90.0 - grid.lat)*np.pi/180.0\n", + "theta = np.radians(90.0 - grid.lat)\n", "PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(theta))\n", "# square of legendre polynomials truncated to order MMAX\n", "mm = np.arange(0,MMAX+1)\n", "PLM2 = PLM[:,mm,:]**2\n", "# Calculating cos(m*phi)^2 and sin(m*phi)^2\n", - "phi = grid.lon[np.newaxis,:]*np.pi/180.0\n", + "phi = np.radians(grid.lon[np.newaxis,:])\n", "ccos = np.cos(np.dot(mm[:,np.newaxis],phi))**2\n", "ssin = np.sin(np.dot(mm[:,np.newaxis],phi))**2\n", " \n", diff --git a/doc/source/notebooks/GRACE-Spatial-Maps.ipynb b/doc/source/notebooks/GRACE-Spatial-Maps.ipynb index 2b1b908f..c8802c90 100644 --- a/doc/source/notebooks/GRACE-Spatial-Maps.ipynb +++ b/doc/source/notebooks/GRACE-Spatial-Maps.ipynb @@ -405,7 +405,7 @@ " nlat = len(grid.lat)\n", "\n", "# Computing plms for converting to spatial domain\n", - "theta = (90.0-grid.lat)*np.pi/180.0\n", + "theta = np.radians(90.0-grid.lat)\n", "PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(theta))\n", "\n", "# read load love numbers file\n", diff --git a/geocenter/calc_degree_one.py b/geocenter/calc_degree_one.py index bf0f5386..adf8a4e0 100755 --- a/geocenter/calc_degree_one.py +++ b/geocenter/calc_degree_one.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" calc_degree_one.py -Written by Tyler Sutterley (01/2025) +Written by Tyler Sutterley (07/2026) Calculates degree 1 variations using GRACE coefficients of degree 2 and greater, and ocean bottom pressure variations from ECCO and OMCT/MPIOM @@ -99,7 +99,16 @@ HDF5 --ocean-file X: Index file for ocean model harmonics --mean-file X: GRACE/GRACE-FO mean file to remove from the harmonic data - --mean-format X: Input data format for GRACE/GRACE-FO mean file + --mean-format X: Input data format for GRACE/GRACE-FO mean file' + --remove-file X: Monthly files to be removed from the GRACE/GRACE-FO data + --remove-format X: Input data format for files to be removed + ascii + netCDF4 + HDF5 + index-ascii + index-netCDF4 + index-HDF5 + --redistribute-removed: redistribute removed mass fields over the ocean --iterative: Iterate degree one solutions -s X, --solver X: Least squares solver for degree one solutions inv: matrix inversion @@ -167,6 +176,9 @@ https://doi.org/10.1029/2007JB005338 UPDATE HISTORY: + Updated 07/2026: use np.einsum for spherical harmonic summations + can remove sets of harmonic files from the GRACE/GRACE-FO data + use np.radians to convert from degrees to radians Updated 01/2025: fixed deprecated tick label resizing Updated 06/2024: use wrapper to importlib for optional dependencies Updated 10/2023: generalize mission variable to be GRACE/GRACE-FO @@ -340,12 +352,12 @@ def model_seasonal_geocenter(grace_date): SAPz = 75.0 # calculate each geocenter component from the amplitude and phase # converting the phase from degrees to radians - X = AAx*np.sin(2.0*np.pi*grace_date + APx*np.pi/180.0) + \ - SAAx*np.sin(4.0*np.pi*grace_date + SAPx*np.pi/180.0) - Y = AAy*np.sin(2.0*np.pi*grace_date + APy*np.pi/180.0) + \ - SAAy*np.sin(4.0*np.pi*grace_date + SAPy*np.pi/180.0) - Z = AAz*np.sin(2.0*np.pi*grace_date + APz*np.pi/180.0) + \ - SAAz*np.sin(4.0*np.pi*grace_date + SAPz*np.pi/180.0) + X = AAx*np.sin(2.0*np.pi*grace_date + np.radians(APx)) + \ + SAAx*np.sin(4.0*np.pi*grace_date + np.radians(SAPx)) + Y = AAy*np.sin(2.0*np.pi*grace_date + np.radians(APy)) + \ + SAAy*np.sin(4.0*np.pi*grace_date + np.radians(SAPy)) + Z = AAz*np.sin(2.0*np.pi*grace_date + np.radians(APz)) + \ + SAAz*np.sin(4.0*np.pi*grace_date + np.radians(SAPz)) DEG1 = gravtk.geocenter(X=X-X.mean(), Y=Y-Y.mean(), Z=Z-Z.mean()) return DEG1.from_cartesian() @@ -371,6 +383,9 @@ def calc_degree_one(base_dir, PROC, DREL, MODEL, LMAX, RAD, DATAFORM=None, MEAN_FILE=None, MEANFORM=None, + REMOVE_FILES=None, + REMOVE_FORMAT=None, + REDISTRIBUTE_REMOVED=False, MODEL_INDEX=None, ITERATIVE=False, SOLVER=None, @@ -484,13 +499,13 @@ def calc_degree_one(base_dir, PROC, DREL, MODEL, LMAX, RAD, dlon,dlat = landsea.spacing nlat, nlon = landsea.shape # spatial parameters in radians - dphi = dlon*np.pi/180.0 - dth = dlat*np.pi/180.0 + dphi = np.radians(dlon) + dth = np.radians(dlat) # longitude and colatitude in radians - phi = landsea.lon[np.newaxis,:]*np.pi/180.0 - th = (90.0 - np.squeeze(landsea.lat))*np.pi/180.0 + phi = np.radians(np.squeeze(landsea.lon)) + th = np.radians(90.0 - np.squeeze(landsea.lat)) # create land function - land_function = np.zeros((nlon, nlat),dtype=np.float64) + land_function = np.zeros((nlon, nlat), dtype=np.float64) # extract land function from file # combine land and island levels for land function indx,indy = np.nonzero((landsea.data.T >= 1) & (landsea.data.T <= 3)) @@ -575,17 +590,16 @@ def calc_degree_one(base_dir, PROC, DREL, MODEL, LMAX, RAD, GAD = gravtk.geocenter() GAD.time = np.copy(GAD_Ylms.time) GAD.month = np.copy(GAD_Ylms.month) - GAD.C10 = np.zeros((n_files)) - GAD.C11 = np.zeros((n_files)) - GAD.S11 = np.zeros((n_files)) - for t in range(0,n_files): - # converting GAD degree 1 harmonics to mass - # NOTE: following Swenson (2008): do not use the kl Load Love number - # to convert the GAD coefficients into coefficients of mass as - # the GAC and GAD products are computed with a Load Love number of 0 - GAD.C10[t] = rho_e*rad_e*np.squeeze(GAD_Ylms.clm[1,0,t])*(2.0 + 1.0)/3.0 - GAD.C11[t] = rho_e*rad_e*np.squeeze(GAD_Ylms.clm[1,1,t])*(2.0 + 1.0)/3.0 - GAD.S11[t] = rho_e*rad_e*np.squeeze(GAD_Ylms.slm[1,1,t])*(2.0 + 1.0)/3.0 + GAD.C10 = np.empty((n_files)) + GAD.C11 = np.empty((n_files)) + GAD.S11 = np.empty((n_files)) + # converting GAD degree 1 harmonics to mass + # NOTE: following Swenson (2008): do not use the kl Load Love number + # to convert the GAD coefficients into coefficients of mass as + # the GAC and GAD products are computed with a Load Love number of 0 + GAD.C10[:] = rho_e*rad_e*np.squeeze(GAD_Ylms.clm[1,0,:])*(2.0 + 1.0)/3.0 + GAD.C11[:] = rho_e*rad_e*np.squeeze(GAD_Ylms.clm[1,1,:])*(2.0 + 1.0)/3.0 + GAD.S11[:] = rho_e*rad_e*np.squeeze(GAD_Ylms.slm[1,1,:])*(2.0 + 1.0)/3.0 # removing the mean of the GAD OBP coefficients GAD.mean(apply=True) @@ -622,14 +636,66 @@ def calc_degree_one(base_dir, PROC, DREL, MODEL, LMAX, RAD, # converting ecco degree 1 harmonics to coefficients of mass OBP = gravtk.geocenter.from_harmonics(OBP_Ylms).scale(dfactor[1]) + # input spherical harmonic datafiles to be removed from the GRACE data + # Remove sets of Ylms from the GRACE data before returning + remove_Ylms = GSM_Ylms.zeros_like() + remove_Ylms.time[:] = np.copy(GSM_Ylms.time) + remove_Ylms.month[:] = np.copy(GSM_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 = gravtk.harmonics().from_file(REMOVE_FILE, + format=REMOVEFORM) + attributes['lineage'].append(Ylms.filename) + elif REMOVEFORM in ('index-ascii','index-netCDF4','index-HDF5'): + # read from index file + _,removeform = REMOVEFORM.split('-') + # index containing files in data format + Ylms = gravtk.harmonics().from_index(REMOVE_FILE, + format=removeform) + attributes['lineage'].extend([f.name for f in Ylms.filename]) + # reduce to GRACE/GRACE-FO months and truncate to degree and order + Ylms = Ylms.subset(GSM_Ylms.month).truncate(lmax=LMAX, mmax=MMAX) + # remove the temporal mean of the coefficients + Ylms.mean(apply=True) + # 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) + # save geocenter coefficients of the auxiliary corrections + remove = gravtk.geocenter().from_harmonics(remove_Ylms) + # Calculating cos/sin of phi arrays # output [m,phi] - m = GSM_Ylms.m[:, np.newaxis] + m = GSM_Ylms.m # Integration factors (solid angle) int_fact = np.sin(th)*dphi*dth - # Calculating cos(m*phi) and sin(m*phi) - ccos = np.cos(np.dot(m,phi)) - ssin = np.sin(np.dot(m,phi)) + # 4-pi normalization + norm = 1.0/(4.0*np.pi) + # calculating cos(m*phi) and sin(m*phi) using Euler's formula + m_phi = np.exp(1j * np.einsum("m...,p...->mp...", m, phi)) # Legendre polynomials for degree 1 P10 = np.squeeze(PLM[1,0,:]) @@ -637,34 +703,31 @@ def calc_degree_one(base_dir, PROC, DREL, MODEL, LMAX, RAD, # PLM for spherical harmonic degrees 2+ up to LMAX # converted into mass and smoothed if specified plmout = np.zeros((LMAX+1, MMAX+1, nlat)) - for l in range(1,LMAX+1): - m = np.arange(0,np.min([l,MMAX])+1) - # convert to smoothed coefficients of mass - # Convolving plms with degree dependent factor and smoothing - plmout[l,m,:] = PLM[l,m,:]*dfactor[l]*wt[l] + # convert to smoothed coefficients of mass + # Convolving plms with degree dependent factor and smoothing + plmout[:] = np.einsum("l,l,lmh->lmh", dfactor, wt, PLM[:LMAX+1,:MMAX+1,:]) # Initializing 3x3 I-Parameter matrix - IMAT = np.zeros((3,3)) - # Calculating I-Parameter matrix by integrating over latitudes + # (see equations 12 and 13 of Swenson et al., 2008) + IMAT = np.zeros((3, 3)) # I-Parameter matrix accounts for the fact that the GRACE data only # includes spherical harmonic degrees greater than or equal to 2 - for i in range(0,nlat): - # C10, C11, S11 - PC10 = P10[i]*ccos[0,:] - PC11 = P11[i]*ccos[1,:] - PS11 = P11[i]*ssin[1,:] - # C10: C10, C11, S11 (see equations 12 and 13 of Swenson et al., 2008) - IMAT[0,0] += np.sum(int_fact[i]*PC10*ocean_function[:,i]*PC10)/(4.0*np.pi) - IMAT[1,0] += np.sum(int_fact[i]*PC10*ocean_function[:,i]*PC11)/(4.0*np.pi) - IMAT[2,0] += np.sum(int_fact[i]*PC10*ocean_function[:,i]*PS11)/(4.0*np.pi) - # C11: C10, C11, S11 (see equations 12 and 13 of Swenson et al., 2008) - IMAT[0,1] += np.sum(int_fact[i]*PC11*ocean_function[:,i]*PC10)/(4.0*np.pi) - IMAT[1,1] += np.sum(int_fact[i]*PC11*ocean_function[:,i]*PC11)/(4.0*np.pi) - IMAT[2,1] += np.sum(int_fact[i]*PC11*ocean_function[:,i]*PS11)/(4.0*np.pi) - # S11: C10, C11, S11 (see equations 12 and 13 of Swenson et al., 2008) - IMAT[0,2] += np.sum(int_fact[i]*PS11*ocean_function[:,i]*PC10)/(4.0*np.pi) - IMAT[1,2] += np.sum(int_fact[i]*PS11*ocean_function[:,i]*PC11)/(4.0*np.pi) - IMAT[2,2] += np.sum(int_fact[i]*PS11*ocean_function[:,i]*PS11)/(4.0*np.pi) + # C10, C11, S11 + PC10 = np.einsum("h...,p...->ph...", P10, m_phi[0,:].real) + PC11 = np.einsum("h...,p...->ph...", P11, m_phi[1,:].real) + PS11 = np.einsum("h...,p...->ph...", P11, m_phi[1,:].imag) + # C10: C10, C11, S11 + IMAT[0,0] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC10, ocean_function, PC10) + IMAT[1,0] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC10, ocean_function, PC11) + IMAT[2,0] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC10, ocean_function, PS11) + # C11: C10, C11, S11 + IMAT[0,1] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC11, ocean_function, PC10) + IMAT[1,1] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC11, ocean_function, PC11) + IMAT[2,1] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC11, ocean_function, PS11) + # S11: C10, C11, S11 + IMAT[0,2] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PS11, ocean_function, PC10) + IMAT[1,2] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PS11, ocean_function, PC11) + IMAT[2,2] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PS11, ocean_function, PS11) # get seasonal variations of an initial geocenter correction # for use in the land water mass calculation @@ -691,39 +754,32 @@ def calc_degree_one(base_dir, PROC, DREL, MODEL, LMAX, RAD, DMAT = np.zeros((3,n_files)) # degree 1 iterations iteration = gravtk.geocenter() - iteration.C10 = np.zeros((n_files,max_iter)) - iteration.C11 = np.zeros((n_files,max_iter)) - iteration.S11 = np.zeros((n_files,max_iter)) + iteration.C10 = np.zeros((n_files, max_iter)) + iteration.C11 = np.zeros((n_files, max_iter)) + iteration.S11 = np.zeros((n_files, max_iter)) # calculate non-iterated terms for each file (G-matrix parameters) for t in range(n_files): # calculate geocenter component of ocean mass with GRACE - # allocate for product of grace and legendre polynomials - pcos = np.zeros((MMAX+1, nlat))#-[m,lat] - psin = np.zeros((MMAX+1, nlat))#-[m,lat] # Summing product of plms and c/slms over all SH degrees >= 2 - # Removing monthly GIA signal and atmospheric correction + # Removing monthly GIA signal, atmospheric correction + # and the auxiliary coefficients Ylms = GSM_Ylms.index(t) Ylms.subtract(GIA_Ylms.index(t)) Ylms.subtract(ATM_Ylms.index(t)) - for i in range(0, nlat): - l = np.arange(2,LMAX+1) - pcos[:,i] = np.sum(plmout[l,:,i]*Ylms.clm[l,:], axis=0) - psin[:,i] = np.sum(plmout[l,:,i]*Ylms.slm[l,:], axis=0) + Ylms.subtract(remove_Ylms.index(t)) + # subset GRACE to degrees 2+ for calculating ocean mass + l2 = slice(2, LMAX+1) + pconv = np.einsum("lmh...,lm...->mh...", plmout[l2, :, :], Ylms.ilm[l2, :]) # Multiplying by c/s(phi#m) to get surface density in cmwe (lon,lat) # ccos/ssin are mXphi, pcos/psin are mXtheta: resultant matrices are phiXtheta # The summation over spherical harmonic order is in this multiplication - rmass = np.dot(np.transpose(ccos),pcos) + np.dot(np.transpose(ssin),psin) + rmass = np.einsum("mp...,mh...->ph...", m_phi, pconv) # calculate G matrix parameters through a summation of each latitude - for i in range(0,nlat): - # C10, C11, S11 - PC10 = P10[i]*ccos[0,:] - PC11 = P11[i]*ccos[1,:] - PS11 = P11[i]*ssin[1,:] - # summation of integration factors, Legendre polynomials, - # (convolution of order and harmonics) and the ocean mass at t - G.C10[t] += np.sum(int_fact[i]*PC10*ocean_function[:,i]*rmass[:,i])/(4.0*np.pi) - G.C11[t] += np.sum(int_fact[i]*PC11*ocean_function[:,i]*rmass[:,i])/(4.0*np.pi) - G.S11[t] += np.sum(int_fact[i]*PS11*ocean_function[:,i]*rmass[:,i])/(4.0*np.pi) + # summation of integration factors, Legendre polynomials, + # (convolution of order and harmonics) and the ocean mass at t + G.C10[t] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC10, ocean_function, rmass) + G.C11[t] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC11, ocean_function, rmass) + G.S11[t] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PS11, ocean_function, rmass) # calculate degree one solution for each iteration (or single if not) while (eps > eps_max) and (n_iter < max_iter): @@ -743,26 +799,22 @@ def calc_degree_one(base_dir, PROC, DREL, MODEL, LMAX, RAD, GSM_Ylms.clm[1,1,t] = iteration.C11[t,n_iter-1] GSM_Ylms.slm[1,1,t] = iteration.S11[t,n_iter-1] - # allocate for product of grace and legendre polynomials - pcos = np.zeros((MMAX+1, nlat))#-[m,lat] - psin = np.zeros((MMAX+1, nlat))#-[m,lat] # Summing product of plms and c/slms over all SH degrees - # Removing monthly GIA signal and atmospheric correction + # Removing monthly GIA signal, atmospheric correction + # and the auxiliary coefficients Ylms = GSM_Ylms.index(t) Ylms.subtract(GIA_Ylms.index(t)) Ylms.subtract(ATM_Ylms.index(t)) - for i in range(0, nlat): - # for land water: use an initial seasonal geocenter estimate - # from Chen et al. (1999) then the iterative if specified - l = np.arange(1,LMAX+1) - pcos[:,i] = np.sum(plmout[l,:,i]*Ylms.clm[l,:], axis=0) - psin[:,i] = np.sum(plmout[l,:,i]*Ylms.slm[l,:], axis=0) + Ylms.subtract(remove_Ylms.index(t)) + # for land water: use an initial seasonal geocenter estimate + # from Chen et al. (1999) then the iterative if specified + l1 = slice(1, LMAX+1) + pconv = np.einsum("lmh...,lm...->mh...", plmout[l1, :, :], Ylms.ilm[l1, :]) # Multiplying by c/s(phi#m) to get surface density in cm w.e. (lonxlat) - # this will be a spatial field similar to outputs from stokes_combine.py # ccos/ssin are mXphi, pcos/psin are mXtheta: resultant matrices are phiXtheta # The summation over spherical harmonic order is in this multiplication - lmass = np.dot(np.transpose(ccos),pcos) + np.dot(np.transpose(ssin),psin) + lmass = np.einsum("mp...,mh...->ph...", m_phi, pconv) # use sea level fingerprints or eustatic from GRACE land components if FINGERPRINT: @@ -780,8 +832,7 @@ def calc_degree_one(base_dir, PROC, DREL, MODEL, LMAX, RAD, sea_level = gravtk.sea_level_equation(land_Ylms.clm, land_Ylms.slm, landsea.lon, landsea.lat, land_function, LMAX=EXPANSION, LOVE=LOVE, BODY_TIDE_LOVE=0, FLUID_LOVE=0, ITERATIONS=3, - POLAR=True, PLM=PLM, ASTYPE=np.float64, SCALE=1e-32, - FILL_VALUE=0) + POLAR=True, PLM=PLM, FILL_VALUE=0) # 3) convert sea level fingerprints into spherical harmonics slf_Ylms = gravtk.gen_stokes(sea_level, landsea.lon, landsea.lat, UNITS=1, LMIN=0, LMAX=1, PLM=PLM[:2,:2,:], LOVE=LOVE) @@ -838,10 +889,14 @@ def calc_degree_one(base_dir, PROC, DREL, MODEL, LMAX, RAD, elif SOLVER in ('gelsd', 'gelsy', 'gelss'): DMAT[:,t], res, rnk, s = scipy.linalg.lstsq(IMAT, (CMAT-GMAT), lapack_driver=SOLVER) - # save geocenter for iteration and time t after restoring GIA+ATM - iteration.C10[t,n_iter] = DMAT[0,t]/dfactor[1]+gia.C10[t]+atm.C10[t] - iteration.C11[t,n_iter] = DMAT[1,t]/dfactor[1]+gia.C11[t]+atm.C11[t] - iteration.S11[t,n_iter] = DMAT[2,t]/dfactor[1]+gia.S11[t]+atm.S11[t] + # save geocenter for iteration and time t after restoring fields + iteration.C10[t,n_iter] = DMAT[0,t]/dfactor[1] + \ + gia.C10[t] + atm.C10[t] + remove.C10[t] + iteration.C11[t,n_iter] = DMAT[1,t]/dfactor[1] + \ + gia.C11[t] + atm.C11[t] + remove.C11[t] + iteration.S11[t,n_iter] = DMAT[2,t]/dfactor[1] + \ + gia.S11[t] + atm.S11[t] + remove.S11[t] + # remove mean of each solution for iteration iteration.C10[:,n_iter] -= iteration.C10[:,n_iter].mean() iteration.C11[:,n_iter] -= iteration.C11[:,n_iter].mean() @@ -862,9 +917,9 @@ def calc_degree_one(base_dir, PROC, DREL, MODEL, LMAX, RAD, # for each of the geocenter solutions (C10, C11, S11) # for the iterative case this will be the final iteration DEG1 = gravtk.geocenter() - DEG1.C10 = DMAT[0,:]/dfactor[1] + gia.C10[:] + atm.C10[:] - DEG1.C11 = DMAT[1,:]/dfactor[1] + gia.C11[:] + atm.C11[:] - DEG1.S11 = DMAT[2,:]/dfactor[1] + gia.S11[:] + atm.S11[:] + DEG1.C10 = DMAT[0,:]/dfactor[1] + gia.C10[:] + atm.C10[:] + remove.C10[t] + DEG1.C11 = DMAT[1,:]/dfactor[1] + gia.C11[:] + atm.C11[:] + remove.C11[t] + DEG1.S11 = DMAT[2,:]/dfactor[1] + gia.S11[:] + atm.S11[:] + remove.S11[t] # remove mean of geocenter for each component DEG1.mean(apply=True) # calculate geocenter variations with dealiasing restored @@ -1528,6 +1583,19 @@ def arguments(): parser.add_argument('--mean-format', type=str, default='netCDF4', choices=['ascii','netCDF4','HDF5','gfc'], help='Input data format for GRACE/GRACE-FO mean file') + # monthly files to be removed from the GRACE/GRACE-FO data + parser.add_argument('--remove-file', + type=pathlib.Path, nargs='+', + help='Monthly files to be removed from the GRACE/GRACE-FO data') + choices = [] + choices.extend(['ascii','netCDF4','HDF5']) + choices.extend(['index-ascii','index-netCDF4','index-HDF5']) + parser.add_argument('--remove-format', + type=str, nargs='+', choices=choices, + help='Input data format for files to be removed') + parser.add_argument('--redistribute-removed', + default=False, action='store_true', + help='Redistribute removed mass fields over the ocean') # run with iterative scheme parser.add_argument('--iterative', default=False, action='store_true', @@ -1617,6 +1685,9 @@ def main(): MODEL_INDEX=args.ocean_file, MEAN_FILE=args.mean_file, MEANFORM=args.mean_format, + REMOVE_FILES=args.remove_file, + REMOVE_FORMAT=args.remove_format, + REDISTRIBUTE_REMOVED=args.redistribute_removed, ITERATIVE=args.iterative, SOLVER=args.solver, FINGERPRINT=args.fingerprint, diff --git a/geocenter/delta_degree_one.py b/geocenter/delta_degree_one.py index a621affb..ef6c2824 100644 --- a/geocenter/delta_degree_one.py +++ b/geocenter/delta_degree_one.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" delta_degree_one.py -Written by Tyler Sutterley (10/2023) +Written by Tyler Sutterley (07/2026) Calculates degree 1 errors using GRACE coefficients of degree 2 and greater, and ocean bottom pressure variations from OMCT/MPIOM @@ -147,6 +147,8 @@ https://doi.org/10.1029/2005GL025305 UPDATE HISTORY: + Updated 07/2026: use np.einsum for spherical harmonic summations + use np.radians to convert from degrees to radians Updated 10/2023: generalize mission variable to be GRACE/GRACE-FO Updated 09/2023: simplify I-matrix and G-matrix calculations Updated 05/2023: use pathlib to define and operate on paths @@ -317,11 +319,11 @@ def delta_degree_one(base_dir, PROC, DREL, LMAX, RAD, dlon,dlat = landsea.spacing nlat, nlon = landsea.shape # spatial parameters in radians - dphi = dlon*np.pi/180.0 - dth = dlat*np.pi/180.0 + dphi = np.radians(dlon) + dth = np.radians(dlat) # longitude and colatitude in radians - phi = landsea.lon[np.newaxis,:]*np.pi/180.0 - th = (90.0 - np.squeeze(landsea.lat))*np.pi/180.0 + phi = np.radians(landsea.lon[np.newaxis,:]) + th = np.radians(90.0 - np.squeeze(landsea.lat)) # create land function land_function = np.zeros((nlon, nlat),dtype=np.float64) # extract land function from file @@ -442,47 +444,45 @@ def delta_degree_one(base_dir, PROC, DREL, LMAX, RAD, # Calculating cos/sin of phi arrays # output [m,phi] - m = GSM_Ylms.m[:, np.newaxis] + m = GSM_Ylms.m # Integration factors (solid angle) int_fact = np.sin(th)*dphi*dth - # Calculating cos(m*phi) and sin(m*phi) - ccos = np.cos(np.dot(m,phi)) - ssin = np.sin(np.dot(m,phi)) + # 4-pi normalization + norm = 1.0/(4.0*np.pi) + # calculating cos(m*phi) and sin(m*phi) using Euler's formula + m_phi = np.exp(1j * np.einsum("m...,p...->mp...", m, phi)) # Legendre polynomials for degree 1 P10 = np.squeeze(PLM[1,0,:]) P11 = np.squeeze(PLM[1,1,:]) - # PLM for spherical harmonic degrees 2+ + # PLM for spherical harmonic degrees 2+ up to LMAX # converted into mass and smoothed if specified plmout = np.zeros((LMAX+1, MMAX+1, nlat)) - for l in range(1,LMAX+1): - m = np.arange(0,np.min([l,MMAX])+1) - # convert to smoothed coefficients of mass - # Convolving plms with degree dependent factor and smoothing - plmout[l,m,:] = PLM[l,m,:]*dfactor[l]*wt[l] + # convert to smoothed coefficients of mass + # Convolving plms with degree dependent factor and smoothing + plmout[:] = np.einsum("l,l,lmh->lmh", dfactor, wt, PLM[:LMAX+1,:MMAX+1,:]) # Initializing 3x3 I-Parameter matrix - IMAT = np.zeros((3,3)) - # Calculating I-Parameter matrix by integrating over latitudes + # (see equations 12 and 13 of Swenson et al., 2008) + IMAT = np.zeros((3, 3)) # I-Parameter matrix accounts for the fact that the GRACE data only # includes spherical harmonic degrees greater than or equal to 2 - for i in range(0,nlat): - # C10, C11, S11 - PC10 = P10[i]*ccos[0,:] - PC11 = P11[i]*ccos[1,:] - PS11 = P11[i]*ssin[1,:] - # C10: C10, C11, S11 (see equations 12 and 13 of Swenson et al., 2008) - IMAT[0,0] += np.sum(int_fact[i]*PC10*ocean_function[:,i]*PC10)/(4.0*np.pi) - IMAT[1,0] += np.sum(int_fact[i]*PC10*ocean_function[:,i]*PC11)/(4.0*np.pi) - IMAT[2,0] += np.sum(int_fact[i]*PC10*ocean_function[:,i]*PS11)/(4.0*np.pi) - # C11: C10, C11, S11 (see equations 12 and 13 of Swenson et al., 2008) - IMAT[0,1] += np.sum(int_fact[i]*PC11*ocean_function[:,i]*PC10)/(4.0*np.pi) - IMAT[1,1] += np.sum(int_fact[i]*PC11*ocean_function[:,i]*PC11)/(4.0*np.pi) - IMAT[2,1] += np.sum(int_fact[i]*PC11*ocean_function[:,i]*PS11)/(4.0*np.pi) - # S11: C10, C11, S11 (see equations 12 and 13 of Swenson et al., 2008) - IMAT[0,2] += np.sum(int_fact[i]*PS11*ocean_function[:,i]*PC10)/(4.0*np.pi) - IMAT[1,2] += np.sum(int_fact[i]*PS11*ocean_function[:,i]*PC11)/(4.0*np.pi) - IMAT[2,2] += np.sum(int_fact[i]*PS11*ocean_function[:,i]*PS11)/(4.0*np.pi) + # C10, C11, S11 + PC10 = np.einsum("h...,p...->ph...", P10, m_phi[0,:].real) + PC11 = np.einsum("h...,p...->ph...", P11, m_phi[1,:].real) + PS11 = np.einsum("h...,p...->ph...", P11, m_phi[1,:].imag) + # C10: C10, C11, S11 + IMAT[0,0] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC10, ocean_function, PC10) + IMAT[1,0] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC10, ocean_function, PC11) + IMAT[2,0] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC10, ocean_function, PS11) + # C11: C10, C11, S11 + IMAT[0,1] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC11, ocean_function, PC10) + IMAT[1,1] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC11, ocean_function, PC11) + IMAT[2,1] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC11, ocean_function, PS11) + # S11: C10, C11, S11 + IMAT[0,2] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PS11, ocean_function, PC10) + IMAT[1,2] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PS11, ocean_function, PC11) + IMAT[2,2] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PS11, ocean_function, PS11) # iterate solutions: if not single iteration n_iter = 0 @@ -499,9 +499,6 @@ def delta_degree_one(base_dir, PROC, DREL, LMAX, RAD, # Allocate for G matrix parameters # G matrix calculates the GRACE ocean mass variations G = gravtk.geocenter() - G.C10 = 0.0 - G.C11 = 0.0 - G.S11 = 0.0 # degree 1 iterations iteration = gravtk.geocenter() iteration.C10 = np.zeros((max_iter)) @@ -509,29 +506,19 @@ def delta_degree_one(base_dir, PROC, DREL, LMAX, RAD, iteration.S11 = np.zeros((max_iter)) # calculate non-iterated terms (G-matrix parameters) # calculate geocenter component of ocean mass with GRACE - # allocate for product of grace and legendre polynomials - pcos = np.zeros((MMAX+1, nlat))#-[m,lat] - psin = np.zeros((MMAX+1, nlat))#-[m,lat] - # Summing product of plms and c/slms over all SH degrees >= 2 - for i in range(0, nlat): - l = np.arange(2,LMAX+1) - pcos[:,i] = np.sum(((plmout[l,:,i]*delta_Ylms.clm[l,:])**2)/nsmth, axis=0) - psin[:,i] = np.sum(((plmout[l,:,i]*delta_Ylms.slm[l,:])**2)/nsmth, axis=0) + # subset GRACE to degrees 2+ for calculating ocean mass + l2 = slice(2, LMAX+1) + pconv = np.einsum("lmh...,lm...->mh...", plmout[l2, :, :], delta_Ylms.ilm[l2, :]) # Multiplying by c/s(phi#m) to get surface density in cmwe (lon,lat) # ccos/ssin are mXphi, pcos/psin are mXtheta: resultant matrices are phiXtheta # The summation over spherical harmonic order is in this multiplication - rmass = np.sqrt(np.dot(np.transpose(ccos**2),pcos) + np.dot(np.transpose(ssin**2),psin)) + rmass = np.einsum("mp...,mh...->ph...", m_phi, pconv) # calculate G matrix parameters through a summation of each latitude - for i in range(0,nlat): - # C10, C11, S11 - PC10 = P10[i]*ccos[0,:] - PC11 = P11[i]*ccos[1,:] - PS11 = P11[i]*ssin[1,:] - # summation of integration factors, Legendre polynomials, - # (convolution of order and harmonics) and the ocean mass at t - G.C10 += np.sum(int_fact[i]*PC10*ocean_function[:,i]*rmass[:,i])/(4.0*np.pi) - G.C11 += np.sum(int_fact[i]*PC11*ocean_function[:,i]*rmass[:,i])/(4.0*np.pi) - G.S11 += np.sum(int_fact[i]*PS11*ocean_function[:,i]*rmass[:,i])/(4.0*np.pi) + # summation of integration factors, Legendre polynomials, + # (convolution of order and harmonics) and the ocean mass at t + G.C10 = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC10, ocean_function, rmass) + G.C11 = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC11, ocean_function, rmass) + G.S11 = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PS11, ocean_function, rmass) # calculate degree one solution for each iteration (or single if not) while (eps > eps_max) and (n_iter < max_iter): @@ -549,22 +536,14 @@ def delta_degree_one(base_dir, PROC, DREL, LMAX, RAD, delta_Ylms.clm[1,1] = iteration.C11[n_iter-1] delta_Ylms.slm[1,1] = iteration.S11[n_iter-1] - # allocate for product of grace and legendre polynomials - pcos = np.zeros((MMAX+1, nlat))#-[m,lat] - psin = np.zeros((MMAX+1, nlat))#-[m,lat] # Summing product of plms and c/slms over all SH degrees - for i in range(0, nlat): - # for land water: use an initial seasonal geocenter estimate - # from Chen et al. (1999) - l = np.arange(1,LMAX+1) - pcos[:,i] = np.sum(((plmout[l,:,i]*delta_Ylms.clm[l,:])**2)/nsmth, axis=0) - psin[:,i] = np.sum(((plmout[l,:,i]*delta_Ylms.slm[l,:])**2)/nsmth, axis=0) + l1 = slice(1, LMAX+1) + pconv = np.einsum("lmh...,lm...->mh...", plmout[l1, :, :], delta_Ylms.ilm[l1, :]) # Multiplying by c/s(phi#m) to get surface density in cm w.e. (lonxlat) - # this will be a spatial field similar to outputs from stokes_combine.py # ccos/ssin are mXphi, pcos/psin are mXtheta: resultant matrices are phiXtheta # The summation over spherical harmonic order is in this multiplication - lmass = np.sqrt(np.dot(np.transpose(ccos**2),pcos) + np.dot(np.transpose(ssin**2),psin)) + lmass = np.einsum("mp...,mh...->ph...", m_phi, pconv) # use sea level fingerprints or eustatic from GRACE land components if FINGERPRINT: diff --git a/geocenter/geocenter_monte_carlo.py b/geocenter/geocenter_monte_carlo.py index 925627a1..62d578d6 100644 --- a/geocenter/geocenter_monte_carlo.py +++ b/geocenter/geocenter_monte_carlo.py @@ -128,7 +128,7 @@ def geocenter_monte_carlo(grace_dir,PROC,DREL,START_MON,END_MON,MISSING): artist = matplotlib.offsetbox.AnchoredText(axes_labels[key], pad=0., prop=dict(size=16,weight='bold'), frameon=False, loc=2) ax[j].add_artist(artist) - lbl = f'$\sigma$ = {RMS:0.2f} mm\nmax = {max_var:0.2f} mm' + lbl = r'$\sigma$' + f' = {RMS:0.2f} mm\nmax = {max_var:0.2f} mm' artist = matplotlib.offsetbox.AnchoredText(lbl, pad=0., prop=dict(size=12), frameon=False, loc=3) ax[j].add_artist(artist) diff --git a/geocenter/geocenter_spatial_maps.py b/geocenter/geocenter_spatial_maps.py index 1d06bcda..84640abc 100644 --- a/geocenter/geocenter_spatial_maps.py +++ b/geocenter/geocenter_spatial_maps.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" geocenter_spatial_maps.py -Written by Tyler Sutterley (05/2023) +Written by Tyler Sutterley (07/2026) Reads in GRACE/GRACE-FO geocenter coefficients and exports trends in the monthly spatial fields in millimeters water equivalent @@ -90,6 +90,7 @@ utilities.py: download and management utilities for files UPDATE HISTORY: + Updated 07/2026: use np.radians to convert from degrees to radians Updated 05/2023: split S2 tidal aliasing terms into GRACE and GRACE-FO eras use pathlib to define and operate on paths Updated 01/2023: refactored time series analysis functions @@ -265,7 +266,7 @@ def geocenter_spatial_maps(base_dir, PROC, DREL, nlat = len(grid.lat) # Computing plms for converting to spatial domain - theta = (90.0-grid.lat)*np.pi/180.0 + theta = np.radians(90.0-grid.lat) PLM, dPLM = gravtk.plm_holmes(2, np.cos(theta)) # fit coefficients fits = ['x1','x2','SS','SC','AS','AC','S2SGRC','S2CGRC','S2SGFO','S2CGFO'] @@ -342,8 +343,7 @@ def geocenter_spatial_maps(base_dir, PROC, DREL, # output phase to file for key in ['SP','AP','S2PGRC','S2PGFO']: # convert phase from -180:180 to 0:360 - ix,iy = np.nonzero(var[key] < 0) - var[key][ix,iy] += 360.0 + var[key] = np.where(var[key] < 0, var[key] + 360.0, var[key]) # copy variables to output grid grid.data = np.copy(var[key]) grid.time = np.copy(MEAN.time) diff --git a/geocenter/kernel_degree_one.py b/geocenter/kernel_degree_one.py index 30d4d194..4f59f1eb 100644 --- a/geocenter/kernel_degree_one.py +++ b/geocenter/kernel_degree_one.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" kernel_degree_one.py -Written by Tyler Sutterley (06/2024) +Written by Tyler Sutterley (07/2026) Calculates the sensitivity of geocenter calculations for each degree and order Can be used to estimate the geocenter uncertainties for sets of harmonics @@ -107,6 +107,8 @@ https://doi.org/10.1029/2007JB005338 UPDATE HISTORY: + Updated 07/2026: use np.einsum for spherical harmonic summations + use np.radians to convert from degrees to radians Updated 06/2024: use wrapper to importlib for optional dependencies Updated 09/2023: simplify I-matrix and G-matrix calculations Updated 05/2023: use pathlib to define and operate on paths @@ -201,11 +203,11 @@ def kernel_degree_one(base_dir, LMAX, RAD, dlon,dlat = landsea.spacing nlat, nlon = landsea.shape # spatial parameters in radians - dphi = dlon*np.pi/180.0 - dth = dlat*np.pi/180.0 + dphi = np.radians(dlon) + dth = np.radians(dlat) # longitude and colatitude in radians - phi = landsea.lon[np.newaxis,:]*np.pi/180.0 - th = (90.0 - np.squeeze(landsea.lat))*np.pi/180.0 + phi = np.radians(landsea.lon[np.newaxis,:]) + th = np.radians(90.0 - np.squeeze(landsea.lat)) # create land function land_function = np.zeros((nlon, nlat),dtype=np.float64) # extract land function from file @@ -236,39 +238,39 @@ def kernel_degree_one(base_dir, LMAX, RAD, # Calculating cos/sin of phi arrays # output [m,phi] - m = np.arange(0,MMAX+1)[:, np.newaxis] + m = np.arange(0,MMAX+1) # Integration factors (solid angle) int_fact = np.sin(th)*dphi*dth - # Calculating cos(m*phi) and sin(m*phi) - ccos = np.cos(np.dot(m,phi)) - ssin = np.sin(np.dot(m,phi)) + # 4-pi normalization + norm = 1.0/(4.0*np.pi) + # calculating cos(m*phi) and sin(m*phi) using Euler's formula + m_phi = np.exp(1j * np.einsum("m...,p...->mp...", m, phi)) # Legendre polynomials for degree 1 P10 = np.squeeze(PLM[1,0,:]) P11 = np.squeeze(PLM[1,1,:]) # Initializing 3x3 I-Parameter matrix - IMAT = np.zeros((3,3)) - # Calculating I-Parameter matrix by integrating over latitudes + # (see equations 12 and 13 of Swenson et al., 2008) + IMAT = np.zeros((3, 3)) # I-Parameter matrix accounts for the fact that the GRACE data only # includes spherical harmonic degrees greater than or equal to 2 - for i in range(0,nlat): - # C10, C11, S11 - PC10 = P10[i]*ccos[0,:] - PC11 = P11[i]*ccos[1,:] - PS11 = P11[i]*ssin[1,:] - # C10: C10, C11, S11 (see equations 12 and 13 of Swenson et al., 2008) - IMAT[0,0] += np.sum(int_fact[i]*PC10*ocean_function[:,i]*PC10)/(4.0*np.pi) - IMAT[1,0] += np.sum(int_fact[i]*PC10*ocean_function[:,i]*PC11)/(4.0*np.pi) - IMAT[2,0] += np.sum(int_fact[i]*PC10*ocean_function[:,i]*PS11)/(4.0*np.pi) - # C11: C10, C11, S11 (see equations 12 and 13 of Swenson et al., 2008) - IMAT[0,1] += np.sum(int_fact[i]*PC11*ocean_function[:,i]*PC10)/(4.0*np.pi) - IMAT[1,1] += np.sum(int_fact[i]*PC11*ocean_function[:,i]*PC11)/(4.0*np.pi) - IMAT[2,1] += np.sum(int_fact[i]*PC11*ocean_function[:,i]*PS11)/(4.0*np.pi) - # S11: C10, C11, S11 (see equations 12 and 13 of Swenson et al., 2008) - IMAT[0,2] += np.sum(int_fact[i]*PS11*ocean_function[:,i]*PC10)/(4.0*np.pi) - IMAT[1,2] += np.sum(int_fact[i]*PS11*ocean_function[:,i]*PC11)/(4.0*np.pi) - IMAT[2,2] += np.sum(int_fact[i]*PS11*ocean_function[:,i]*PS11)/(4.0*np.pi) + # C10, C11, S11 + PC10 = np.einsum("h...,p...->ph...", P10, m_phi[0,:].real) + PC11 = np.einsum("h...,p...->ph...", P11, m_phi[1,:].real) + PS11 = np.einsum("h...,p...->ph...", P11, m_phi[1,:].imag) + # C10: C10, C11, S11 + IMAT[0,0] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC10, ocean_function, PC10) + IMAT[1,0] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC10, ocean_function, PC11) + IMAT[2,0] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC10, ocean_function, PS11) + # C11: C10, C11, S11 + IMAT[0,1] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC11, ocean_function, PC10) + IMAT[1,1] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC11, ocean_function, PC11) + IMAT[2,1] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC11, ocean_function, PS11) + # S11: C10, C11, S11 + IMAT[0,2] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PS11, ocean_function, PC10) + IMAT[1,2] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PS11, ocean_function, PC11) + IMAT[2,2] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PS11, ocean_function, PS11) # output flag for using sea level fingerprints slf_str = '_SLF' if FINGERPRINT else '' @@ -313,9 +315,9 @@ def kernel_degree_one(base_dir, LMAX, RAD, fit_factor[ii] = wt[l]*(2.0*l + 1.0)/(1.0 + LOVE.kl[l]) # cosine and sine factors if (csharm == 'clm'): - mphi[ii,:] = ccos[m,:] + mphi[ii,:] = m_phi[m,:].real elif (csharm == 'slm'): - mphi[ii,:] = ssin[m,:] + mphi[ii,:] = m_phi[m,:].imag # add 1 to counter ii += 1 @@ -323,10 +325,10 @@ def kernel_degree_one(base_dir, LMAX, RAD, output['covariance'] = np.zeros((n_harm, n_harm, 3)) for i in range(n_harm): # setting kern_i equal to 1 for d/o - kern_i = np.zeros((n_harm,nlat)) + kern_i = np.zeros((n_harm, nlat)) kern_i[i,:] = 1.0*fit_factor[i] # calculate land mass maps - lmass = np.dot(mphi.T,plm*kern_i) + lmass = np.dot(mphi.T, plm*kern_i) # Calculating data matrices # GRACE Eustatic degree 1 from land variations eustatic = gravtk.geocenter() @@ -346,7 +348,7 @@ def kernel_degree_one(base_dir, LMAX, RAD, sea_level = gravtk.sea_level_equation(land_Ylms.clm, land_Ylms.slm, landsea.lon, landsea.lat, land_function, LMAX=EXPANSION, LOVE=LOVE_K1, BODY_TIDE_LOVE=0, FLUID_LOVE=0, ITERATIONS=3, - POLAR=True, PLM=PLM, ASTYPE=np.float64, SCALE=1e-32, FILL_VALUE=0) + POLAR=True, PLM=PLM, FILL_VALUE=0) # 3) convert sea level fingerprints into spherical harmonics slf_Ylms = gravtk.gen_stokes(sea_level, landsea.lon, landsea.lat, UNITS=1, LMIN=0, LMAX=1, PLM=PLM[:2,:2,:], LOVE=LOVE) @@ -385,24 +387,16 @@ def kernel_degree_one(base_dir, LMAX, RAD, if (j >= 3): kern_j[j,:] = 1.0*fit_factor[j] # calculate ocean mass maps - rmass = np.dot(mphi.T,plm*kern_j) + rmass = np.dot(mphi.T, plm*kern_j) # Allocate for G matrix parameters # G matrix calculates the GRACE ocean mass variations G = gravtk.geocenter() - G.C10 = 0.0 - G.C11 = 0.0 - G.S11 = 0.0 # calculate G matrix parameters through a summation of each latitude - for n in range(0,nlat): - # C10, C11, S11 - PC10 = P10[i]*ccos[0,:] - PC11 = P11[i]*ccos[1,:] - PS11 = P11[i]*ssin[1,:] - # summation of integration factors, Legendre polynomials, - # (convolution of order and harmonics) and the ocean mass - G.C10 += np.sum(int_fact[n]*PC10*ocean_function[:,n]*rmass[:,n])/(4.0*np.pi) - G.C11 += np.sum(int_fact[n]*PC11*ocean_function[:,n]*rmass[:,n])/(4.0*np.pi) - G.S11 += np.sum(int_fact[n]*PS11*ocean_function[:,n]*rmass[:,n])/(4.0*np.pi) + # summation of integration factors, Legendre polynomials, + # (convolution of order and harmonics) and the ocean mass at t + G.C10 = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC10, ocean_function, rmass) + G.C11 = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC11, ocean_function, rmass) + G.S11 = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PS11, ocean_function, rmass) # G Matrix for time t GMAT = np.array([G.C10, G.C11, G.S11]) diff --git a/geocenter/model_degree_one.py b/geocenter/model_degree_one.py index ec788f15..e6aeb516 100755 --- a/geocenter/model_degree_one.py +++ b/geocenter/model_degree_one.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" model_degree_one.py -Written by Tyler Sutterley (01/2025) +Written by Tyler Sutterley (07/2026) Calculates degree 1 variations using synthetic coefficients of degree 2 and greater for testing the reliability of the algorithm @@ -120,6 +120,8 @@ https://doi.org/10.1029/2007JB005338 UPDATE HISTORY: + Updated 07/2026: use np.einsum for spherical harmonic summations + use np.radians to convert from degrees to radians Updated 01/2025: fixed deprecated tick label resizing Updated 09/2023: simplify I-matrix and G-matrix calculations Updated 05/2023: use pathlib to define and operate on paths @@ -208,12 +210,12 @@ def model_seasonal_geocenter(grace_date): SAPz = 75.0 # calculate each geocenter component from the amplitude and phase # converting the phase from degrees to radians - X = AAx*np.sin(2.0*np.pi*grace_date + APx*np.pi/180.0) + \ - SAAx*np.sin(4.0*np.pi*grace_date + SAPx*np.pi/180.0) - Y = AAy*np.sin(2.0*np.pi*grace_date + APy*np.pi/180.0) + \ - SAAy*np.sin(4.0*np.pi*grace_date + SAPy*np.pi/180.0) - Z = AAz*np.sin(2.0*np.pi*grace_date + APz*np.pi/180.0) + \ - SAAz*np.sin(4.0*np.pi*grace_date + SAPz*np.pi/180.0) + X = AAx*np.sin(2.0*np.pi*grace_date + np.radians(APx)) + \ + SAAx*np.sin(4.0*np.pi*grace_date + np.radians(SAPx)) + Y = AAy*np.sin(2.0*np.pi*grace_date + np.radians(APy)) + \ + SAAy*np.sin(4.0*np.pi*grace_date + np.radians(SAPy)) + Z = AAz*np.sin(2.0*np.pi*grace_date + np.radians(APz)) + \ + SAAz*np.sin(4.0*np.pi*grace_date + np.radians(SAPz)) DEG1 = gravtk.geocenter(X=X-X.mean(), Y=Y-Y.mean(), Z=Z-Z.mean()) return DEG1.from_cartesian() @@ -271,11 +273,11 @@ def model_degree_one(input_file, LMAX, RAD, dlon,dlat = landsea.spacing nlat, nlon = landsea.shape # spatial parameters in radians - dphi = dlon*np.pi/180.0 - dth = dlat*np.pi/180.0 + dphi = np.radians(dlon) + dth = np.radians(dlat) # longitude and colatitude in radians - phi = landsea.lon[np.newaxis,:]*np.pi/180.0 - th = (90.0 - np.squeeze(landsea.lat))*np.pi/180.0 + phi = np.radians(landsea.lon[np.newaxis,:]) + th = np.radians(90.0 - np.squeeze(landsea.lat)) # create land function land_function = np.zeros((nlon, nlat),dtype=np.float64) # extract land function from file @@ -326,12 +328,13 @@ def model_degree_one(input_file, LMAX, RAD, # Calculating cos/sin of phi arrays # output [m,phi] - m = data_Ylms.m[:, np.newaxis] + m = data_Ylms.m # Integration factors (solid angle) int_fact = np.sin(th)*dphi*dth - # Calculating cos(m*phi) and sin(m*phi) - ccos = np.cos(np.dot(m,phi)) - ssin = np.sin(np.dot(m,phi)) + # 4-pi normalization + norm = 1.0/(4.0*np.pi) + # calculating cos(m*phi) and sin(m*phi) using Euler's formula + m_phi = np.exp(1j * np.einsum("m...,p...->mp...", m, phi)) # Legendre polynomials for degree 1 P10 = np.squeeze(PLM[1,0,:]) @@ -339,34 +342,31 @@ def model_degree_one(input_file, LMAX, RAD, # PLM for spherical harmonic degrees 2+ up to LMAX # converted into mass and smoothed if specified plmout = np.zeros((LMAX+1, MMAX+1, nlat)) - for l in range(1,LMAX+1): - m = np.arange(0,np.min([l,MMAX])+1) - # convert to smoothed coefficients of mass - # Convolving plms with degree dependent factor and smoothing - plmout[l,m,:] = PLM[l,m,:]*dfactor[l]*wt[l] + # convert to smoothed coefficients of mass + # Convolving plms with degree dependent factor and smoothing + plmout[:] = np.einsum("l,l,lmh->lmh", dfactor, wt, PLM[:LMAX+1,:MMAX+1,:]) # Initializing 3x3 I-Parameter matrix - IMAT = np.zeros((3,3)) - # Calculating I-Parameter matrix by integrating over latitudes - # I-Parameter matrix accounts for the fact that the harmonic data only + # (see equations 12 and 13 of Swenson et al., 2008) + IMAT = np.zeros((3, 3)) + # I-Parameter matrix accounts for the fact that the GRACE data only # includes spherical harmonic degrees greater than or equal to 2 - for i in range(0,nlat): - # C10, C11, S11 - PC10 = P10[i]*ccos[0,:] - PC11 = P11[i]*ccos[1,:] - PS11 = P11[i]*ssin[1,:] - # C10: C10, C11, S11 (see equations 12 and 13 of Swenson et al., 2008) - IMAT[0,0] += np.sum(int_fact[i]*PC10*ocean_function[:,i]*PC10)/(4.0*np.pi) - IMAT[1,0] += np.sum(int_fact[i]*PC10*ocean_function[:,i]*PC11)/(4.0*np.pi) - IMAT[2,0] += np.sum(int_fact[i]*PC10*ocean_function[:,i]*PS11)/(4.0*np.pi) - # C11: C10, C11, S11 (see equations 12 and 13 of Swenson et al., 2008) - IMAT[0,1] += np.sum(int_fact[i]*PC11*ocean_function[:,i]*PC10)/(4.0*np.pi) - IMAT[1,1] += np.sum(int_fact[i]*PC11*ocean_function[:,i]*PC11)/(4.0*np.pi) - IMAT[2,1] += np.sum(int_fact[i]*PC11*ocean_function[:,i]*PS11)/(4.0*np.pi) - # S11: C10, C11, S11 (see equations 12 and 13 of Swenson et al., 2008) - IMAT[0,2] += np.sum(int_fact[i]*PS11*ocean_function[:,i]*PC10)/(4.0*np.pi) - IMAT[1,2] += np.sum(int_fact[i]*PS11*ocean_function[:,i]*PC11)/(4.0*np.pi) - IMAT[2,2] += np.sum(int_fact[i]*PS11*ocean_function[:,i]*PS11)/(4.0*np.pi) + # C10, C11, S11 + PC10 = np.einsum("h...,p...->ph...", P10, m_phi[0,:].real) + PC11 = np.einsum("h...,p...->ph...", P11, m_phi[1,:].real) + PS11 = np.einsum("h...,p...->ph...", P11, m_phi[1,:].imag) + # C10: C10, C11, S11 + IMAT[0,0] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC10, ocean_function, PC10) + IMAT[1,0] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC10, ocean_function, PC11) + IMAT[2,0] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC10, ocean_function, PS11) + # C11: C10, C11, S11 + IMAT[0,1] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC11, ocean_function, PC10) + IMAT[1,1] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC11, ocean_function, PC11) + IMAT[2,1] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC11, ocean_function, PS11) + # S11: C10, C11, S11 + IMAT[0,2] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PS11, ocean_function, PC10) + IMAT[1,2] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PS11, ocean_function, PC11) + IMAT[2,2] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PS11, ocean_function, PS11) # get seasonal variations of an initial geocenter correction # for use in the land water mass calculation @@ -400,29 +400,21 @@ def model_degree_one(input_file, LMAX, RAD, # calculate non-iterated terms for each file (G-matrix parameters) for t in range(n_files): # calculate geocenter component of ocean mass - # allocate for product of grace and legendre polynomials - pcos = np.zeros((MMAX+1, nlat))#-[m,lat] - psin = np.zeros((MMAX+1, nlat))#-[m,lat] # Summing product of plms and c/slms over all SH degrees >= 2 - for i in range(0, nlat): - l = np.arange(2,LMAX+1) - pcos[:,i] = np.sum(plmout[l,:,i]*data_Ylms.clm[l,:,t], axis=0) - psin[:,i] = np.sum(plmout[l,:,i]*data_Ylms.slm[l,:,t], axis=0) + Ylms = data_Ylms.index(t) + # subset GRACE to degrees 2+ for calculating ocean mass + l2 = slice(2, LMAX+1) + pconv = np.einsum("lmh...,lm...->mh...", plmout[l2, :, :], Ylms.ilm[l2, :]) # Multiplying by c/s(phi#m) to get surface density in cmwe (lon,lat) # ccos/ssin are mXphi, pcos/psin are mXtheta: resultant matrices are phiXtheta # The summation over spherical harmonic order is in this multiplication - rmass = np.dot(np.transpose(ccos),pcos) + np.dot(np.transpose(ssin),psin) + rmass = np.einsum("mp...,mh...->ph...", m_phi, pconv) # calculate G matrix parameters through a summation of each latitude - for i in range(0,nlat): - # C10, C11, S11 - PC10 = P10[i]*ccos[0,:] - PC11 = P11[i]*ccos[1,:] - PS11 = P11[i]*ssin[1,:] - # summation of integration factors, Legendre polynomials, - # (convolution of order and harmonics) and the ocean mass at t - G.C10[t] += np.sum(int_fact[i]*PC10*ocean_function[:,i]*rmass[:,i])/(4.0*np.pi) - G.C11[t] += np.sum(int_fact[i]*PC11*ocean_function[:,i]*rmass[:,i])/(4.0*np.pi) - G.S11[t] += np.sum(int_fact[i]*PS11*ocean_function[:,i]*rmass[:,i])/(4.0*np.pi) + # summation of integration factors, Legendre polynomials, + # (convolution of order and harmonics) and the ocean mass at t + G.C10[t] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC10, ocean_function, rmass) + G.C11[t] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC11, ocean_function, rmass) + G.S11[t] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PS11, ocean_function, rmass) # calculate degree one solution for each iteration (or single if not) while (eps > eps_max) and (n_iter < max_iter): @@ -442,20 +434,15 @@ def model_degree_one(input_file, LMAX, RAD, data_Ylms.clm[1,1,t] = iteration.C11[t,n_iter-1] data_Ylms.slm[1,1,t] = iteration.S11[t,n_iter-1] - # allocate for product of grace and legendre polynomials - pcos = np.zeros((MMAX+1, nlat))#-[m,lat] - psin = np.zeros((MMAX+1, nlat))#-[m,lat] # Summing product of plms and c/slms over all SH degrees - for i in range(0, nlat): - l = np.arange(1,LMAX+1) - pcos[:,i] = np.sum(plmout[l,:,i]*data_Ylms.clm[l,:,t], axis=0) - psin[:,i] = np.sum(plmout[l,:,i]*data_Ylms.slm[l,:,t], axis=0) + Ylms = data_Ylms.index(t) + l1 = slice(1, LMAX+1) + pconv = np.einsum("lmh...,lm...->mh...", plmout[l1, :, :], Ylms.ilm[l1, :]) # Multiplying by c/s(phi#m) to get surface density in cm w.e. (lonxlat) - # this will be a spatial field similar to outputs from stokes_combine.py # ccos/ssin are mXphi, pcos/psin are mXtheta: resultant matrices are phiXtheta # The summation over spherical harmonic order is in this multiplication - lmass = np.dot(np.transpose(ccos),pcos) + np.dot(np.transpose(ssin),psin) + lmass = np.einsum("mp...,mh...->ph...", m_phi, pconv) # use sea level fingerprints or eustatic from land components if FINGERPRINT: diff --git a/geocenter/monte_carlo_degree_one.py b/geocenter/monte_carlo_degree_one.py index 7ecb2b6f..e7c72114 100644 --- a/geocenter/monte_carlo_degree_one.py +++ b/geocenter/monte_carlo_degree_one.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" monte_carlo_degree_one.py -Written by Tyler Sutterley (01/2025) +Written by Tyler Sutterley (07/2026) Calculates degree 1 errors using GRACE coefficients of degree 2 and greater, and ocean bottom pressure variations from OMCT/MPIOM in a Monte Carlo scheme @@ -157,6 +157,9 @@ https://doi.org/10.1029/2005GL025305 UPDATE HISTORY: + Updated 07/2026: use np.einsum for spherical harmonic summations + can remove sets of harmonic files from the GRACE/GRACE-FO data + use np.radians to convert from degrees to radians Updated 01/2025: fixed deprecated tick label resizing Updated 06/2024: use wrapper to importlib for optional dependencies Updated 10/2023: generalize mission variable to be GRACE/GRACE-FO @@ -263,12 +266,12 @@ def model_seasonal_geocenter(grace_date): SAPz = 75.0 # calculate each geocenter component from the amplitude and phase # converting the phase from degrees to radians - X = AAx*np.sin(2.0*np.pi*grace_date + APx*np.pi/180.0) + \ - SAAx*np.sin(4.0*np.pi*grace_date + SAPx*np.pi/180.0) - Y = AAy*np.sin(2.0*np.pi*grace_date + APy*np.pi/180.0) + \ - SAAy*np.sin(4.0*np.pi*grace_date + SAPy*np.pi/180.0) - Z = AAz*np.sin(2.0*np.pi*grace_date + APz*np.pi/180.0) + \ - SAAz*np.sin(4.0*np.pi*grace_date + SAPz*np.pi/180.0) + X = AAx*np.sin(2.0*np.pi*grace_date + np.radians(APx)) + \ + SAAx*np.sin(4.0*np.pi*grace_date + np.radians(SAPx)) + Y = AAy*np.sin(2.0*np.pi*grace_date + np.radians(APy)) + \ + SAAy*np.sin(4.0*np.pi*grace_date + np.radians(SAPy)) + Z = AAz*np.sin(2.0*np.pi*grace_date + np.radians(APz)) + \ + SAAz*np.sin(4.0*np.pi*grace_date + np.radians(SAPz)) DEG1 = gravtk.geocenter(X=X-X.mean(), Y=Y-Y.mean(), Z=Z-Z.mean()) return DEG1.from_cartesian() @@ -295,6 +298,9 @@ def monte_carlo_degree_one(base_dir, PROC, DREL, LMAX, RAD, DATAFORM=None, MEAN_FILE=None, MEANFORM=None, + REMOVE_FILES=None, + REMOVE_FORMAT=None, + REDISTRIBUTE_REMOVED=False, ERROR_FILES=[], SOLVER=None, FINGERPRINT=False, @@ -404,11 +410,11 @@ def monte_carlo_degree_one(base_dir, PROC, DREL, LMAX, RAD, dlon,dlat = landsea.spacing nlat, nlon = landsea.shape # spatial parameters in radians - dphi = dlon*np.pi/180.0 - dth = dlat*np.pi/180.0 + dphi = np.radians(dlon) + dth = np.radians(dlat) # longitude and colatitude in radians - phi = landsea.lon[np.newaxis,:]*np.pi/180.0 - th = (90.0 - np.squeeze(landsea.lat))*np.pi/180.0 + phi = np.radians(landsea.lon[np.newaxis,:]) + th = np.radians(90.0 - np.squeeze(landsea.lat)) # create land function land_function = np.zeros((nlon, nlat),dtype=np.float64) # extract land function from file @@ -505,6 +511,57 @@ def monte_carlo_degree_one(base_dir, PROC, DREL, LMAX, RAD, # save geocenter coefficients of the atmospheric jump corrections atm = gravtk.geocenter().from_harmonics(ATM_Ylms) + # input spherical harmonic datafiles to be removed from the GRACE data + # Remove sets of Ylms from the GRACE data before returning + remove_Ylms = GSM_Ylms.zeros_like() + remove_Ylms.time[:] = np.copy(GSM_Ylms.time) + remove_Ylms.month[:] = np.copy(GSM_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 = gravtk.harmonics().from_file(REMOVE_FILE, + format=REMOVEFORM) + attributes['lineage'].append(Ylms.filename) + elif REMOVEFORM in ('index-ascii','index-netCDF4','index-HDF5'): + # read from index file + _,removeform = REMOVEFORM.split('-') + # index containing files in data format + Ylms = gravtk.harmonics().from_index(REMOVE_FILE, + format=removeform) + attributes['lineage'].extend([f.name for f in Ylms.filename]) + # reduce to GRACE/GRACE-FO months and truncate to degree and order + Ylms = Ylms.subset(GSM_Ylms.month).truncate(lmax=LMAX, mmax=MMAX) + # remove the temporal mean of the coefficients + Ylms.mean(apply=True) + # 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) + # save geocenter coefficients of the auxiliary corrections + remove = gravtk.geocenter().from_harmonics(remove_Ylms) + # input spherical harmonic datafiles to be used in monte carlo error_Ylms = [] # for each file to be removed @@ -580,47 +637,45 @@ def monte_carlo_degree_one(base_dir, PROC, DREL, LMAX, RAD, # Calculating cos/sin of phi arrays # output [m,phi] - m = GSM_Ylms.m[:, np.newaxis] + m = GSM_Ylms.m # Integration factors (solid angle) int_fact = np.sin(th)*dphi*dth - # Calculating cos(m*phi) and sin(m*phi) - ccos = np.cos(np.dot(m,phi)) - ssin = np.sin(np.dot(m,phi)) + # 4-pi normalization + norm = 1.0/(4.0*np.pi) + # calculating cos(m*phi) and sin(m*phi) using Euler's formula + m_phi = np.exp(1j * np.einsum("m...,p...->mp...", m, phi)) # Legendre polynomials for degree 1 P10 = np.squeeze(PLM[1,0,:]) P11 = np.squeeze(PLM[1,1,:]) - # PLM for spherical harmonic degrees 2+ + # PLM for spherical harmonic degrees 2+ up to LMAX # converted into mass and smoothed if specified plmout = np.zeros((LMAX+1, MMAX+1, nlat)) - for l in range(1,LMAX+1): - m = np.arange(0,np.min([l,MMAX])+1) - # convert to smoothed coefficients of mass - # Convolving plms with degree dependent factor and smoothing - plmout[l,m,:] = PLM[l,m,:]*dfactor[l]*wt[l] + # convert to smoothed coefficients of mass + # Convolving plms with degree dependent factor and smoothing + plmout[:] = np.einsum("l,l,lmh->lmh", dfactor, wt, PLM[:LMAX+1,:MMAX+1,:]) # Initializing 3x3 I-Parameter matrix - IMAT = np.zeros((3,3)) - # Calculating I-Parameter matrix by integrating over latitudes + # (see equations 12 and 13 of Swenson et al., 2008) + IMAT = np.zeros((3, 3)) # I-Parameter matrix accounts for the fact that the GRACE data only # includes spherical harmonic degrees greater than or equal to 2 - for i in range(0,nlat): - # C10, C11, S11 - PC10 = P10[i]*ccos[0,:] - PC11 = P11[i]*ccos[1,:] - PS11 = P11[i]*ssin[1,:] - # C10: C10, C11, S11 (see equations 12 and 13 of Swenson et al., 2008) - IMAT[0,0] += np.sum(int_fact[i]*PC10*ocean_function[:,i]*PC10)/(4.0*np.pi) - IMAT[1,0] += np.sum(int_fact[i]*PC10*ocean_function[:,i]*PC11)/(4.0*np.pi) - IMAT[2,0] += np.sum(int_fact[i]*PC10*ocean_function[:,i]*PS11)/(4.0*np.pi) - # C11: C10, C11, S11 (see equations 12 and 13 of Swenson et al., 2008) - IMAT[0,1] += np.sum(int_fact[i]*PC11*ocean_function[:,i]*PC10)/(4.0*np.pi) - IMAT[1,1] += np.sum(int_fact[i]*PC11*ocean_function[:,i]*PC11)/(4.0*np.pi) - IMAT[2,1] += np.sum(int_fact[i]*PC11*ocean_function[:,i]*PS11)/(4.0*np.pi) - # S11: C10, C11, S11 (see equations 12 and 13 of Swenson et al., 2008) - IMAT[0,2] += np.sum(int_fact[i]*PS11*ocean_function[:,i]*PC10)/(4.0*np.pi) - IMAT[1,2] += np.sum(int_fact[i]*PS11*ocean_function[:,i]*PC11)/(4.0*np.pi) - IMAT[2,2] += np.sum(int_fact[i]*PS11*ocean_function[:,i]*PS11)/(4.0*np.pi) + # C10, C11, S11 + PC10 = np.einsum("h...,p...->ph...", P10, m_phi[0,:].real) + PC11 = np.einsum("h...,p...->ph...", P11, m_phi[1,:].real) + PS11 = np.einsum("h...,p...->ph...", P11, m_phi[1,:].imag) + # C10: C10, C11, S11 + IMAT[0,0] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC10, ocean_function, PC10) + IMAT[1,0] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC10, ocean_function, PC11) + IMAT[2,0] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC10, ocean_function, PS11) + # C11: C10, C11, S11 + IMAT[0,1] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC11, ocean_function, PC10) + IMAT[1,1] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC11, ocean_function, PC11) + IMAT[2,1] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC11, ocean_function, PS11) + # S11: C10, C11, S11 + IMAT[0,2] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PS11, ocean_function, PC10) + IMAT[1,2] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PS11, ocean_function, PC11) + IMAT[2,2] = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PS11, ocean_function, PS11) # get seasonal variations of an initial geocenter correction # for use in the land water mass calculation @@ -644,67 +699,56 @@ def monte_carlo_degree_one(base_dir, PROC, DREL, LMAX, RAD, Ylms.clm += (1.0-2.0*np.random.rand(LMAX+1,MMAX+1))*eYlms.clm Ylms.slm += (1.0-2.0*np.random.rand(LMAX+1,MMAX+1))*eYlms.slm - # Removing monthly GIA signal and atmospheric correction + # Removing monthly GIA signal, atmospheric correction + # and the auxiliary coefficients GRACE_Ylms = GSM_Ylms.index(t) GRACE_Ylms.subtract(GIA_Ylms.index(t)) GRACE_Ylms.subtract(ATM_Ylms.index(t)) + GRACE_Ylms.subtract(remove_Ylms.index(t)) + # combining GRACE/GRACE-FO with uncertainty for monte carlo run + GRACE_Ylms.add(Ylms) # G matrix calculates the GRACE ocean mass variations G = gravtk.geocenter() G.C10 = 0.0 G.C11 = 0.0 G.S11 = 0.0 - # calculate non-iterated terms (G-matrix parameters) - # calculate geocenter component of ocean mass with GRACE - # allocate for product of grace and legendre polynomials - pcos = np.zeros((MMAX+1, nlat))#-[m,lat] - psin = np.zeros((MMAX+1, nlat))#-[m,lat] - # Summing product of plms and c/slms over all SH degrees >= 2 - for i in range(0, nlat): - l = np.arange(2,LMAX+1) - pcos[:,i] = np.sum(plmout[l,:,i]*(GRACE_Ylms.clm[l,:]+Ylms.clm[l,:]), axis=0) - psin[:,i] = np.sum(plmout[l,:,i]*(GRACE_Ylms.slm[l,:]+Ylms.slm[l,:]), axis=0) + # subset GRACE to degrees 2+ for calculating ocean mass + l2 = slice(2, LMAX+1) + pconv = np.einsum("lmh...,lm...->mh...", plmout[l2, :, :], GRACE_Ylms.ilm[l2, :]) # Multiplying by c/s(phi#m) to get surface density in cmwe (lon,lat) # ccos/ssin are mXphi, pcos/psin are mXtheta: resultant matrices are phiXtheta # The summation over spherical harmonic order is in this multiplication - rmass = np.dot(np.transpose(ccos),pcos) + np.dot(np.transpose(ssin),psin) + rmass = np.einsum("mp...,mh...->ph...", m_phi, pconv) # calculate G matrix parameters through a summation of each latitude - for i in range(0,nlat): - # C10, C11, S11 - PC10 = P10[i]*ccos[0,:] - PC11 = P11[i]*ccos[1,:] - PS11 = P11[i]*ssin[1,:] - # summation of integration factors, Legendre polynomials, - # (convolution of order and harmonics) and the ocean mass at t - G.C10 += np.sum(int_fact[i]*PC10*ocean_function[:,i]*rmass[:,i])/(4.0*np.pi) - G.C11 += np.sum(int_fact[i]*PC11*ocean_function[:,i]*rmass[:,i])/(4.0*np.pi) - G.S11 += np.sum(int_fact[i]*PS11*ocean_function[:,i]*rmass[:,i])/(4.0*np.pi) + # summation of integration factors, Legendre polynomials, + # (convolution of order and harmonics) and the ocean mass at t + G.C10 = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC10, ocean_function, rmass) + G.C11 = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PC11, ocean_function, rmass) + G.S11 = norm*np.einsum("h...,ph...,ph...,ph...->...", int_fact, PS11, ocean_function, rmass) # seasonal component of geocenter variation for land water GSM_Ylms.clm[1,0,t] = seasonal_geocenter.C10[t] GSM_Ylms.clm[1,1,t] = seasonal_geocenter.C11[t] GSM_Ylms.slm[1,1,t] = seasonal_geocenter.S11[t] - # Removing monthly GIA signal and atmospheric correction + # Removing monthly GIA signal, atmospheric correction + # and the auxiliary coefficients GRACE_Ylms = GSM_Ylms.index(t) GRACE_Ylms.subtract(GIA_Ylms.index(t)) GRACE_Ylms.subtract(ATM_Ylms.index(t)) + GRACE_Ylms.subtract(remove_Ylms.index(t)) + # combining GRACE/GRACE-FO with uncertainty for monte carlo run + GRACE_Ylms.add(Ylms) - # allocate for product of grace and legendre polynomials - pcos = np.zeros((MMAX+1, nlat))#-[m,lat] - psin = np.zeros((MMAX+1, nlat))#-[m,lat] - # Summing product of plms and c/slms over all SH degrees - for i in range(0, nlat): - # for land water: use an initial seasonal geocenter estimate - # from Chen et al. (1999) - l = np.arange(1,LMAX+1) - pcos[:,i] = np.sum(plmout[l,:,i]*(GRACE_Ylms.clm[l,:]+Ylms.clm[l,:]), axis=0) - psin[:,i] = np.sum(plmout[l,:,i]*(GRACE_Ylms.slm[l,:]+Ylms.slm[l,:]), axis=0) + # for land water: use an initial seasonal geocenter estimate + # from Chen et al. (1999) then the iterative if specified + l1 = slice(1, LMAX+1) + pconv = np.einsum("lmh...,lm...->mh...", plmout[l1, :, :], GRACE_Ylms.ilm[l1, :]) # Multiplying by c/s(phi#m) to get surface density in cm w.e. (lonxlat) - # this will be a spatial field similar to outputs from stokes_combine.py # ccos/ssin are mXphi, pcos/psin are mXtheta: resultant matrices are phiXtheta # The summation over spherical harmonic order is in this multiplication - lmass = np.dot(np.transpose(ccos),pcos) + np.dot(np.transpose(ssin),psin) + lmass = np.einsum("mp...,mh...->ph...", m_phi, pconv) # use sea level fingerprints or eustatic from GRACE land components if FINGERPRINT: @@ -761,10 +805,13 @@ def monte_carlo_degree_one(base_dir, PROC, DREL, LMAX, RAD, elif SOLVER in ('gelsd', 'gelsy', 'gelss'): DMAT, res, rnk, s = scipy.linalg.lstsq(IMAT, (CMAT-GMAT), lapack_driver=SOLVER) - # save geocenter for iteration and time t after restoring GIA+ATM - iteration.C10[t,n_iter] = DMAT[0]+gia.C10[t]+atm.C10[t] - iteration.C11[t,n_iter] = DMAT[1]+gia.C11[t]+atm.C11[t] - iteration.S11[t,n_iter] = DMAT[2]+gia.S11[t]+atm.S11[t] + # save geocenter for iteration and time t after restoring fields + iteration.C10[t,n_iter] = DMAT[0,t]/dfactor[1] + \ + gia.C10[t] + atm.C10[t] + remove.C10[t] + iteration.C11[t,n_iter] = DMAT[1,t]/dfactor[1] + \ + gia.C11[t] + atm.C11[t] + remove.C11[t] + iteration.S11[t,n_iter] = DMAT[2,t]/dfactor[1] + \ + gia.S11[t] + atm.S11[t] + remove.S11[t] # remove mean of each solution for iteration iteration.C10[:,n_iter] -= iteration.C10[:,n_iter].mean() iteration.C11[:,n_iter] -= iteration.C11[:,n_iter].mean() @@ -866,6 +913,8 @@ def monte_carlo_degree_one(base_dir, PROC, DREL, LMAX, RAD, nc[key].setncattr(att_name, att_val) # define global attributes + for att_name, att_val in attributes.items(): + fileID.setncattr(att_name, att_val) fileID.date_created = time.strftime('%Y-%m-%d',time.localtime()) # close the output file fileID.close() @@ -1311,6 +1360,19 @@ def arguments(): parser.add_argument('--mean-format', type=str, default='netCDF4', choices=['ascii','netCDF4','HDF5','gfc'], help='Input data format for GRACE/GRACE-FO mean file') + # monthly files to be removed from the GRACE/GRACE-FO data + parser.add_argument('--remove-file', + type=pathlib.Path, nargs='+', + help='Monthly files to be removed from the GRACE/GRACE-FO data') + choices = [] + choices.extend(['ascii','netCDF4','HDF5']) + choices.extend(['index-ascii','index-netCDF4','index-HDF5']) + parser.add_argument('--remove-format', + type=str, nargs='+', choices=choices, + help='Input data format for files to be removed') + parser.add_argument('--redistribute-removed', + default=False, action='store_true', + help='Redistribute removed mass fields over the ocean') # additional error files to be used in the monte carlo run parser.add_argument('--error-file', type=pathlib.Path, @@ -1396,6 +1458,9 @@ def main(): DATAFORM=args.format, MEAN_FILE=args.mean_file, MEANFORM=args.mean_format, + REMOVE_FILES=args.remove_file, + REMOVE_FORMAT=args.remove_format, + REDISTRIBUTE_REMOVED=args.redistribute_removed, ERROR_FILES=args.error_file, SOLVER=args.solver, FINGERPRINT=args.fingerprint, diff --git a/gravity_toolkit/SLR/C20.py b/gravity_toolkit/SLR/C20.py index 992018f6..48369361 100644 --- a/gravity_toolkit/SLR/C20.py +++ b/gravity_toolkit/SLR/C20.py @@ -330,7 +330,7 @@ def C20(SLR_file, AOD=True, HEADER=True): YY,MM,DD,hh,mm,ss = gravity_toolkit.time.convert_julian( MJD+2400000.5, format='tuple') # converting from month, day, year into decimal year - dinput['time'][t] = gravity_toolkit.time.convert_calendar_decimal( + dinput['time'][t], = gravity_toolkit.time.convert_calendar_decimal( YY, MM, day=DD, hour=hh) # Spherical Harmonic data for line dinput['data'][t] = np.float64(line_contents[2]) @@ -390,7 +390,7 @@ def C20(SLR_file, AOD=True, HEADER=True): YY,MM,DD,hh,mm,ss = gravity_toolkit.time.convert_julian( MJD+2400000.5, format='tuple') # converting from month, day, year into decimal year - date_conv[t] = gravity_toolkit.time.convert_calendar_decimal( + date_conv[t], = gravity_toolkit.time.convert_calendar_decimal( YY, MM, day=DD, hour=hh) # Spherical Harmonic data for line C20_input[t] = np.float64(line_contents[2]) diff --git a/gravity_toolkit/SLR/C30.py b/gravity_toolkit/SLR/C30.py index 84c2dda4..3cd9d3b7 100644 --- a/gravity_toolkit/SLR/C30.py +++ b/gravity_toolkit/SLR/C30.py @@ -161,7 +161,7 @@ def C30(SLR_file, C30_MEAN=9.5717395773300e-07, HEADER=True): YY,MM,DD,hh,mm,ss = gravity_toolkit.time.convert_julian( MJD+2400000.5, format='tuple') # converting from month, day, year into decimal year - dinput['time'][t] = gravity_toolkit.time.convert_calendar_decimal( + dinput['time'][t], = gravity_toolkit.time.convert_calendar_decimal( YY, MM, day=DD, hour=hh) # Spherical Harmonic data for line dinput['data'][t] = np.float64(line_contents[5]) diff --git a/gravity_toolkit/SLR/C50.py b/gravity_toolkit/SLR/C50.py index af93dada..53dff904 100644 --- a/gravity_toolkit/SLR/C50.py +++ b/gravity_toolkit/SLR/C50.py @@ -143,7 +143,7 @@ def C50(SLR_file, C50_MEAN=0.0, DATE=None, HEADER=True): YY,MM,DD,hh,mm,ss = gravity_toolkit.time.convert_julian( MJD+2400000.5, format='tuple') # converting from month, day, year into decimal year - dinput['time'][t] = gravity_toolkit.time.convert_calendar_decimal( + dinput['time'][t], = gravity_toolkit.time.convert_calendar_decimal( YY, MM, day=DD, hour=hh) # Spherical Harmonic data for line dinput['data'][t] = np.float64(line_contents[10]) diff --git a/gravity_toolkit/clenshaw_summation.py b/gravity_toolkit/clenshaw_summation.py index c7bbeaff..28598e00 100644 --- a/gravity_toolkit/clenshaw_summation.py +++ b/gravity_toolkit/clenshaw_summation.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" clenshaw_summation.py -Written by Tyler Sutterley (04/2023) +Written by Tyler Sutterley (07/2026) Calculates the spatial field for a series of spherical harmonics for a sequence of ungridded points @@ -49,6 +49,8 @@ Bollettino di Geodesia e Scienze (1982) UPDATE HISTORY: + Updated 07/2026: use np.einsum for spherical harmonic summations + use np.radians to convert from degrees to radians Updated 04/2023: allow love numbers to be None for custom units case Updated 03/2023: improve typing for variables in docstrings Updated 02/2023: set custom units as top option in if/else statements @@ -123,8 +125,8 @@ def clenshaw_summation(clm, slm, lon, lat, raise ValueError('Incompatible vector dimensions (lon, lat)') # calculate colatitude and longitude in radians - th = (90.0 - lat)*np.pi/180.0 - phi = np.squeeze(lon*np.pi/180.0) + th = np.radians(90.0 - lat) + phi = np.squeeze(np.radians(lon)) # calculate cos and sin of colatitudes t = np.cos(th) u = np.sin(th) @@ -134,7 +136,7 @@ def clenshaw_summation(clm, slm, lon, lat, # Gaussian Smoothing if (RAD != 0): - wl = 2.0*np.pi*gauss_weights(RAD,LMAX) + wl = 2.0*np.pi*gauss_weights(RAD, LMAX) else: # else = 1 wl = np.ones((LMAX+1)) @@ -154,43 +156,42 @@ def clenshaw_summation(clm, slm, lon, lat, else: raise ValueError(f'Unknown units {UNITS}') + # complex spherical harmonics + ylm = clm - 1j * slm + # smooth degree dependent factors + f = dfactor*wl # calculate arrays for clenshaw summations over colatitudes - s_m_c = np.zeros((npts,LMAX*2+2)) + cs_m = np.zeros((npts, LMAX+1), dtype=np.clongdouble) for m in range(LMAX, -1, -1): # convolve harmonics with unit factors and smoothing - s_m_c[:,2*m:2*m+2] = clenshaw_s_m(t, dfactor*wl, m, clm, slm, - LMAX, ASTYPE=ASTYPE, SCALE=SCALE) + cs_m[:, m] = _clenshaw(t, f, m, ylm, LMAX, SCALE=SCALE) # calculate cos(phi) cos_phi_2 = 2.0*np.cos(phi) - # matrix of cos/sin m*phi summation - cos_m_phi = np.zeros((npts,LMAX+2),dtype=ASTYPE) - sin_m_phi = np.zeros((npts,LMAX+2),dtype=ASTYPE) + # matrix of cos/sin m*phi summation (Euler's form) + m_phi = np.zeros((npts, LMAX+2), dtype=np.clongdouble) # initialize matrix with values at lmax+1 and lmax - cos_m_phi[:,LMAX+1] = np.cos(ASTYPE(LMAX + 1)*phi) - sin_m_phi[:,LMAX+1] = np.sin(ASTYPE(LMAX + 1)*phi) - cos_m_phi[:,LMAX] = np.cos(ASTYPE(LMAX)*phi) - sin_m_phi[:,LMAX] = np.sin(ASTYPE(LMAX)*phi) + m_phi[:,LMAX+1] = np.exp(1j * (LMAX + 1) * phi) + m_phi[:,LMAX] = np.exp(1j * LMAX*phi) # calculate summation for order LMAX - s_m = s_m_c[:,2*LMAX]*cos_m_phi[:,LMAX] + s_m_c[:,2*LMAX+1]*sin_m_phi[:,LMAX] + g = np.einsum("h...,p...->ph...", cs_m[:,LMAX], m_phi[:,LMAX]) + s_m = g.real # iterate to calculate complete summation for m in range(LMAX-1, 0, -1): - cos_m_phi[:,m] = cos_phi_2*cos_m_phi[:,m+1] - cos_m_phi[:,m+2] - sin_m_phi[:,m] = cos_phi_2*sin_m_phi[:,m+1] - sin_m_phi[:,m+2] # calculate summation for order m - a_m = np.sqrt((2.0*m+3.0)/(2.0*m+2.0)) - s_m = a_m*u*s_m + s_m_c[:,2*m]*cos_m_phi[:,m] + s_m_c[:,2*m+1]*sin_m_phi[:,m] + m_phi[:,m] = cos_phi_2*m_phi[:,m+1] - m_phi[:,m+2] + a_m = np.sqrt((2.0*m + 3.0)/(2.0*m + 2.0)) + g = np.einsum("h...,p...->ph...", cs_m[:,m], m_phi[:,m]) + # update summation and discard imaginary component + s_m = a_m*u*s_m + g.real # calculate spatial field - spatial = np.sqrt(3.0)*u*s_m + s_m_c[:,0] + spatial = np.sqrt(3.0)*u*s_m + cs_m[:,0].real # return the calculated spatial field return spatial -# PURPOSE: compute conditioned arrays for Clenshaw summation from the -# fully-normalized associated Legendre's function for an order m -def clenshaw_s_m(t, f, m, clm1, slm1, lmax, - ASTYPE=np.longdouble, - SCALE=1e-280 - ): +# PURPOSE: compute Clenshaw summation of the fully normalized associated +# Legendre's function for constant order m +def _clenshaw(t, f, m, Ylm1, lmax, SCALE=1e-280): """ Compute conditioned arrays for Clenshaw summation from the fully-normalized associated Legendre's function for an order m @@ -203,14 +204,10 @@ def clenshaw_s_m(t, f, m, clm1, slm1, lmax, degree dependent factors m: int spherical harmonic order - clm1: np.ndarray - cosine spherical harmonics - slm1: np.ndarray - sine spherical harmonics + Ylm1: np.ndarray + complex form of spherical harmonics lmax: int maximum spherical harmonic degree - ASTYPE: np.dtype, default np.longdouble - floating point precision for calculating Clenshaw summation SCALE: float, default 1e-280 scaling factor to prevent underflow in Clenshaw summation @@ -221,49 +218,40 @@ def clenshaw_s_m(t, f, m, clm1, slm1, lmax, """ # allocate for output matrix N = len(t) - s_m = np.zeros((N,2),dtype=ASTYPE) + s_m = np.zeros((N), dtype=np.clongdouble) # scaling to prevent overflow - clm = SCALE*clm1.astype(ASTYPE) - slm = SCALE*slm1.astype(ASTYPE) + ylm = SCALE*Ylm1.astype(np.clongdouble) # convert lmax and m to float - lm = ASTYPE(lmax) - mm = ASTYPE(m) + lm = np.float64(lmax) + mm = np.float64(m) if (m == lmax): - s_m[:,0] = f[lmax]*clm[lmax,lmax] - s_m[:,1] = f[lmax]*slm[lmax,lmax] + s_m[:] = f[lmax]*ylm[lmax,lmax] elif (m == (lmax-1)): a_lm = np.sqrt(((2.0*lm-1.0)*(2.0*lm+1.0))/((lm-mm)*(lm+mm)))*t - s_m[:,0] = a_lm*f[lmax]*clm[lmax,lmax-1] + f[lmax-1]*clm[lmax-1,lmax-1] - s_m[:,1] = a_lm*f[lmax]*slm[lmax,lmax-1] + f[lmax-1]*slm[lmax-1,lmax-1] + s_m[:] = a_lm*f[lmax]*ylm[lmax,lmax-1] + f[lmax-1]*ylm[lmax-1,lmax-1] elif ((m <= (lmax-2)) and (m >= 1)): - s_mm_c_pre_2 = f[lmax]*clm[lmax,m] - s_mm_s_pre_2 = f[lmax]*slm[lmax,m] + s_mm_minus_2 = f[lmax]*ylm[lmax,m] a_lm = np.sqrt(((2.0*lm-1.0)*(2.0*lm+1.0))/((lm-mm)*(lm+mm)))*t - s_mm_c_pre_1 = a_lm*s_mm_c_pre_2 + f[lmax-1]*clm[lmax-1,m] - s_mm_s_pre_1 = a_lm*s_mm_s_pre_2 + f[lmax-1]*slm[lmax-1,m] + s_mm_minus_1 = a_lm*s_mm_minus_2 + f[lmax-1]*ylm[lmax-1,m] for l in range(lmax-2, m-1, -1): - ll = ASTYPE(l) + ll = np.float64(l) a_lm=np.sqrt(((2.0*ll+1.0)*(2.0*ll+3.0))/((ll+1.0-mm)*(ll+1.0+mm)))*t b_lm=np.sqrt(((2.*ll+5.)*(ll+mm+1.)*(ll-mm+1.))/((ll+2.-mm)*(ll+2.+mm)*(2.*ll+1.))) - s_mm_c = a_lm * s_mm_c_pre_1 - b_lm * s_mm_c_pre_2 + f[l]*clm[l,m] - s_mm_s = a_lm * s_mm_s_pre_1 - b_lm * s_mm_s_pre_2 + f[l]*slm[l,m] - s_mm_c_pre_2 = np.copy(s_mm_c_pre_1) - s_mm_s_pre_2 = np.copy(s_mm_s_pre_1) - s_mm_c_pre_1 = np.copy(s_mm_c) - s_mm_s_pre_1 = np.copy(s_mm_s) - s_m[:,0] = np.copy(s_mm_c) - s_m[:,1] = np.copy(s_mm_s) + s_mm_l = a_lm * s_mm_minus_1 - b_lm * s_mm_minus_2 + f[l]*ylm[l,m] + s_mm_minus_2 = np.copy(s_mm_minus_1) + s_mm_minus_1 = np.copy(s_mm_l) + s_m[:] = np.copy(s_mm_l) elif (m == 0): - s_mm_c_pre_2 = f[lmax]*clm[lmax,0] + s_mm_minus_2 = f[lmax]*ylm[lmax,0] a_lm = np.sqrt(((2.0*lm-1.0)*(2.0*lm+1.0))/(lm*lm))*t - s_mm_c_pre_1 = a_lm * s_mm_c_pre_2 + f[lmax-1]*clm[lmax-1,0] + s_mm_minus_1 = a_lm * s_mm_minus_2 + f[lmax-1]*ylm[lmax-1,0] for l in range(lmax-2, m-1, -1): - ll = ASTYPE(l) + ll = np.float64(l) a_lm=np.sqrt(((2.0*ll+1.0)*(2.0*ll+3.0))/((ll+1.0)*(ll+1.0)))*t b_lm=np.sqrt(((2.0*ll+5.0)*(ll+1.0)*(ll+1.0))/((ll+2.0)*(ll+2.0)*(2.0*ll+1.0))) - s_mm_c = a_lm * s_mm_c_pre_1 - b_lm * s_mm_c_pre_2 + f[l]*clm[l,0] - s_mm_c_pre_2 = np.copy(s_mm_c_pre_1) - s_mm_c_pre_1 = np.copy(s_mm_c) - s_m[:,0] = np.copy(s_mm_c) - # return s_m rescaled with scalef + s_mm_l = a_lm * s_mm_minus_1 - b_lm * s_mm_minus_2 + f[l]*ylm[l,0] + s_mm_minus_2 = np.copy(s_mm_minus_1) + s_mm_minus_1 = np.copy(s_mm_l) + s_m[:] = np.copy(s_mm_l) + # return rescaled s_m return s_m/SCALE diff --git a/gravity_toolkit/gen_disc_load.py b/gravity_toolkit/gen_disc_load.py index 474b1b3f..ca8de548 100644 --- a/gravity_toolkit/gen_disc_load.py +++ b/gravity_toolkit/gen_disc_load.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" gen_disc_load.py -Written by Tyler Sutterley (06/2023) +Written by Tyler Sutterley (07/2026) Calculates gravitational spherical harmonic coefficients for a uniform disc load CALLING SEQUENCE: @@ -55,6 +55,8 @@ https://doi.org/10.1007/s00190-011-0522-7 UPDATE HISTORY: + Updated 07/2026: use np.einsum for spherical harmonic summations + use np.radians to convert from degrees to radians 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 @@ -129,8 +131,8 @@ def gen_disc_load(data, lon, lat, area, LMAX=60, MMAX=None, UNITS=2, 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 + phi = np.radians(lon)# Longitude in radians + th = np.radians(90.0 - lat)# Colatitude in radians # Earth Parameters factors = gravity_toolkit.units(lmax=LMAX) @@ -204,29 +206,20 @@ def gen_disc_load(data, lon, lat, area, LMAX=60, MMAX=None, UNITS=2, # 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) + d = unit_conv*data*np.exp(1j*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 disc load to be centered at lat/lon - plm[l,m] = plmout[l,m]*pl_alpha[l] - # multiplying clm by cos(m*phi) and slm by sin(m*phi) - # to get a field of spherical harmonics - yclm[l,m] = plm[l,m]*dcos[m] - yslm[l,m] = plm[l,m]*dsin[m] - # multiplying by factors to convert to geoid coefficients - Ylms.clm[l,m] = dfactor[l]*yclm[l,m] - Ylms.slm[l,m] = dfactor[l]*yslm[l,m] + # rotate disc load to be centered at lat/lon + plm = np.einsum("lm...,l...->lm...", plmout, pl_alpha) + # multiplying clm by cos(m*phi) and slm by sin(m*phi) + # to get a field of spherical harmonics + ylm = np.einsum("lm...,m...->lm...", plm, d) + # Multiplying by factors to convert to fully normalized coefficients + Ylms.clm = np.einsum("l...,lm...->lm...", dfactor, ylm.real) + Ylms.slm = np.einsum("l...,lm...->lm...", dfactor, ylm.imag) # return the output spherical harmonics object return Ylms diff --git a/gravity_toolkit/gen_harmonics.py b/gravity_toolkit/gen_harmonics.py index 8044f695..e24779a8 100644 --- a/gravity_toolkit/gen_harmonics.py +++ b/gravity_toolkit/gen_harmonics.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" gen_harmonics.py -Written by Tyler Sutterley (03/2023) +Written by Tyler Sutterley (07/2026) Converts data from the spatial domain to spherical harmonic coefficients Does not compute the solid Earth elastic response or convert units @@ -47,6 +47,8 @@ Associated Legendre Functions", Journal of Geodesy (2002) UPDATE HISTORY: + Updated 07/2026: use np.einsum for spherical harmonic summations + use np.radians to convert from degrees to radians Updated 03/2023: improve typing for variables in docstrings Updated 01/2023: refactored associated legendre polynomials Updated 04/2022: updated docstrings to numpy documentation format @@ -154,30 +156,20 @@ def integration(data, lon, lat, LMAX=60, MMAX=None, PLM=0, **kwargs): m: int spherical harmonic order to MMAX """ - - # dimensions of the longitude and latitude arrays - nlon = np.int64(len(lon)) - nlat = np.int64(len(lat)) - # grid step - dlon = np.abs(lon[1]-lon[0]) - dlat = np.abs(lat[1]-lat[0]) - # longitude degree spacing in radians - dphi = dlon*np.pi/180.0 - # colatitude degree spacing in radians - dth = dlat*np.pi/180.0 - - # reformatting longitudes to range 0:360 (if previously -180:180) - if np.count_nonzero(lon < 0): - lon[lon < 0] += 360.0 # calculate longitude and colatitude arrays in radians - phi = np.reshape(lon,(1,nlon))*np.pi/180.0# reshape to 1xnlon - th = (90.0 - np.squeeze(lat))*np.pi/180.0# remove singleton dimensions + phi = np.radians(np.squeeze(lon)) + th = np.radians(90.0 - np.squeeze(lat)) + # reformatting longitudes to range 0:360 (if previously -180:180) + phi = np.where(phi < 0, phi + 2.0*np.pi, phi) + # grid step in radians + dphi = np.abs(phi[1] - phi[0]) + dth = np.abs(th[1] - th[0]) - # Calculating cos/sin of phi arrays (output [m,phi]) # LMAX+1 as there are LMAX+1 elements between 0 and LMAX - m = np.arange(MMAX+1)[:, np.newaxis] - ccos = np.cos(np.dot(m,phi)) - ssin = np.sin(np.dot(m,phi)) + ll = np.arange(LMAX+1) + mm = np.arange(MMAX+1) + # Calculating cos/sin of phi arrays (output [m,phi]) + m_phi = np.exp(1j * np.einsum("m...,p...->mp...", mm, phi)) # Multiplying sin(th) with differentials of theta and phi # to calculate the integration factor at each latitude @@ -185,41 +177,28 @@ def integration(data, lon, lat, LMAX=60, MMAX=None, PLM=0, **kwargs): coeff = 1.0/(4.0*np.pi) # Calculate polynomials using Holmes and Featherstone (2002) relation - plm = np.zeros((LMAX+1, MMAX+1, nlat)) if (np.ndim(PLM) == 0): - plmout,dplm = plm_holmes(LMAX, np.cos(th)) + plmout, dplm = plm_holmes(LMAX, np.cos(th)) else: # use precomputed plms to improve computational speed # or to use a different recursion relation for polynomials - plmout = PLM - + plmout = PLM[ll, :, :].copy() # Multiply plms by integration factors [sin(theta)*dtheta*dphi] # truncate plms to maximum spherical harmonic order if MMAX < LMAX - m = np.arange(MMAX+1) - for j in range(0,nlat): - plm[:,m,j] = plmout[:,m,j]*int_fact[j] - - # Initializing preliminary spherical harmonic matrices - yclm = np.zeros((LMAX+1, MMAX+1)) - yslm = np.zeros((LMAX+1, MMAX+1)) + plm = np.einsum("lmh...,h...->lmh...", plmout[:,mm,:], int_fact) # Initializing output spherical harmonic matrices Ylms = gravity_toolkit.harmonics(lmax=LMAX, mmax=MMAX) Ylms.clm = np.zeros((LMAX+1, MMAX+1)) Ylms.slm = np.zeros((LMAX+1, MMAX+1)) # Multiplying gridded data with sin/cos of m#phis (output [m,theta]) # This will sum through all phis in the dot product - dcos = np.dot(ccos,data) - dsin = np.dot(ssin,data) - for l in range(0,LMAX+1): - mm = np.min([MMAX,l])# truncate to MMAX if specified (if l > MMAX) - m = np.arange(0,mm+1)# mm+1 elements between 0 and mm - # Summing product of plms and data over all latitudes - yclm[l,m] = np.sum(plm[l,m,:]*dcos[m,:], axis=1) - yslm[l,m] = np.sum(plm[l,m,:]*dsin[m,:], axis=1) - # convert to output normalization (4-pi normalized harmonics) - Ylms.clm[l,m] = coeff*yclm[l,m] - Ylms.slm[l,m] = coeff*yslm[l,m] - + d = np.einsum("mp...,ph...->mh...", m_phi, data) + # Summing product of plms and data over all latitudes + ylm = np.einsum("lmh...,mh...->lm...", plm, d) + # convert to output normalization (4-pi normalized harmonics) + # truncate to MMAX if specified (if l > MMAX) + Ylms.clm = coeff*ylm.real[:LMAX+1, :MMAX+1] + Ylms.slm = coeff*ylm.imag[:LMAX+1, :MMAX+1] # return the output spherical harmonics object return Ylms @@ -257,82 +236,65 @@ def fourier(data, lon, lat, LMAX=60, MMAX=None, PLM=0, **kwargs): # dimensions of the longitude and latitude arrays nlon = np.int64(len(lon)) nlat = np.int64(len(lat)) - # remove singleton dimensions and convert to radians - phi = (np.squeeze(lon)*np.pi/180.0) - # Colatitude in radians - theta = ((90.0 - np.squeeze(lat))*np.pi/180.0) + # calculate longitude and colatitude arrays in radians + phi = np.radians(np.squeeze(lon)) + th = np.radians(90.0 - np.squeeze(lat)) + # reformatting longitudes to range 0:360 (if previously -180:180) + phi = np.where(phi < 0, phi + 2.0*np.pi, phi) + # grid step in radians + dphi = np.abs(phi[1] - phi[0]) + dth = np.abs(th[1] - th[0]) # MMAX+1 to include MMAX - mm = np.arange(MMAX+1)[:, np.newaxis] + mm = np.arange(MMAX+1) # Calculate cos and sin coefficients of signal - ccos = np.cos(np.dot(mm,phi[np.newaxis,:])) - ssin = np.sin(np.dot(mm,phi[np.newaxis,:])) - dcos = np.dot(ccos,data) - dsin = np.dot(ssin,data) - - # Normalize fourier coefficients - dcos[0,:] = dcos[0,:]/nlon - dcos[1:MMAX+1,:] = 2.0*dcos[1:MMAX+1,:]/nlon - dsin[0,:] = dsin[0,:]/nlon - dsin[1:MMAX+1,:] = 2.0*dsin[1:MMAX+1,:]/nlon + m_phi = np.exp(1j * np.einsum("m...,p...->mp...", mm, phi)) + d = np.einsum("mp...,hp...->mh...", m_phi, data) + # normalize coefficients + d[0, :] *= 1.0 / nlon + d[1:, :] *= 2.0 / nlon # Calculate cos and sin coefficients of theta component # Because the function is defined on (0,pi) # it can be expanded in just cosine terms. # this routine assumes that 0 and pi are not included - theta_cc = np.zeros((MMAX+1,MMAX+1)) - theta_sc = np.zeros((MMAX+1,MMAX+1)) - m_even = np.arange(0,MMAX+1,2) - m_odd = np.arange(1,MMAX,2) + f = np.zeros((MMAX+1,MMAX+1), dtype=np.complex128) + m_even = slice(0, MMAX+1, 2) + m_odd = slice(1, MMAX, 2) n_even = len(m_even) n_odd = len(m_odd) - if np.isclose([theta[0],theta[nlat-1]],[0.0,np.pi]).all(): + if np.isclose([th[0],th[nlat-1]], [0.0,np.pi]).all(): + # global case (includes poles) # non-endpoints - nt = np.dot(mm,theta[1:nlat-1][np.newaxis,:]) - theta_cc[m_even,:] = 2.0*np.dot(dcos[m_even,1:nlat-1],np.cos(nt).T) - theta_sc[m_even,:] = 2.0*np.dot(dsin[m_even,1:nlat-1],np.cos(nt).T) - theta_cc[m_odd,:] = 2.0*np.dot(dcos[m_odd,1:nlat-1],np.sin(nt).T) - theta_sc[m_odd,:] = 2.0*np.dot(dsin[m_odd,1:nlat-1],np.sin(nt).T) - + n_th = np.exp(1j * np.einsum("h...,n...->nh...", th[1:nlat-1], mm)) + f[m_even,:] = 2.0*np.einsum("mh...,nh...->mn", d[m_even,1:nlat-1],n_th.real) + f[m_odd,:] = 2.0*np.einsum("mh...,nh...->mn", d[m_even,1:nlat-1],n_th.imag) # endpoints - theta_cc[m_even,:] += np.dot((dcos[m_even,0]*np.cos(theta[0]) + - dcos[m_even,nlat-1]*np.cos(theta[nlat-1]))[:,np.newaxis], mm.T) - theta_sc[m_even,:] += np.dot((dsin[m_even,0]*np.cos(theta[0]) + - dsin[m_even,nlat-1]*np.cos(theta[nlat-1]))[:,np.newaxis], mm.T) - theta_cc[m_odd,:] += np.dot((dcos[m_odd,0]*np.sin(theta[0]) + - dcos[m_odd,nlat-1]*np.sin(theta[nlat-1]))[:,np.newaxis], mm.T) - theta_sc[m_odd,:] += np.dot((dsin[m_odd,0]*np.sin(theta[0]) + - dsin[m_odd,nlat-1]*np.sin(theta[nlat-1]))[:,np.newaxis], mm.T) - - elif not np.isclose([theta[0],theta[nlat-1]],[0.0,np.pi]).any(): - nt = np.dot(mm,theta[np.newaxis,:]) - theta_cc[m_even,:] = 2.0*np.dot(dcos[m_even,:],np.cos(nt).T) - theta_sc[m_even,:] = 2.0*np.dot(dsin[m_even,:],np.cos(nt).T) - theta_cc[m_odd,:] = 2.0*np.dot(dcos[m_odd,:],np.sin(nt).T) - theta_sc[m_odd,:] = 2.0*np.dot(dsin[m_odd,:],np.sin(nt).T) + c_th = d[:,0]*np.cos(th[0]) + d[:,nlat-1]*np.cos(th[nlat-1]) + s_th = d[:,0]*np.sin(th[0]) + d[:,nlat-1]*np.sin(th[nlat-1]) + f[m_even,:] += np.einsum("m...,n...->mn", c_th[m_even], mm) + f[m_odd,:] += np.einsum("m...,n...->mn", s_th[m_odd], mm) + elif not np.isclose([th[0],th[nlat-1]], [0.0,np.pi]).any(): + n_th = np.exp(1j * np.einsum("h...,n...->nh...", th, mm)) + f[m_even,:] = 2.0*np.einsum("mh...,nh...->mn", d[m_even,:],n_th.real) + f[m_odd,:] = 2.0*np.einsum("mh...,nh...->mn", d[m_odd,:],n_th.imag) else: raise ValueError('Latitude coordinates incompatible') # Normalize theta fourier coefficients - theta_cc[:,0] = theta_cc[:,0]/(2.0*nlat) - theta_cc[:,1:MMAX+1] = theta_cc[:,1:MMAX+1]/nlat - theta_sc[:,0] = theta_sc[:,0]/(2.0*nlat) - theta_sc[:,1:MMAX+1] = theta_sc[:,1:MMAX+1]/nlat - + f[:,0] *= 1.0/(2.0*nlat) + f[:,1:MMAX+1] *= 1.0/nlat # Correct normalization for the incomplete coverage of the sphere - delphi = np.abs(phi[1]-phi[0]) - deltheta = np.abs(theta[1]-theta[0]) - norm = nlon*delphi/(2.0*np.pi)*nlat*deltheta/np.pi - theta_cc = theta_cc*norm - theta_sc = theta_sc*norm + norm = nlon*dphi/(2.0*np.pi) * nlat*dth/np.pi + f *= norm # Calculate cos and sin coefficients of Legendre functions # Expand m = even terms in a cosine series # Expand m = odd terms in a sine series # Both are stride 2 if (np.ndim(PLM) == 0): - plm = fourier_legendre(LMAX,MMAX) + plm = fourier_legendre(LMAX, MMAX) else: # use precomputed plms to improve computational speed plm = PLM @@ -348,26 +310,28 @@ def fourier(data, lon, lat, LMAX=60, MMAX=None, PLM=0, **kwargs): # n and k must have like parities # m = even terms - k_even = np.zeros((n_even,n_even)) + k_even = np.zeros((n_even, n_even)) + mm = np.arange(m_even.start, m_even.stop, m_even.step) for n in range(0,MMAX+2,2): - k_even[:,n//2] = 0.5*(1.0/(1.0-m_even-n) + 1.0/(1.0+m_even-n) + - 1.0/(1.0-m_even+n) + 1.0/(1.0+m_even+n)) + k_even[:,n//2] = 0.5*(1.0/(1.0-mm-n) + 1.0/(1.0+mm-n) + + 1.0/(1.0-mm+n) + 1.0/(1.0+mm+n)) - k_odd = np.zeros((n_odd,n_odd)) + k_odd = np.zeros((n_odd, n_odd)) + mm = np.arange(m_odd.start, m_odd.stop, m_odd.step) for n in range(1,MMAX+1,2): - k_odd[:,(n-1)//2] = 0.5*(1.0/(1-m_odd-n) + 1.0/(1+m_odd-n) + - 1.0/(1-m_odd+n) + 1.0/(1+m_odd+n)) + k_odd[:,(n-1)//2] = 0.5*(1.0/(1-mm-n) + 1.0/(1+mm-n) + + 1.0/(1-mm+n) + 1.0/(1+mm+n)) # calculate spherical harmonics for m == even terms - l_even = np.arange(0,LMAX+1,2) - l_odd = np.arange(1,LMAX,2) + l_even = slice(0, LMAX+1, 2) + l_odd = slice(1, LMAX, 2) for m in range(0,MMAX+2,2): - temp = np.dot(plm[l_even,m,m_even[:,np.newaxis]].T,k_even) - Ylms.clm[l_even,m] = np.dot(theta_cc[m,m_even[:,np.newaxis]].T,temp.T) - Ylms.slm[l_even,m] = np.dot(theta_sc[m,m_even[:,np.newaxis]].T,temp.T) - temp = np.dot(plm[l_odd,m,m_odd[:,np.newaxis]].T,k_odd) - Ylms.clm[l_odd,m] = np.dot(theta_cc[m,m_odd[:,np.newaxis]].T,temp.T) - Ylms.slm[l_odd,m] = np.dot(theta_sc[m,m_odd[:,np.newaxis]].T,temp.T) + temp = np.einsum("ln...,mn...->lm", plm[l_even,m,m_even], k_even) + Ylms.clm[l_even,m] = np.einsum("n...,lm...->l", f.real[m,m_even], temp) + Ylms.slm[l_even,m] = np.einsum("n...,lm...->l", f.imag[m,m_even], temp) + temp = np.einsum("ln...,mn...->lm", plm[l_odd,m,m_odd], k_odd) + Ylms.clm[l_odd,m] = np.einsum("n...,lm...->l", f.real[m,m_odd], temp) + Ylms.slm[l_odd,m] = np.einsum("n...,lm...->l", f.imag[m,m_odd], temp) # m = odd terms k_even = np.zeros((n_even,n_even)) @@ -384,12 +348,12 @@ def fourier(data, lon, lat, LMAX=60, MMAX=None, PLM=0, **kwargs): l_even = np.arange(2,LMAX+1,2)# do not in include l=0 l_odd = np.arange(1,LMAX,2) for m in range(1,MMAX+1,2): - temp = np.dot(plm[l_even,m,m_even[:,np.newaxis]].T,k_even) - Ylms.clm[l_even,m] = np.dot(theta_cc[m,m_even[:,np.newaxis]].T,temp.T) - Ylms.slm[l_even,m] = np.dot(theta_sc[m,m_even[:,np.newaxis]].T,temp.T) - temp = np.dot(plm[l_odd,m,m_odd[:,np.newaxis]].T,k_odd) - Ylms.clm[l_odd,m] = np.dot(theta_cc[m,m_odd[:,np.newaxis]].T,temp.T) - Ylms.slm[l_odd,m] = np.dot(theta_sc[m,m_odd[:,np.newaxis]].T,temp.T) + temp = np.einsum("ln...,mn...->lm", plm[l_even,m,m_even], k_even) + Ylms.clm[l_even,m] = np.einsum("n...,lm...->l", f.real[m,m_even], temp) + Ylms.slm[l_even,m] = np.einsum("n...,lm...->l", f.imag[m,m_even], temp) + temp = np.einsum("ln...,mn...->lm", plm[l_odd,m,m_odd], k_odd) + Ylms.clm[l_odd,m] = np.einsum("n...,lm...->l", f.real[m,m_odd], temp) + Ylms.slm[l_odd,m] = np.einsum("n...,lm...->l", f.imag[m,m_odd], temp) # Divide by Plm normalization Ylms.clm[:,0] /= 2.0 diff --git a/gravity_toolkit/gen_point_load.py b/gravity_toolkit/gen_point_load.py index 78ff3de3..563e2807 100644 --- a/gravity_toolkit/gen_point_load.py +++ b/gravity_toolkit/gen_point_load.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" gen_point_load.py -Written by Tyler Sutterley (04/2023) +Written by Tyler Sutterley (07/2026) Calculates gravitational spherical harmonic coefficients for point masses CALLING SEQUENCE: @@ -47,6 +47,8 @@ https://doi.org/10.1029/JB078i011p01760 UPDATE HISTORY: + Updated 07/2026: use np.einsum for spherical harmonic summations + use np.radians to convert from degrees to radians Updated 04/2023: allow love numbers to be None for custom units case Updated 03/2023: improve typing for variables in docstrings Updated 02/2023: set custom units as top option in if/else statements @@ -107,8 +109,8 @@ def gen_point_load(data, lon, lat, LMAX=60, MMAX=None, UNITS=1, LOVE=None): # number of input data points npts = len(data.flatten()) # convert output longitude and latitude into radians - phi = np.pi*lon.flatten()/180.0 - theta = np.pi*(90.0 - lat.flatten())/180.0 + phi = np.radians(lon.flatten()) + theta = np.radians(90.0 - lat.flatten()) # extract degree dependent factor for specific units factors = gravity_toolkit.units(lmax=LMAX) @@ -137,7 +139,7 @@ def gen_point_load(data, lon, lat, LMAX=60, MMAX=None, UNITS=1, LOVE=None): # for each degree l for l in range(LMAX+1): m1 = np.min([l,MMAX]) + 1 - SPH = spherical_harmonic_matrix(l, D, phi, theta, dfactor[l]) + SPH = _complex_harmonics(l, D, phi, theta, dfactor[l]) # truncate to spherical harmonic order and save to output Ylms.clm[l,:m1] = SPH.real[:m1] Ylms.slm[l,:m1] = SPH.imag[:m1] @@ -145,7 +147,7 @@ def gen_point_load(data, lon, lat, LMAX=60, MMAX=None, UNITS=1, LOVE=None): return Ylms # calculate spherical harmonics of degree l evaluated at (theta,phi) -def spherical_harmonic_matrix(l, data, phi, theta, coeff): +def _complex_harmonics(l, data, phi, theta, coeff): """ Calculates the spherical harmonics for a particular degree evaluated from data at coordinates @@ -168,15 +170,15 @@ def spherical_harmonic_matrix(l, data, phi, theta, coeff): Ylms: np.ndarray spherical harmonic coefficients in Eulerian form """ - # calculate normalized legendre polynomials (points, order) - Pl = legendre(l, np.cos(theta), NORMALIZE=True).T + # calculate normalized legendre polynomials (order, points) + Pl = legendre(l, np.cos(theta), NORMALIZE=True) # spherical harmonic orders up to degree l m = np.arange(0, l+1) - # calculate Euler's of spherical harmonic order multiplied by azimuth phi - mphi = np.exp(1j*np.dot(np.squeeze(phi)[:,np.newaxis], m[np.newaxis,:])) - # reshape data to order - D = np.kron(np.ones((1, l+1)), data[:,np.newaxis]) - # calculate spherical harmonics and multiply by coefficients and data - Ylms = coeff*D*Pl*mphi - # calculate the sum over all points and return harmonics for degree l - return np.sum(Ylms, axis=0) + # calculate Euler's of order m multiplied by azimuth phi + m_phi = np.exp(1j * np.einsum("m...,p...->mp...", m, phi)) + # reshape data to (order, points) + D = np.kron(np.ones((l+1, 1)), data[np.newaxis, :]) + # calculate spherical harmonics summing over all points + Yl = np.einsum("mp...,mp...,mp...->m...", D, Pl, m_phi) + # return harmonics for degree l multiplied by coefficients + return coeff*Yl diff --git a/gravity_toolkit/gen_spherical_cap.py b/gravity_toolkit/gen_spherical_cap.py index bea477b9..6dc6d8c5 100755 --- a/gravity_toolkit/gen_spherical_cap.py +++ b/gravity_toolkit/gen_spherical_cap.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" gen_spherical_cap.py -Written by Tyler Sutterley (06/2023) +Written by Tyler Sutterley (07/2026) Calculates gravitational spherical harmonic coefficients for a spherical cap Creating a spherical cap with generating angle alpha is a 2 step process: @@ -61,6 +61,8 @@ https://doi.org/10.1007/s00190-011-0522-7 UPDATE HISTORY: + Updated 07/2026: use np.einsum for spherical harmonic summations + use np.radians to convert from degrees to radians 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 @@ -147,8 +149,8 @@ def gen_spherical_cap(data, lon, lat, LMAX=60, MMAX=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 + phi = np.radians(lon)# Longitude in radians + th = np.radians(90.0 - lat)# Colatitude in radians # Earth Parameters factors = gravity_toolkit.units(lmax=LMAX) @@ -160,7 +162,7 @@ def gen_spherical_cap(data, lon, lat, LMAX=60, MMAX=None, if (RAD_CAP != 0): # if given spherical cap radius in degrees # converting to radians - alpha = RAD_CAP*np.pi/180.0 + alpha = np.radians(RAD_CAP) elif (AREA != 0): # if given spherical cap area in cm^2 # radius in centimeters @@ -247,29 +249,20 @@ def gen_spherical_cap(data, lon, lat, LMAX=60, MMAX=None, # 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) + d = unit_conv*data*np.exp(1j*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] + # rotate spherical cap to be centered at lat/lon + plm = np.einsum("lm...,l...->lm...", plmout, pl_alpha) + # multiplying clm by cos(m*phi) and slm by sin(m*phi) + # to get a field of spherical harmonics + ylm = np.einsum("lm...,m...->lm...", plm, d) + # Multiplying by factors to convert to fully normalized coefficients + Ylms.clm = np.einsum("l...,lm...->lm...", dfactor, ylm.real) + Ylms.slm = np.einsum("l...,lm...->lm...", dfactor, ylm.imag) # return the output spherical harmonics object return Ylms diff --git a/gravity_toolkit/gen_stokes.py b/gravity_toolkit/gen_stokes.py index 6b43f3fa..271c7e64 100755 --- a/gravity_toolkit/gen_stokes.py +++ b/gravity_toolkit/gen_stokes.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" gen_stokes.py -Written by Tyler Sutterley (04/2023) +Written by Tyler Sutterley (07/2026) Converts data from the spatial domain to spherical harmonic coefficients @@ -43,6 +43,8 @@ and filters the GRACE/GRACE-FO coefficients for striping errors UPDATE HISTORY: + Updated 07/2026: use np.einsum for spherical harmonic summations + use np.radians to convert from degrees to radians Updated 06/2025: copy latitude and longitude as float64 for numpy 2.0 stability Updated 04/2023: allow love numbers to be None for custom units case Updated 03/2023: improve typing for variables in docstrings @@ -129,25 +131,15 @@ def gen_stokes(data, lon, lat, LMIN=0, LMAX=60, MMAX=None, UNITS=1, # grid dimensions nlat = np.int64(len(lat)) - # grid step - dlon = np.abs(lon[1]-lon[0]) - dlat = np.abs(lat[1]-lat[0]) - # longitude degree spacing in radians - dphi = dlon*np.pi/180.0 - # colatitude degree spacing in radians - dth = dlat*np.pi/180.0 - - # convert latitude and longitude to float if integers - lon = lon.astype(np.float64) - lat = lat.astype(np.float64) - # reformatting longitudes to range 0:360 (if previously -180:180) - lon = np.squeeze(lon.copy()) - if np.any(lon < 0): - lon[lon < 0] += 360.0 # Longitude in radians - phi = lon[np.newaxis,:]*np.pi/180.0 - # Colatitude in radians - th = (90.0 - np.squeeze(lat.copy()))*np.pi/180.0 + phi = np.radians(np.squeeze(lon.copy())) + # reformatting longitudes to range 0:360 (if previously -180:180) + phi = np.where(phi < 0, phi + 2.0*np.pi, phi) + # colatitude in radians + th = np.radians(90.0 - np.squeeze(lat.copy())) + # grid step in radians + dphi = np.abs(phi[1] - phi[0]) + dth = np.abs(th[1] - th[0]) # reforming data to lonXlat if input latXlon sz = np.shape(data) @@ -181,9 +173,8 @@ def gen_stokes(data, lon, lat, LMIN=0, LMAX=60, MMAX=None, UNITS=1, # Calculating cos/sin of phi arrays # output [m,phi] - m = np.arange(MMAX+1) - ccos = np.cos(np.dot(m[:,np.newaxis],phi)) - ssin = np.sin(np.dot(m[:,np.newaxis],phi)) + mm = np.arange(MMAX+1) + m_phi = np.exp(1j * np.einsum("m...,p...->mp...", mm, phi)) # Calculating fully-normalized Legendre Polynomials # Output is plm[l,m,th] @@ -193,33 +184,20 @@ def gen_stokes(data, lon, lat, LMIN=0, LMAX=60, MMAX=None, UNITS=1, # if plms are not pre-computed: calculate Legendre polynomials PLM, dPLM = plm_holmes(LMAX, np.cos(th)) - # Multiplying by integration factors [sin(theta)*dtheta*dphi] - # truncate legendre polynomials to spherical harmonic order MMAX - for j in range(0,nlat): - plm[:,m,j] = PLM[:,m,j]*int_fact[j] + # truncate legendre polynomials to degree and order + plm = np.einsum("lmh...,h...->lmh...", PLM[:LMAX+1,:MMAX+1,:], int_fact) - # Initializing preliminary spherical harmonic matrices - yclm = np.zeros((LMAX+1, MMAX+1)) - yslm = np.zeros((LMAX+1, MMAX+1)) # Initializing output spherical harmonic matrices Ylms = gravity_toolkit.harmonics(lmax=LMAX, mmax=MMAX) - Ylms.clm = np.zeros((LMAX+1, MMAX+1)) - Ylms.slm = np.zeros((LMAX+1, MMAX+1)) # Multiplying gridded data with sin/cos of m#phis # This will sum through all phis in the dot product # output [m,theta] - dcos = np.dot(ccos,data) - dsin = np.dot(ssin,data) - for l in range(LMIN,LMAX+1):# equivalent to LMIN:LMAX - mm = np.min([MMAX,l])# truncate to MMAX if specified (if l > MMAX) - m = np.arange(0,mm+1)# mm+1 elements between 0 and mm - # Summing product of plms and data over all latitudes - # axis=1 signifies the direction of the summation - yclm[l,m] = np.sum(plm[l,m,:]*dcos[m,:], axis=1) - yslm[l,m] = np.sum(plm[l,m,:]*dsin[m,:], axis=1) - # Multiplying by factors to convert to fully normalized coefficients - Ylms.clm[l,m] = dfactor[l]*yclm[l,m] - Ylms.slm[l,m] = dfactor[l]*yslm[l,m] + d = np.einsum("mp...,ph...->mh...", m_phi, data) + # Summing product of plms and data over all latitudes + ylm = np.einsum("lmh...,mh...->lm...", plm, d) + # Multiplying by factors to convert to fully normalized coefficients + Ylms.clm = np.einsum("l...,lm...->lm...", dfactor, ylm.real) + Ylms.slm = np.einsum("l...,lm...->lm...", dfactor, ylm.imag) # return the output spherical harmonics object return Ylms \ No newline at end of file diff --git a/gravity_toolkit/grace_input_months.py b/gravity_toolkit/grace_input_months.py index 12cf35b7..3bc40197 100644 --- a/gravity_toolkit/grace_input_months.py +++ b/gravity_toolkit/grace_input_months.py @@ -682,7 +682,7 @@ def grace_input_months(base_dir, PROC, DREL, DSET, LMAX, start_mon, end_mon, for i,grace_month in enumerate(months): count = np.count_nonzero(C20_input['month'] == grace_month) if (count != 0): - k, = np.nonzero(C20_input['month'] == grace_month) + k, = np.flatnonzero(C20_input['month'] == grace_month) grace_Ylms['clm'][2,0,i] = np.copy(C20_input['data'][k]) grace_Ylms['eclm'][2,0,i] = np.copy(C20_input['error'][k]) @@ -697,7 +697,7 @@ def grace_input_months(base_dir, PROC, DREL, DSET, LMAX, start_mon, end_mon, for i,grace_month in enumerate(months): count = np.count_nonzero(C21_input['month'] == grace_month) if (count != 0) and (grace_month > 176): - k, = np.nonzero(C21_input['month'] == grace_month) + k, = np.flatnonzero(C21_input['month'] == grace_month) grace_Ylms['clm'][2,1,i] = np.copy(C21_input['C2m'][k]) grace_Ylms['slm'][2,1,i] = np.copy(C21_input['S2m'][k]) grace_Ylms['eclm'][2,1,i] = np.copy(C21_input['eC2m'][k]) @@ -714,7 +714,7 @@ def grace_input_months(base_dir, PROC, DREL, DSET, LMAX, start_mon, end_mon, for i,grace_month in enumerate(months): count = np.count_nonzero(C22_input['month'] == grace_month) if (count != 0) and (grace_month > 176): - k, = np.nonzero(C22_input['month'] == grace_month) + k, = np.flatnonzero(C22_input['month'] == grace_month) grace_Ylms['clm'][2,2,i] = np.copy(C22_input['C2m'][k]) grace_Ylms['slm'][2,2,i] = np.copy(C22_input['S2m'][k]) grace_Ylms['eclm'][2,2,i] = np.copy(C22_input['eC2m'][k]) @@ -731,7 +731,7 @@ def grace_input_months(base_dir, PROC, DREL, DSET, LMAX, start_mon, end_mon, for i,grace_month in enumerate(months): count = np.count_nonzero(C30_input['month'] == grace_month) if (count != 0) and (grace_month > 176): - k, = np.nonzero(C30_input['month'] == grace_month) + k, = np.flatnonzero(C30_input['month'] == grace_month) grace_Ylms['clm'][3,0,i] = np.copy(C30_input['data'][k]) grace_Ylms['eclm'][3,0,i] = np.copy(C30_input['error'][k]) @@ -746,7 +746,7 @@ def grace_input_months(base_dir, PROC, DREL, DSET, LMAX, start_mon, end_mon, for i,grace_month in enumerate(months): count = np.count_nonzero(C40_input['month'] == grace_month) if (count != 0) and (grace_month > 176): - k, = np.nonzero(C40_input['month'] == grace_month) + k, = np.flatnonzero(C40_input['month'] == grace_month) grace_Ylms['clm'][4,0,i] = np.copy(C40_input['data'][k]) grace_Ylms['eclm'][4,0,i] = np.copy(C40_input['error'][k]) @@ -761,7 +761,7 @@ def grace_input_months(base_dir, PROC, DREL, DSET, LMAX, start_mon, end_mon, for i,grace_month in enumerate(months): count = np.count_nonzero(C50_input['month'] == grace_month) if (count != 0) and (grace_month > 176): - k, = np.nonzero(C50_input['month'] == grace_month) + k, = np.flatnonzero(C50_input['month'] == grace_month) grace_Ylms['clm'][5,0,i] = np.copy(C50_input['data'][k]) grace_Ylms['eclm'][5,0,i] = np.copy(C50_input['error'][k]) @@ -790,7 +790,7 @@ def grace_input_months(base_dir, PROC, DREL, DSET, LMAX, start_mon, end_mon, raise IOError(f'No Matching Geocenter Months ({gm})') # for each considered date for i,grace_month in enumerate(months): - k, = np.nonzero(DEG1_input.month == grace_month) + k, = np.flatnonzero(DEG1_input.month == grace_month) count = np.count_nonzero(DEG1_input.month == grace_month) # Degree 1 is missing for particular month if (count == 0) and kwargs['MODEL_DEG1']: diff --git a/gravity_toolkit/harmonic_gradients.py b/gravity_toolkit/harmonic_gradients.py index 8e0704b2..a4aa6353 100644 --- a/gravity_toolkit/harmonic_gradients.py +++ b/gravity_toolkit/harmonic_gradients.py @@ -2,7 +2,7 @@ u""" harmonic_gradients.py Original IDL code calc_grad.pro written by Sean Swenson -Adapted by Tyler Sutterley (03/2023) +Adapted by Tyler Sutterley (07/2026) Calculates the zonal and meridional gradients of a scalar field from a series of spherical harmonics @@ -29,6 +29,8 @@ Legendre functions UPDATE HISTORY: + Updated 07/2026: use np.einsum for spherical harmonic summations + use np.radians to convert from degrees to radians Updated 03/2023: improve typing for variables in docstrings added geostrophic currents program from Wahr et al. (2002) Updated 10/2022: cleaned up program for public release @@ -81,87 +83,51 @@ def harmonic_gradients(clm1, slm1, lon, lat, MMAX = np.copy(LMAX) # Longitude in radians - phi = (np.squeeze(lon)*np.pi/180.0)[np.newaxis,:] + phi = np.radians(np.squeeze(lon)) # Colatitude in radians - th = (90.0 - np.squeeze(lat))*np.pi/180.0 - thmax = len(np.squeeze(lat)) - phimax = len(np.squeeze(lon)) + th = np.radians(90.0 - np.squeeze(lat)) + thmax = len(th) + phimax = len(phi) + # spherical harmonic degree and order + ll = np.arange(0,LMAX+1)# lmax+1 to include lmax + mm = np.arange(0,MMAX+1)# mmax+1 to include mmax + # real (cosine) and imaginary (sine) components + Ylm = np.zeros((LMAX+1, MMAX+1), dtype=np.complex128) # Truncating harmonics to degree and order LMAX # removing coefficients below LMIN and above MMAX - mm = np.arange(0,MMAX+1) - clm = np.zeros((LMAX+1, MMAX+1)) - slm = np.zeros((LMAX+1, MMAX+1)) - clm[LMIN:LMAX+1,mm] = clm1[LMIN:LMAX+1,mm] - slm[LMIN:LMAX+1,mm] = slm1[LMIN:LMAX+1,mm] - # spherical harmonic degree and order - ll = np.arange(0,LMAX+1)[np.newaxis, :]# lmax+1 to include lmax - mm = np.arange(0,MMAX+1)[:, np.newaxis]# mmax+1 to include mmax + Ylm.imag[LMIN:LMAX+1,mm] = -clm1[LMIN:LMAX+1,mm].copy() + Ylm.real[LMIN:LMAX+1,mm] = -slm1[LMIN:LMAX+1,mm].copy() + dlm = np.einsum("l...,lm...->lm", np.sqrt((ll+1.0)*ll), Ylm) # generate Vlm coefficients (vlm and wlm) vlm, wlm = legendre_gradient(LMAX, MMAX) - - dlm = np.zeros((LMAX+1,LMAX+1,2)) - # minus sign is because lat and theta change with opposite sign - for l in range(0,LMAX+1): - dlm[l,:,0] = -clm[l,:]*np.sqrt((l+1.0)*l) - dlm[l,:,1] = -slm[l,:]*np.sqrt((l+1.0)*l) - + # even and odd spherical harmonic orders m_even = np.arange(0,MMAX+2,2) m_odd = np.arange(1,MMAX,2) + # Euler's formula for theta * n and m * phi + n_th = np.exp(1j * np.einsum("h...,n...->nh...", th, ll)) + m_phi = np.exp(1j * np.einsum("m...,p...->mp...", mm, phi)) # Calculate fourier coefficients from legendre coefficients - d_cos = np.zeros((LMAX+1,thmax,2)) - d_sin = np.zeros((LMAX+1,thmax,2)) - cnk = np.cos(np.dot(th[:,np.newaxis],ll)) - snk = np.sin(np.dot(th[:,np.newaxis],ll)) - - wtmp = np.zeros((len(m_even),LMAX+1,2)) - vtmp = np.zeros((len(m_even),LMAX+1,2)) - # m = even terms (vlm,wlm sine series) - for n in range(0,LMAX+1): - wtmp[:,n,0] = np.sum(wlm[:,m_even,n]*dlm[:,m_even,0],axis=0) - wtmp[:,n,1] = np.sum(wlm[:,m_even,n]*dlm[:,m_even,1],axis=0) - vtmp[:,n,0] = np.sum(vlm[:,m_even,n]*dlm[:,m_even,0],axis=0) - vtmp[:,n,1] = np.sum(vlm[:,m_even,n]*dlm[:,m_even,1],axis=0) - - d_cos[m_even,:,0] = np.dot(wtmp[:,:,1],np.transpose(snk)) - d_sin[m_even,:,0] = np.dot(-wtmp[:,:,0],np.transpose(snk)) - d_cos[m_even,:,1] = np.dot(vtmp[:,:,1],np.transpose(snk)) - d_sin[m_even,:,1] = np.dot(-vtmp[:,:,0],np.transpose(snk)) - - # m = odd terms (vlm,wlm cosine series) - wtmp = np.zeros((len(m_odd),LMAX+1,2)) - vtmp = np.zeros((len(m_odd),LMAX+1,2)) - for n in range(0,LMAX+1): - wtmp[:,n,0] = np.sum(wlm[:,m_odd,n]*dlm[:,m_odd,0],axis=0) - wtmp[:,n,1] = np.sum(wlm[:,m_odd,n]*dlm[:,m_odd,1],axis=0) - vtmp[:,n,0] = np.sum(vlm[:,m_odd,n]*dlm[:,m_odd,0],axis=0) - vtmp[:,n,1] = np.sum(vlm[:,m_odd,n]*dlm[:,m_odd,1],axis=0) - - d_cos[m_odd,:,0] = np.dot(wtmp[:,:,1],np.transpose(cnk)) - d_sin[m_odd,:,0] = np.dot(-wtmp[:,:,0],np.transpose(cnk)) - d_cos[m_odd,:,1] = np.dot(vtmp[:,:,1],np.transpose(cnk)) - d_sin[m_odd,:,1] = np.dot(-vtmp[:,:,0],np.transpose(cnk)) - - # Calculating cos(m*phi) and sin(m*phi) - ccos = np.cos(np.dot(mm,phi)) - ssin = np.sin(np.dot(mm,phi)) - # Final signal recovery from fourier coefficients - gradients = np.zeros((phimax,thmax,2)) - gradients[:,:,0] = np.dot(np.transpose(ccos), d_cos[:,:,0]) + \ - np.dot(np.transpose(ssin), d_sin[:,:,0]) - gradients[:,:,1] = np.dot(np.transpose(ccos), d_cos[:,:,1]) + \ - np.dot(np.transpose(ssin), d_sin[:,:,1]) - # return the gradient fields - return gradients + d = np.zeros((LMAX+1,thmax,2), dtype=np.complex128) + wtmp = np.einsum("lmn...,lm...->mn", wlm, dlm) + vtmp = np.einsum("lmn...,lm...->mn", vlm, dlm) + d[m_even,:,0] = np.einsum("mn...,nh...->mh", wtmp[m_even,:], n_th.imag) + d[m_even,:,1] = np.einsum("mn...,nh...->mh", vtmp[m_even,:], n_th.imag) + d[m_odd,:,0] = np.einsum("mn...,nh...->mh", wtmp[m_odd,:], n_th.real) + d[m_odd,:,1] = np.einsum("mn...,nh...->mh", vtmp[m_odd,:], n_th.real) + # calculate the zonal and meridional gradients of the scalar field + gradients = np.einsum("mp...,mhd...->phd...", m_phi, d) + # return the gradient fields and drop imaginary component + return gradients.real def geostrophic_currents(clm1, slm1, lon, lat, LMIN=0, LMAX=60, MMAX=None, RAD=0, DENSITY=1.035, LOVE=None, PLM=None): r""" - Converts data from spherical harmonic coefficients to a - spatial fields of ocean geostrophic currents following + Converts data from spherical harmonic coefficients to spatial + fields of approximate ocean geostrophic currents following :cite:p:`Wahr:1998hy,Wahr:2002ie` Parameters @@ -205,10 +171,10 @@ def geostrophic_currents(clm1, slm1, lon, lat, MMAX = np.copy(LMAX) # Longitude in radians - phi = (np.squeeze(lon)*np.pi/180.0)[np.newaxis,:] + phi = np.radians(np.squeeze(lon)) phmax = len(lon) # colatitude in radians - th = (90.0 - np.squeeze(lat))*np.pi/180.0 + th = np.radians(90.0 - np.squeeze(lat)) thmax = len(th) # Gaussian Smoothing @@ -230,7 +196,8 @@ def geostrophic_currents(clm1, slm1, lon, lat, # smooth harmonics and convert to output units clm = np.zeros((LMAX+1, MMAX+1, 2)) slm = np.zeros((LMAX+1, MMAX+1, 2)) - # zonal flow harmonics + # zonal flow harmonics (equation 3) + # differentiating Legendre polynomials with respect to longitude for l in range(1, LMAX): # truncate to degree and order mm = np.arange(0, np.min([l,MMAX])+1) @@ -240,7 +207,8 @@ def geostrophic_currents(clm1, slm1, lon, lat, np.sqrt(((l+1)**2 - mm**2)*(2.0*l + 3.0)/(2.0*l + 1)) clm[l,mm,0] = coeff*wl[l]*(temp1*clm1[l-1,mm] - temp2*clm1[l+1,mm]) slm[l,mm,0] = coeff*wl[l]*(temp1*slm1[l-1,mm] - temp2*slm1[l+1,mm]) - # meridional flow harmonics + # meridional flow harmonics (equation 4) + # differentiating Legendre polynomials with respect to colatitude for l in range(0, LMAX+1): # truncate to degree and order mm = np.arange(0, np.min([l,MMAX])+1) @@ -251,32 +219,18 @@ def geostrophic_currents(clm1, slm1, lon, lat, # Truncating harmonics to degree and order LMAX # removing coefficients below LMIN and above MMAX mm = np.arange(0, MMAX+1) - clm[LMIN:LMAX+1,mm,:] = clm[LMIN:LMAX+1,mm,:] - slm[LMIN:LMAX+1,mm,:] = slm[LMIN:LMAX+1,mm,:] - - # Calculate fourier coefficients from legendre coefficients - d_cos = np.zeros((MMAX+1,thmax,2))# [m,th] - d_sin = np.zeros((MMAX+1,thmax,2))# [m,th] - for k in range(0,thmax): - # summation over all spherical harmonic degrees - temp = 1.0/(np.cos(th[k])*np.sin(th[k])) - # for each direction of flow - for d in range(2): - d_cos[:,k,d] = temp*np.sum(PLM[:,mm,k]*clm[:,mm,d],axis=0) - d_sin[:,k,d] = temp*np.sum(PLM[:,mm,k]*slm[:,mm,d],axis=0) - - # Final signal recovery from fourier coefficients - m = np.arange(0,MMAX+1)[:,np.newaxis] - # Calculating cos(m*phi) and sin(m*phi) - ccos = np.cos(np.dot(m,phi)) - ssin = np.sin(np.dot(m,phi)) + # real (cosine) and imaginary (sine) components + Ylm = clm[LMIN:LMAX+1,mm,:] - 1j * slm[LMIN:LMAX+1,mm,:] + # summation over all spherical harmonic degrees + iint = 1.0/(np.cos(th)*np.sin(th)) + pconv = np.einsum("h...,lmh...,lmd...->mhd...", iint, PLM, Ylm) + + # calculating cos(m*phi) and sin(m*phi) using Euler's formula + m_phi = np.exp(1j * np.einsum("m...,p...->mp...", mm, phi)) # output geostrophic current fields currents = np.zeros((phmax,thmax,2)) - # for each direction of flow - for d in range(2): - # summation of cosine and sine harmonics - currents[:,:,d] = np.dot(np.transpose(ccos),d_cos[:,:,d]) + \ - np.dot(np.transpose(ssin),d_sin[:,:,d]) + # summation of cosine and sine harmonics + currents = np.einsum("mp...,mhd...->phd...", m_phi, pconv) - # return the current fields - return currents + # return the current fields and drop imaginary component + return currents.real diff --git a/gravity_toolkit/harmonic_summation.py b/gravity_toolkit/harmonic_summation.py index 3eed02ce..973be100 100755 --- a/gravity_toolkit/harmonic_summation.py +++ b/gravity_toolkit/harmonic_summation.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" harmonic_summation.py -Written by Tyler Sutterley (03/2023) +Written by Tyler Sutterley (07/2026) Returns the spatial field for a series of spherical harmonics @@ -30,6 +30,8 @@ units.py: class for converting spherical harmonic data to specific units UPDATE HISTORY: + Updated 07/2026: use np.einsum for spherical harmonic summations + use np.radians to convert from degrees to radians Updated 04/2023: allow love numbers to be None for custom units case Updated 03/2023: allow units inputs to be strings for named types improve typing for variables in docstrings @@ -85,41 +87,32 @@ def harmonic_summation(clm1, slm1, lon, lat, if MMAX is None: MMAX = np.copy(LMAX) - # Longitude in radians - phi = (np.squeeze(lon)*np.pi/180.0)[np.newaxis,:] + # longitude in radians + phi = np.radians(np.squeeze(lon)) # colatitude in radians - th = (90.0 - np.squeeze(lat))*np.pi/180.0 - thmax = len(th) + th = np.radians(90.0 - np.squeeze(lat)) # if plms are not pre-computed: calculate Legendre polynomials if PLM is None: PLM, dPLM = plm_holmes(LMAX, np.cos(th)) + # spherical harmonic order + mm = np.arange(0,MMAX+1)# mmax+1 to include mmax + # real (cosine) and imaginary (sine) components + Ylm = np.zeros((LMAX+1, MMAX+1), dtype=np.complex128) # Truncating harmonics to degree and order LMAX # removing coefficients below LMIN and above MMAX - mm = np.arange(0, MMAX+1) - clm = np.zeros((LMAX+1, MMAX+1)) - slm = np.zeros((LMAX+1, MMAX+1)) - clm[LMIN:LMAX+1,mm] = clm1[LMIN:LMAX+1,mm] - slm[LMIN:LMAX+1,mm] = slm1[LMIN:LMAX+1,mm] + Ylm.real[LMIN:LMAX+1,mm] = clm1[LMIN:LMAX+1,mm] + Ylm.imag[LMIN:LMAX+1,mm] = -slm1[LMIN:LMAX+1,mm] # Calculate fourier coefficients from legendre coefficients - d_cos = np.zeros((MMAX+1,thmax))# [m,th] - d_sin = np.zeros((MMAX+1,thmax))# [m,th] - for k in range(0,thmax): - # summation over all spherical harmonic degrees - d_cos[:,k] = np.sum(PLM[:,mm,k]*clm[:,mm],axis=0) - d_sin[:,k] = np.sum(PLM[:,mm,k]*slm[:,mm],axis=0) - - # Final signal recovery from fourier coefficients - m = np.arange(0,MMAX+1)[:,np.newaxis] - # Calculating cos(m*phi) and sin(m*phi) - ccos = np.cos(np.dot(m,phi)) - ssin = np.sin(np.dot(m,phi)) + # summation over all spherical harmonic degrees + pconv = np.einsum("lmh...,lm...->mh...", PLM, Ylm) + # calculating cos(m*phi) and sin(m*phi) using Euler's formula + m_phi = np.exp(1j * np.einsum("m...,p...->mp...", mm, phi)) # summation of cosine and sine harmonics - s = np.dot(np.transpose(ccos),d_cos) + np.dot(np.transpose(ssin),d_sin) - - # return output data - return s + spatial = np.einsum("mp...,mh...->ph...", m_phi, pconv) + # return output data and drop imaginary component + return spatial.real def harmonic_transform(clm1, slm1, lon, lat, LMIN=0, LMAX=60, MMAX=None, PLM=None): @@ -164,25 +157,23 @@ def harmonic_transform(clm1, slm1, lon, lat, # number of longitudinal points phimax = len(np.squeeze(lon)) # colatitude in radians - th = (90.0 - np.squeeze(lat))*np.pi/180.0 + th = np.radians(90.0 - np.squeeze(lat)) thmax = len(th) # if plms are not pre-computed: calculate Legendre polynomials if PLM is None: PLM, dPLM = plm_holmes(LMAX, np.cos(th)) - # combined Ylms and Fourier coefficients (complex) - Ylms = np.zeros((LMAX+1, MMAX+1),dtype=np.complex128) - delta_M = np.zeros((MMAX+1,thmax),dtype=np.complex128)# [m,th] - # Real (cosine) and imaginary (sine) components + # spherical harmonic order + mm = np.arange(0,MMAX+1)# mmax+1 to include mmax + # real (cosine) and imaginary (sine) components + Ylm = np.zeros((LMAX+1, MMAX+1), dtype=np.complex128) # Truncating harmonics to degree and order LMAX # removing coefficients below LMIN and above MMAX - Ylms[LMIN:LMAX+1,:MMAX+1] = clm1[LMIN:LMAX+1,0:MMAX+1] - \ - slm1[LMIN:LMAX+1,0:MMAX+1]*1j + Ylm.real[LMIN:LMAX+1,mm] = clm1[LMIN:LMAX+1,mm] + Ylm.imag[LMIN:LMAX+1,mm] = -slm1[LMIN:LMAX+1,mm] # calculate Ylms summation for each theta band - for k in range(0,thmax): - # summation over all spherical harmonic degrees - delta_M[:,k] = np.sum(PLM[:,:,k]*Ylms[:,:],axis=0)/2.0 + delta_M = np.einsum("lmh...,lm...->mh...", PLM, Ylm / 2.0) # output spatial field from FFT transformation s = np.zeros((phimax,thmax)) @@ -269,14 +260,11 @@ def stokes_summation(clm1, slm1, lon, lat, else: raise ValueError(f'Unknown units {UNITS}') - # truncate to degree and order - mm = np.arange(0, MMAX+1) + # spherical harmonic order + mm = np.arange(0,MMAX+1)# mmax+1 to include mmax # smooth harmonics and convert to output units - clm = np.zeros((LMAX+1, MMAX+1)) - slm = np.zeros((LMAX+1, MMAX+1)) - for l in range(0, LMAX+1):# LMAX+1 to include LMAX - clm[l,:] = wl[l]*dfactor[l]*clm1[l,mm] - slm[l,:] = wl[l]*dfactor[l]*slm1[l,mm] + clm = np.einsum("l,l,lm->lm", wl, dfactor, clm1[:, mm]) + slm = np.einsum("l,l,lm->lm", wl, dfactor, slm1[:, mm]) # return the spatial field return harmonic_summation(clm, slm, lon, lat, diff --git a/gravity_toolkit/harmonics.py b/gravity_toolkit/harmonics.py index 877c0edf..ffd5cc70 100644 --- a/gravity_toolkit/harmonics.py +++ b/gravity_toolkit/harmonics.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" harmonics.py -Written by Tyler Sutterley (06/2024) +Written by Tyler Sutterley (07/2026) Contributions by Hugo Lecomte Spherical harmonic data class for processing GRACE/GRACE-FO Level-2 data @@ -25,6 +25,7 @@ destripe_harmonics.py: filters spherical harmonics for correlated errors UPDATE HISTORY: + Updated 07/2026: add dunder (magic) methods for mathematical operations Updated 06/2024: use wrapper to importlib for optional dependencies Updated 05/2024: make subscriptable and allow item assignment Updated 10/2023: place time and month variables in try/except block @@ -1893,6 +1894,71 @@ def __str__(self): properties.append(f" end_month: {max(self.month)}") return '\n'.join(properties) + def __add__(self, other): + """Add values to a ``harmonics`` object""" + temp = self.copy() + return temp.add(other) + + def __div__(self, other): + """Divide values from a ``harmonics`` object""" + temp = self.copy() + if isinstance(other, (int, float, np.ndarray)): + return temp.scale(1.0 / other) + else: + return temp.divide(other) + + def __iadd__(self, other): + """In-place add values to a ``harmonics`` object""" + return self.add(other) + + def __idiv__(self, other): + """In-place divide values from a ``harmonics`` object""" + if isinstance(other, (int, float, np.ndarray)): + return self.scale(1.0 / other) + else: + return self.divide(other) + + def __imul__(self, other): + """In-place multiply values from a ``harmonics`` object""" + if isinstance(other, (int, float, np.ndarray)): + return self.scale(other) + else: + return self.multiply(other) + + def __ipow__(self, other): + """In-place raise values from a ``harmonics`` object to a power""" + return self.power(other) + + def __isub__(self, other): + """In-place subtract values from a ``harmonics`` object""" + return self.subtract(other) + + def __mul__(self, other): + """Multiply values from a ``harmonics`` object""" + temp = self.copy() + if isinstance(other, (int, float, np.ndarray)): + return temp.scale(other) + else: + return temp.multiply(other) + + def __pow__(self, other): + """Raise values from a ``harmonics`` object to a power""" + temp = self.copy() + return temp.power(other) + + def __sub__(self, other): + """Subtract values from a ``harmonics`` object""" + temp = self.copy() + return temp.subtract(other) + + def __truediv__(self, other): + """Divide values from a ``harmonics`` object""" + temp = self.copy() + if isinstance(other, (int, float, np.ndarray)): + return temp.scale(1.0 / other) + else: + return temp.divide(other) + def __len__(self): """Number of months """ diff --git a/gravity_toolkit/mascons.py b/gravity_toolkit/mascons.py index 2a8f08c1..98a01c74 100644 --- a/gravity_toolkit/mascons.py +++ b/gravity_toolkit/mascons.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" mascons.py -Written by Tyler Sutterley (03/2023) +Written by Tyler Sutterley (07/2026) Conversion routines for publicly available GRACE/GRACE-FO mascon solutions PYTHON DEPENDENCIES: @@ -12,6 +12,7 @@ mascon2grid.m written by Felix Landerer and David Wiese (JPL) UPDATE HISTORY: + Updated 07/2026: use np.radians to convert from degrees to radians Updated 03/2023: improve typing for variables in docstrings Updated 11/2022: use lowercase keyword arguments Updated 04/2022: updated docstrings to numpy documentation format @@ -63,15 +64,13 @@ def to_gsfc(gdata, lon, lat, lon_center, lat_center, lon_span, lat_span): # number of mascons nmas = len(lon_center) # convert mascon centers to -180:180 - gt180, = np.nonzero(lon_center > 180) - lon_center[gt180] -= 360.0 + lon_center = np.where(lon_center > 180, lon_center - 360.0, lon_center) # remove singleton dimensions lat = np.squeeze(lat) lon = np.squeeze(lon) # for mascons centered on 180: use 0:360 alon = np.copy(lon) - lt0, = np.nonzero(lon < 0) - alon[lt0] += 360.0 + alon = np.where(alon < 0, alon + 360.0, alon) # loop over each mascon bin and average gdata with the cos-lat weights # for that bin @@ -98,12 +97,12 @@ def to_gsfc(gdata, lon, lat, lon_center, lat_center, lon_span, lat_span): else: ilon = lon.copy() # indices for grid points within the mascon - I, = np.nonzero((lat >= lat_bound[0]) & (lat < lat_bound[1])) - J, = np.nonzero((ilon >= lon_bound[0]) & (ilon < lon_bound[1])) + I, = np.flatnonzero((lat >= lat_bound[0]) & (lat < lat_bound[1])) + J, = np.flatnonzero((ilon >= lon_bound[0]) & (ilon < lon_bound[1])) I,J = (I[np.newaxis,:], J[:,np.newaxis]) # calculate average data for mascon bin - mascon_array['data'][k] = np.mean((np.cos(lat[I]*np.pi/180.0) / - np.mean(np.cos(lat[I]*np.pi/180.0)))*gdata[I,J]/len(I)) + mascon_array['data'][k] = np.mean((np.cos(np.radians(lat[I])) / + np.mean(np.cos(np.radians(lat[I]))))*gdata[I,J]/len(I)) mascon_array['lat_center'][k] = lat_center[k] mascon_array['lon_center'][k] = lon_center[k] @@ -154,13 +153,13 @@ def to_jpl(gdata, lon, lat, lon_bound, lat_bound): mascon_array['lat'] = np.zeros((nmas)) for k in range(0,nmas): # indices for grid points within the mascon - I, = np.nonzero((lat >= lat_bound[k,1]) & (lat < lat_bound[k,0])) - J, = np.nonzero((lon >= lon_bound[k,0]) & (lon < lon_bound[k,2])) + I, = np.flatnonzero((lat >= lat_bound[k,1]) & (lat < lat_bound[k,0])) + J, = np.flatnonzero((lon >= lon_bound[k,0]) & (lon < lon_bound[k,2])) nlt = np.count_nonzero((lat >= lat_bound[k,1]) & (lat < lat_bound[k,0])) I,J = (I[np.newaxis,:], J[:,np.newaxis]) # calculate average data for mascon bin - mascon_array['data'][k] = np.mean((np.cos(lat[I]*np.pi/180.0) / - np.mean(np.cos(lat[I]*np.pi/180.0)))*gdata[I,J]/nlt) + mascon_array['data'][k] = np.mean((np.cos(np.radians(lat[I])) / + np.mean(np.cos(np.radians(lat[I]))))*gdata[I,J]/nlt) # calculate coordinates of mascon center mascon_array['lat'][k] = (lat_bound[k,1]+lat_bound[k,0])/2.0 mascon_array['lon'][k] = (lon_bound[k,1]+lon_bound[k,2])/2.0 @@ -219,8 +218,7 @@ def from_gsfc(mscdata, grid_spacing, lon_center, lat_center, lon_span, lat_span, # number of mascons nmas = len(lon_center) # convert mascon centers to -180:180 - gt180, = np.nonzero(lon_center > 180) - lon_center[gt180] -= 360.0 + lon_center = np.where(lon_center > 180, lon_center - 360.0, lon_center) # Define output latitude and longitude grids lon = np.arange(-180.0+grid_spacing/2.0,180.0+grid_spacing/2.0,grid_spacing) @@ -228,8 +226,7 @@ def from_gsfc(mscdata, grid_spacing, lon_center, lat_center, lon_span, lat_span, nlon, nlat = (len(lon),len(lat)) # for mascons centered on 180: use 0:360 alon = np.copy(lon) - lt0, = np.nonzero(lon < 0) - alon[lt0] += 360.0 + alon = np.where(alon < 0, alon + 360.0, alon) # loop over each mascon bin and assign value to grid points inside bin: mdata = np.zeros((nlat, nlon)) @@ -252,8 +249,8 @@ def from_gsfc(mscdata, grid_spacing, lon_center, lat_center, lon_span, lat_span, else: ilon = lon.copy() # indices for grid points within the mascon - I, = np.nonzero((lat >= lat_bound[0]) & (lat < lat_bound[1])) - J, = np.nonzero((ilon >= lon_bound[0]) & (ilon < lon_bound[1])) + I, = np.flatnonzero((lat >= lat_bound[0]) & (lat < lat_bound[1])) + J, = np.flatnonzero((ilon >= lon_bound[0]) & (ilon < lon_bound[1])) I,J = (I[np.newaxis,:], J[:,np.newaxis]) mdata[I,J] = mscdata[k] @@ -310,8 +307,8 @@ def from_jpl(mscdata, grid_spacing, lon_bound, lat_bound, **kwargs): # loop over each mascon bin and assign value to grid points inside bin: mdata = np.zeros((nlat, nlon)) for k in range(0, nmas): - I, = np.nonzero((lat >= lat_bound[k,1]) & (lat < lat_bound[k,0])) - J, = np.nonzero((lon >= lon_bound[k,0]) & (lon < lon_bound[k,2])) + I, = np.flatnonzero((lat >= lat_bound[k,1]) & (lat < lat_bound[k,0])) + J, = np.flatnonzero((lon >= lon_bound[k,0]) & (lon < lon_bound[k,2])) I,J = (I[np.newaxis,:], J[:,np.newaxis]) mdata[I,J] = mscdata[k] diff --git a/gravity_toolkit/sea_level_equation.py b/gravity_toolkit/sea_level_equation.py index 24b53231..1e1e3906 100644 --- a/gravity_toolkit/sea_level_equation.py +++ b/gravity_toolkit/sea_level_equation.py @@ -1,6 +1,6 @@ #!/usr/bin/env python u""" -sea_level_equation.py (06/2025) +sea_level_equation.py (07/2026) Solves the sea level equation with the option of including polar motion feedback Uses a Clenshaw summation to calculate the spherical harmonic summation @@ -37,7 +37,6 @@ ITERATIONS: maximum number of iterations for the solver PLM: input Legendre polynomials FILL_VALUE: value used over land points - ASTYPE: floating point precision for calculating Clenshaw summation SCALE: scaling factor to prevent underflow in Clenshaw summation PYTHON DEPENDENCIES: @@ -91,6 +90,8 @@ https://doi.org/10.1029/JB090iB11p09363 UPDATE HISTORY: + Updated 07/2026: use np.einsum for spherical harmonic summations + use np.radians to convert from degrees to radians Updated 06/2025: added option to set the density of sea water (g/cm^3) Updated 03/2023: improve typing for variables in docstrings Updated 01/2023: refactored associated legendre polynomials @@ -131,8 +132,7 @@ # PURPOSE: Computes Sea Level Fingerprints including polar motion feedback def sea_level_equation(loadClm, loadSlm, glon, glat, land_function, LMAX=0, LOVE=None, BODY_TIDE_LOVE=0, FLUID_LOVE=0, DENSITY=1.0, POLAR=True, - ITERATIONS=6, PLM=None, FILL_VALUE=0, ASTYPE=np.longdouble, SCALE=1e-280, - **kwargs): + ITERATIONS=6, PLM=None, FILL_VALUE=0, SCALE=1e-280, **kwargs): r""" Solves the sea level equation with the option of including polar motion feedback :cite:p:`Farrell:1976hm,Kendall:2005ds,Mitrovica:2003cq` @@ -180,8 +180,6 @@ def sea_level_equation(loadClm, loadSlm, glon, glat, land_function, LMAX=0, Legendre polynomials FILL_VALUE: float, default 0 Invalid value used over land points - ASTYPE: np.dtype, default np.longdouble - Floating point precision for calculating Clenshaw summation SCALE: float, default 1e-280 Scaling factor to prevent underflow in Clenshaw summation @@ -192,17 +190,17 @@ def sea_level_equation(loadClm, loadSlm, glon, glat, land_function, LMAX=0, """ # dimensions of land function - nphi,nth = np.shape(land_function) + nphi, nth = np.shape(land_function) # calculate colatitude and longitude in radians - th = (90.0 - glat)*np.pi/180.0 - phi = np.squeeze(glon*np.pi/180.0) + th = np.radians(90.0 - glat) + phi = np.radians(np.squeeze(glon)) # calculate ocean function from land function ocean_function = 1.0 - land_function # indices of the ocean function ii,jj = np.nonzero(ocean_function) # extract arrays of kl, hl, and ll Love Numbers - hl,kl,ll = LOVE + hl, kl, ll = LOVE # density of water [g/cm^3] rho_water = np.float64(DENSITY) # Earth Parameters @@ -271,21 +269,25 @@ def sea_level_equation(loadClm, loadSlm, glon, glat, land_function, LMAX=0, # calculate coefh and coefp for each degree and order # see equation 11 from Tamisiea et al (2010) - coefh = np.zeros((LMAX+1,LMAX+1)) - coefp = np.zeros((LMAX+1,LMAX+1)) + coefh = np.zeros((LMAX+1, LMAX+1)) + coefp = np.zeros((LMAX+1, LMAX+1)) for l in range(LMAX+1): + m = np.arange(0, l+1) + # tilt factor for degree l + gamma_l = (1.0 + kl[l] - hl[l]) # coefh and coefp will be the same for all orders except for degree 2 # and order 1 (if POLAR motion feedback is included) - m = np.arange(0,l+1) - coefh[l,m] = 3.0*rho_water*(1.0 + kl[l] - hl[l])/rho_e/np.float64(2*l+1) - coefp[l,m] = (1.0 + kl[l] - hl[l])/(kl[l] + 1.0) + coefh[l,m] = 3.0*rho_water*gamma_l/rho_e/np.float64(2*l+1) + coefp[l,m] = gamma_l/(kl[l] + 1.0) # if degree 2 and POLAR parameter is set if (l == 2) and POLAR: + # tilt factor for body tides + gamma_2b = (1.0 + k2b - h2b) # calculate coefficient for polar motion feedback and add to coefs # For small perturbations in rotation vector: driving potential # will be dominated by degree two and order one polar wander # effects (quadrantal geometry effects) (Kendall et al., 2005) - coefpmf = (1.0 + k2b - h2b)*(1.0 + kl[l])/(klf - k2b) + coefpmf = gamma_2b*(1.0 + kl[l])/(klf - k2b) # add effects of polar motion feedback to order 1 coefficients coefh[l,1] += 3.0*rho_water*coefpmf/rho_e/np.float64(2*l+1) coefp[l,1] += coefpmf/(kl[l] + 1.0) @@ -295,15 +297,15 @@ def sea_level_equation(loadClm, loadSlm, glon, glat, land_function, LMAX=0, # calculate Legendre polynomials using Holmes and Featherstone relation PLM, dPLM = plm_holmes(LMAX, np.cos(th)) # calculate sin of colatitudes - gth,gphi = np.meshgrid(th, phi) + gth, gphi = np.meshgrid(th, phi) u = np.sin(gth[ii,jj]) # indices of spherical harmonics for calculating eps - l1,m1 = np.tril_indices(LMAX+1) + l1, m1 = np.tril_indices(LMAX+1) # total mass of the surface mass load [g] from harmonics tmass = 4.0*np.pi*(rad_e**3.0)*rho_e*loadClm[0,0]/3.0 # convert ocean function into a series of spherical harmonics - ocean_Ylms = gen_harmonics(ocean_function,glon,glat,LMAX=LMAX,PLM=PLM) + ocean_Ylms = gen_harmonics(ocean_function, glon, glat, LMAX=LMAX, PLM=PLM) # total area of ocean calculated by integrating the ocean function ocean_area = 4.0*np.pi*ocean_Ylms.clm[0,0] @@ -316,8 +318,12 @@ def sea_level_equation(loadClm, loadSlm, glon, glat, land_function, LMAX=0, logging.info(f'Total Ocean Area: {ocean_area:0.10g}') logging.info(f'Uniform Ocean Height: {sea_height:0.10g}') + # allocate for output sea level field + sea_level = np.empty((nphi, nth)) + # complex load spherical harmonics + loadYlms = loadClm - 1j*loadSlm # distribute sea height over ocean harmonics - height_Ylms = ocean_Ylms.scale(sea_height) + height_Ylms = ocean_Ylms * sea_height # iterate solutions until convergence or reaching total iterations n_iter = 1 # use maximum eps values from Mitrovica and Peltier (1991) @@ -325,42 +331,38 @@ def sea_level_equation(loadClm, loadSlm, glon, glat, land_function, LMAX=0, eps = np.inf eps_max = 1e-4 while (eps > eps_max) and (n_iter <= ITERATIONS): - # allocate for sea level field of iteration - sea_level = np.zeros((nphi,nth)) + # zero out the sea level field for this iteration + sea_level[:,:] = 0.0 # calculate combined spherical harmonics for Clenshaw summation - clm1 = coefh*height_Ylms.clm + rad_e*coefp*loadClm - slm1 = coefh*height_Ylms.slm + rad_e*coefp*loadSlm + Ylm1 = coefh*height_Ylms.ilm + rad_e*coefp*loadYlms # calculate clenshaw summations over colatitudes - s_m_c = np.zeros((nth,LMAX*2+2)) + cs_m = np.zeros((nth, LMAX+1), dtype=np.clongdouble) for m in range(LMAX, -1, -1): - s_m_c[:,2*m:2*m+2] = clenshaw_s_m(np.cos(th), m, clm1, slm1, LMAX, - ASTYPE=ASTYPE, SCALE=SCALE) + cs_m[:,m] = _clenshaw(np.cos(th), m, Ylm1, LMAX, SCALE=SCALE) # calculate cos(phi) cos_phi_2 = 2.0*np.cos(phi) # matrix of cos/sin m*phi summation - cos_m_phi = np.zeros((nphi,LMAX+2),dtype=ASTYPE) - sin_m_phi = np.zeros((nphi,LMAX+2),dtype=ASTYPE) + m_phi = np.zeros((nphi, LMAX+2), dtype=np.clongdouble) # initialize matrix with values at lmax+1 and lmax - cos_m_phi[:,LMAX+1] = np.cos(ASTYPE(LMAX + 1)*phi) - sin_m_phi[:,LMAX+1] = np.sin(ASTYPE(LMAX + 1)*phi) - cos_m_phi[:,LMAX] = np.cos(ASTYPE(LMAX)*phi) - sin_m_phi[:,LMAX] = np.sin(ASTYPE(LMAX)*phi) + m_phi[:,LMAX+1] = np.exp(1j * (LMAX + 1) * phi) + m_phi[:,LMAX] = np.exp(1j * LMAX*phi) # calculate summation - gc=np.multiply(s_m_c[np.newaxis,:,2*LMAX],cos_m_phi[:,np.newaxis,LMAX]) - gs=np.multiply(s_m_c[np.newaxis,:,2*LMAX+1],sin_m_phi[:,np.newaxis,LMAX]) - s_m = gc[ii,jj] + gs[ii,jj] + g = np.einsum("h...,p...->ph...", cs_m[:,LMAX], m_phi[:,LMAX]) + # discard imaginary component + s_m = g[ii,jj].real # iterate to calculate complete summation for m in range(LMAX-1, 0, -1): - cos_m_phi[:,m] = cos_phi_2*cos_m_phi[:,m+1] - cos_m_phi[:,m+2] - sin_m_phi[:,m] = cos_phi_2*sin_m_phi[:,m+1] - sin_m_phi[:,m+2] - a_m = np.sqrt((2.0*m+3.0)/(2.0*m+2.0)) - gc=np.multiply(s_m_c[np.newaxis,:,2*m],cos_m_phi[:,np.newaxis,m]) - gs=np.multiply(s_m_c[np.newaxis,:,2*m+1],sin_m_phi[:,np.newaxis,m]) - s_m = a_m*u*s_m + gc[ii,jj] + gs[ii,jj] + # calculate summation for order m + m_phi[:,m] = cos_phi_2*m_phi[:,m+1] - m_phi[:,m+2] + a_m = np.sqrt((2.0*m + 3.0)/(2.0*m + 2.0)) + g = np.einsum("h...,p...->ph...", cs_m[:,m], m_phi[:,m]) + # update summation and discard imaginary component + s_m = a_m*u*s_m + g[ii,jj].real + # add the l=0/m=0 term + gs_m = np.kron(np.ones((nphi, 1)), cs_m.real[:, 0]) # calculate new sea level for iteration - gsmc,gcmp = np.meshgrid(s_m_c[:,0],cos_m_phi[:,0]) - sea_level[ii,jj] = np.sqrt(3.0)*u*s_m + gsmc[ii,jj] + sea_level[ii,jj] = np.sqrt(3.0)*u*s_m + gs_m[ii,jj] # calculate spherical harmonic field for iteration Ylms = gen_harmonics(sea_level, glon, glat, LMAX=LMAX, PLM=PLM) @@ -383,11 +385,10 @@ def sea_level_equation(loadClm, loadSlm, glon, glat, land_function, LMAX=0, # Equation 48 of Mitrovica and Peltier (1991) # add difference to total sea level field to force mass conservation sea_level += sea_height*ocean_function[:,:] - uniform_Ylms = ocean_Ylms.scale(sea_height) - Ylms.add(uniform_Ylms) + Ylms += ocean_Ylms * sea_height # calculate eps to determine if solution is appropriately converged - mod1 = np.sqrt(height_Ylms.clm**2 + height_Ylms.slm**2) - mod2 = np.sqrt(Ylms.clm**2 + Ylms.slm**2) + mod1 = np.hypot(height_Ylms.clm, height_Ylms.slm) + mod2 = np.hypot(Ylms.clm, Ylms.slm) eps = np.abs(np.sum(mod2[l1,m1] - mod1[l1,m1])/np.sum(mod1[l1,m1])) # save height harmonics for use in the next iteration height_Ylms = Ylms.copy() @@ -397,8 +398,8 @@ def sea_level_equation(loadClm, loadSlm, glon, glat, land_function, LMAX=0, # calculate final total mass for sanity check omass = 4.0*np.pi*(rad_e**2.0)*rho_water*height_Ylms.clm[0,0] # if verbose output: sanity check of masses - logging.info('Original Total Ocean Mass: {0:0.10g}'.format(-tmass/1e15)) - logging.info('Final Iterated Ocean Mass: {0:0.10g}'.format(omass/1e15)) + logging.info(f'Original Total Ocean Mass: {-tmass/1e15:0.10g}') + logging.info(f'Final Iterated Ocean Mass: {omass/1e15:0.10g}') # set final invalid points to fill value if applicable if (FILL_VALUE != 0): @@ -410,9 +411,7 @@ def sea_level_equation(loadClm, loadSlm, glon, glat, land_function, LMAX=0, # PURPOSE: compute Clenshaw summation of the fully normalized associated # Legendre's function for constant order m -def clenshaw_s_m(t, m, clm1, slm1, lmax, - ASTYPE=np.longdouble, SCALE=1e-280 - ): +def _clenshaw(t, m, Ylm1, lmax, SCALE=1e-280): """ Compute conditioned arrays for Clenshaw summation from the fully-normalized associated Legendre's function for an order m @@ -423,14 +422,10 @@ def clenshaw_s_m(t, m, clm1, slm1, lmax, elements ranging from -1 to 1, typically cos(th) m: int spherical harmonic order - clm1: np.ndarray - cosine spherical harmonics - slm1: np.ndarray - sine spherical harmonics + Ylm1: np.ndarray + complex form of spherical harmonics lmax: int maximum spherical harmonic degree - ASTYPE: np.dtype, default np.longdouble - floating point precision for calculating Clenshaw summation SCALE: float, default 1e-280 scaling factor to prevent underflow in Clenshaw summation @@ -441,49 +436,40 @@ def clenshaw_s_m(t, m, clm1, slm1, lmax, """ # allocate for output matrix N = len(t) - s_m = np.zeros((N,2),dtype=ASTYPE) + s_m = np.zeros((N), dtype=np.clongdouble) # scaling to prevent overflow - clm = SCALE*clm1.astype(ASTYPE) - slm = SCALE*slm1.astype(ASTYPE) + ylm = SCALE*Ylm1.astype(np.clongdouble) # convert lmax and m to float - lm = ASTYPE(lmax) - mm = ASTYPE(m) + lm = np.float64(lmax) + mm = np.float64(m) if (m == lmax): - s_m[:,0] = np.copy(clm[lmax,lmax]) - s_m[:,1] = np.copy(slm[lmax,lmax]) + s_m[:] = np.copy(ylm[lmax,lmax]) elif (m == (lmax-1)): a_lm = np.sqrt(((2.0*lm-1.0)*(2.0*lm+1.0))/((lm-mm)*(lm+mm)))*t - s_m[:,0] = a_lm*clm[lmax,lmax-1] + clm[lmax-1,lmax-1] - s_m[:,1] = a_lm*slm[lmax,lmax-1] + slm[lmax-1,lmax-1] + s_m[:] = a_lm*ylm[lmax,lmax-1] + ylm[lmax-1,lmax-1] elif ((m <= (lmax-2)) and (m >= 1)): - s_mm_c_pre_2 = np.copy(clm[lmax,m]) - s_mm_s_pre_2 = np.copy(slm[lmax,m]) + s_mm_minus_2 = np.copy(ylm[lmax,m]) a_lm = np.sqrt(((2.0*lm-1.0)*(2.0*lm+1.0))/((lm-mm)*(lm+mm)))*t - s_mm_c_pre_1 = a_lm*s_mm_c_pre_2 + clm[lmax-1,m] - s_mm_s_pre_1 = a_lm*s_mm_s_pre_2 + slm[lmax-1,m] + s_mm_minus_1 = a_lm*s_mm_minus_2 + ylm[lmax-1,m] for l in range(lmax-2, m-1, -1): - ll = ASTYPE(l) + ll = np.float64(l) a_lm=np.sqrt(((2.0*ll+1.0)*(2.0*ll+3.0))/((ll+1.0-mm)*(ll+1.0+mm)))*t b_lm=np.sqrt(((2.*ll+5.)*(ll+mm+1.)*(ll-mm+1.))/((ll+2.-mm)*(ll+2.+mm)*(2.*ll+1.))) - s_mm_c = a_lm * s_mm_c_pre_1 - b_lm * s_mm_c_pre_2 + clm[l,m] - s_mm_s = a_lm * s_mm_s_pre_1 - b_lm * s_mm_s_pre_2 + slm[l,m] - s_mm_c_pre_2 = np.copy(s_mm_c_pre_1) - s_mm_s_pre_2 = np.copy(s_mm_s_pre_1) - s_mm_c_pre_1 = np.copy(s_mm_c) - s_mm_s_pre_1 = np.copy(s_mm_s) - s_m[:,0] = np.copy(s_mm_c) - s_m[:,1] = np.copy(s_mm_s) + s_mm_l = a_lm * s_mm_minus_1 - b_lm * s_mm_minus_2 + ylm[l,m] + s_mm_minus_2 = np.copy(s_mm_minus_1) + s_mm_minus_1 = np.copy(s_mm_l) + s_m[:] = np.copy(s_mm_l) elif (m == 0): - s_mm_c_pre_2 = np.copy(clm[lmax,0]) + s_mm_minus_2 = np.copy(ylm[lmax,0]) a_lm = np.sqrt(((2.0*lm-1.0)*(2.0*lm+1.0))/(lm*lm))*t - s_mm_c_pre_1 = a_lm * s_mm_c_pre_2 + clm[lmax-1,0] + s_mm_minus_1 = a_lm * s_mm_minus_2 + ylm[lmax-1,0] for l in range(lmax-2, m-1, -1): - ll = ASTYPE(l) + ll = np.float64(l) a_lm=np.sqrt(((2.0*ll+1.0)*(2.0*ll+3.0))/((ll+1.0)*(ll+1.0)))*t b_lm=np.sqrt(((2.0*ll+5.0)*(ll+1.0)*(ll+1.0))/((ll+2.0)*(ll+2.0)*(2.0*ll+1.0))) - s_mm_c = a_lm * s_mm_c_pre_1 - b_lm * s_mm_c_pre_2 + clm[l,0] - s_mm_c_pre_2 = np.copy(s_mm_c_pre_1) - s_mm_c_pre_1 = np.copy(s_mm_c) - s_m[:,0] = np.copy(s_mm_c) + s_mm_l = a_lm * s_mm_minus_1 - b_lm * s_mm_minus_2 + ylm[l,0] + s_mm_minus_2 = np.copy(s_mm_minus_1) + s_mm_minus_1 = np.copy(s_mm_l) + s_m[:] = np.copy(s_mm_l) # return rescaled s_m return s_m/SCALE diff --git a/gravity_toolkit/spatial.py b/gravity_toolkit/spatial.py index adc80ea5..15c402e3 100644 --- a/gravity_toolkit/spatial.py +++ b/gravity_toolkit/spatial.py @@ -20,6 +20,8 @@ time.py: utilities for calculating time operations UPDATE HISTORY: + Updated 07/2026: add dunder (magic) methods for mathematical operations + add option to change the output format for ascii files Updated 10/2024: allow 2D and 3D arrays in output netCDF4 files Updated 06/2024: use wrapper to importlib for optional dependencies Updated 05/2024: make subscriptable and allow item assignment @@ -734,6 +736,8 @@ def to_ascii(self, filename, **kwargs): full path of output ascii file date: bool, default True ``spatial`` objects contain date information + float_format: str, default '12.4f' + format for floating point numbers in output ascii file verbose: bool, default False Output file and variable information """ @@ -744,14 +748,16 @@ def to_ascii(self, filename, **kwargs): logging.info(str(self.filename)) # open the output file fid = self.filename.open(mode='w', encoding='utf8') + file_format = '{0:10.4f} {1:10.4f} ' + float_format = kwargs.get('float_format', '12.4f') if hasattr(self, 'error') and kwargs['date']: - file_format = '{0:10.4f} {1:10.4f} {2:12.4f} {3:12.4f} {4:10.4f}' + file_format += ' '.join([f'{{{i}:' + float_format + '}' for i in (2,3,4)]) elif hasattr(self, 'error'): - file_format = '{0:10.4f} {1:10.4f} {2:12.4f} {3:12.4f}' + file_format += ' '.join([f'{{{i}:' + float_format + '}' for i in (2,3)]) elif kwargs['date']: - file_format = '{0:10.4f} {1:10.4f} {2:12.4f} {4:10.4f}' + file_format += ' '.join([f'{{{i}:' + float_format + '}' for i in (2,4)]) else: - file_format = '{0:10.4f} {1:10.4f} {2:12.4f}' + file_format += ' '.join([f'{{{i}:' + float_format + '}' for i in (2,)]) # write to file for each valid latitude and longitude ii,jj = np.nonzero((self.data != self.fill_value) & (~self.mask)) for i,j in zip(ii,jj): @@ -1707,6 +1713,56 @@ def __str__(self): properties.append(f" end_month: {max(self.month)}") return '\n'.join(properties) + def __add__(self, other): + """Add values to a ``spatial`` object""" + temp = self.copy() + return temp.offset(other) + + def __div__(self, other): + """Divide values from a ``spatial`` object""" + temp = self.copy() + return temp.scale(1.0 / other) + + def __iadd__(self, other): + """In-place add values to a ``spatial`` object""" + return self.offset(other) + + def __idiv__(self, other): + """In-place divide values from a ``spatial`` object""" + return self.scale(1.0 / other) + + def __imul__(self, other): + """In-place multiply values from a ``spatial`` object""" + return self.scale(other) + + def __ipow__(self, other): + """In-place raise values from a ``spatial`` object to a power""" + return self.power(other) + + def __isub__(self, other): + """In-place subtract values from a ``spatial`` object""" + return self.offset(-other) + + def __mul__(self, other): + """Multiply values from a ``spatial`` object""" + temp = self.copy() + return temp.scale(other) + + def __pow__(self, other): + """Raise values from a ``spatial`` object to a power""" + temp = self.copy() + return temp.power(other) + + def __sub__(self, other): + """Subtract values from a ``spatial`` object""" + temp = self.copy() + return temp.offset(-other) + + def __truediv__(self, other): + """Divide values from a ``spatial`` object""" + temp = self.copy() + return temp.scale(1.0 / other) + def __len__(self): """Number of months """ diff --git a/gravity_toolkit/time_series/amplitude.py b/gravity_toolkit/time_series/amplitude.py index 7d174cb2..ad4df441 100755 --- a/gravity_toolkit/time_series/amplitude.py +++ b/gravity_toolkit/time_series/amplitude.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" amplitude.py -Written by Tyler Sutterley (01/2023) +Written by Tyler Sutterley (07/2026) Calculate the amplitude and phase of a harmonic function from calculated sine and cosine of a series of measurements @@ -21,6 +21,7 @@ numpy: Scientific Computing Tools For Python (https://numpy.org) UPDATE HISTORY: + Updated 07/2026: use np.radians to convert from degrees to radians Updated 01/2023: refactored time series analysis functions Updated 04/2022: updated docstrings to numpy documentation format Updated 07/2020: added function docstrings @@ -49,5 +50,5 @@ def amplitude(bsin, bcos): phase from the harmonic functions in degrees """ ampl = np.sqrt(bsin**2.0 + bcos**2.0) - ph = 180.0*np.arctan2(bcos, bsin)/np.pi - return (ampl,ph) + ph = np.degrees(np.arctan2(bcos, bsin)) + return (ampl, ph) diff --git a/gravity_toolkit/time_series/piecewise.py b/gravity_toolkit/time_series/piecewise.py index e2349007..66042163 100755 --- a/gravity_toolkit/time_series/piecewise.py +++ b/gravity_toolkit/time_series/piecewise.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" piecewise.py -Written by Tyler Sutterley (04/2023) +Written by Tyler Sutterley (07/2026) Fits a synthetic signal to data over a time period by ordinary or weighted least-squares for breakpoint analysis @@ -61,6 +61,7 @@ scipy: Scientific Tools for Python (https://docs.scipy.org/doc/) UPDATE HISTORY: + Updated 07/2026: use np.hypot to calculate the sum of two squares Updated 04/2023: option to include extra fit terms in the design matrix Updated 01/2023: refactored time series analysis functions Updated 04/2022: updated docstrings to numpy documentation format @@ -293,7 +294,7 @@ def piecewise(t_in, d_in, BREAK_TIME=None, BREAKPOINT=None, # Recalculating beta2 error beta_err = np.copy(temp_err) - beta_err[2] = np.sqrt(temp_err[1]**2 + temp_err[2]**2) + beta_err[2] = np.hypot(temp_err[1], temp_err[2]) # Weighted sum of squares Error WSSE = np.dot(np.transpose(wi*(d_in[0:nmax] - np.dot(DMAT,beta_mat))), wi*(d_in[0:nmax] - np.dot(DMAT,beta_mat)))/np.float64(nu) @@ -314,7 +315,7 @@ def piecewise(t_in, d_in, BREAK_TIME=None, BREAKPOINT=None, temp_err[i] = np.sum((NORMEQ[i,:]*P_err)**2) # Recalculating beta2 error beta_err = np.copy(temp_err) - beta_err[2] = np.sqrt(temp_err[1]**2 + temp_err[2]**2) + beta_err[2] = np.hypot(temp_err[1], temp_err[2]) # Mean square error MSE = np.dot(np.transpose(d_in[0:nmax] - np.dot(DMAT,beta_mat)), (d_in[0:nmax] - np.dot(DMAT,beta_mat)))/np.float64(nu) @@ -359,10 +360,10 @@ def piecewise(t_in, d_in, BREAK_TIME=None, BREAKPOINT=None, # Recalculating standard error for beta2 st_err = np.copy(temp_std) - st_err[2] = np.sqrt(temp_std[1]**2 + temp_std[2]**2) + st_err[2] = np.hypot(temp_std[1], temp_std[2]) # Recalculating beta2 error beta_err = np.copy(temp_err) - beta_err[2] = np.sqrt(temp_err[1]**2 + temp_err[2]**2) + beta_err[2] = np.hypot(temp_err[1], temp_err[2]) return {'beta':beta_out, 'error':beta_err, 'std_err':st_err, 'R2':rsquare, 'R2Adj':rsq_adj, 'MSE':MSE, 'NRMSE':NRMSE, 'AIC':AIC, 'BIC':BIC, diff --git a/gravity_toolkit/time_series/smooth.py b/gravity_toolkit/time_series/smooth.py index 247e934c..d80050e6 100755 --- a/gravity_toolkit/time_series/smooth.py +++ b/gravity_toolkit/time_series/smooth.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" smooth.py -Written by Tyler Sutterley (01/2023) +Written by Tyler Sutterley (07/2026) Computes a moving average of a time-series using three possible routines: 1) centered moving average @@ -48,6 +48,7 @@ scipy: Scientific Tools for Python (https://docs.scipy.org/doc/) UPDATE HISTORY: + Updated 07/2026: use np.hypot to calculate the sum of two squares Updated 01/2023: refactored time series analysis functions Updated 04/2022: updated docstrings to numpy documentation format Updated 05/2021: define int/float precision to prevent deprecation warning @@ -224,13 +225,13 @@ def smooth(t_in, d_in, HFWTH=6, MOVING=False, DATA_ERR=0, WEIGHT=0, # annual component AS,AC = beta_mat[SEAS:SEAS+2] dannual[ran] += wi*np.dot(TMAT[:,SEAS:SEAS+2],[AS,AC]) - annamp[ran] += wi*np.sqrt(AS**2 + AC**2) - annphase[ran] += wi*np.arctan2(AC,AS)*180.0/np.pi + annamp[ran] += wi*np.hypot(AS, AC) + annphase[ran] += wi*np.degrees(np.arctan2(AC, AS)) # semi-annual component SS,SC = beta_mat[SEAS+2:SEAS+4] dsemian[ran] += wi*np.dot(TMAT[:,SEAS+2:SEAS+4],[SS,SC]) - semiamp[ran] += wi*np.sqrt(SS**2 + SC**2) - semiphase[ran] += wi*np.arctan2(SC,SS)*180.0/np.pi + semiamp[ran] += wi*np.hypot(SS, SC) + semiphase[ran] += wi*np.degrees(np.arctan2(SC, SS)) # add weights weight[ran] += wi # divide weighted smoothed time-series by weights @@ -326,13 +327,13 @@ def smooth(t_in, d_in, HFWTH=6, MOVING=False, DATA_ERR=0, WEIGHT=0, # annual component AS,AC = beta_mat[SEAS:SEAS+2] dannual[i] = np.dot(TMAT[HFWTH,SEAS:SEAS+2],[AS,AC]) - annphase[i] = np.arctan2(AC,AS)*180.0/np.pi - annamp[i] = np.sqrt(AS**2 + AC**2) + annphase[i] = np.degrees(np.arctan2(AC, AS)) + annamp[i] = np.hypot(AS, AC) # semi-annual component SS,SC = beta_mat[SEAS+2:SEAS+4] dsemian[i] = np.dot(TMAT[HFWTH,SEAS+2:SEAS+4],[SS,SC]) - semiamp[i] = np.sqrt(SS**2 + SC**2) - semiphase[i] = np.arctan2(SC,SS)*180.0/np.pi + semiamp[i] = np.hypot(SS, SC) + semiphase[i] = np.degrees(np.arctan2(SC, SS)) # noise component dnoise[i] = d_in[i+HFWTH] - dsmth[i] - dseason[i] # reduced time-series diff --git a/gravity_toolkit/tools.py b/gravity_toolkit/tools.py index 36c93f0e..16cc786a 100644 --- a/gravity_toolkit/tools.py +++ b/gravity_toolkit/tools.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" tools.py -Written by Tyler Sutterley (11/2024) +Written by Tyler Sutterley (07/2026) Jupyter notebook, user interface and plotting tools PYTHON DEPENDENCIES: @@ -27,6 +27,7 @@ utilities.py: download and management utilities for files UPDATE HISTORY: + Updated 07/2026: use np.radians and np.degrees for angle conversions Updated 11/2024: fix deprecated widget object copies Updated 04/2024: add widget for setting endpoint for accessing PODAAC data place colormap registration within try/except to check for existing @@ -1123,9 +1124,9 @@ def wrap_longitudes(lon): lon: np.ndarray longitude (degrees east) """ - phi = np.arctan2(np.sin(lon*np.pi/180.0), np.cos(lon*np.pi/180.0)) + phi = np.arctan2(np.sin(np.radians(lon)), np.cos(np.radians(lon))) # convert phi from radians to degrees - return phi*180.0/np.pi + return np.degrees(phi) # PURPOSE: parallels the matplotlib basemap shiftgrid function def shift_grid(lon0, data, lon, CYCLIC=360.0): diff --git a/mapping/plot_AIS_GrIS_maps.py b/mapping/plot_AIS_GrIS_maps.py index a082fb43..381638cf 100644 --- a/mapping/plot_AIS_GrIS_maps.py +++ b/mapping/plot_AIS_GrIS_maps.py @@ -578,9 +578,9 @@ def plot_grid(base_dir, FILENAMES, # plot line contour for global average if MEAN_CONTOUR and CONTOURS: # calculate areas of each grid cell - dphi,dth = (dlon*np.pi/180.0,dlat*np.pi/180.0) + dphi,dth = (np.radians(dlon), np.radians(dlat)) indy,indx = np.nonzero(np.logical_not(data.mask)) - area = (rad_e**2)*dth*dphi*np.cos(lat[indy,indx]*np.pi/180.0) + area = (rad_e**2)*dth*dphi*np.cos(np.radians(lat[indy,indx])) # calculate average ave=np.sum(area*data[indy,indx])/np.sum(area) # plot line contour of global average diff --git a/mapping/plot_AIS_grid_3maps.py b/mapping/plot_AIS_grid_3maps.py index 352ea0db..1e12ed3c 100644 --- a/mapping/plot_AIS_grid_3maps.py +++ b/mapping/plot_AIS_grid_3maps.py @@ -473,9 +473,9 @@ def plot_grid(base_dir, FILENAMES, # plot line contour for global average if MEAN_CONTOUR and CONTOURS: # calculate areas of each grid cell - dphi,dth = (dlon*np.pi/180.0,dlat*np.pi/180.0) + dphi,dth = (np.radians(dlon), np.radians(dlat)) indy,indx = np.nonzero(np.logical_not(data.mask)) - area = (rad_e**2)*dth*dphi*np.cos(lat[indy,indx]*np.pi/180.0) + area = (rad_e**2)*dth*dphi*np.cos(np.radians(lat[indy,indx])) # calculate average ave=np.sum(area*data[indy,indx])/np.sum(area) # plot line contour of global average diff --git a/mapping/plot_AIS_grid_4maps.py b/mapping/plot_AIS_grid_4maps.py index 550ef497..8aa43cf8 100644 --- a/mapping/plot_AIS_grid_4maps.py +++ b/mapping/plot_AIS_grid_4maps.py @@ -473,11 +473,11 @@ def plot_grid(base_dir, FILENAMES, # plot line contour for global average if MEAN_CONTOUR and CONTOURS: # calculate areas of each grid cell - dphi,dth = (dlon*np.pi/180.0,dlat*np.pi/180.0) + dphi,dth = (np.radians(dlon), np.radians(dlat)) indy,indx = np.nonzero(np.logical_not(data.mask)) - area = (rad_e**2)*dth*dphi*np.cos(lat[indy,indx]*np.pi/180.0) + area = (rad_e**2)*dth*dphi*np.cos(np.radians(lat[indy,indx])) # calculate average - ave=np.sum(area*data[indy,indx])/np.sum(area) + ave = np.sum(area*data[indy,indx])/np.sum(area) # plot line contour of global average ax1.contour(lon,lat,data,[ave],colors='blue',linestyles='solid', linewidths=1.5,transform=ccrs.PlateCarree()) diff --git a/mapping/plot_AIS_grid_maps.py b/mapping/plot_AIS_grid_maps.py index 0125d3b1..e73e5a90 100644 --- a/mapping/plot_AIS_grid_maps.py +++ b/mapping/plot_AIS_grid_maps.py @@ -461,9 +461,9 @@ def plot_grid(base_dir, FILENAME, # plot line contour for global average if MEAN_CONTOUR and CONTOURS: # calculate areas of each grid cell - dphi,dth = (dlon*np.pi/180.0,dlat*np.pi/180.0) + dphi,dth = (np.radians(dlon), np.radians(dlat)) indy,indx = np.nonzero(np.logical_not(data.mask)) - area = (rad_e**2)*dth*dphi*np.cos(lat[indy,indx]*np.pi/180.0) + area = (rad_e**2)*dth*dphi*np.cos(np.radians(lat[indy,indx])) # calculate average ave = np.sum(area*data[indy,indx])/np.sum(area) # plot line contour of global average diff --git a/mapping/plot_AIS_grid_movie.py b/mapping/plot_AIS_grid_movie.py index ce956b41..0b0fe5fa 100644 --- a/mapping/plot_AIS_grid_movie.py +++ b/mapping/plot_AIS_grid_movie.py @@ -583,9 +583,9 @@ def animate_grid(base_dir, FILENAME, # plot line contour for global average if MEAN_CONTOUR and CONTOURS: # calculate areas of each grid cell - dphi,dth = (dlon*np.pi/180.0,dlat*np.pi/180.0) + dphi,dth = (np.radians(dlon), np.radians(dlat)) indy,indx = np.nonzero(np.logical_not(data.mask)) - area = (rad_e**2)*dth*dphi*np.cos(lat[indy,indx]*np.pi/180.0) + area = (rad_e**2)*dth*dphi*np.cos(np.radians(lat[indy,indx])) # calculate average ave = np.sum(area*data[indy,indx])/np.sum(area) # plot line contour of global average diff --git a/mapping/plot_AIS_regional_maps.py b/mapping/plot_AIS_regional_maps.py index 4196dcd9..139f5974 100644 --- a/mapping/plot_AIS_regional_maps.py +++ b/mapping/plot_AIS_regional_maps.py @@ -544,9 +544,9 @@ def plot_grid(base_dir, FILENAME, # plot line contour for global average if MEAN_CONTOUR and CONTOURS: # calculate areas of each grid cell - dphi,dth = (dlon*np.pi/180.0,dlat*np.pi/180.0) + dphi,dth = (np.radians(dlon), np.radians(dlat)) indy,indx = np.nonzero(np.logical_not(data.mask)) - area = (rad_e**2)*dth*dphi*np.cos(lat[indy,indx]*np.pi/180.0) + area = (rad_e**2)*dth*dphi*np.cos(np.radians(lat[indy,indx])) # calculate average ave = np.sum(area*data[indy,indx])/np.sum(area) # plot line contour of global average diff --git a/mapping/plot_AIS_regional_movie.py b/mapping/plot_AIS_regional_movie.py index ac966ddd..6555577e 100644 --- a/mapping/plot_AIS_regional_movie.py +++ b/mapping/plot_AIS_regional_movie.py @@ -672,9 +672,9 @@ def animate_grid(base_dir, FILENAME, # plot line contour for global average if MEAN_CONTOUR and CONTOURS: # calculate areas of each grid cell - dphi,dth = (dlon*np.pi/180.0,dlat*np.pi/180.0) + dphi,dth = (np.radians(dlon), np.radians(dlat)) indy,indx = np.nonzero(np.logical_not(data.mask)) - area = (rad_e**2)*dth*dphi*np.cos(lat[indy,indx]*np.pi/180.0) + area = (rad_e**2)*dth*dphi*np.cos(np.radians(lat[indy,indx])) # calculate average ave = np.sum(area*data[indy,indx])/np.sum(area) # plot line contour of global average diff --git a/mapping/plot_GrIS_grid_3maps.py b/mapping/plot_GrIS_grid_3maps.py index 5e80b57f..9fb522ab 100644 --- a/mapping/plot_GrIS_grid_3maps.py +++ b/mapping/plot_GrIS_grid_3maps.py @@ -490,9 +490,9 @@ def plot_grid(base_dir, FILENAMES, # plot line contour for global average if MEAN_CONTOUR and CONTOURS: # calculate areas of each grid cell - dphi,dth = (dlon*np.pi/180.0,dlat*np.pi/180.0) + dphi,dth = (np.radians(dlon), np.radians(dlat)) indy,indx = np.nonzero(np.logical_not(data.mask)) - area = (rad_e**2)*dth*dphi*np.cos(lat[indy,indx]*np.pi/180.0) + area = (rad_e**2)*dth*dphi*np.cos(np.radians(lat[indy,indx])) # calculate average ave=np.sum(area*data[indy,indx])/np.sum(area) # plot line contour of global average diff --git a/mapping/plot_GrIS_grid_5maps.py b/mapping/plot_GrIS_grid_5maps.py index 9d804ab4..382569f9 100644 --- a/mapping/plot_GrIS_grid_5maps.py +++ b/mapping/plot_GrIS_grid_5maps.py @@ -474,9 +474,9 @@ def plot_grid(base_dir, FILENAMES, # plot line contour for global average if MEAN_CONTOUR and CONTOURS: # calculate areas of each grid cell - dphi,dth = (dlon*np.pi/180.0,dlat*np.pi/180.0) + dphi,dth = (np.radians(dlon), np.radians(dlat)) indy,indx = np.nonzero(np.logical_not(data.mask)) - area = (rad_e**2)*dth*dphi*np.cos(lat[indy,indx]*np.pi/180.0) + area = (rad_e**2)*dth*dphi*np.cos(np.radians(lat[indy,indx])) # calculate average ave=np.sum(area*data[indy,indx])/np.sum(area) # plot line contour of global average diff --git a/mapping/plot_GrIS_grid_maps.py b/mapping/plot_GrIS_grid_maps.py index d42373bc..ca3de2ac 100644 --- a/mapping/plot_GrIS_grid_maps.py +++ b/mapping/plot_GrIS_grid_maps.py @@ -474,9 +474,9 @@ def plot_grid(base_dir, FILENAME, # plot line contour for global average if MEAN_CONTOUR and CONTOURS: # calculate areas of each grid cell - dphi,dth = (dlon*np.pi/180.0,dlat*np.pi/180.0) + dphi,dth = (np.radians(dlon), np.radians(dlat)) indy,indx = np.nonzero(np.logical_not(data.mask)) - area = (rad_e**2)*dth*dphi*np.cos(lat[indy,indx]*np.pi/180.0) + area = (rad_e**2)*dth*dphi*np.cos(np.radians(lat[indy,indx])) # calculate average ave = np.sum(area*data[indy,indx])/np.sum(area) # plot line contour of global average diff --git a/mapping/plot_GrIS_grid_movie.py b/mapping/plot_GrIS_grid_movie.py index b2ebc63e..63fa29e8 100644 --- a/mapping/plot_GrIS_grid_movie.py +++ b/mapping/plot_GrIS_grid_movie.py @@ -605,9 +605,9 @@ def animate_grid(base_dir, FILENAME, # plot line contour for global average if MEAN_CONTOUR and CONTOURS: # calculate areas of each grid cell - dphi,dth = (dlon*np.pi/180.0,dlat*np.pi/180.0) + dphi,dth = (np.radians(dlon), np.radians(dlat)) indy,indx = np.nonzero(np.logical_not(data.mask)) - area = (rad_e**2)*dth*dphi*np.cos(lat[indy,indx]*np.pi/180.0) + area = (rad_e**2)*dth*dphi*np.cos(np.radians(lat[indy,indx])) # calculate average ave = np.sum(area*data[indy,indx])/np.sum(area) # plot line contour of global average diff --git a/mapping/plot_QML_grid_3maps.py b/mapping/plot_QML_grid_3maps.py index ae50db05..71eca64c 100644 --- a/mapping/plot_QML_grid_3maps.py +++ b/mapping/plot_QML_grid_3maps.py @@ -479,9 +479,9 @@ def plot_grid(base_dir, FILENAMES, # plot line contour for global average if MEAN_CONTOUR and CONTOURS: # calculate areas of each grid cell - dphi,dth = (dlon*np.pi/180.0,dlat*np.pi/180.0) + dphi,dth = (np.radians(dlon), np.radians(dlat)) indy,indx = np.nonzero(np.logical_not(data.mask)) - area = (rad_e**2)*dth*dphi*np.cos(lat[indy,indx]*np.pi/180.0) + area = (rad_e**2)*dth*dphi*np.cos(np.radians(lat[indy,indx])) # calculate average ave=np.sum(area*data[indy,indx])/np.sum(area) # plot line contour of global average diff --git a/mapping/plot_global_grid_3maps.py b/mapping/plot_global_grid_3maps.py index d8736213..5594e476 100644 --- a/mapping/plot_global_grid_3maps.py +++ b/mapping/plot_global_grid_3maps.py @@ -346,9 +346,9 @@ def plot_grid(base_dir, FILENAMES, # plot line contour for global average if MEAN_CONTOUR and CONTOURS: # calculate areas of each grid cell - dphi,dth = (dlon*np.pi/180.0,dlat*np.pi/180.0) + dphi,dth = (np.radians(dlon), np.radians(dlat)) indy,indx = np.nonzero(np.logical_not(dinput.mask)) - area = (rad_e**2)*dth*dphi*np.cos(lat[indy,indx]*np.pi/180.0) + area = (rad_e**2)*dth*dphi*np.cos(np.radians(lat[indy,indx])) # calculate average ave = np.sum(area*dinput.data[indy,indx])/np.sum(area) # plot line contour of global average diff --git a/mapping/plot_global_grid_4maps.py b/mapping/plot_global_grid_4maps.py index 96fcf4c4..517db035 100644 --- a/mapping/plot_global_grid_4maps.py +++ b/mapping/plot_global_grid_4maps.py @@ -346,9 +346,9 @@ def plot_grid(base_dir, FILENAMES, # plot line contour for global average if MEAN_CONTOUR and CONTOURS: # calculate areas of each grid cell - dphi,dth = (dlon*np.pi/180.0,dlat*np.pi/180.0) + dphi,dth = (np.radians(dlon), np.radians(dlat)) indy,indx = np.nonzero(np.logical_not(dinput.mask)) - area = (rad_e**2)*dth*dphi*np.cos(lat[indy,indx]*np.pi/180.0) + area = (rad_e**2)*dth*dphi*np.cos(np.radians(lat[indy,indx])) # calculate average ave = np.sum(area*dinput.data[indy,indx])/np.sum(area) # plot line contour of global average diff --git a/mapping/plot_global_grid_5maps.py b/mapping/plot_global_grid_5maps.py index 5deef916..d63e1bb0 100644 --- a/mapping/plot_global_grid_5maps.py +++ b/mapping/plot_global_grid_5maps.py @@ -352,9 +352,9 @@ def plot_grid(base_dir, FILENAMES, # plot line contour for global average if MEAN_CONTOUR and CONTOURS: # calculate areas of each grid cell - dphi,dth = (dlon*np.pi/180.0,dlat*np.pi/180.0) + dphi,dth = (np.radians(dlon), np.radians(dlat)) indy,indx = np.nonzero(np.logical_not(dinput.mask)) - area = (rad_e**2)*dth*dphi*np.cos(lat[indy,indx]*np.pi/180.0) + area = (rad_e**2)*dth*dphi*np.cos(np.radians(lat[indy,indx])) # calculate average ave = np.sum(area*dinput.data[indy,indx])/np.sum(area) # plot line contour of global average diff --git a/mapping/plot_global_grid_9maps.py b/mapping/plot_global_grid_9maps.py index 4406f4ce..2b1dba5e 100644 --- a/mapping/plot_global_grid_9maps.py +++ b/mapping/plot_global_grid_9maps.py @@ -347,9 +347,9 @@ def plot_grid(base_dir, FILENAMES, # plot line contour for global average if MEAN_CONTOUR and CONTOURS: # calculate areas of each grid cell - dphi,dth = (dlon*np.pi/180.0,dlat*np.pi/180.0) + dphi,dth = (np.radians(dlon), np.radians(dlat)) indy,indx = np.nonzero(np.logical_not(dinput.mask)) - area = (rad_e**2)*dth*dphi*np.cos(lat[indy,indx]*np.pi/180.0) + area = (rad_e**2)*dth*dphi*np.cos(np.radians(lat[indy,indx])) # calculate average ave = np.sum(area*dinput.data[indy,indx])/np.sum(area) # plot line contour of global average diff --git a/mapping/plot_global_grid_maps.py b/mapping/plot_global_grid_maps.py index 761abd59..da57df62 100644 --- a/mapping/plot_global_grid_maps.py +++ b/mapping/plot_global_grid_maps.py @@ -320,9 +320,9 @@ def plot_grid(base_dir, FILENAME, # plot line contour for global average if MEAN_CONTOUR and CONTOURS: # calculate areas of each grid cell - dphi,dth = (dlon*np.pi/180.0,dlat*np.pi/180.0) + dphi,dth = (np.radians(dlon), np.radians(dlat)) indy,indx = np.nonzero(np.logical_not(dinput.mask)) - area = (rad_e**2)*dth*dphi*np.cos(lat[indy,indx]*np.pi/180.0) + area = (rad_e**2)*dth*dphi*np.cos(np.radians(lat[indy,indx])) # calculate average ave = np.sum(area*dinput.data[indy,indx])/np.sum(area) # plot line contour of global average diff --git a/mapping/plot_global_grid_movie.py b/mapping/plot_global_grid_movie.py index eff08e2e..40b98eb3 100644 --- a/mapping/plot_global_grid_movie.py +++ b/mapping/plot_global_grid_movie.py @@ -420,9 +420,9 @@ def animate_grid(base_dir, FILENAME, # plot line contour for global average if MEAN_CONTOUR and CONTOURS: # calculate areas of each grid cell - dphi,dth = (dlon*np.pi/180.0,dlat*np.pi/180.0) + dphi,dth = (np.radians(dlon), np.radians(dlat)) indy,indx = np.nonzero(np.logical_not(subset.mask)) - area = (rad_e**2)*dth*dphi*np.cos(lat[indy,indx]*np.pi/180.0) + area = (rad_e**2)*dth*dphi*np.cos(np.radians(lat[indy,indx])) # calculate average ave = np.sum(area*subset.data[indy,indx])/np.sum(area) # plot line contour of global average diff --git a/scripts/calc_sensitivity_kernel.py b/scripts/calc_sensitivity_kernel.py index f3563e2f..840d7e56 100644 --- a/scripts/calc_sensitivity_kernel.py +++ b/scripts/calc_sensitivity_kernel.py @@ -441,7 +441,7 @@ def calc_sensitivity_kernel(LMAX, RAD, n_lat = len(grid.lat) # Computing plms for converting to spatial domain - theta = (90.0-grid.lat)*np.pi/180.0 + theta = np.radians(90.0 - grid.lat) PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(theta)) # for each mascon diff --git a/scripts/combine_HEX_spherical_caps.py b/scripts/combine_HEX_spherical_caps.py index c1d8dc2c..f2874129 100644 --- a/scripts/combine_HEX_spherical_caps.py +++ b/scripts/combine_HEX_spherical_caps.py @@ -305,7 +305,7 @@ def combine_HEX_spherical_caps(PROC, DREL, DSET, LMAX, RAD, # output GRACE regional time-series total_mass = grace_reg[i] total_thick = 1e15*total_mass/area_reg[i] - total_error = np.sqrt(grace_error[i]**2 + statistical_reg[i]**2) + total_error = np.hypot(grace_error[i], statistical_reg[i]) thick_error = 1e15*total_error/area_reg[i] # total area in kilometers^2 area_km = area_reg[i]/1e10 diff --git a/scripts/combine_harmonics.py b/scripts/combine_harmonics.py index 17df8b35..714cff2f 100644 --- a/scripts/combine_harmonics.py +++ b/scripts/combine_harmonics.py @@ -273,7 +273,7 @@ def combine_harmonics(INPUT_FILE, OUTPUT_FILE, attributes['ROOT']['earth_gravity_constant'] = f'{factors.GM:0.3f} cm^3/s^2' # Computing plms for converting to spatial domain - theta = (90.0 - grid.lat)*np.pi/180.0 + theta = np.radians(90.0 - grid.lat) PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(theta)) # converting harmonics to truncated, smoothed coefficients in output units diff --git a/scripts/combine_sea_level_data.py b/scripts/combine_sea_level_data.py index f1000855..0465c8e1 100755 --- a/scripts/combine_sea_level_data.py +++ b/scripts/combine_sea_level_data.py @@ -109,7 +109,7 @@ def combine_sea_level_data(index_file, dlon,dlat = landsea.spacing nlat, nlon = landsea.shape # longitude and colatitude in radians - th = (90.0 - np.squeeze(landsea.lat))*np.pi/180.0 + th = np.radians(90.0 - np.squeeze(landsea.lat)) # Calculating Legendre Polynomials using Holmes and Featherstone relation PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(th)) diff --git a/scripts/convert_harmonics.py b/scripts/convert_harmonics.py index 482ade8c..c6d7eeee 100644 --- a/scripts/convert_harmonics.py +++ b/scripts/convert_harmonics.py @@ -187,7 +187,7 @@ def convert_harmonics(INPUT_FILE, OUTPUT_FILE, attributes['earth_gravity_constant'] = f'{factors.GM:0.3f} cm^3/s^2' # calculate associated Legendre polynomials - th = (90.0 - input_spatial.lat)*np.pi/180.0 + th = np.radians(90.0 - input_spatial.lat) PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(th)) # create list of harmonics objects diff --git a/scripts/grace_spatial_error.py b/scripts/grace_spatial_error.py index f20672b5..149e9270 100755 --- a/scripts/grace_spatial_error.py +++ b/scripts/grace_spatial_error.py @@ -419,11 +419,11 @@ def grace_spatial_error(base_dir, PROC, DREL, DSET, LMAX, RAD, delta.attributes['ROOT'] = attributes # 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 + phi = np.radians(delta.lon[np.newaxis,:]) + theta = np.radians(90.0 - delta.lat) PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(theta)) # square of legendre polynomials truncated to order MMAX - mm = np.arange(0,MMAX+1) + mm = np.arange(0, MMAX+1) PLM2 = PLM[:,mm,:]**2 # Calculating cos(m*phi)^2 and sin(m*phi)^2 diff --git a/scripts/grace_spatial_maps.py b/scripts/grace_spatial_maps.py index ea65903a..d4544897 100755 --- a/scripts/grace_spatial_maps.py +++ b/scripts/grace_spatial_maps.py @@ -426,7 +426,7 @@ def grace_spatial_maps(base_dir, PROC, DREL, DSET, LMAX, RAD, nlat = len(grid.lat) # Computing plms for converting to spatial domain - theta = (90.0-grid.lat)*np.pi/180.0 + theta = np.radians(90.0 - grid.lat) PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(theta)) # output spatial units diff --git a/scripts/piecewise_grace_maps.py b/scripts/piecewise_grace_maps.py index f936dc1f..39df4b75 100755 --- a/scripts/piecewise_grace_maps.py +++ b/scripts/piecewise_grace_maps.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" piecewise_grace_maps.py -Written by Tyler Sutterley (06/2023) +Written by Tyler Sutterley (07/2026) Reads in GRACE/GRACE-FO spatial files and fits a piecewise regression model at each grid point for breakpoint analysis @@ -60,6 +60,7 @@ utilities.py: download and management utilities for files UPDATE HISTORY: + Updated 07/2026: use np.hypot to calculate the sum of two squares Updated 06/2023: append amplitude and phase titles when creating flags more tidal aliasing periods using values from Ray and Luthcke (2006) Updated 05/2023: split S2 tidal aliasing terms into GRACE and GRACE-FO eras @@ -216,7 +217,7 @@ def piecewise_grace_maps(LMAX, RAD, # find index of breakpoint within GRACE/GRACE-FO months if BREAKPOINT not in grid.month: raise ValueError(f'{BREAKPOINT} not found in GRACE/GRACE-FO months') - breakpoint_index, = np.nonzero(grid.month == BREAKPOINT) + breakpoint_index, = np.flatnonzero(grid.month == BREAKPOINT) # Setting output parameters coef_str = ['x0', 'px1', 'px1'] @@ -366,16 +367,15 @@ def piecewise_grace_maps(LMAX, RAD, out.data[:,:,j], out.data[:,:,j+1] ) # convert phase from -180:180 to 0:360 - ii,jj = np.nonzero(ph.data < 0) - ph.data[ii,jj] += 360.0 + ph.data = np.where(ph.data < 0, ph.data + 360.0, ph.data) # Amplitude Error comp1 = out.error[:,:,j]*out.data[:,:,j]/amp.data comp2 = out.error[:,:,j+1]*out.data[:,:,j+1]/amp.data - amp.error = np.sqrt(comp1**2 + comp2**2) + amp.error = np.hypot(comp1, comp2) # Phase Error (degrees) comp1 = out.error[:,:,j]*out.data[:,:,j+1]/(amp.data**2) comp2 = out.error[:,:,j+1]*out.data[:,:,j]/(amp.data**2) - ph.error = (180.0/np.pi)*np.sqrt(comp1**2 + comp2**2) + ph.error = np.degrees(np.hypot(comp1, comp2)) # output file names for amplitude, phase and errors f2 = (FILE_PREFIX, units, LMAX, order_str, diff --git a/scripts/plot_SLR_azimuthal.py b/scripts/plot_SLR_azimuthal.py index 21d55ac3..55746faf 100644 --- a/scripts/plot_SLR_azimuthal.py +++ b/scripts/plot_SLR_azimuthal.py @@ -103,7 +103,7 @@ def plot_SLR_azimuthal(base_dir, PROC, DREL, START_MON, END_MON, MISSING): 'CSR SLR','GSFC SLR','GFZ GravIS'] plot_zorder = [0,3,1,2] fig_text = {'C21':'a)','S21':'b)'} - plot_ylabel = {'C21':'C$\mathregular{_{21}}$','S21':'S$\mathregular{_{21}}$'} + plot_ylabel = {'C21':r'C$\mathregular{_{21}}$','S21':r'S$\mathregular{_{21}}$'} CSR = dict(time=CSR_CS2['time'],month=CSR_CS2['month']) GSFC = dict(time=GSFC_CS2['time'],month=GSFC_CS2['month']) GFZ = dict(time=GFZ_CS2['time'],month=GFZ_CS2['month']) diff --git a/scripts/regional_spherical_caps.py b/scripts/regional_spherical_caps.py index bf03edde..7a332e8a 100644 --- a/scripts/regional_spherical_caps.py +++ b/scripts/regional_spherical_caps.py @@ -169,7 +169,7 @@ def regional_spherical_caps(LMAX, rad_e = dfactor.rad_e# Average Radius of the Earth [cm] # colatitude of the spherical cap centers - th = (90.0 - lat)*np.pi/180.0 + th = np.radians(90.0 - lat) # Legendre polynomials of the spherical caps PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(th)) @@ -195,7 +195,7 @@ def regional_spherical_caps(LMAX, # area = (volume*density)/(mass/area) ar = 4.0*np.pi*(rad_e**3.0)*rho_e*np.squeeze(Ylms.clm[0,0])/3.0 # calculate equivalent radius (should equal RAD_CAP) - rad = np.sqrt(ar/np.pi)/rad_e*180.0/np.pi + rad = np.degrees(np.sqrt(ar/np.pi) / rad_e) # if verbose output: sanity check of radii args = (cap_number, RAD_CAP[i], rad) logging.info('{0:4d} {1:10.4f} {2:10.4f}'.format(*args)) diff --git a/scripts/regress_grace_maps.py b/scripts/regress_grace_maps.py index 62be313b..1e65a043 100755 --- a/scripts/regress_grace_maps.py +++ b/scripts/regress_grace_maps.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" regress_grace_maps.py -Written by Tyler Sutterley (06/2023) +Written by Tyler Sutterley (07/2026) Reads in GRACE/GRACE-FO spatial files and fits a regression model at each grid point @@ -60,6 +60,7 @@ utilities.py: download and management utilities for files UPDATE HISTORY: + Updated 07/2026: use np.hypot to calculate the sum of two squares Updated 06/2023: append amplitude and phase titles when creating flags more tidal aliasing periods using values from Ray and Luthcke (2006) Updated 05/2023: split S2 tidal aliasing terms into GRACE and GRACE-FO eras @@ -358,16 +359,15 @@ def regress_grace_maps(LMAX, RAD, out.data[:,:,j], out.data[:,:,j+1] ) # convert phase from -180:180 to 0:360 - ii,jj = np.nonzero(ph.data < 0) - ph.data[ii,jj] += 360.0 + ph.data = np.where(ph.data < 0, ph.data + 360.0, ph.data) # Amplitude Error comp1 = out.error[:,:,j]*out.data[:,:,j]/amp.data comp2 = out.error[:,:,j+1]*out.data[:,:,j+1]/amp.data - amp.error = np.sqrt(comp1**2 + comp2**2) + amp.error = np.hypot(comp1, comp2) # Phase Error (degrees) comp1 = out.error[:,:,j]*out.data[:,:,j+1]/(amp.data**2) comp2 = out.error[:,:,j+1]*out.data[:,:,j]/(amp.data**2) - ph.error = (180.0/np.pi)*np.sqrt(comp1**2 + comp2**2) + ph.error = np.degrees(np.hypot(comp1, comp2)) # output file names for amplitude, phase and errors f2 = (FILE_PREFIX, units, LMAX, order_str, diff --git a/scripts/run_sea_level_equation.py b/scripts/run_sea_level_equation.py index e4a0e27d..fdeb7f46 100644 --- a/scripts/run_sea_level_equation.py +++ b/scripts/run_sea_level_equation.py @@ -178,7 +178,7 @@ def run_sea_level_equation(INPUT_FILE, OUTPUT_FILE, nth,nphi = landsea.shape land_function = np.zeros((nth, nphi), dtype=np.float64) # calculate colatitude in radians - th = (90.0 - landsea.lat)*np.pi/180.0 + th = np.radians(90.0 - landsea.lat) # extract land function from file # combine land and island levels for land function indx,indy = np.nonzero((landsea.data >= 1) & (landsea.data <= 3)) diff --git a/scripts/scale_grace_maps.py b/scripts/scale_grace_maps.py index 3f29117e..62552bc9 100644 --- a/scripts/scale_grace_maps.py +++ b/scripts/scale_grace_maps.py @@ -478,11 +478,11 @@ def scale_grace_maps(base_dir, PROC, DREL, DSET, LMAX, RAD, 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 + phi = np.radians(grid.lon[np.newaxis,:]) + theta = np.radians(90.0 - grid.lat) PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(theta)) # square of legendre polynomials truncated to order MMAX - mm = np.arange(0,MMAX+1) + mm = np.arange(0, MMAX+1) PLM2 = PLM[:,mm,:]**2 # dfactor is the degree dependent coefficients diff --git a/scripts/sea_level_error.py b/scripts/sea_level_error.py index 40d83bde..8681073b 100755 --- a/scripts/sea_level_error.py +++ b/scripts/sea_level_error.py @@ -140,7 +140,7 @@ def sea_level_error(PROC, DREL, DSET, LMAX, dlon,dlat = landsea.spacing nlat, nlon = landsea.shape # calculate Fully-Normalized Legendre Polynomials - th = (90.0 - landsea.lat)*np.pi/180.0 + th = np.radians(90.0 - landsea.lat) PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(th)) # create index file for least_squares_mascons.py diff --git a/scripts/sea_level_regress.py b/scripts/sea_level_regress.py index 33136b55..10d3e523 100644 --- a/scripts/sea_level_regress.py +++ b/scripts/sea_level_regress.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" sea_level_regress.py -Written by Tyler Sutterley (06/2023) +Written by Tyler Sutterley (07/2026) Reads in sea level grid files and calculates the trends at each grid point following an input regression model @@ -57,6 +57,7 @@ spatial.py: spatial data class for reading, writing and processing data UPDATE HISTORY: + Updated 07/2026: use np.hypot to calculate the sum of two squares Updated 06/2023: append amplitude and phase titles when creating flags more tidal aliasing periods using values from Ray and Luthcke (2006) Updated 05/2023: split S2 tidal aliasing terms into GRACE and GRACE-FO eras @@ -345,16 +346,17 @@ def sea_level_regress(PROC, DREL, DSET, LMAX, out.data[indy,indx,j], out.data[indy,indx,j+1] ) # convert phase from -180:180 to 0:360 - ii,jj = np.nonzero((ph.data < 0) & np.logical_not(ph.mask)) - ph.data[ii,jj] += 360.0 + ph.data = np.where( + ph.data < 0 & np.logical_not(ph.mask), ph.data + 360.0, ph.data + ) # Amplitude Error comp1=out.error[indy,indx,j]*out.data[indy,indx,j]/amp.data[indy,indx] comp2=out.error[indy,indx,j+1]*out.data[indy,indx,j+1]/amp.data[indy,indx] - amp.error[indy,indx] = np.sqrt(comp1**2 + comp2**2) + amp.error[indy,indx] = np.hypot(comp1, comp2) # Phase Error (degrees) comp1=out.error[indy,indx,j]*out.data[indy,indx,j+1]/(amp.data[indy,indx]**2) comp2=out.error[indy,indx,j+1]*out.data[indy,indx,j]/(amp.data[indy,indx]**2) - ph.error[indy,indx] = (180.0/np.pi)*np.sqrt(comp1**2 + comp2**2) + ph.error[indy,indx] = np.degrees(np.hypot(comp1, comp2)) # output file names for amplitude and phase f2 = (ITERATION, dset_str, gia_str, ocean_str, EXPANSION, diff --git a/scripts/sea_level_stokes.py b/scripts/sea_level_stokes.py index 235e6be7..67545289 100755 --- a/scripts/sea_level_stokes.py +++ b/scripts/sea_level_stokes.py @@ -172,7 +172,7 @@ def sea_level_stokes(PROC, DREL, DSET, LMAX, dlon,dlat = landsea.spacing nlat, nlon = landsea.shape # calculate Fully-Normalized Legendre Polynomials - th = (90.0 - landsea.lat)*np.pi/180.0 + th = np.radians(90.0 - landsea.lat) PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(th)) # create index file for calc_mascon.py diff --git a/test/test_download_and_read.py b/test/test_download_and_read.py index eb3a311e..cd83b698 100644 --- a/test/test_download_and_read.py +++ b/test/test_download_and_read.py @@ -3,6 +3,7 @@ test_download_and_read.py (11/2021) Tests the read program to verify that coefficients are being extracted """ +import pytest import pathlib import posixpath import gravity_toolkit as gravtk @@ -42,6 +43,7 @@ def test_gfz_http_download_and_read(): assert (Ylms['clm'][2,0] == -0.484169355584e-03) # PURPOSE: Download a GRACE file from GFZ and check that read program runs +@pytest.mark.skip(reason="Deprecated GFZ FTP server") def test_gfz_ftp_download_and_read(): HOST=['isdcftp.gfz-potsdam.de','grace','Level-2','CSR','RL06', 'GSM-2_2002095-2002120_GRAC_UTCSR_BA01_0600.gz'] @@ -90,7 +92,7 @@ def test_esa_swarm_download_and_read(): # PURPOSE: Download a GRACE ITSG GRAZ file and check that read program runs def test_itsg_graz_download_and_read(): - HOST=['http://ftp.tugraz.at','outgoing','ITSG','GRACE', + HOST=['http://ftp.tugraz.at','pub','ITSG','GRACE', 'ITSG-Grace_operational','monthly','monthly_n60', 'ITSG-Grace_operational_n60_2018-06.gfc'] # download and read as virtual file object diff --git a/test/test_harmonics.py b/test/test_harmonics.py index 62c931b1..34de7e0d 100755 --- a/test/test_harmonics.py +++ b/test/test_harmonics.py @@ -39,7 +39,7 @@ def test_harmonics(): RAD = 250.0 # calculate colatitudes of input distribution - theta = (90.0 - input_distribution.lat)*np.pi/180.0 + theta = np.radians(90.0 - input_distribution.lat) # use fortran thresholds for colatitude bounds theta[theta > np.arccos(-0.9999999)] = np.arccos(-0.9999999) theta[theta < np.arccos(0.9999999)] = np.arccos(0.9999999) @@ -68,13 +68,14 @@ def test_harmonics(): dfactor = gravtk.units(lmax=LMAX).harmonic(*LOVE) wt = 2.0*np.pi*gravtk.gauss_weights(RAD,LMAX) smooth_Ylms.convolve(dfactor.cmwe*wt) + transform_Ylms = smooth_Ylms.copy() # convert harmonics back to spatial domain at same grid spacing test_distribution = gravtk.harmonic_summation(smooth_Ylms.clm, smooth_Ylms.slm, input_distribution.lon, input_distribution.lat, LMAX=LMAX, PLM=PLM).T # convert harmonics using fast-fourier transform method - test_transform = gravtk.harmonic_transform(smooth_Ylms.clm, - smooth_Ylms.slm, input_distribution.lon, input_distribution.lat, + test_transform = gravtk.harmonic_transform(transform_Ylms.clm, + transform_Ylms.slm, input_distribution.lon, input_distribution.lat, LMAX=LMAX, PLM=PLM).T # convert harmonics back to spatial domain using wrapper function test_combine = gravtk.stokes_summation(test_Ylms.clm, diff --git a/test/test_masks.py b/test/test_masks.py index 70c1f923..d04cb873 100644 --- a/test/test_masks.py +++ b/test/test_masks.py @@ -25,10 +25,10 @@ def test_lsmask(LANDMASK): nlat, nlon = landsea.shape # colatitude in radians gridlon, gridlat = np.meshgrid(landsea.lon, landsea.lat) - th = (90.0 - gridlat)*np.pi/180.0 + th = np.radians(90.0 - gridlat) # grid spacing in radians - dphi = np.pi*np.abs(dlon)/180.0 - dth = np.pi*np.abs(dlat)/180.0 + dphi = np.radians(np.abs(dlon)) + dth = np.radians(np.abs(dlat)) # create land function land_function = np.zeros((nlat, nlon), dtype=np.float64) # combine land and island levels for land function diff --git a/test/test_sea_level.py b/test/test_sea_level.py new file mode 100644 index 00000000..6af7387d --- /dev/null +++ b/test/test_sea_level.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +u""" +test_sea_level.py (07/2026) +""" +import pytest +import inspect +import pathlib +import numpy as np +import gravity_toolkit as gravtk + +# path to test files +filename = inspect.getframeinfo(inspect.currentframe()).filename +filepath = pathlib.Path(filename).absolute().parent + +# PURPOSE: test sea level equation programs +def test_sea_level(): + # path to load Love numbers file + love_numbers_file = gravtk.utilities.get_data_path( + ['data','love_numbers']) + # read load Love numbers + LOVE = gravtk.read_love_numbers(love_numbers_file, FORMAT='class') + + # read land function file + LANDMASK = filepath.joinpath('land.fcn.1_deg.gz') + landsea = gravtk.spatial().from_ascii(LANDMASK, + date=False, spacing=[1.0, 1.0], nlat=180, nlon=360, + extent=[0.5,359.5,-89.5,89.5], compression='gzip') + + # spherical harmonic parameters + # maximum spherical harmonic degree + LMAX = 60 + # read harmonics from file + harmonics_file = filepath.joinpath('out.geoid.green_ice.0.5.2008.60.gz') + Ylms = gravtk.harmonics(lmax=LMAX, mmax=LMAX).from_ascii( + harmonics_file, date=False, compression='gzip') + # calculate the legendre functions using Martin Mohlenkamp's relation + th = np.radians(90.0 - landsea.lat) + PLM, dPLM = gravtk.plm_mohlenkamp(LMAX, np.cos(th)) + + # run pseudo-spectral sea level equation solver + sea_level = gravtk.sea_level_equation(Ylms.clm, Ylms.slm, + landsea.lon, landsea.lat, landsea.data.T, LMAX=LMAX, + LOVE=LOVE, BODY_TIDE_LOVE=0, + FLUID_LOVE=0, DENSITY=1.0, POLAR=True, + PLM=PLM, ITERATIONS=2, FILL_VALUE=np.nan).T + + # check that sea level data is equal to file precision + valid_file = filepath.joinpath('out.slf.green_ice.1_deg.2008.60.gz') + validation = gravtk.spatial().from_ascii(valid_file, + date=False, spacing=[1.0, 1.0], nlat=180, nlon=360, + extent=[0.5,359.5,-89.5,89.5], compression='gzip') + # check differences + difference = validation.data - sea_level + valid_difference = difference[np.isfinite(difference)] + assert np.all(np.abs(valid_difference) < 1e-8) diff --git a/utilities/quick_mascon_regress.py b/utilities/quick_mascon_regress.py index 2fcad343..77524d39 100755 --- a/utilities/quick_mascon_regress.py +++ b/utilities/quick_mascon_regress.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" quick_mascon_regress.py -Written by Tyler Sutterley (06/2023) +Written by Tyler Sutterley (07/2026) Creates a regression summary file for a mascon time series file COMMAND LINE OPTIONS: @@ -22,6 +22,7 @@ time_series.amplitude.py: calculates the amplitude and phase of a harmonic UPDATE HISTORY: + Updated 07/2026: use np.hypot to calculate the sum of two squares Updated 06/2023: can choose different tidal aliasing periods Updated 05/2023: split S2 tidal aliasing terms into GRACE and GRACE-FO eras allow fit to be piecewise using a known breakpoint GRACE/GRACE-FO month @@ -185,11 +186,11 @@ def run_regress(input_file, # Amplitude Errors comp1 = fit[err][j]*fit['beta'][j]/amp['beta'][i] comp2 = fit[err][j+1]*fit['beta'][j+1]/amp['beta'][i] - amp[err][i] = np.sqrt(comp1**2 + comp2**2) + amp[err][i] = np.hypot(comp1, comp2) # Phase Error (degrees) comp1 = fit[err][j]*fit['beta'][j+1]/(amp['beta'][i]**2) comp2 = fit[err][j+1]*fit['beta'][j]/(amp['beta'][i]**2) - ph[err][i] = (180.0/np.pi)*np.sqrt(comp1**2 + comp2**2) + ph[err][i] = np.degrees(np.hypot(comp1, comp2)) # Amplitude, Error and Statistical Significance args = ('Ampl.','Estimate','Std. Error','95% Conf.','Units') From 152e1512a508f5fde5aa95bf44866ea4986f604b Mon Sep 17 00:00:00 2001 From: Tyler Sutterley Date: Tue, 7 Jul 2026 17:04:23 -0700 Subject: [PATCH 2/5] fix: forgot to add slf test files --- test/land.fcn.1_deg.gz | Bin 0 -> 153649 bytes test/out.slf.green_ice.1_deg.2008.60.gz | Bin 0 -> 332838 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100755 test/land.fcn.1_deg.gz create mode 100644 test/out.slf.green_ice.1_deg.2008.60.gz diff --git a/test/land.fcn.1_deg.gz b/test/land.fcn.1_deg.gz new file mode 100755 index 0000000000000000000000000000000000000000..fc977c42de618c3df478a7790d371450daa253d2 GIT binary patch literal 153649 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Ξ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 WlL!3Kw)7QGe9`O9Yqt%!;{OMOGd(5% literal 0 HcmV?d00001 diff --git a/test/out.slf.green_ice.1_deg.2008.60.gz b/test/out.slf.green_ice.1_deg.2008.60.gz new file mode 100644 index 0000000000000000000000000000000000000000..0f13a9333e42d2832f3f6c50ba4931713521b3ec GIT binary patch literal 332838 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%0DW^m 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

p1#=D?fQfUg_sH5 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%0DQJf4_C1SNe=Ptg1^j8J=xtK9D(1X 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

E%4mIynjpDj1vJ!q#^QfCg4 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 NMv-0S9gA!K{SR9*(q8}o literal 0 HcmV?d00001 From d3790388dd22209f61617b129388c3c24a39defe Mon Sep 17 00:00:00 2001 From: Tyler Sutterley Date: Tue, 7 Jul 2026 17:27:55 -0700 Subject: [PATCH 3/5] fix: copilot finds --- .../api_reference/clenshaw_summation.rst | 2 -- .../api_reference/sea_level_equation.rst | 2 -- gravity_toolkit/gen_harmonics.py | 30 +++++++++---------- gravity_toolkit/gen_spherical_cap.py | 9 ++---- gravity_toolkit/harmonic_gradients.py | 10 ++++--- gravity_toolkit/harmonic_summation.py | 2 +- gravity_toolkit/harmonics.py | 14 ++++----- gravity_toolkit/spatial.py | 8 ++--- scripts/sea_level_regress.py | 2 +- 9 files changed, 36 insertions(+), 43 deletions(-) diff --git a/doc/source/api_reference/clenshaw_summation.rst b/doc/source/api_reference/clenshaw_summation.rst index 92ab7897..e6ff5c99 100644 --- a/doc/source/api_reference/clenshaw_summation.rst +++ b/doc/source/api_reference/clenshaw_summation.rst @@ -18,5 +18,3 @@ Calling Sequence .. __: https://github.com/tsutterley/gravity-toolkit/blob/main/gravity_toolkit/clenshaw_summation.py .. autofunction:: gravity_toolkit.clenshaw_summation - -.. autofunction:: gravity_toolkit.clenshaw_summation._clenshaw diff --git a/doc/source/api_reference/sea_level_equation.rst b/doc/source/api_reference/sea_level_equation.rst index c5d86961..0be5ac99 100644 --- a/doc/source/api_reference/sea_level_equation.rst +++ b/doc/source/api_reference/sea_level_equation.rst @@ -20,5 +20,3 @@ Calling Sequence .. __: https://github.com/tsutterley/gravity-toolkit/blob/main/gravity_toolkit/sea_level_equation.py .. autofunction:: gravity_toolkit.sea_level_equation - -.. autofunction:: gravity_toolkit.sea_level_equation._clenshaw diff --git a/gravity_toolkit/gen_harmonics.py b/gravity_toolkit/gen_harmonics.py index e24779a8..f370fee5 100644 --- a/gravity_toolkit/gen_harmonics.py +++ b/gravity_toolkit/gen_harmonics.py @@ -178,14 +178,10 @@ def integration(data, lon, lat, LMAX=60, MMAX=None, PLM=0, **kwargs): # Calculate polynomials using Holmes and Featherstone (2002) relation if (np.ndim(PLM) == 0): - plmout, dplm = plm_holmes(LMAX, np.cos(th)) - else: - # use precomputed plms to improve computational speed - # or to use a different recursion relation for polynomials - plmout = PLM[ll, :, :].copy() + PLM, dplm = plm_holmes(LMAX, np.cos(th)) # Multiply plms by integration factors [sin(theta)*dtheta*dphi] # truncate plms to maximum spherical harmonic order if MMAX < LMAX - plm = np.einsum("lmh...,h...->lmh...", plmout[:,mm,:], int_fact) + plm = np.einsum("lmh...,h...->lmh...", PLM[:LMAX+1,:MMAX+1,:], int_fact) # Initializing output spherical harmonic matrices Ylms = gravity_toolkit.harmonics(lmax=LMAX, mmax=MMAX) Ylms.clm = np.zeros((LMAX+1, MMAX+1)) @@ -261,15 +257,13 @@ def fourier(data, lon, lat, LMAX=60, MMAX=None, PLM=0, **kwargs): f = np.zeros((MMAX+1,MMAX+1), dtype=np.complex128) m_even = slice(0, MMAX+1, 2) m_odd = slice(1, MMAX, 2) - n_even = len(m_even) - n_odd = len(m_odd) if np.isclose([th[0],th[nlat-1]], [0.0,np.pi]).all(): # global case (includes poles) # non-endpoints n_th = np.exp(1j * np.einsum("h...,n...->nh...", th[1:nlat-1], mm)) f[m_even,:] = 2.0*np.einsum("mh...,nh...->mn", d[m_even,1:nlat-1],n_th.real) - f[m_odd,:] = 2.0*np.einsum("mh...,nh...->mn", d[m_even,1:nlat-1],n_th.imag) + f[m_odd,:] = 2.0*np.einsum("mh...,nh...->mn", d[m_odd,1:nlat-1],n_th.imag) # endpoints c_th = d[:,0]*np.cos(th[0]) + d[:,nlat-1]*np.cos(th[nlat-1]) s_th = d[:,0]*np.sin(th[0]) + d[:,nlat-1]*np.sin(th[nlat-1]) @@ -310,14 +304,16 @@ def fourier(data, lon, lat, LMAX=60, MMAX=None, PLM=0, **kwargs): # n and k must have like parities # m = even terms - k_even = np.zeros((n_even, n_even)) mm = np.arange(m_even.start, m_even.stop, m_even.step) + n_even = len(mm) + k_even = np.zeros((n_even, n_even)) for n in range(0,MMAX+2,2): k_even[:,n//2] = 0.5*(1.0/(1.0-mm-n) + 1.0/(1.0+mm-n) + 1.0/(1.0-mm+n) + 1.0/(1.0+mm+n)) - k_odd = np.zeros((n_odd, n_odd)) mm = np.arange(m_odd.start, m_odd.stop, m_odd.step) + n_odd = len(mm) + k_odd = np.zeros((n_odd, n_odd)) for n in range(1,MMAX+1,2): k_odd[:,(n-1)//2] = 0.5*(1.0/(1-mm-n) + 1.0/(1+mm-n) + 1.0/(1-mm+n) + 1.0/(1+mm+n)) @@ -334,15 +330,19 @@ def fourier(data, lon, lat, LMAX=60, MMAX=None, PLM=0, **kwargs): Ylms.slm[l_odd,m] = np.einsum("n...,lm...->l", f.imag[m,m_odd], temp) # m = odd terms + mm = np.arange(m_even.start, m_even.stop, m_even.step) + n_even = len(mm) k_even = np.zeros((n_even,n_even)) for n in range(0,MMAX+2,2): - k_even[:,n//2] = 0.5*(-1.0/(1-m_even-n) + 1.0/(1.0+m_even-n) + - 1.0/(1.0-m_even+n) - 1.0/(1.0+m_even+n)) + k_even[:,n//2] = 0.5*(-1.0/(1-mm-n) + 1.0/(1.0+mm-n) + + 1.0/(1.0-mm+n) - 1.0/(1.0+mm+n)) + mm = np.arange(m_odd.start, m_odd.stop, m_odd.step) + n_odd = len(mm) k_odd = np.zeros((n_odd,n_odd)) for n in range(1,MMAX+1,2): - k_odd[:,(n-1)//2] = 0.5*(-1.0/(1-m_odd-n) + 1.0/(1.0+m_odd-n) + - 1.0/(1.0-m_odd+n) - 1.0/(1.0+m_odd+n)) + k_odd[:,(n-1)//2] = 0.5*(-1.0/(1-mm-n) + 1.0/(1.0+mm-n) + + 1.0/(1.0-mm+n) - 1.0/(1.0+mm+n)) # calculate spherical harmonics for m == odd terms l_even = np.arange(2,LMAX+1,2)# do not in include l=0 diff --git a/gravity_toolkit/gen_spherical_cap.py b/gravity_toolkit/gen_spherical_cap.py index 6dc6d8c5..cc95fd78 100755 --- a/gravity_toolkit/gen_spherical_cap.py +++ b/gravity_toolkit/gen_spherical_cap.py @@ -233,12 +233,7 @@ def gen_spherical_cap(data, lon, lat, LMAX=60, MMAX=None, # 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] + PLM, _ = plm_holmes(LMAX, np.cos(th)) # calculate array of m values ranging from 0 to MMAX (harmonic orders) # MMAX+1 as there are MMAX+1 elements between 0 and MMAX @@ -256,7 +251,7 @@ def gen_spherical_cap(data, lon, lat, LMAX=60, MMAX=None, # Initializing output spherical harmonic matrices Ylms = gravity_toolkit.harmonics(lmax=LMAX, mmax=MMAX) # rotate spherical cap to be centered at lat/lon - plm = np.einsum("lm...,l...->lm...", plmout, pl_alpha) + plm = np.einsum("lm...,l...->lm...", PLM[:LMAX+1,:MMAX+1], pl_alpha) # multiplying clm by cos(m*phi) and slm by sin(m*phi) # to get a field of spherical harmonics ylm = np.einsum("lm...,m...->lm...", plm, d) diff --git a/gravity_toolkit/harmonic_gradients.py b/gravity_toolkit/harmonic_gradients.py index a4aa6353..aa84d54d 100644 --- a/gravity_toolkit/harmonic_gradients.py +++ b/gravity_toolkit/harmonic_gradients.py @@ -221,16 +221,18 @@ def geostrophic_currents(clm1, slm1, lon, lat, mm = np.arange(0, MMAX+1) # real (cosine) and imaginary (sine) components Ylm = clm[LMIN:LMAX+1,mm,:] - 1j * slm[LMIN:LMAX+1,mm,:] - # summation over all spherical harmonic degrees + # convolve legendre polynomials and truncate to degree and order iint = 1.0/(np.cos(th)*np.sin(th)) - pconv = np.einsum("h...,lmh...,lmd...->mhd...", iint, PLM, Ylm) + plm = np.einsum("h...,lmh...->lmh...", iint, PLM[LMIN:LMAX+1,:MMAX+1,:]) + # summation over all spherical harmonic degrees + pconv = np.einsum("lmh...,lmd...->mhd...", plm, Ylm) # calculating cos(m*phi) and sin(m*phi) using Euler's formula m_phi = np.exp(1j * np.einsum("m...,p...->mp...", mm, phi)) # output geostrophic current fields - currents = np.zeros((phmax,thmax,2)) + currents = np.empty((phmax,thmax,2)) # summation of cosine and sine harmonics - currents = np.einsum("mp...,mhd...->phd...", m_phi, pconv) + currents[:] = np.einsum("mp...,mhd...->phd...", m_phi, pconv) # return the current fields and drop imaginary component return currents.real diff --git a/gravity_toolkit/harmonic_summation.py b/gravity_toolkit/harmonic_summation.py index 973be100..efb071e1 100755 --- a/gravity_toolkit/harmonic_summation.py +++ b/gravity_toolkit/harmonic_summation.py @@ -106,7 +106,7 @@ def harmonic_summation(clm1, slm1, lon, lat, Ylm.imag[LMIN:LMAX+1,mm] = -slm1[LMIN:LMAX+1,mm] # Calculate fourier coefficients from legendre coefficients # summation over all spherical harmonic degrees - pconv = np.einsum("lmh...,lm...->mh...", PLM, Ylm) + pconv = np.einsum("lmh...,lm...->mh...", PLM[:LMAX+1,:MMAX+1,:], Ylm) # calculating cos(m*phi) and sin(m*phi) using Euler's formula m_phi = np.exp(1j * np.einsum("m...,p...->mp...", mm, phi)) # summation of cosine and sine harmonics diff --git a/gravity_toolkit/harmonics.py b/gravity_toolkit/harmonics.py index ffd5cc70..982ca9f9 100644 --- a/gravity_toolkit/harmonics.py +++ b/gravity_toolkit/harmonics.py @@ -1911,13 +1911,6 @@ def __iadd__(self, other): """In-place add values to a ``harmonics`` object""" return self.add(other) - def __idiv__(self, other): - """In-place divide values from a ``harmonics`` object""" - if isinstance(other, (int, float, np.ndarray)): - return self.scale(1.0 / other) - else: - return self.divide(other) - def __imul__(self, other): """In-place multiply values from a ``harmonics`` object""" if isinstance(other, (int, float, np.ndarray)): @@ -1933,6 +1926,13 @@ def __isub__(self, other): """In-place subtract values from a ``harmonics`` object""" return self.subtract(other) + def __itruediv__(self, other): + """In-place divide values from a ``harmonics`` object""" + if isinstance(other, (int, float, np.ndarray)): + return self.scale(1.0 / other) + else: + return self.divide(other) + def __mul__(self, other): """Multiply values from a ``harmonics`` object""" temp = self.copy() diff --git a/gravity_toolkit/spatial.py b/gravity_toolkit/spatial.py index 15c402e3..78d3abb2 100644 --- a/gravity_toolkit/spatial.py +++ b/gravity_toolkit/spatial.py @@ -1727,10 +1727,6 @@ def __iadd__(self, other): """In-place add values to a ``spatial`` object""" return self.offset(other) - def __idiv__(self, other): - """In-place divide values from a ``spatial`` object""" - return self.scale(1.0 / other) - def __imul__(self, other): """In-place multiply values from a ``spatial`` object""" return self.scale(other) @@ -1743,6 +1739,10 @@ def __isub__(self, other): """In-place subtract values from a ``spatial`` object""" return self.offset(-other) + def __itruediv__(self, other): + """In-place divide values from a ``spatial`` object""" + return self.scale(1.0 / other) + def __mul__(self, other): """Multiply values from a ``spatial`` object""" temp = self.copy() diff --git a/scripts/sea_level_regress.py b/scripts/sea_level_regress.py index 10d3e523..bac512d7 100644 --- a/scripts/sea_level_regress.py +++ b/scripts/sea_level_regress.py @@ -347,7 +347,7 @@ def sea_level_regress(PROC, DREL, DSET, LMAX, ) # convert phase from -180:180 to 0:360 ph.data = np.where( - ph.data < 0 & np.logical_not(ph.mask), ph.data + 360.0, ph.data + (ph.data < 0) & np.logical_not(ph.mask), ph.data + 360.0, ph.data ) # Amplitude Error comp1=out.error[indy,indx,j]*out.data[indy,indx,j]/amp.data[indy,indx] From e14439a6c4b11ba3ad0073c95b04fca5196a2c95 Mon Sep 17 00:00:00 2001 From: Tyler Sutterley Date: Wed, 8 Jul 2026 08:16:46 -0700 Subject: [PATCH 4/5] fix: more copilot finds feat: add dunder methods to geocenter class --- geocenter/calc_degree_one.py | 9 ++-- geocenter/monte_carlo_degree_one.py | 6 +-- gravity_toolkit/clenshaw_summation.py | 6 +-- gravity_toolkit/geocenter.py | 68 ++++++++++++++++++++++++++- gravity_toolkit/harmonics.py | 10 ++-- gravity_toolkit/spatial.py | 7 ++- 6 files changed, 87 insertions(+), 19 deletions(-) diff --git a/geocenter/calc_degree_one.py b/geocenter/calc_degree_one.py index adf8a4e0..beeb9c22 100755 --- a/geocenter/calc_degree_one.py +++ b/geocenter/calc_degree_one.py @@ -99,7 +99,7 @@ HDF5 --ocean-file X: Index file for ocean model harmonics --mean-file X: GRACE/GRACE-FO mean file to remove from the harmonic data - --mean-format X: Input data format for GRACE/GRACE-FO mean file' + --mean-format X: Input data format for GRACE/GRACE-FO mean file --remove-file X: Monthly files to be removed from the GRACE/GRACE-FO data --remove-format X: Input data format for files to be removed ascii @@ -914,12 +914,13 @@ def calc_degree_one(base_dir, PROC, DREL, MODEL, LMAX, RAD, # Convert inverted solutions into fully normalized spherical harmonics # restore geocenter variation from glacial isostatic adjustment (GIA) # restore atmospheric jump corrections from Fagiolini (2015) if applicable + # restore auxiliary removed spherical harmonics if applicable # for each of the geocenter solutions (C10, C11, S11) # for the iterative case this will be the final iteration DEG1 = gravtk.geocenter() - DEG1.C10 = DMAT[0,:]/dfactor[1] + gia.C10[:] + atm.C10[:] + remove.C10[t] - DEG1.C11 = DMAT[1,:]/dfactor[1] + gia.C11[:] + atm.C11[:] + remove.C11[t] - DEG1.S11 = DMAT[2,:]/dfactor[1] + gia.S11[:] + atm.S11[:] + remove.S11[t] + DEG1.C10 = DMAT[0,:]/dfactor[1] + gia.C10[:] + atm.C10[:] + remove.C10[:] + DEG1.C11 = DMAT[1,:]/dfactor[1] + gia.C11[:] + atm.C11[:] + remove.C11[:] + DEG1.S11 = DMAT[2,:]/dfactor[1] + gia.S11[:] + atm.S11[:] + remove.S11[:] # remove mean of geocenter for each component DEG1.mean(apply=True) # calculate geocenter variations with dealiasing restored diff --git a/geocenter/monte_carlo_degree_one.py b/geocenter/monte_carlo_degree_one.py index e7c72114..d822a76b 100644 --- a/geocenter/monte_carlo_degree_one.py +++ b/geocenter/monte_carlo_degree_one.py @@ -806,11 +806,11 @@ def monte_carlo_degree_one(base_dir, PROC, DREL, LMAX, RAD, DMAT, res, rnk, s = scipy.linalg.lstsq(IMAT, (CMAT-GMAT), lapack_driver=SOLVER) # save geocenter for iteration and time t after restoring fields - iteration.C10[t,n_iter] = DMAT[0,t]/dfactor[1] + \ + iteration.C10[t,n_iter] = DMAT[0]/dfactor[1] + \ gia.C10[t] + atm.C10[t] + remove.C10[t] - iteration.C11[t,n_iter] = DMAT[1,t]/dfactor[1] + \ + iteration.C11[t,n_iter] = DMAT[1]/dfactor[1] + \ gia.C11[t] + atm.C11[t] + remove.C11[t] - iteration.S11[t,n_iter] = DMAT[2,t]/dfactor[1] + \ + iteration.S11[t,n_iter] = DMAT[2]/dfactor[1] + \ gia.S11[t] + atm.S11[t] + remove.S11[t] # remove mean of each solution for iteration iteration.C10[:,n_iter] -= iteration.C10[:,n_iter].mean() diff --git a/gravity_toolkit/clenshaw_summation.py b/gravity_toolkit/clenshaw_summation.py index 28598e00..4315d322 100644 --- a/gravity_toolkit/clenshaw_summation.py +++ b/gravity_toolkit/clenshaw_summation.py @@ -174,16 +174,14 @@ def clenshaw_summation(clm, slm, lon, lat, m_phi[:,LMAX+1] = np.exp(1j * (LMAX + 1) * phi) m_phi[:,LMAX] = np.exp(1j * LMAX*phi) # calculate summation for order LMAX - g = np.einsum("h...,p...->ph...", cs_m[:,LMAX], m_phi[:,LMAX]) - s_m = g.real + s_m = (cs_m[:,LMAX]*m_phi[:,LMAX]).real # iterate to calculate complete summation for m in range(LMAX-1, 0, -1): # calculate summation for order m m_phi[:,m] = cos_phi_2*m_phi[:,m+1] - m_phi[:,m+2] a_m = np.sqrt((2.0*m + 3.0)/(2.0*m + 2.0)) - g = np.einsum("h...,p...->ph...", cs_m[:,m], m_phi[:,m]) # update summation and discard imaginary component - s_m = a_m*u*s_m + g.real + s_m = a_m*u*s_m + (cs_m[:,m]*m_phi[:,m]).real # calculate spatial field spatial = np.sqrt(3.0)*u*s_m + cs_m[:,0].real # return the calculated spatial field diff --git a/gravity_toolkit/geocenter.py b/gravity_toolkit/geocenter.py index 247b77ff..b419f7ca 100644 --- a/gravity_toolkit/geocenter.py +++ b/gravity_toolkit/geocenter.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" geocenter.py -Written by Tyler Sutterley (06/2024) +Written by Tyler Sutterley (07/2026) Data class for reading and processing geocenter data PYTHON DEPENDENCIES: @@ -15,6 +15,7 @@ https://github.com/yaml/pyyaml UPDATE HISTORY: + Updated 07/2026: add dunder (magic) methods for mathematical operations Updated 06/2024: use wrapper to importlib for optional dependencies Updated 05/2024: make subscriptable and allow item assignment Updated 09/2023: add group option to netCDF read function @@ -1218,6 +1219,71 @@ def __str__(self): properties.append(f" end_month: {max(self.month)}") return '\n'.join(properties) + def __add__(self, other): + """Add values to a ``geocenter`` object""" + temp = self.copy() + return temp.add(other) + + def __div__(self, other): + """Divide values from a ``geocenter`` object""" + return self.__truediv__(other) + + def __iadd__(self, other): + """In-place add values to a ``geocenter`` object""" + return self.add(other) + + def __idiv__(self, other): + """In-place divide values from a ``geocenter`` object""" + return self.__itruediv__(other) + + def __imul__(self, other): + """In-place multiply values from a ``geocenter`` object""" + if isinstance(other, (int, float, np.ndarray)): + return self.scale(other) + else: + return self.multiply(other) + + def __ipow__(self, other): + """In-place raise values from a ``geocenter`` object to a power""" + return self.power(other) + + def __isub__(self, other): + """In-place subtract values from a ``geocenter`` object""" + return self.subtract(other) + + def __itruediv__(self, other): + """In-place divide values from a ``geocenter`` object""" + if isinstance(other, (int, float, np.ndarray)): + return self.scale(1.0 / other) + else: + return self.divide(other) + + def __mul__(self, other): + """Multiply values from a ``geocenter`` object""" + temp = self.copy() + if isinstance(other, (int, float, np.ndarray)): + return temp.scale(other) + else: + return temp.multiply(other) + + def __pow__(self, other): + """Raise values from a ``geocenter`` object to a power""" + temp = self.copy() + return temp.power(other) + + def __sub__(self, other): + """Subtract values from a ``geocenter`` object""" + temp = self.copy() + return temp.subtract(other) + + def __truediv__(self, other): + """Divide values from a ``geocenter`` object""" + temp = self.copy() + if isinstance(other, (int, float, np.ndarray)): + return temp.scale(1.0 / other) + else: + return temp.divide(other) + def __len__(self): """Number of months """ diff --git a/gravity_toolkit/harmonics.py b/gravity_toolkit/harmonics.py index 982ca9f9..f90a1ad0 100644 --- a/gravity_toolkit/harmonics.py +++ b/gravity_toolkit/harmonics.py @@ -1901,16 +1901,16 @@ def __add__(self, other): def __div__(self, other): """Divide values from a ``harmonics`` object""" - temp = self.copy() - if isinstance(other, (int, float, np.ndarray)): - return temp.scale(1.0 / other) - else: - return temp.divide(other) + return self.__truediv__(other) def __iadd__(self, other): """In-place add values to a ``harmonics`` object""" return self.add(other) + def __idiv__(self, other): + """In-place divide values from a ``harmonics`` object""" + return self.__itruediv__(other) + def __imul__(self, other): """In-place multiply values from a ``harmonics`` object""" if isinstance(other, (int, float, np.ndarray)): diff --git a/gravity_toolkit/spatial.py b/gravity_toolkit/spatial.py index 78d3abb2..ded4d4bc 100644 --- a/gravity_toolkit/spatial.py +++ b/gravity_toolkit/spatial.py @@ -1720,13 +1720,16 @@ def __add__(self, other): def __div__(self, other): """Divide values from a ``spatial`` object""" - temp = self.copy() - return temp.scale(1.0 / other) + return self.__truediv__(other) def __iadd__(self, other): """In-place add values to a ``spatial`` object""" return self.offset(other) + def __idiv__(self, other): + """In-place divide values from a ``spatial`` object""" + return self.__itruediv__(other) + def __imul__(self, other): """In-place multiply values from a ``spatial`` object""" return self.scale(other) From 8320d4670f032afff20d272e1d53e4e43a98084f Mon Sep 17 00:00:00 2001 From: Tyler Sutterley Date: Wed, 8 Jul 2026 18:05:37 -0700 Subject: [PATCH 5/5] refactor: work a little more on harmonic gradients test: add fourier vs integration test --- doc/source/_assets/gravity-refs.bib | 38 +++ .../notebooks/GRACE-Geostrophic-Maps.ipynb | 16 +- gravity_toolkit/fourier_legendre.py | 233 ++++++++---------- gravity_toolkit/gen_harmonics.py | 131 +++++----- gravity_toolkit/harmonic_gradients.py | 29 ++- test/test_sea_level.py | 22 +- 6 files changed, 247 insertions(+), 222 deletions(-) diff --git a/doc/source/_assets/gravity-refs.bib b/doc/source/_assets/gravity-refs.bib index cbbf4cce..156723bf 100644 --- a/doc/source/_assets/gravity-refs.bib +++ b/doc/source/_assets/gravity-refs.bib @@ -186,6 +186,20 @@ @book{Dershowitz:2007cc publisher = {Cambridge University Press}, edition = {3}, } +@article{Driscoll:1994bp, +author = {Driscoll, J R and Healy, D M}, +title = {{Computing Fourier Transforms and Convolutions on the 2-Sphere}}, +journal = {Advances in Applied Mathematics}, +year = {1994}, +month = jun, +volume = {15}, +number = {2}, +issn = {0196-8858}, +url = {https://doi.org/10.1006/aama.1994.1008}, +doi = {10.1006/aama.1994.1008}, +pages = {202--250}, +publisher = {Elsevier BV}, +} @article{Dziewonski:1981bz, author = {Dziewonski, A M and Anderson, D L}, title = {{Preliminary reference Earth model}}, @@ -250,6 +264,20 @@ @misc{Gegout:2010gc url = {https://doi.org/10.13140/RG.2.1.1866.7045}, doi = {10.13140/RG.2.1.1866.7045}, } +@article{Gruber:2016hn, +author = {Gruber, C and Abrykosov, O}, +title = {{On computation and use of Fourier coefficients for associated Legendre functions}}, +journal = {Journal of Geodesy}, +year = {2016}, +month = jun, +volume = {90}, +number = {6}, +issn = {0949-7714}, +url = {https://doi.org/10.1007/s00190-016-0891-z}, +doi = {10.1007/s00190-016-0891-z}, +pages = {525--535}, +publisher = {Springer Science and Business Media LLC}, +} @inbook{Han:1989kj, author = {Han, D and Wahr, J}, title = {{Post-Glacial Rebound Analysis for a Rotating Earth}}, @@ -300,6 +328,16 @@ @book{HofmannWellenhof:2006hy doi = {10.1007/978-3-211-33545-1}, publisher = {Springer Vienna}, } +@article{Hofsommer:1960wg, +author = {Hofsommer, D J and Potters, M L}, +title = {{Table of Fourier coefficients of associated Legendre functions}}, +journal = {Proceedings of the Koninklijke Nederlandse Akademie van Wetenschappen: Series A: Mathematical Sciences}, +year = {1960}, +volume = {63}, +number = {5}, +issn = {0023-3358}, +pages = {460--480}, +} @article{Holmes:2002ff, author = {Holmes, S A and Featherstone, W E}, title = {{A unified approach to the Clenshaw summation and the recursive computation of very high degree and order normalised associated Legendre functions}}, diff --git a/doc/source/notebooks/GRACE-Geostrophic-Maps.ipynb b/doc/source/notebooks/GRACE-Geostrophic-Maps.ipynb index 631bb230..ca4ce4c2 100644 --- a/doc/source/notebooks/GRACE-Geostrophic-Maps.ipynb +++ b/doc/source/notebooks/GRACE-Geostrophic-Maps.ipynb @@ -310,6 +310,7 @@ "widgets.select_corrections()\n", "widgets.select_output()\n", "# display widgets for setting GRACE/GRACE-FO corrections parameters\n", + "widgets.gaussian.value = 600.0\n", "ipywidgets.VBox([\n", " widgets.GIA_file,\n", " widgets.GIA,\n", @@ -363,10 +364,12 @@ "# grid latitude and longitude\n", "grid.lon = np.copy(landsea.lon)\n", "grid.lat = np.copy(landsea.lat)\n", + "# mask equatorial regions due to hydrostrophic inaccuracies\n", + "valid, = np.nonzero((np.abs(grid.lat) > 10))\n", "\n", "# Computing plms for converting to spatial domain\n", "theta = np.radians(90.0 - grid.lat)\n", - "PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(theta))\n", + "PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(theta[valid]))\n", "RAD = widgets.gaussian.value\n", "\n", "# read load love numbers file\n", @@ -443,8 +446,6 @@ "# output geostrophic current grid\n", "grid.data = np.zeros((nlat, nlon, 2,nt))\n", "grid.mask = np.ones((nlat, nlon, 2,nt), dtype=bool)\n", - "# mask equatorial regions due to hydrostrophic inaccuracies\n", - "valid, = np.flatnonzero((np.abs(grid.lat) > 10))\n", "grid.mask[valid,:,:,:] = False\n", "# set land values from land-sea mask to invalid\n", "indy,indx = np.nonzero(np.logical_not(landsea.mask))\n", @@ -575,7 +576,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.8.10 64-bit", + "display_name": "py13", "language": "python", "name": "python3" }, @@ -589,12 +590,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.6" - }, - "vscode": { - "interpreter": { - "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" - } + "version": "3.13.0" } }, "nbformat": 4, diff --git a/gravity_toolkit/fourier_legendre.py b/gravity_toolkit/fourier_legendre.py index 6cc8b5cd..44f60826 100755 --- a/gravity_toolkit/fourier_legendre.py +++ b/gravity_toolkit/fourier_legendre.py @@ -2,24 +2,25 @@ u""" fourier_legendre.py Original IDL code gen_plms.pro written by Sean Swenson -Adapted by Tyler Sutterley (03/2023) +Adapted by Tyler Sutterley (07/2026) Computes Fourier coefficients of the associated Legendre functions CALLING SEQUENCE: - plm = fourier_legendre(lmax,mmax) + Almk = fourier_legendre(lmax,mmax) INPUTS: lmax: maximum spherical harmonic degree mmax: maximum spherical harmonic order OUTPUTS: - plm: Fourier coefficients + Almk: Fourier coefficients PYTHON DEPENDENCIES: numpy: Scientific Computing Tools For Python (https://numpy.org) UPDATE HISTORY: + Updated 07/2027: add citations to docstrings Updated 03/2023: improve typing for variables in docstrings Updated 10/2022: add polynomial function for calculating gradients Updated 04/2022: updated docstrings to numpy documentation format @@ -34,6 +35,7 @@ def fourier_legendre(lmax, mmax): """ Computes Fourier coefficients of the associated Legendre functions + :cite:p:`Hofsommer:1960wg,Gruber:2016hn` Parameters ---------- @@ -44,12 +46,12 @@ def fourier_legendre(lmax, mmax): Returns ------- - plm: np.ndarray + Almk: np.ndarray Fourier coefficients """ # allocate for output fourier coefficients - plm = np.zeros((lmax+1,lmax+1,lmax+1)) + Almk = np.zeros((lmax+1,lmax+1,lmax+1)) l_even = np.arange(0,lmax+1,2) l_odd = np.arange(1,lmax,2) m_even = np.arange(0,mmax+1,2) @@ -57,100 +59,100 @@ def fourier_legendre(lmax, mmax): # First compute m=0, m=1 terms # Compute m = 0, l = even terms - plm[l_even,0,0] = 1.0 - p1 = (l_even*(l_even+1.0))*plm[l_even,0,0] - plm[l_even,0,2] = p1 / (l_even*(l_even+1.0)-2.0) + Almk[l_even,0,0] = 1.0 + a1 = (l_even*(l_even+1.0))*Almk[l_even,0,0] + Almk[l_even,0,2] = a1 / (l_even*(l_even+1.0)-2.0) for j in range(2,lmax,2):# equivalent to 2:lmax-2 - p1 = 2.0*(l_even*(l_even+1.0)-j**2.0)*plm[l_even,0,j] - p2 = ((j-2.0)*(j-1.0)-l_even*(l_even+1.0))*plm[l_even,0,j-2] + a1 = 2.0*(l_even*(l_even+1.0)-j**2.0)*Almk[l_even,0,j] + a2 = ((j-2.0)*(j-1.0)-l_even*(l_even+1.0))*Almk[l_even,0,j-2] dfactor = (l_even*(l_even+1.0)-(j+2.0)*(j+1.0)) - plm[l_even,0,j+2] = (p1 + p2) / dfactor + Almk[l_even,0,j+2] = (a1 + a2) / dfactor # Special case for j = 0 fourier coefficient - plm[l_even,0,0] = plm[l_even,0,0]/2.0 + Almk[l_even,0,0] = Almk[l_even,0,0]/2.0 # Normalize overall sum to 2 for m == 0 norm = np.zeros((len(l_even))) for j in range(0,lmax+2,2):# equivalent to 0:lmax - ptemp = np.squeeze(plm[l_even[:, np.newaxis],0,m_even]) + ptemp = np.squeeze(Almk[l_even[:, np.newaxis],0,m_even]) dtemp = 1.0/(1.0-j-m_even) + 1.0/(1.0+j-m_even) + \ 1.0/(1.0-j+m_even) + 1.0/(1.0+j+m_even) - norm[l_even//2] = norm[l_even//2] + plm[l_even,0,j] * \ + norm[l_even//2] = norm[l_even//2] + Almk[l_even,0,j] * \ np.dot(ptemp, dtemp)/2.0 - # normalize plms + # normalize Almks norm = np.sqrt(norm/2.0) for l in range(0,lmax+2,2):# equivalent to 0:lmax - plm[l,0,:] = plm[l,0,:]/norm[l//2] + Almk[l,0,:] = Almk[l,0,:]/norm[l//2] # Compute m = 0, l = odd terms - plm[l_odd,0,1] = 1.0 - p1 = (2.0-l_odd*(l_odd+1.0))*plm[l_odd,0,1] - plm[l_odd,0,3] = p1 / (6.0-l_odd*(l_odd+1.0)) + Almk[l_odd,0,1] = 1.0 + a1 = (2.0-l_odd*(l_odd+1.0))*Almk[l_odd,0,1] + Almk[l_odd,0,3] = a1 / (6.0-l_odd*(l_odd+1.0)) for j in range(3,lmax-1,2):# equivalent to 3:lmax-3 - p1 = 2.0*(l_odd*(l_odd+1.0)-j**2.0)*plm[l_odd,0,j] - p2 = ((j-2.0)*(j-1.0)-l_odd*(l_odd+1.0))*plm[l_odd,0,j-2] + a1 = 2.0*(l_odd*(l_odd+1.0)-j**2.0)*Almk[l_odd,0,j] + a2 = ((j-2.0)*(j-1.0)-l_odd*(l_odd+1.0))*Almk[l_odd,0,j-2] dfactor = (l_odd*(l_odd+1.0)-(j+2.0)*(j+1.0)) - plm[l_odd,0,j+2] = (p1 + p2) / dfactor + Almk[l_odd,0,j+2] = (a1 + a2) / dfactor # Normalize overall sum to 2 for m == 0 norm = np.zeros((len(l_odd))) for j in range(1,lmax+1,2):# equivalent to 1:lmax-1 - ptemp = np.squeeze(plm[l_odd[:, np.newaxis],0,m_odd]) + ptemp = np.squeeze(Almk[l_odd[:, np.newaxis],0,m_odd]) dtemp = 1.0/(1.0-j-m_odd) + 1.0/(1.0+j-m_odd) + \ 1.0/(1.0-j+m_odd) + 1.0/(1.0+j+m_odd) - norm[(l_odd-1)//2] = norm[(l_odd-1)//2] + plm[l_odd,0,j] * \ + norm[(l_odd-1)//2] = norm[(l_odd-1)//2] + Almk[l_odd,0,j] * \ np.dot(ptemp, dtemp)/2.0 - # normalize plms + # normalize Almks norm = np.sqrt(norm/2.0) for l in range(1,lmax+1,2):# equivalent to 1:lmax-1 - plm[l,0,:] = plm[l,0,:]/norm[(l-1)//2] + Almk[l,0,:] = Almk[l,0,:]/norm[(l-1)//2] # Compute m = 1, l = even terms - plm[l_even,1,0] = 0.0 - plm[l_even,1,2] = 1.0 + Almk[l_even,1,0] = 0.0 + Almk[l_even,1,2] = 1.0 for j in range(2,lmax,2):# equivalent to 2:lmax-2 - p1 = 2.0*(l_even*(l_even+1)-j**2.0-2.0)*plm[l_even,1,j] - p2 = ((j-2.0)*(j-1.0)-l_even*(l_even+1))*plm[l_even,1,j-2] + a1 = 2.0*(l_even*(l_even+1)-j**2.0-2.0)*Almk[l_even,1,j] + a2 = ((j-2.0)*(j-1.0)-l_even*(l_even+1))*Almk[l_even,1,j-2] dfactor = (l_even*(l_even+1.0)-(j+2.0)*(j+1.0)) - plm[l_even,1,j+2] = (p1 + p2) / dfactor + Almk[l_even,1,j+2] = (a1 + a2) / dfactor # Normalize overall sum to 4 for m == 1 # different norm than that of the cosine series norm = np.zeros((len(l_even))) for j in range(0,lmax+2,2):# equivalent to 0:lmax - ptemp = np.squeeze(plm[l_even[:, np.newaxis],1,m_even]) + ptemp = np.squeeze(Almk[l_even[:, np.newaxis],1,m_even]) dtemp = -1.0/(1.0-j-m_even) + 1.0/(1+j-m_even) + \ 1.0/(1.0-j+m_even) - 1.0/(1+j+m_even) - norm[l_even//2] = norm[l_even//2] + plm[l_even,1,j] * \ + norm[l_even//2] = norm[l_even//2] + Almk[l_even,1,j] * \ np.dot(ptemp, dtemp)/2.0 - # normalize plms + # normalize Almks norm = np.sqrt(norm/4.0) for l in range(0,lmax+2,2):# equivalent to 0:lmax - plm[l,1,:] = plm[l,1,:]/norm[l//2] + Almk[l,1,:] = Almk[l,1,:]/norm[l//2] # Compute m = 1, l = odd terms - plm[l_odd,1,1] = 1.0 - plm[l_odd,1,3] = 3.0*(l_odd*(l_odd+1)-2)*plm[l_odd,1,1]/(l_odd*(l_odd+1)-6) + Almk[l_odd,1,1] = 1.0 + Almk[l_odd,1,3] = 3.0*(l_odd*(l_odd+1)-2)*Almk[l_odd,1,1]/(l_odd*(l_odd+1)-6) for j in range(3,lmax-1,2):# equivalent to 3:lmax-3 - p1 = 2.0*(l_odd*(l_odd+1.0)-j**2.0-2.0)*plm[l_odd,1,j] - p2 = ((j-2.0)*(j-1.0)-l_odd*(l_odd+1.0))*plm[l_odd,1,j-2] + a1 = 2.0*(l_odd*(l_odd+1.0)-j**2.0-2.0)*Almk[l_odd,1,j] + a2 = ((j-2.0)*(j-1.0)-l_odd*(l_odd+1.0))*Almk[l_odd,1,j-2] dfactor = (l_odd*(l_odd+1.0)-(j+2.0)*(j+1.0)) - plm[l_odd,1,j+2] = (p1 + p2) / dfactor + Almk[l_odd,1,j+2] = (a1 + a2) / dfactor # Normalize overall sum to 4 for m == 1 norm = np.zeros((len(l_odd))) for j in range(1,lmax+1,2):# equivalent to 1:lmax-1 - ptemp = np.squeeze(plm[l_odd[:, np.newaxis],1,m_odd]) + ptemp = np.squeeze(Almk[l_odd[:, np.newaxis],1,m_odd]) dtemp = -1.0/(1.0-j-m_odd) + 1.0/(1.0+j-m_odd) + \ 1.0/(1.0-j+m_odd) - 1.0/(1.0+j+m_odd) - norm[(l_odd-1)//2] = norm[(l_odd-1)//2] + plm[l_odd,1,j] * \ + norm[(l_odd-1)//2] = norm[(l_odd-1)//2] + Almk[l_odd,1,j] * \ np.dot(ptemp, dtemp)/2.0 - # normalize plms + # normalize Almks norm = np.sqrt(norm/4.0) for l in range(1,lmax+1,2):# equivalent to 1:lmax-1 - plm[l,1,:] = plm[l,1,:]/norm[(l-1)//2] + Almk[l,1,:] = Almk[l,1,:]/norm[(l-1)//2] # Compute coefficients for m > 0 @@ -158,58 +160,58 @@ def fourier_legendre(lmax, mmax): m = 0 # m = 0, l = even terms for l in range(m,lmax-1):# equivalent to m:lmax-2 - p1 = np.sqrt((l+m+2.0)*(l+m+1.0)/(2.0*l+1.0))*plm[l,m,m_even] - p2 = np.sqrt((l-m+1.0)*(l-m+2.0)/(2.0*l+5.0))*plm[l+2,m,m_even] - p3 = np.sqrt((l-m)*(l-m-1.0)/(2.0*l+1.0)/2.0)*plm[l,m+2,m_even] + a1 = np.sqrt((l+m+2.0)*(l+m+1.0)/(2.0*l+1.0))*Almk[l,m,m_even] + a2 = np.sqrt((l-m+1.0)*(l-m+2.0)/(2.0*l+5.0))*Almk[l+2,m,m_even] + a3 = np.sqrt((l-m)*(l-m-1.0)/(2.0*l+1.0)/2.0)*Almk[l,m+2,m_even] dfactor = np.sqrt((l+m+4.0)*(l+m+3.0)/(2.0*l+5.0)/2.0) - plm[l+2,m+2,m_even] = (p1 - p2 + p3) / dfactor + Almk[l+2,m+2,m_even] = (a1 - a2 + a3) / dfactor # m = 0, l = odd terms for l in range(m+1,lmax-1):# equivalent to m+1:lmax-2 - p1 = np.sqrt((l+m+2.0)*(l+m+1.0)/(2.0*l+1.0))*plm[l,m,m_odd] - p2 = np.sqrt((l-m+1.0)*(l-m+2.0)/(2.0*l+5.0))*plm[l+2,m,m_odd] - p3 = np.sqrt((l-m)*(l-m-1.0)/(2.0*l+1.0)/2.0)*plm[l,m+2,m_odd] + a1 = np.sqrt((l+m+2.0)*(l+m+1.0)/(2.0*l+1.0))*Almk[l,m,m_odd] + a2 = np.sqrt((l-m+1.0)*(l-m+2.0)/(2.0*l+5.0))*Almk[l+2,m,m_odd] + a3 = np.sqrt((l-m)*(l-m-1.0)/(2.0*l+1.0)/2.0)*Almk[l,m+2,m_odd] dfactor = np.sqrt((l+m+4.0)*(l+m+3.0)/(2.0*l+5.0)/2.0) - plm[l+2,m+2,m_odd] = (p1 - p2 + p3) / dfactor + Almk[l+2,m+2,m_odd] = (a1 - a2 + a3) / dfactor # m = even terms for m in range(2,lmax,2):# equivalent to 2:lmax-2 # m = even, > 2, l = even terms for l in range(m,lmax,2):# equivalent to m:lmax-2 - p1 = np.sqrt((l+m+2.0)*(l+m+1.0)/(2.0*l+1.0))*plm[l,m,m_even] - p2 = np.sqrt((l-m+1.0)*(l-m+2.0)/(2.0*l+5.0))*plm[l+2,m,m_even] - p3 = np.sqrt((l-m)*(l-m-1.0)/(2.0*l+1.0))*plm[l,m+2,m_even] + a1 = np.sqrt((l+m+2.0)*(l+m+1.0)/(2.0*l+1.0))*Almk[l,m,m_even] + a2 = np.sqrt((l-m+1.0)*(l-m+2.0)/(2.0*l+5.0))*Almk[l+2,m,m_even] + a3 = np.sqrt((l-m)*(l-m-1.0)/(2.0*l+1.0))*Almk[l,m+2,m_even] dfactor = np.sqrt((l+m+4.0)*(l+m+3.0)/(2.0*l+5.0)) - plm[l+2,m+2,m_even] = (p1 - p2 + p3) / dfactor + Almk[l+2,m+2,m_even] = (a1 - a2 + a3) / dfactor # m = even, > 2, l = odd terms for l in range(m+1,lmax-1,2): - p1 = np.sqrt((l+m+2.0)*(l+m+1.0)/(2.0*l+1.0))*plm[l,m,m_odd] - p2 = np.sqrt((l-m+1.0)*(l-m+2.0)/(2.0*l+5.0))*plm[l+2,m,m_odd] - p3 = np.sqrt((l-m)*(l-m-1.0)/(2.0*l+1.0))*plm[l,m+2,m_odd] + a1 = np.sqrt((l+m+2.0)*(l+m+1.0)/(2.0*l+1.0))*Almk[l,m,m_odd] + a2 = np.sqrt((l-m+1.0)*(l-m+2.0)/(2.0*l+5.0))*Almk[l+2,m,m_odd] + a3 = np.sqrt((l-m)*(l-m-1.0)/(2.0*l+1.0))*Almk[l,m+2,m_odd] dfactor = np.sqrt((l+m+4.0)*(l+m+3.0)/(2.0*l+5.0)) - plm[l+2,m+2,m_odd] = (p1 - p2 + p3) / dfactor + Almk[l+2,m+2,m_odd] = (a1 - a2 + a3) / dfactor # m = odd terms for m in range(1,lmax-1,2):# equivalent to 1:lmax-3 # m = odd, > 1, l = even terms for l in range(m+1,lmax-1,2):# equivalent to m+1,lmax-2 - p1 = np.sqrt((l+m+2.0)*(l+m+1.0)/(2.0*l+1.0))*plm[l,m,m_even] - p2 = np.sqrt((l-m+1.0)*(l-m+2.0)/(2.0*l+5.0))*plm[l+2,m,m_even] - p3 = np.sqrt((l-m)*(l-m-1.0)/(2.0*l+1.0))*plm[l,m+2,m_even] + a1 = np.sqrt((l+m+2.0)*(l+m+1.0)/(2.0*l+1.0))*Almk[l,m,m_even] + a2 = np.sqrt((l-m+1.0)*(l-m+2.0)/(2.0*l+5.0))*Almk[l+2,m,m_even] + a3 = np.sqrt((l-m)*(l-m-1.0)/(2.0*l+1.0))*Almk[l,m+2,m_even] dfactor = np.sqrt((l+m+4.0)*(l+m+3.0)/(2.0*l+5.0)) - plm[l+2,m+2,m_even] = (p1 - p2 + p3) / dfactor + Almk[l+2,m+2,m_even] = (a1 - a2 + a3) / dfactor # m = odd, > 1, l = odd terms for l in range(m,lmax-1,2):# equivalent to m:lmax-2 - p1 = np.sqrt((l+m+2.0)*(l+m+1.0)/(2.0*l+1.0))*plm[l,m,m_odd] - p2 = np.sqrt((l-m+1.0)*(l-m+2.0)/(2.0*l+5.0))*plm[l+2,m,m_odd] - p3 = np.sqrt((l-m)*(l-m-1.0)/(2.0*l+1.0))*plm[l,m+2,m_odd] + a1 = np.sqrt((l+m+2.0)*(l+m+1.0)/(2.0*l+1.0))*Almk[l,m,m_odd] + a2 = np.sqrt((l-m+1.0)*(l-m+2.0)/(2.0*l+5.0))*Almk[l+2,m,m_odd] + a3 = np.sqrt((l-m)*(l-m-1.0)/(2.0*l+1.0))*Almk[l,m+2,m_odd] dfactor = np.sqrt((l+m+4.0)*(l+m+3.0)/(2.0*l+5.0)) - plm[l+2,m+2,m_odd] = (p1 - p2 + p3) / dfactor + Almk[l+2,m+2,m_odd] = (a1 - a2 + a3) / dfactor # return the fourier coefficients - return plm + return Almk def legendre_gradient(lmax, mmax): """ @@ -225,67 +227,40 @@ def legendre_gradient(lmax, mmax): Returns ------- - vlm: np.ndarray + Vlmk: np.ndarray Fourier coefficients for meridional gradients - wlm: np.ndarray + Wlmk: np.ndarray Fourier coefficients for zonal gradients """ - - plm = fourier_legendre(lmax, mmax) - vlm = np.zeros((lmax+1,lmax+1,lmax+1)) - wlm = np.zeros((lmax+1,lmax+1,lmax+1)) - - # l=0 zero by definition - lind = np.arange(1,lmax+1) - # m=0 special case - # terms with m=0, m=1 have different coefficients - vlm[lind,0,:] = 2.0*np.dot(np.diag(np.sqrt((lind+1)*lind/2.0)), plm[lind,1,:]) - - # m+1 terms - for l in range(2,lmax+1):# from 2 to lmax - m = np.arange(1,l)# from 1 to l-1 - lplus = np.arange(l+2,2*l+1)# from l+2 to 2*l - lminus = np.arange(l-1,0,-1)# from l-1 to 1 - vlm[l,m,:] = np.dot(np.diag(np.sqrt(lplus*lminus/4.0)), plm[l,m+1,:]) - - # m-1 terms, m-1=0 has different coefficients - vlm[lind,1,:] -= np.dot(np.diag(np.sqrt((lind+1)*lind/2.0)), plm[lind,0,:]) - - for l in range(2,lmax+1): - m = np.arange(2,l+1)# from 2 to l - lplus = np.arange(l+2,2*l+1)# from l+2 to 2*l - lminus = np.arange(l-1,0,-1)# from l-1 to 1 - vlm[l,m,:] -= np.dot(np.diag(np.sqrt(lplus*lminus/4.0)), plm[l,m-1,:]) - # normalizations - for l in range(1,lmax+1): - vlm[l,:,:] /= np.sqrt((l+1)*l) - - # m+1 terms - for l in range(2, lmax+1): - m = np.arange(1,l)# from 1 to l-1 - dfactor = (2.0*l+1.0)/(2.0*l-1.0) - lminus2 = np.arange(l-2,-1,-1)# from l-2 to 0 - lminus1 = np.arange(l-1,0,-1)# from l-1 to 1 - wlm[l,m,:] = np.sqrt(dfactor) * \ - np.dot(np.diag(np.sqrt(lminus2*lminus1/4.0)), plm[l,m+1,:]) - - # m-1 terms, m-1=0 has different coefficients - # m=1 term - for l in range(1, lmax+1): - dfactor = (2.0*l+1.0)/(2.0*l-1.0) - wlm[l,1,:] += np.sqrt(dfactor)*np.sqrt(l*(l+1)/2.0)*plm[l-1,0,:] - - for l in range(2,lmax+1): - m = np.arange(2,l+1) - dfactor = (2.0*l+1.0)/(2.0*l-1.0) - lplus2 = np.arange(l+2,2*l+1)# from l+2 to 2*l - lplus1 = np.arange(l+1,2*l)# from l+1 to (2*l-1) - wlm[l,m,:] += np.sqrt(dfactor) * \ - np.dot(np.diag(np.sqrt(lplus2*lplus1)/4.0), plm[l-1,m-1,:]) - # normalizations + # compute the fourier coefficients of the associated legendre functions + Almk = fourier_legendre(lmax, mmax) + # allocate for output fourier coefficients + Vlmk = np.zeros((lmax+1,lmax+1,lmax+1)) + Wlmk = np.zeros((lmax+1,lmax+1,lmax+1)) + # for each spherical harmonic degree for l in range(1, lmax+1): - wlm[l,:,:] /= np.sqrt((l+1)*l) - # normalize vlm - vlm[:,0,:] /= 2.0 - - return (vlm, wlm) + # degree dependent factor + dfactor = np.sqrt((2.0*l + 1.0)/(2.0*l - 1.0)) + # m=0 special case + Vfact = np.sqrt(l*(l + 1.0)/2.0) + Vlmk[l,0,:] = Vfact * Almk[l,1,:] + for m in range(2, l+1):# from 2 to l + Vfact = np.sqrt((l + m) * (l - m + 1.0) / 4.0) + Wfact = dfactor * np.sqrt((l - m) * (l - m + 1) / 4.0) + Vlmk[l,m-1,:] = Vfact * Almk[l,m,:] + Wlmk[l,m-1,:] = -Wfact * Almk[l-1,m,:] + # m = 1 terms + Vfact = np.sqrt(l*(l + 1.0)/2.0) + Wfact = dfactor * np.sqrt(l*(l + 1.0)/2.0) + Vlmk[l,1,:] -= Vfact*Almk[l,0,:] + Wlmk[l,1,:] += dfactor*Wfact*Almk[l-1,0,:] + for m in range(2, l + 1):# from 2 to l + Vfact = np.sqrt((l + m) * (l - m + 1.0) / 4.0) + Wfact = dfactor * np.sqrt((l + m) * (l + m - 1) / 4.0) + Vlmk[l,m,:] -= Vfact * Almk[l,m-1,:] + Wlmk[l,m,:] += Wfact * Almk[l-1,m-1,:] + # normalizations + Vlmk[l,:,:] /= np.sqrt(l * (l + 1.0)) + Wlmk[l,:,:] /= np.sqrt(l * (l + 1.0)) + # return the coefficients + return (Vlmk, Wlmk) diff --git a/gravity_toolkit/gen_harmonics.py b/gravity_toolkit/gen_harmonics.py index f370fee5..f4d561cd 100644 --- a/gravity_toolkit/gen_harmonics.py +++ b/gravity_toolkit/gen_harmonics.py @@ -245,7 +245,7 @@ def fourier(data, lon, lat, LMAX=60, MMAX=None, PLM=0, **kwargs): mm = np.arange(MMAX+1) # Calculate cos and sin coefficients of signal m_phi = np.exp(1j * np.einsum("m...,p...->mp...", mm, phi)) - d = np.einsum("mp...,hp...->mh...", m_phi, data) + d = np.einsum("mp...,ph...->mh...", m_phi, data) # normalize coefficients d[0, :] *= 1.0 / nlon d[1:, :] *= 2.0 / nlon @@ -261,18 +261,20 @@ def fourier(data, lon, lat, LMAX=60, MMAX=None, PLM=0, **kwargs): if np.isclose([th[0],th[nlat-1]], [0.0,np.pi]).all(): # global case (includes poles) # non-endpoints - n_th = np.exp(1j * np.einsum("h...,n...->nh...", th[1:nlat-1], mm)) - f[m_even,:] = 2.0*np.einsum("mh...,nh...->mn", d[m_even,1:nlat-1],n_th.real) - f[m_odd,:] = 2.0*np.einsum("mh...,nh...->mn", d[m_odd,1:nlat-1],n_th.imag) + k_th = np.exp(1j * np.einsum("h...,k...->kh...", th[1:nlat-1], mm)) + f[m_even,:] = 2.0*np.einsum("mh...,kh...->mk", d[m_even,1:nlat-1],k_th.real) + f[m_odd,:] = 2.0*np.einsum("mh...,kh...->mk", d[m_odd,1:nlat-1],k_th.imag) # endpoints - c_th = d[:,0]*np.cos(th[0]) + d[:,nlat-1]*np.cos(th[nlat-1]) - s_th = d[:,0]*np.sin(th[0]) + d[:,nlat-1]*np.sin(th[nlat-1]) - f[m_even,:] += np.einsum("m...,n...->mn", c_th[m_even], mm) - f[m_odd,:] += np.einsum("m...,n...->mn", s_th[m_odd], mm) + k_th = np.exp(1j * mm* th[0]) + f[m_even,:] += np.einsum("m...,k...->mk", d[m_even,0], k_th) + f[m_odd,:] += np.einsum("m...,k...->mk", d[m_odd,0], k_th) + k_th = np.exp(1j * mm * th[nlat-1]) + f[m_even,:] += np.einsum("m...,k...->mk", d[m_even,nlat-1], k_th) + f[m_odd,:] += np.einsum("m...,k...->mk", d[m_odd,nlat-1], k_th) elif not np.isclose([th[0],th[nlat-1]], [0.0,np.pi]).any(): - n_th = np.exp(1j * np.einsum("h...,n...->nh...", th, mm)) - f[m_even,:] = 2.0*np.einsum("mh...,nh...->mn", d[m_even,:],n_th.real) - f[m_odd,:] = 2.0*np.einsum("mh...,nh...->mn", d[m_odd,:],n_th.imag) + k_th = np.exp(1j * np.einsum("h...,k...->kh...", th, mm)) + f[m_even,:] = 2.0*np.einsum("mh...,kh...->mk", d[m_even,:],k_th.real) + f[m_odd,:] = 2.0*np.einsum("mh...,kh...->mk", d[m_odd,:],k_th.imag) else: raise ValueError('Latitude coordinates incompatible') @@ -280,80 +282,75 @@ def fourier(data, lon, lat, LMAX=60, MMAX=None, PLM=0, **kwargs): f[:,0] *= 1.0/(2.0*nlat) f[:,1:MMAX+1] *= 1.0/nlat # Correct normalization for the incomplete coverage of the sphere - norm = nlon*dphi/(2.0*np.pi) * nlat*dth/np.pi - f *= norm + f[:] *= nlon*dphi/(2.0*np.pi) * nlat*dth/np.pi # Calculate cos and sin coefficients of Legendre functions # Expand m = even terms in a cosine series # Expand m = odd terms in a sine series # Both are stride 2 if (np.ndim(PLM) == 0): - plm = fourier_legendre(LMAX, MMAX) + Almk = fourier_legendre(LMAX, MMAX) else: - # use precomputed plms to improve computational speed - plm = PLM + # use precomputed alms to improve computational speed + Almk = PLM # 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)) - # Sum theta fourier coefficients - # temp is the integral of cos(n theta) cos(k theta) dcos(theta) - # over the interval 0 to pi - # n and k must have like parities - - # m = even terms - mm = np.arange(m_even.start, m_even.stop, m_even.step) - n_even = len(mm) - k_even = np.zeros((n_even, n_even)) - for n in range(0,MMAX+2,2): - k_even[:,n//2] = 0.5*(1.0/(1.0-mm-n) + 1.0/(1.0+mm-n) + - 1.0/(1.0-mm+n) + 1.0/(1.0+mm+n)) - - mm = np.arange(m_odd.start, m_odd.stop, m_odd.step) - n_odd = len(mm) - k_odd = np.zeros((n_odd, n_odd)) - for n in range(1,MMAX+1,2): - k_odd[:,(n-1)//2] = 0.5*(1.0/(1-mm-n) + 1.0/(1+mm-n) + - 1.0/(1-mm+n) + 1.0/(1+mm+n)) - # calculate spherical harmonics for m == even terms + # even l terms (l even, m even, k even) l_even = slice(0, LMAX+1, 2) + n_even = np.arange(m_even.start, m_even.stop, m_even.step) + k_even = np.zeros((len(n_even), len(n_even))) + for k in range(0,MMAX+2,2): + k_even[:,k//2] = 0.5*(1.0/(1.0-n_even-k) + 1.0/(1.0+n_even-k) + + 1.0/(1.0-n_even+k) + 1.0/(1.0+n_even+k)) + # calculate summation over coefficients + Aeven = np.einsum("lmk...,kn...->lmn...", Almk[l_even,m_even,m_even], k_even) + Yeven = np.einsum("lmn...,mn...->lm...", Aeven, f[m_even,m_even]) + Ylms.clm[l_even,m_even] = Yeven.real + Ylms.slm[l_even,m_even] = Yeven.imag + + # odd l terms (l odd, m even, k odd) l_odd = slice(1, LMAX, 2) - for m in range(0,MMAX+2,2): - temp = np.einsum("ln...,mn...->lm", plm[l_even,m,m_even], k_even) - Ylms.clm[l_even,m] = np.einsum("n...,lm...->l", f.real[m,m_even], temp) - Ylms.slm[l_even,m] = np.einsum("n...,lm...->l", f.imag[m,m_even], temp) - temp = np.einsum("ln...,mn...->lm", plm[l_odd,m,m_odd], k_odd) - Ylms.clm[l_odd,m] = np.einsum("n...,lm...->l", f.real[m,m_odd], temp) - Ylms.slm[l_odd,m] = np.einsum("n...,lm...->l", f.imag[m,m_odd], temp) - - # m = odd terms - mm = np.arange(m_even.start, m_even.stop, m_even.step) - n_even = len(mm) - k_even = np.zeros((n_even,n_even)) - for n in range(0,MMAX+2,2): - k_even[:,n//2] = 0.5*(-1.0/(1-mm-n) + 1.0/(1.0+mm-n) + - 1.0/(1.0-mm+n) - 1.0/(1.0+mm+n)) - - mm = np.arange(m_odd.start, m_odd.stop, m_odd.step) - n_odd = len(mm) - k_odd = np.zeros((n_odd,n_odd)) - for n in range(1,MMAX+1,2): - k_odd[:,(n-1)//2] = 0.5*(-1.0/(1-mm-n) + 1.0/(1.0+mm-n) + - 1.0/(1.0-mm+n) - 1.0/(1.0+mm+n)) + n_odd = np.arange(m_odd.start, m_odd.stop, m_odd.step) + k_odd = np.zeros((len(n_odd), len(n_odd))) + for k in range(1, MMAX+1, 2): + k_odd[:,(k-1)//2] = 0.5*(1.0/(1.0-n_odd-k) + 1.0/(1.0+n_odd-k) + + 1.0/(1.0-n_odd+k) + 1.0/(1.0+n_odd+k)) + # calculate summation over coefficients + Aodd = np.einsum("lmk...,kn...->lmn...", Almk[l_odd,m_even,m_odd], k_odd) + Yodd = np.einsum("lmn...,mn...->lm...", Aodd, f[m_even,m_odd]) + Ylms.clm[l_odd,m_even] = Yodd.real + Ylms.slm[l_odd,m_even] = Yodd.imag # calculate spherical harmonics for m == odd terms - l_even = np.arange(2,LMAX+1,2)# do not in include l=0 - l_odd = np.arange(1,LMAX,2) - for m in range(1,MMAX+1,2): - temp = np.einsum("ln...,mn...->lm", plm[l_even,m,m_even], k_even) - Ylms.clm[l_even,m] = np.einsum("n...,lm...->l", f.real[m,m_even], temp) - Ylms.slm[l_even,m] = np.einsum("n...,lm...->l", f.imag[m,m_even], temp) - temp = np.einsum("ln...,mn...->lm", plm[l_odd,m,m_odd], k_odd) - Ylms.clm[l_odd,m] = np.einsum("n...,lm...->l", f.real[m,m_odd], temp) - Ylms.slm[l_odd,m] = np.einsum("n...,lm...->l", f.imag[m,m_odd], temp) + # even l terms (l even, m odd, k even) + l_even = slice(2, LMAX+1, 2)# do not in include l=0 + n_even = np.arange(m_even.start, m_even.stop, m_even.step) + k_even = np.zeros((len(n_even), len(n_even))) + for k in range(0,MMAX+2,2): + k_even[:,k//2] = 0.5*(-1.0/(1.0-n_even-k) + 1.0/(1.0+n_even-k) + + 1.0/(1.0-n_even+k) - 1.0/(1.0+n_even+k)) + Aeven = np.einsum("lmk...,kn...->lmn...", Almk[l_even,m_odd,m_even], k_even) + Yeven = np.einsum("lmn...,mn...->lm...", Aeven, f[m_odd,m_even]) + Ylms.clm[l_even,m_odd] = Yeven.real + Ylms.slm[l_even,m_odd] = Yeven.imag + + # odd l terms (l odd, m odd, k odd) + l_odd = slice(1, LMAX, 2) + n_odd = np.arange(m_odd.start, m_odd.stop, m_odd.step) + k_odd = np.zeros((len(n_odd), len(n_odd))) + for k in range(1,MMAX+1,2): + k_odd[:,(k-1)//2] = 0.5*(-1.0/(1.0-n_odd-k) + 1.0/(1.0+n_odd-k) + + 1.0/(1.0-n_odd+k) - 1.0/(1.0+n_odd+k)) + # calculate summation over coefficients + Aodd = np.einsum("lmk...,kn...->lmn...", Almk[l_odd,m_odd,m_odd], k_odd) + Yodd = np.einsum("lmn...,mn...->lm...", Aodd, f[m_odd,m_odd]) + Ylms.clm[l_odd,m_odd] = Yodd.real + Ylms.slm[l_odd,m_odd] = Yodd.imag # Divide by Plm normalization Ylms.clm[:,0] /= 2.0 diff --git a/gravity_toolkit/harmonic_gradients.py b/gravity_toolkit/harmonic_gradients.py index aa84d54d..f306c6eb 100644 --- a/gravity_toolkit/harmonic_gradients.py +++ b/gravity_toolkit/harmonic_gradients.py @@ -50,7 +50,7 @@ def harmonic_gradients(clm1, slm1, lon, lat, LMIN=0, LMAX=60, MMAX=None): """ Calculates the gradient of a scalar field from a series of - spherical harmonics + spherical harmonics :cite:p:`Driscoll:1994bp` Parameters ---------- @@ -87,7 +87,6 @@ def harmonic_gradients(clm1, slm1, lon, lat, # Colatitude in radians th = np.radians(90.0 - np.squeeze(lat)) thmax = len(th) - phimax = len(phi) # spherical harmonic degree and order ll = np.arange(0,LMAX+1)# lmax+1 to include lmax @@ -96,27 +95,27 @@ def harmonic_gradients(clm1, slm1, lon, lat, Ylm = np.zeros((LMAX+1, MMAX+1), dtype=np.complex128) # Truncating harmonics to degree and order LMAX # removing coefficients below LMIN and above MMAX - Ylm.imag[LMIN:LMAX+1,mm] = -clm1[LMIN:LMAX+1,mm].copy() - Ylm.real[LMIN:LMAX+1,mm] = -slm1[LMIN:LMAX+1,mm].copy() - dlm = np.einsum("l...,lm...->lm", np.sqrt((ll+1.0)*ll), Ylm) + Ylm.real[LMIN:LMAX+1,mm] = clm1[LMIN:LMAX+1,mm].copy() + Ylm.imag[LMIN:LMAX+1,mm] = -slm1[LMIN:LMAX+1,mm].copy() + dlm = np.einsum("l...,lm...->lm", np.sqrt((ll+1.0)*ll), -1j*Ylm) # generate Vlm coefficients (vlm and wlm) - vlm, wlm = legendre_gradient(LMAX, MMAX) + Vlmk, Wlmk = legendre_gradient(LMAX, MMAX) # even and odd spherical harmonic orders m_even = np.arange(0,MMAX+2,2) m_odd = np.arange(1,MMAX,2) - # Euler's formula for theta * n and m * phi - n_th = np.exp(1j * np.einsum("h...,n...->nh...", th, ll)) + # Euler's formula for theta * k and m * phi + k_th = np.exp(1j * np.einsum("h...,k...->kh...", th, ll)) m_phi = np.exp(1j * np.einsum("m...,p...->mp...", mm, phi)) # Calculate fourier coefficients from legendre coefficients d = np.zeros((LMAX+1,thmax,2), dtype=np.complex128) - wtmp = np.einsum("lmn...,lm...->mn", wlm, dlm) - vtmp = np.einsum("lmn...,lm...->mn", vlm, dlm) - d[m_even,:,0] = np.einsum("mn...,nh...->mh", wtmp[m_even,:], n_th.imag) - d[m_even,:,1] = np.einsum("mn...,nh...->mh", vtmp[m_even,:], n_th.imag) - d[m_odd,:,0] = np.einsum("mn...,nh...->mh", wtmp[m_odd,:], n_th.real) - d[m_odd,:,1] = np.einsum("mn...,nh...->mh", vtmp[m_odd,:], n_th.real) + wtmp = np.einsum("lmk...,lm...->mk", Wlmk, dlm) + vtmp = np.einsum("lmk...,lm...->mk", Vlmk, dlm) + d[m_even,:,0] = np.einsum("mk...,kh...->mh", wtmp[m_even,:], k_th.imag) + d[m_even,:,1] = np.einsum("mk...,kh...->mh", vtmp[m_even,:], k_th.imag) + d[m_odd,:,0] = np.einsum("mk...,kh...->mh", wtmp[m_odd,:], k_th.real) + d[m_odd,:,1] = np.einsum("mk...,kh...->mh", vtmp[m_odd,:], k_th.real) # calculate the zonal and meridional gradients of the scalar field gradients = np.einsum("mp...,mhd...->phd...", m_phi, d) # return the gradient fields and drop imaginary component @@ -220,7 +219,7 @@ def geostrophic_currents(clm1, slm1, lon, lat, # removing coefficients below LMIN and above MMAX mm = np.arange(0, MMAX+1) # real (cosine) and imaginary (sine) components - Ylm = clm[LMIN:LMAX+1,mm,:] - 1j * slm[LMIN:LMAX+1,mm,:] + Ylm = clm[LMIN:LMAX+1,:MMAX+1,:] - 1j * slm[LMIN:LMAX+1,:MMAX+1,:] # convolve legendre polynomials and truncate to degree and order iint = 1.0/(np.cos(th)*np.sin(th)) plm = np.einsum("h...,lmh...->lmh...", iint, PLM[LMIN:LMAX+1,:MMAX+1,:]) diff --git a/test/test_sea_level.py b/test/test_sea_level.py index 6af7387d..ebe093ad 100644 --- a/test/test_sea_level.py +++ b/test/test_sea_level.py @@ -2,7 +2,6 @@ u""" test_sea_level.py (07/2026) """ -import pytest import inspect import pathlib import numpy as np @@ -53,3 +52,24 @@ def test_sea_level(): difference = validation.data - sea_level valid_difference = difference[np.isfinite(difference)] assert np.all(np.abs(valid_difference) < 1e-8) + +def test_harmonics(): + # read land function file + LANDMASK = filepath.joinpath('land.fcn.1_deg.gz') + landsea = gravtk.spatial().from_ascii(LANDMASK, + date=False, spacing=[1.0, 1.0], nlat=180, nlon=360, + extent=[0.5,359.5,-89.5,89.5], compression='gzip') + # calculate ocean function from land function + land_function = landsea.data.T + ocean_function = 1.0 - land_function + # maximum spherical harmonic degree + LMAX = 60 + # calculate spherical harmonics using integration and fourier methods + YlmI = gravtk.gen_harmonics(ocean_function, landsea.lon, landsea.lat, + LMAX=LMAX, METHOD="integration") + YlmF = gravtk.gen_harmonics(ocean_function, landsea.lon, landsea.lat, + LMAX=LMAX, METHOD="fourier") + # check that amplitudes of harmonic data are nearly equal + difference = YlmI.amplitude - YlmF.amplitude + harmonic_eps = np.finfo(np.float16).eps + assert np.all(np.abs(difference) < harmonic_eps)