Skip to content

Commit ae20ecf

Browse files
Merge branch 'devel' into devel
2 parents 4bf908c + 6a829dc commit ae20ecf

File tree

15 files changed

+1247
-552
lines changed

15 files changed

+1247
-552
lines changed

.github/workflows/marsfiles_test.yml

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,42 @@ jobs:
5959
pip install numpy netCDF4 xarray scipy matplotlib
6060
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
6161
62-
# Install the package in editable mode
63-
- name: Install package
64-
run: pip install -e .
62+
# Install pyshtools for spatial analysis capabilities
63+
- name: Install pyshtools and spectral dependencies (Ubuntu)
64+
if: runner.os == 'Linux'
65+
shell: bash
66+
run: |
67+
sudo apt-get update
68+
sudo apt-get install -y libfftw3-dev
69+
pip install pyshtools
70+
71+
- name: Install pyshtools and spectral dependencies (macOS)
72+
if: runner.os == 'macOS'
73+
shell: bash
74+
run: |
75+
pip install pyshtools
76+
77+
- name: Install pyshtools and spectral dependencies (Windows)
78+
if: runner.os == 'Windows'
79+
shell: pwsh
80+
run: |
81+
pip install pyshtools
82+
83+
# Install the package with spectral extension
84+
- name: Install package with spectral capabilities
85+
run: |
86+
pip install -e .
87+
88+
# Create a test profile if needed
89+
- name: Create amescap profile
90+
shell: bash
91+
run: |
92+
mkdir -p $HOME
93+
mkdir -p mars_templates
94+
echo "# AmesCAP profile" > mars_templates/amescap_profile
95+
echo "export PYTHONPATH=$PYTHONPATH:$(pwd)" >> mars_templates/amescap_profile
96+
cp mars_templates/amescap_profile $HOME/.amescap_profile
97+
echo "Created profile at $HOME/.amescap_profile"
6598
6699
# Create a patch for the test file to fix Windows path issues
67100
- name: Create test_marsfiles.py path fix for Windows

README.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ For reproducible analysis, we recommend installing CAP in a dedicated virtual en
3939

4040
# Install CAP and its dependencies
4141
pip install git+https://github.com/NASA-Planetary-Science/AmesCAP.git
42+
# OR install a specific branch with:
43+
pip install git+https://github.com/NASA-Planetary-Science/AmesCAP.git@devel
44+
45+
# For spectral analysis capabilities, please follow the installation instructions.
4246

4347
# Copy amescap_profile to your home directory, which varies by OS, shell, and package manager:
4448
# pip + Unix/MacOS (bash, csh, tcsh, zsh) OR Windows Cygwin:
@@ -54,6 +58,8 @@ For reproducible analysis, we recommend installing CAP in a dedicated virtual en
5458

5559
This ensures consistent package versions across different systems.
5660

61+
For spectral analysis capabilities, please follow the `installation instructions <https://amescap.readthedocs.io/en/latest/installation.html>`_.
62+
5763
Available Commands
5864
^^^^^^^^^^^^^^^
5965
After installation, the following commands will be available:

amescap/Spectral_utils.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,28 @@
3131
# ======================================================================
3232
# DEFINITIONS
3333
# ======================================================================
34+
# Try to import pyshtools with proper error handling
35+
try:
36+
import pyshtools
37+
PYSHTOOLS_AVAILABLE = True
38+
except ImportError:
39+
PYSHTOOLS_AVAILABLE = False
40+
print(
41+
f"{Yellow}__________________\n"
42+
f"Zonal decomposition relies on the pyshtools library, "
43+
f"referenced at:\n\n"
44+
f"Mark A. Wieczorek and Matthias Meschede (2018). "
45+
f"SHTools - Tools for working with spherical harmonics,"
46+
f"Geochemistry, Geophysics, Geosystems, 2574-2592, "
47+
f"doi:10.1029/2018GC007529\n\nPlease consult pyshtools "
48+
f"documentation at:\n"
49+
f" {Cyan}https://pypi.org/project/pyshtools\n"
50+
f"{Yellow}And installation instructions for CAP with pyshtools:\n"
51+
f" {Cyan}https://amescap.readthedocs.io/en/latest/installation."
52+
f"html#_spectral_analysis{Yellow}\n"
53+
f"__________________{Nclr}\n\n"
54+
)
55+
3456
def diurn_extract(VAR, N, tod, lon):
3557
"""
3658
Extract the diurnal component of a field. Original code by John
@@ -414,3 +436,122 @@ def zeroPhi_filter(VAR, btype, low_highcut, fs, axis=0, order=4,
414436
return VAR_trend + VAR_f
415437
else:
416438
return VAR_f
439+
440+
def zonal_decomposition(VAR):
441+
"""
442+
Decomposition into spherical harmonics. [A. Kling, 2020]
443+
444+
:param VAR: Detrend variable for decomposition. Lat is SECOND to
445+
LAST dimension and lon is LAST (e.g., ``[time,lat,lon]`` or
446+
``[time,lev,lat,lon]``)
447+
448+
:return: (COEFFS) coefficient for harmonic decomposion, shape is
449+
flattened (e.g., ``[time, 2, lat/2, lat/2]``
450+
``[time x lev, 2, lat/2, lat/2]``);
451+
(power_per_l) power spectral density, shape is re-organized
452+
(e.g., [time, lat/2] or [time, lev, lat/2])
453+
454+
.. NOTE:: Output size is (``[...,lat/2, lat/2]``) as lat is the
455+
smallest dimension. This matches the Nyquist frequency.
456+
457+
"""
458+
if not PYSHTOOLS_AVAILABLE:
459+
raise ImportError(
460+
"This function requires pyshtools. Install with:\n"
461+
"conda install -c conda-forge pyshtools\n"
462+
"or\n"
463+
"pip install amescap[spectral]"
464+
)
465+
466+
var_shape = np.array(VAR.shape)
467+
468+
# Flatten array (e.g., [10, 36, lat, lon] -> [360, lat, lon])
469+
nflatten = int(np.prod(var_shape[:-2]))
470+
reshape_flat = np.append(nflatten, var_shape[-2:])
471+
VAR = VAR.reshape(reshape_flat)
472+
473+
coeff_out_shape = np.append(var_shape[0:-2],
474+
[2, var_shape[-2]//2, var_shape[-2]//2])
475+
psd_out_shape = np.append(var_shape[0:-2], var_shape[-2]//2)
476+
coeff_flat_shape = np.append(nflatten, coeff_out_shape[-3:])
477+
COEFFS = np.zeros(coeff_flat_shape)
478+
479+
psd = np.zeros((nflatten,var_shape[-2]//2))
480+
481+
for ii in range(0,nflatten):
482+
COEFFS[ii,...] = pyshtools.expand.SHExpandDH(VAR[ii,...], sampling = 2)
483+
psd[ii,:] = pyshtools.spectralanalysis.spectrum(COEFFS[ii,...])
484+
485+
return COEFFS, psd.reshape(psd_out_shape)
486+
487+
def zonal_construct(COEFFS_flat, VAR_shape, btype=None, low_highcut=None):
488+
"""
489+
Recomposition into spherical harmonics
490+
491+
:param COEFFS_flat: Spherical harmonic coefficients as a flattened
492+
array, (e.g., ``[time, lat, lon]`` or
493+
``[time x lev, 2, lat, lon]``)
494+
:type COEFFS_flat: array
495+
496+
:param VAR_shape: shape of the original variable
497+
:type VAR_shape: tuple
498+
499+
:param btype: filter type: "low", "high", or "band". If None,
500+
returns reconstructed array using all zonal wavenumbers
501+
:type btype: str or None
502+
503+
:param low_high_cut: low, high or [low, high] zonal wavenumber
504+
:type low_high_cut: int
505+
506+
:return: detrended variable reconstructed to original size
507+
(e.g., [time, lev, lat, lon])
508+
509+
.. NOTE:: The minimum and maximum wavelenghts in [km] are computed::
510+
dx = 2*np.pi * 3400
511+
L_min = (1./kmax) * dx
512+
L_max = 1./max(kmin, 1.e-20) * dx
513+
if L_max > 1.e20:
514+
L_max = np.inf
515+
print("(kmin,kmax) = ({kmin}, {kmax})
516+
-> dx min = {L_min} km, dx max = {L_max} km")
517+
518+
"""
519+
if not PYSHTOOLS_AVAILABLE:
520+
raise ImportError(
521+
"This function requires pyshtools. Install with:\n"
522+
"conda install -c conda-forge pyshtools\n"
523+
"or\n"
524+
"pip install amescap[spectral]"
525+
)
526+
527+
# Initialization
528+
nflatten = COEFFS_flat.shape[0]
529+
kmin = 0
530+
kmax = COEFFS_flat.shape[-1]
531+
532+
VAR = np.zeros((nflatten, VAR_shape[-2], VAR_shape[-1]))
533+
534+
if btype == "low":
535+
kmax= int(low_highcut)
536+
if btype == "high":
537+
kmin= int(low_highcut)
538+
if btype == "band":
539+
kmin, kmax= int(low_highcut[0]), int(low_highcut[1])
540+
541+
for ii in range(0, nflatten):
542+
# Filtering
543+
COEFFS_flat[ii, :, :kmin, :] = 0.
544+
COEFFS_flat[ii, :, kmax:, :] = 0.
545+
VAR[ii, :, :] = pyshtools.expand.MakeGridDH(COEFFS_flat[ii, :, :],
546+
sampling = 2)
547+
return VAR.reshape(VAR_shape)
548+
549+
550+
def init_shtools():
551+
"""
552+
Check if pyshtools is available and return its availability status
553+
554+
:return: True if pyshtools is available, False otherwise
555+
:rtype: bool
556+
"""
557+
return PYSHTOOLS_AVAILABLE

bin/MarsCalendar.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,12 @@
2929
import sys # System commands
3030
import argparse # Parse arguments
3131
import numpy as np
32+
import functools # For function decorators
33+
import traceback # For printing stack traces
3234

3335
# Load amesCAP modules
3436
from amescap.FV3_utils import (sol2ls, ls2sol)
3537

36-
import functools
37-
import traceback
38-
3938
def debug_wrapper(func):
4039
"""
4140
A decorator that wraps a function with error handling based on the

0 commit comments

Comments
 (0)