99
1010from __future__ import annotations
1111
12- import glob
13- from typing import TYPE_CHECKING # , Optional
12+ from typing import TYPE_CHECKING , Tuple
1413
1514import numpy as np
1615import xarray as xr
1716
1817from e3sm_diags .driver .utils import zwf_functions as wf
1918from e3sm_diags .driver .utils .climo_xr import ClimoFreq
2019from e3sm_diags .driver .utils .dataset_xr import Dataset
21- from e3sm_diags .driver .utils .general import pad_year
2220from e3sm_diags .logger import _setup_child_logger
2321from e3sm_diags .plot .tropical_subseasonal_plot import plot
2422
@@ -47,35 +45,28 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara
4745 ref_data = Dataset (parameter , data_type = "ref" )
4846
4947 for variable in parameter .variables :
50- test , test_start , test_end = calculate_spectrum (
51- parameter .test_data_path ,
52- variable ,
53- parameter .test_start_yr ,
54- parameter .test_end_yr ,
55- )
48+ # Get test dataset
49+ test_ds = test_data .get_time_series_dataset (variable , single_point = True )
50+ test_spectrum , test_start , test_end = calculate_spectrum (test_ds , variable )
51+
52+ # Update parameters with actual time range
5653 parameter .test_start_yr = test_start
5754 parameter .test_end_yr = test_end
5855 parameter .test_name_yrs = test_data .get_name_yrs_attr (season )
56+
57+ # Get reference dataset
5958 if run_type == "model_vs_model" :
60- ref , ref_start , ref_end = calculate_spectrum (
61- parameter .reference_data_path ,
62- variable ,
63- parameter .ref_start_yr ,
64- parameter .ref_end_yr ,
65- )
59+ ref_ds = ref_data .get_time_series_dataset (variable , single_point = True )
60+ ref_spectrum , ref_start , ref_end = calculate_spectrum (ref_ds , variable )
6661 elif run_type == "model_vs_obs" :
6762 # TODO use pre-calculated spectral power
6863 # if parameter.ref_start_yr == "":
6964 # parameter.ref_name_yrs = parameter.reference_name
7065 # # read precalculated data.
7166 # else:
72- ref_data_path = f"{ parameter .reference_data_path } /{ parameter .ref_name } "
73- ref , ref_start , ref_end = calculate_spectrum (
74- ref_data_path ,
75- variable ,
76- parameter .ref_start_yr ,
77- parameter .ref_end_yr ,
78- )
67+ ref_ds = ref_data .get_time_series_dataset (variable , single_point = True )
68+ ref_spectrum , ref_start , ref_end = calculate_spectrum (ref_ds , variable )
69+
7970 parameter .ref_start_yr = ref_start
8071 parameter .ref_end_yr = ref_end
8172 parameter .ref_name_yrs = ref_data .get_name_yrs_attr (season )
@@ -84,16 +75,24 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara
8475 for diff_name in ["raw_sym" , "raw_asy" , "norm_sym" , "norm_asy" , "background" ]:
8576 diff = (
8677 100
87- * (test [f"spec_{ diff_name } " ] - ref [f"spec_{ diff_name } " ])
88- / ref [f"spec_{ diff_name } " ]
78+ * (
79+ test_spectrum [f"spec_{ diff_name } " ]
80+ - ref_spectrum [f"spec_{ diff_name } " ]
81+ )
82+ / ref_spectrum [f"spec_{ diff_name } " ]
8983 )
9084 diff .name = f"spec_{ diff_name } "
91- diff .attrs .update (test [f"spec_{ diff_name } " ].attrs )
85+ diff .attrs .update (test_spectrum [f"spec_{ diff_name } " ].attrs )
9286
9387 parameter .spec_type = diff_name
9488 parameter .output_file = f"{ parameter .var_id } _{ parameter .spec_type } _15N-15S"
9589 parameter .diff_title = "percent difference"
96- plot (parameter , test [f"spec_{ diff_name } " ], ref [f"spec_{ diff_name } " ], diff )
90+ plot (
91+ parameter ,
92+ test_spectrum [f"spec_{ diff_name } " ],
93+ ref_spectrum [f"spec_{ diff_name } " ],
94+ diff ,
95+ )
9796
9897 if "norm" in diff_name :
9998 parameter .spec_type = f"{ diff_name } _zoom"
@@ -102,16 +101,33 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara
102101 )
103102 plot (
104103 parameter ,
105- test [f"spec_{ diff_name } " ],
106- ref [f"spec_{ diff_name } " ],
104+ test_spectrum [f"spec_{ diff_name } " ],
105+ ref_spectrum [f"spec_{ diff_name } " ],
107106 diff ,
108107 do_zoom = True ,
109108 )
110109
111110 return parameter
112111
113112
114- def calculate_spectrum (path , variable , start_year , end_year ):
113+ def calculate_spectrum (ds : xr .Dataset , variable : str ) -> Tuple [xr .Dataset , str , str ]:
114+ """Calculate wavenumber-frequency power spectra for a variable.
115+
116+ Parameters
117+ ----------
118+ ds : xr.Dataset
119+ Dataset containing the variable of interest
120+ variable : str
121+ Name of the variable to analyze
122+
123+ Returns
124+ -------
125+ Tuple[xr.Dataset, str, str]
126+ Tuple containing:
127+ - Dataset with spectral power components
128+ - Start year (as string)
129+ - End year (as string)
130+ """
115131 # latitude bounds for analysis
116132 latBound = (- 15 , 15 )
117133 # SAMPLES PER DAY
@@ -131,42 +147,13 @@ def calculate_spectrum(path, variable, start_year, end_year):
131147 "dosymmetries" : True ,
132148 "rmvLowFrq" : True ,
133149 }
134- # TODO the time subsetting and variable derivation should be replaced during
135- # cdat revamp.
136-
137- start_year_str = pad_year (start_year )
138- end_year_str = pad_year (end_year )
139- try :
140- var = xr .open_mfdataset (glob .glob (f"{ path } /{ variable } _*.nc" )).sel (
141- lat = slice (- 15 , 15 ),
142- time = slice (f"{ start_year_str } -01-01" , f"{ end_year_str } -12-31" ),
143- )[variable ]
144- actual_start = var .time .dt .year .values [0 ]
145- actual_end = var .time .dt .year .values [- 1 ]
146- except OSError :
147- logger .info (
148- f"No files to open for { variable } within { start_year } and { end_year } from { path } ."
149- )
150- raise
151- # Unit conversion
152- if var .name == "PRECT" :
153- if var .attrs ["units" ] == "m/s" or var .attrs ["units" ] == "m s{-1}" :
154- logger .info (
155- "\n BEFORE unit conversion: Max/min of data: "
156- + str (var .values .max ())
157- + " "
158- + str (var .values .min ())
159- )
160- var .values = (
161- var .values * 1000.0 * 86400.0
162- ) # convert m/s to mm/d, do not alter metadata (yet)
163- var .attrs ["units" ] = "mm/d" # adjust metadata to reflect change in units
164- logger .info (
165- "\n AFTER unit conversion: Max/min of data: "
166- + str (var .values .max ())
167- + " "
168- + str (var .values .min ())
169- )
150+
151+ # Extract variable data from dataset and subset to tropical latitudes
152+ var = ds [variable ].sel (lat = slice (- 15 , 15 ))
153+
154+ # Get actual time range from the data
155+ actual_start = var .time .dt .year .values [0 ]
156+ actual_end = var .time .dt .year .values [- 1 ]
170157
171158 # Wavenumber Frequency Analysis
172159 spec_all = wf_analysis (var , ** opt )
@@ -220,6 +207,13 @@ def wf_analysis(x, **kwargs):
220207 # OPTIONAL kwargs:
221208 # segsize, noverlap, spd, latitude_bounds (tuple: (south, north)), dosymmetries, rmvLowFrq
222209
210+ # Interpolate missing values along longitude before spectral analysis
211+ if np .any (np .isnan (x )):
212+ logger .info (
213+ "Interpolating missing values along longitude before spectral analysis"
214+ )
215+ x = x .interpolate_na (dim = "lon" , method = "linear" , fill_value = "extrapolate" )
216+
223217 z2 = wf .spacetime_power (x , ** kwargs )
224218 z2avg = z2 .mean (dim = "component" )
225219 z2 .loc [{"frequency" : 0 }] = np .nan # get rid of spurious power at \nu = 0 (mean)
0 commit comments