From 943030ba3cfe12c36193ec61bde53d28f9b2b63a Mon Sep 17 00:00:00 2001 From: Jim Benedict Date: Mon, 12 Jun 2023 14:36:23 -0700 Subject: [PATCH 01/46] Initial commit of tropical subseasonal diagnostics --- .../zwf/zwf_functions.py | 640 +++++++++++++ .../zwf/zwf_plot.py | 859 ++++++++++++++++++ .../zwf/zwf_plot_DIFF.py | 321 +++++++ 3 files changed, 1820 insertions(+) create mode 100755 auxiliary_tools/tropical_subseasonal_diags/zwf/zwf_functions.py create mode 100755 auxiliary_tools/tropical_subseasonal_diags/zwf/zwf_plot.py create mode 100755 auxiliary_tools/tropical_subseasonal_diags/zwf/zwf_plot_DIFF.py diff --git a/auxiliary_tools/tropical_subseasonal_diags/zwf/zwf_functions.py b/auxiliary_tools/tropical_subseasonal_diags/zwf/zwf_functions.py new file mode 100755 index 000000000..1b5f6ed81 --- /dev/null +++ b/auxiliary_tools/tropical_subseasonal_diags/zwf/zwf_functions.py @@ -0,0 +1,640 @@ +import sys +import xarray as xr +import numpy as np +from scipy.signal import convolve2d, detrend +import logging +logging.basicConfig(level=logging.INFO) + + +def helper(): + """Prints all the functions that are included in this module.""" + f = [ + "decompose2SymAsym(arr)", + "rmvAnnualCycle(data, spd, fCrit)", + "smoothFrq121(data,nsmth_iter=1)", + "smoothBackground_wavefreq(data)", + "resolveWavesHayashi( varfft: xr.DataArray, nDayWin: int, spd: int ) -> xr.DataArray", + "split_hann_taper(series_length, fraction)", + "spacetime_power(data, segsize=96, noverlap=60, spd=1, latitude_bounds=None, dosymmetries=False, rmvLowFrq=False)", + "genDispersionCurves(nWaveType=6, nPlanetaryWave=50, rlat=0, Ahe=[50, 25, 12])"] + [print(fe) for fe in f] + + + +def getNearestInd1D(arr,val): + """Given a specified value, find the index of an array that most closely matches that value. + arr: numpy array or xarray DataArray + return: Integer + Example: If arr=[0.2, 0.5, -0.9, 1.3] and val=0.4, function would return 1. + Note: Uses python's 'argmin', which returns first occurrence if multiple indices "tie" + for closest to the specified value. + """ + difference_array = np.absolute(arr-val) + i_closest = int(difference_array.argmin()) + return(i_closest) + + + +def decompose2SymAsym(arr): + """Mimic NCL function to decompose into symmetric and asymmetric parts. + + arr: xarray DataArray + + return: DataArray with symmetric in SH, asymmetric in NH + + Note: + This function produces indistinguishable results from NCL version. + """ + lat_dim = arr.dims.index('lat') + # flag to follow NCL convention and put symmetric component in SH + # & asymmetric in NH + # method: use flip to reverse latitude, put in DataArray for coords, use loc/isel + # to assign to negative/positive latitudes (exact equator is left alone) + data_sym = 0.5*(arr.values + np.flip(arr.values, axis=lat_dim)) + data_asy = 0.5*(arr.values - np.flip(arr.values, axis=lat_dim)) + data_sym = xr.DataArray(data_sym, dims=arr.dims, coords=arr.coords) + data_asy = xr.DataArray(data_asy, dims=arr.dims, coords=arr.coords) + out = arr.copy() # might not be best to copy, but is safe + out.loc[{'lat':arr['lat'][arr['lat']<0]}] = data_sym.isel(lat=data_sym.lat<0) + out.loc[{'lat':arr['lat'][arr['lat']>0]}] = data_asy.isel(lat=data_asy.lat>0) + return out + + + +def rmvAnnualCycle(data, spd, fCrit): + """remove frequencies less than fCrit from data. + + data: xarray DataArray + spd: sampling frequency in samples-per-day + fCrit: frequency threshold; remove frequencies < fCrit + + return: xarray DataArray, shape of data + + Note: fft/ifft preserves the mean because z = fft(x), z[0] is the mean. + To keep the mean here, we need to keep the 0 frequency. + + Note: This function reproduces the results from the NCL version. + + Note: Two methods are available, one using fft/ifft and the other rfft/irfft. + They both produce output that is indistinguishable from NCL's result. + """ + dimz = data.sizes + ntim = dimz['time'] + time_ax = list(data.dims).index('time') + # Method 1: Uses the complex FFT, returns the negative frequencies too, but they + # should be redundant b/c they are conjugate of positive ones. + cf = np.fft.fft(data.values, axis=time_ax) + freq = np.fft.fftfreq(ntim, spd) + cf[(freq != 0)&(np.abs(freq) < fCrit), ...] = 0.0 # keeps the mean + z = np.fft.ifft(cf, n=ntim, axis=0) + # Method 2: Uses the real FFT. In this case, + # cf = np.fft.rfft(data.values, axis=time_ax) + # freq = np.linspace(1, (ntim*spd)//2, (ntim*spd)//2) / ntim + # fcrit_ndx = np.argwhere(freq < fCrit).max() + # if fcrit_ndx > 1: + # cf[1:fcrit_ndx+1, ...] = 0.0 + # z = np.fft.irfft(cf, n=ntim, axis=0) + z = xr.DataArray(z.real, dims=data.dims, coords=data.coords) + return z + + + +def smoothFrq121(data,nsmth_iter=1): + """Following what was used in the NCL routine 'wkSpaceTime', smooth input array + [nsmth_iter] times along the frequency dimension, for a sub-section of wavenumbers + (as the smoothing is cosmetic for plotting, only smooth for a subset of wavenumbers + to avoid unnecessary smoothing of non-plotted values). + + Do not use 0 frequency when smoothing. Uses weights that sum to 1 to ensure + smoothing is conservative. + """ + assert isinstance(data, xr.DataArray) + print("\nFrom smoothFrq121: Frequency smoothing " + str(nsmth_iter) + " times for subset of wavenumbers (pos frqs only)") + nfrq = len(data['frequency']) + i_frq0 = int(np.where(data['frequency']==0)[0]) # index of 'frequency' coord where it equals 0 + for smth_iter in range(nsmth_iter): + data[...,i_frq0+1] = 0.75 * data[...,i_frq0+1] + 0.25 * data[...,i_frq0+2] + data[...,-1] = 0.25 * data[...,-2] + 0.75 * data[...,-1] + for i in range(i_frq0+2, nfrq-1): + data[...,i] = 0.25 * data[...,i-1] + 0.5 * data[...,i] + 0.25 * data[...,i+1] + return(data) + + + +def smoothBackground_wavefreq(data): + """From Wheeler and Kiladis (1999, doi: ): + "[Smooth] many times with a 1-2-1 filter in frequency and wavenumber. The number of + passes of the 1-2-1 filter we have used is 10 in frequency throughout, and from 10 + to 40 in wavenumber, being 10 at low frequencies and 40 at higher frequencies + increasing in two different steps." + + Here we do the following smoothing along the wavenumber dimension (note: we only + smooth in the positive frequency domain as these will be the values plotted!): + 0 < frq < 0.1 : 5 1-2-1 smoothing passes + 0.1 <= frq < 0.2: 10 1-2-1 smoothing passes + 0.2 <= frq < 0.3: 20 1-2-1 smoothing passes + 0.3 <= frq : 40 1-2-1 smoothing passes + This follows what was used in the NCL routine 'wkSpaceTime'. + """ + assert isinstance(data, xr.DataArray) + nfrq = len(data['frequency']) + i_frq0 = int(np.where(data['frequency']==0)[0]) # index of 'frequency' coord where it equals 0 +# i_frq03 = getNearestInd1D(data['frequency'], 0.3) # index of 'frequency' coord closest to 0.3 + i_minwav4smth = int(np.where(data['wavenumber']==-27)[0]) # index of 'wavenumber' coord where it equals -27 + i_maxwav4smth = int(np.where(data['wavenumber']==27)[0]) # index of 'wavenumber' coord where it equals 27 + + # Looping over positive frequencies, smooth in wavenumber. The number of smoothing + # passes in wavenumber is dependent on the frequency, with more smoothing at high + # frequencies and less smoothing at low frequencies + print("\nSmoothing background spectrum in wavenumber (pos frq only)...") + for i in range(i_frq0+1,nfrq): # Loop over all positive frequencies + + if(data['frequency'][i] < 0.1): + nsmth_iter = 5 + print(" Wavenumber smoothing " + str(nsmth_iter) + " times for freq: " + str(float(data['frequency'][i]))) + for smth_iter in range(nsmth_iter): + for j in range(i_minwav4smth, i_maxwav4smth+1): + data[j,i] = 0.25 * data[j-1,i] + 0.5 * data[j,i] + 0.25 * data[j+1,i] + + if(data['frequency'][i] >= 0.1 and data['frequency'][i] < 0.2): + nsmth_iter = 10 + print(" Wavenumber smoothing " + str(nsmth_iter) + " times for freq: " + str(float(data['frequency'][i]))) + for smth_iter in range(10): + for j in range(i_minwav4smth, i_maxwav4smth+1): + data[j,i] = 0.25 * data[j-1,i] + 0.5 * data[j,i] + 0.25 * data[j+1,i] + + if(data['frequency'][i] >= 0.2 and data['frequency'][i] < 0.3): + nsmth_iter = 20 + print(" Wavenumber smoothing " + str(nsmth_iter) + " times for freq: " + str(float(data['frequency'][i]))) + for smth_iter in range(20): + for j in range(i_minwav4smth, i_maxwav4smth+1): + data[j,i] = 0.25 * data[j-1,i] + 0.5 * data[j,i] + 0.25 * data[j+1,i] + + if(data['frequency'][i] >= 0.3): + nsmth_iter = 40 + print(" Wavenumber smoothing " + str(nsmth_iter) + " times for freq: " + str(float(data['frequency'][i]))) + for smth_iter in range(40): + for j in range(i_minwav4smth, i_maxwav4smth+1): + data[j,i] = 0.25 * data[j-1,i] + 0.5 * data[j,i] + 0.25 * data[j+1,i] + + # For all wavenumbers, smooth in frequency: 10 passes + # Do not use 0 frequency when smoothing + # Use weights that sum to 1 to ensure smoothing is conservative + nsmth_iter = 10 + print("Frequency smoothing " + str(nsmth_iter) + " times for all wavenumbers (pos frqs only)") + for smth_iter in range(10): + data[:,i_frq0+1] = 0.75 * data[:,i_frq0+1] + 0.25 * data[:,i_frq0+2] + data[:,-1] = 0.25 * data[:,-2] + 0.75 * data[:,-1] + for i in range(i_frq0+2, nfrq-1): + data[:,i] = 0.25 * data[:,i-1] + 0.5 * data[:,i] + 0.25 * data[:,i+1] + return(data) + + + +def resolveWavesHayashi( varfft: xr.DataArray, nDayWin: int, spd: int ) -> xr.DataArray: + """This is a direct translation from the NCL routine to python/xarray. + input: + varfft : expected to have rightmost dimensions of wavenumber and frequency. + varfft : expected to be an xarray DataArray with coordinate variables. + nDayWin : integer that is the length of the segments in days. + spd : the sampling frequency in `timesteps` per day. + + returns: + a DataArray that is reordered to have correct westward & eastward propagation. + + """ + #------------------------------------------------------------- + # Special reordering to resolve the Progressive and Retrogressive waves + # Reference: Hayashi, Y. + # A Generalized Method of Resolving Disturbances into + # Progressive and Retrogressive Waves by Space and + # Fourier and TimeCross Spectral Analysis + # J. Meteor. Soc. Japan, 1971, 49: 125-128. + #------------------------------------------------------------- + + # in NCL varfft is dimensioned (2,mlon,nSampWin), but the first dim doesn't matter b/c python supports complex numbers. + # + # Create array PEE(NL+1,NT+1) which contains the (real) power spectrum. + # all the following assume indexing starting with 0 + # In this array (PEE), the negative wavenumbers will be from pn=0 to NL/2-1 (left). + # The positive wavenumbers will be for pn=NL/2+1 to NL (right). + # Negative frequencies will be from pt=0 to NT/2-1 (left). + # Positive frequencies will be from pt=NT/2+1 to NT (right). + # Information about zonal mean will be for pn=NL/2 (middle). + # Information about time mean will be for pt=NT/2 (middle). + # Information about the Nyquist Frequency is at pt=0 and pt=NT + # + + # In PEE, define the + # WESTWARD waves to be either + # positive frequency and negative wavenumber + # OR + # negative freq and positive wavenumber. + # EASTWARD waves are either positive freq and positive wavenumber + # OR negative freq and negative wavenumber. + + # Note that frequencies are returned from fftpack are ordered like so + # input_time_pos [ 0 1 2 3 4 5 6 7 ] + # ouput_fft_coef [mean 1/7 2/7 3/7 nyquist -3/7 -2/7 -1/7] + # mean,pos freq to nyq,neg freq hi to lo + # + # Rearrange the coef array to give you power array of freq and wave number east/west + # Note east/west wave number *NOT* eq to fft wavenumber see Hayashi '71 + # Hence, NCL's 'cfftf_frq_reorder' can *not* be used. + # BPM: This goes for np.fft.fftshift + # + # For ffts that return the coefficients as described above, here is the algorithm + # coeff array varfft(2,n,t) dimensioned (2,0:numlon-1,0:numtim-1) + # new space/time pee(2,pn,pt) dimensioned (2,0:numlon ,0:numtim ) + # + # NOTE: one larger in both freq/space dims + # the initial index of 2 is for the real (indx 0) and imag (indx 1) parts of the array + # + # + # if | 0 <= pn <= numlon/2-1 then | numlon/2 <= n <= 1 + # | 0 <= pt < numtim/2-1 | numtim/2 <= t <= numtim-1 + # + # if | 0 <= pn <= numlon/2-1 then | numlon/2 <= n <= 1 + # | numtime/2 <= pt <= numtim | 0 <= t <= numtim/2 + # + # if | numlon/2 <= pn <= numlon then | 0 <= n <= numlon/2 + # | 0 <= pt <= numtim/2 | numtim/2 <= t <= 0 + # + # if | numlon/2 <= pn <= numlon then | 0 <= n <= numlon/2 + # | numtim/2+1 <= pt <= numtim | numtim-1 <= t <= numtim/2 + + # local variables : dimvf, numlon, N, varspacetime, pee, wave, freq + + # bpm: if varfft is a numpy array, then we need to know which dim is longitude + # if it is an xr.DataArray, then we can just use that directly. This is + # reason enough to insist on a DataArray. + # varfft should have a last dimension of "segments" of size N; should make a convention for the name of that dimension and insist on it here. + logging.debug(f"[Hayashi] nDayWin: {nDayWin}, spd: {spd}") + dimnames = varfft.dims + dimvf = varfft.shape + mlon = len(varfft['wavenumber']) + N = dimvf[-1] + logging.info(f"[Hayashi] input dims is {dimnames}, {dimvf}") + logging.info(f"[Hayashi] input coords is {varfft.coords}") + if len(dimnames) != len(varfft.coords): + logging.error("The size of varfft.coords is incorrect.") + raise ValueError("STOP") + + nshape = list(dimvf) + nshape[-2] += 1 + nshape[-1] += 1 + logging.debug(f"[Hayashi] The nshape ends up being {nshape}") + # this is a reordering, use Ellipsis to allow arbitrary number of dimensions, + # but we insist that the wavenumber and frequency dims are rightmost. + # we will fill the new array in increasing order (arbitrary choice) + logging.debug("allocate the re-ordered array") + varspacetime = np.full(nshape, np.nan, dtype=type(varfft)) + # first two are the negative wavenumbers (westward), second two are the positive wavenumbers (eastward) + logging.debug(f"[Hayashi] Assign values into array. Notable numbers: mlon//2={mlon//2}, N//2={N//2}") + varspacetime[..., 0:mlon//2, 0:N//2 ] = varfft[..., mlon//2:0:-1, N//2:] # neg.k, pos.w + varspacetime[..., 0:mlon//2, N//2: ] = varfft[..., mlon//2:0:-1, 0:N//2+1] # pos.k, + varspacetime[..., mlon//2: , 0:N//2+1] = varfft[..., 0:mlon//2+1, N//2::-1] # assign eastward & neg.freq. + varspacetime[..., mlon//2: , N//2+1: ] = varfft[..., 0:mlon//2+1, -1:N//2-1:-1] # assign eastward & pos.freq. + print(varspacetime.shape) + # Create the real power spectrum pee = sqrt(real^2+imag^2)^2 + logging.debug("calculate power") + pee = (np.abs(varspacetime))**2 # JJB: abs(a+bi) = sqrt(a**2 + b**2), which is what is done in NCL's resolveWavesHayashi + logging.debug("put into DataArray") + # add meta data for use upon return + wave = np.arange(-mlon // 2, (mlon // 2 )+ 1, 1, dtype=int) + freq = np.linspace(-1*nDayWin*spd/2, nDayWin*spd/2, (nDayWin*spd)+1) / nDayWin + + print(f"freq size is {freq.shape}.") + odims = list(dimnames) + odims[-2] = "wavenumber" + odims[-1] = "frequency" + ocoords = {} + for c in varfft.coords: + logging.debug(f"[hayashi] working on coordinate {c}") + if (c != "wavenumber") and (c != "frequency"): + ocoords[c] = varfft[c] + elif c == "wavenumber": + ocoords['wavenumber'] = wave + elif c == "frequency": + ocoords['frequency'] = freq + pee = xr.DataArray(pee, dims=odims, coords=ocoords) + return pee + + + +def split_hann_taper(series_length, fraction): + """Implements `split cosine bell` taper of length series_length where only fraction of points are tapered (combined on both ends). + + This returns a function that tapers to zero on the ends. To taper to the mean of a series X: + XTAPER = (X - X.mean())*series_taper + X.mean() + """ + npts = int(np.rint(fraction * series_length)) # total size of taper + taper = np.hanning(npts) + series_taper = np.ones(series_length) + series_taper[0:npts//2+1] = taper[0:npts//2+1] + series_taper[-npts//2+1:] = taper[npts//2+1:] + return series_taper + + + +def spacetime_power(data, segsize=96, noverlap=60, spd=1, latitude_bounds=None, dosymmetries=False, rmvLowFrq=False): + """Perform space-time spectral decomposition and return power spectrum following Wheeler-Kiladis approach. + + data: an xarray DataArray to be analyzed; needs to have (time, lat, lon) dimensions. + segsize: integer denoting the size of time samples that will be decomposed (typically about 96) + noverlap: integer denoting the number of days of overlap from one segment to the next (typically about segsize-60 => 2-month overlap) + spd: sampling rate, in "samples per day" (e.g. daily=1, 6-houry=4) + + latitude_bounds: a tuple of (southern_extent, northern_extent) to reduce data size. + + dosymmetries: if True, follow NCL convention of putting symmetric component in SH, antisymmetric in NH + If True, the function returns a DataArray with a `component` dimension. + + rmvLowFrq: if True, remove frequencies < 1/segsize from data. + + Method + ------ + 1. Subsample in latitude if latitude_bounds is specified. + 2. Detrend the data (but keeps the mean value, as in NCL) + 3. High-pass filter if rmvLowFrq is True + 4. Construct symmetric/antisymmetric array if dosymmetries is True. + 5. Construct overlapping window view of data. + 6. Detrend the segments (strange enough, removing mean). + 7. Apply taper in time dimension of windows (aka segments). + 8. Fourier transform + 9. Apply Hayashi reordering to get propagation direction & convert to power. + 10. return DataArray with power. + + Notes + ----- + Upon returning power, this should be comparable to "raw" spectra. + Next step would be be to smooth with `smooth_wavefreq`, + and divide raw spectra by smooth background to obtain "significant" spectral power. + + """ + + segsize_steps = spd*segsize # Segment length, in time step units + noverlap_steps = spd*noverlap # Segment overlap, in time step units + stride_steps = segsize_steps - noverlap_steps # Stride for overlapping windows, in time step units + + if latitude_bounds is not None: + assert isinstance(latitude_bounds, tuple) + data = data.sel(lat=slice(*latitude_bounds)) # CAUTION: is this a mutable argument? + logging.info(f"Data reduced by latitude bounds. Size is {data.sizes}") + slat = latitude_bounds[0] + nlat = latitude_bounds[1] + else: + slat = data['lat'].min().item() + nlat = data['lat'].max().item() + + # Remove the *long-term* temporal linear trend + # Using scipy.signal.detrend will remove the mean as well, but we will add the time + # mean back into the detrended data to be consistent with the approach used in the + # NCL version (https://www.ncl.ucar.edu/Document/Functions/Diagnostics/wkSpaceTime.shtml): + xmean = data.mean(dim='time') + xdetr = detrend(data.values, axis=0, type='linear') + xdetr = xr.DataArray(xdetr, dims=data.dims, coords=data.coords) + xdetr += xmean # put the mean back in + # --> Tested and confirmed that this approach gives same answer as NCL + + # filter low-frequencies + if rmvLowFrq: + data = rmvAnnualCycle(xdetr, spd, 1/segsize_steps) + # --> Tested and confirmed that this function gives same answer as NCL + + # NOTE: at this point "data" has been modified to have its long-term linear trend and + # low frequencies (lower than 1./segsize_steps) removed + + dimsizes = data.sizes # dict + lon_size = dimsizes['lon'] + lat_size = dimsizes['lat'] + lat_dim = data.dims.index('lat') + if dosymmetries: + data = decompose2SymAsym(data) + # testing: pass -- Gets the same result as NCL. + + # Windowing with the xarray "rolling" operation, and then limit overlap with `construct` to produce a new dataArray. + # JJB: Windowing segment bounds have been validated with test prints. + x_roll = data.rolling(time=segsize_steps, min_periods=segsize_steps) # WK99 use 96-day window + x_win = x_roll.construct("segments", stride=stride_steps).dropna("time") # WK99 say "2-month" overlap; dims: (nSegments,nlat,nlon,segment_length_steps) + logging.debug(f"[spacetime_power] x_win shape is {x_win.shape}") + + # For each segment, remove the temporal mean and linear trend: + if np.logical_not(np.any(np.isnan(x_win))): + logging.info("No missing, so use simplest segment detrend.") + x_win_detr = detrend(x_win.values, axis=-1, type='linear') #<-- missing data makes this not work; axis=-1 for segment window dimension + x_win = xr.DataArray(x_win_detr, dims=x_win.dims, coords=x_win.coords) + else: + logging.warning("EXTREME WARNING -- This method to detrend with missing values present does not quite work, probably need to do interpolation instead.") + logging.warning("There are missing data in x_win, so have to try to detrend around them.") + x_win_cp = x_win.values.copy() + logging.info(f"[spacetime_power] x_win_cp windowed data has shape {x_win_cp.shape} \n \t It is a numpy array, copied from x_win which has dims: {x_win.sizes} \n \t ** about to detrend this in the rightmost dimension.") + x_win_cp[np.logical_not(np.isnan(x_win_cp))] = detrend(x_win_cp[np.logical_not(np.isnan(x_win_cp))]) + x_win = xr.DataArray(x_win_cp, dims=x_win.dims, coords=x_win.coords) + + + # 3. Taper in time to reduce spectral "leakage" and make the segment periodic for FFT analysis + # taper = np.hanning(segsize) # WK seem to use some kind of stretched out hanning window; unclear if it matters + taper = split_hann_taper(segsize_steps, 0.1) # try to replicate NCL's approach of tapering 10% of segment window + x_wintap = x_win*taper # would do XTAPER = (X - X.mean())*series_taper + X.mean() + # But since we have removed the mean, taper going to 0 is equivalent to taper going to the mean. + + + # Do the transform using 2D FFT + # JJB: As written below, this does not give the same answer as the 2-step approach, + # not sure why but didn't look into it more. Will use 2-step approach to be + # safe (also mimics NCL version better) + #z = np.fft.fft2(x_wintap, axes=(2,3)) / (lon_size * segsize) + + # Or do the transform with 2 steps + # Note: x_wintap is shape (nSegments, nlat, nlon, segment_length_steps) + z = np.fft.fft(x_wintap, axis=2) / lon_size # note that np.fft.fft() produces same answers as NCL cfftf; axis=2 to do fft along dim=lon + z = np.fft.fft(z, axis=3) / segsize_steps # axis=3 to do fft along dim=segment_length_steps + + z = xr.DataArray(z, dims=("time","lat","wavenumber","frequency"), + coords={"time":x_wintap["time"], + "lat":x_wintap["lat"], + "wavenumber":np.fft.fftfreq(lon_size, 1/lon_size), + "frequency":np.fft.fftfreq(segsize_steps, 1/spd)}) + # + # The FFT is returned following ``standard order`` which has negative frequencies in second half of array. + # + # IMPORTANT: + # If this were typical 2D FFT, we would do the following to get the frequencies and reorder: + # z_k = np.fft.fftfreq(x_wintap.shape[-2], 1/lon_size) + # z_v = np.fft.fftfreq(x_wintap.shape[-1], 1) # Assumes 1/(1-day) timestep + # reshape to get the frequencies centered + # z_centered = np.fft.fftshift(z, axes=(2,3)) + # z_k_c = np.fft.fftshift(z_k) + # z_v_c = np.fft.fftshift(z_v) + # and convert to DataArray as this: + # d1 = list(x_win.dims) + # d1[-2] = "wavenumber" + # d1[-1] = "frequency" + # c1 = {} + # for d in d1: + # if d in x_win.coords: + # c1[d] = x_win[d] + # elif d == "wavenumber": + # c1[d] = z_k_c + # elif d == "frequency": + # c1[d] = z_v_c + # z_centered = xr.DataArray(z_centered, dims=d1, coords=c1) + # BUT THAT IS INCORRECT TO GET THE PROPAGATION DIRECTION OF ZONAL WAVES + # (in testing, it seems to end up opposite in wavenumber) + # Apply reordering per Hayashi to get correct wave propagation convention + # this function is customized to expect z to be a DataArray + z_pee = resolveWavesHayashi(z, segsize_steps//spd, spd) + # z_pee is spectral power already. + # z_pee is a DataArray w/ coordinate vars for wavenumber & frequency + + # average over all available segments and sum over latitude + # OUTPUT DEPENDS ON SYMMETRIES + if dosymmetries: + # multipy by 2 b/c we only used one hemisphere + # JJB: This summing of powers over latitude appears to be done a little bit earlier + # here when compared to NCL's version, but this should not matter. + # JJB: I think the factor of 2 comes in here because the symmetric and antisymmetric + # data were packaged into either the Norther or Southern Hemis, so the 'sum' + # across latitude would need to be multiplied by 2. For example, if the sym/asym data + # were -not- packaged into single hemispheres, no multiplicative factor would + # be needed. + # JJB: Also, 'squeeze' removed degenerate dimensions 'time' and 'lat' + z_symmetric = 2.0 * z_pee.isel(lat=z_pee.lat<=0).mean(dim='time').sum(dim='lat').squeeze() + z_symmetric.name = "power" + z_antisymmetric = 2.0 * z_pee.isel(lat=z_pee.lat>0).mean(dim='time').sum(dim='lat').squeeze() + z_antisymmetric.name = "power" + z_final = xr.concat([z_symmetric, z_antisymmetric], "component") + z_final = z_final.assign_coords({"component":["symmetric","antisymmetric"]}) + print("\nMetadata for z_final is:") + print(z_final.dims) + print(z_final.coords) + print(z_final.attrs) + else: + lat = z_pee['lat'] + lat_inds = np.argwhere(((lat <= nlat)&(lat >= slat)).values).squeeze() + z_final = z_pee.isel(lat=lat_inds).mean(dim='time').sum(dim='lat').squeeze() + return z_final + + + +def genDispersionCurves(nWaveType=6, nPlanetaryWave=50, rlat=0, Ahe=[50, 25, 12]): + """ + Function to derive the shallow water dispersion curves. Closely follows NCL version. + + input: + nWaveType : integer, number of wave types to do + nPlanetaryWave: integer + rlat: latitude in radians (just one latitude, usually 0.0) + Ahe: [50.,25.,12.] equivalent depths + ==> defines parameter: nEquivDepth ; integer, number of equivalent depths to do == len(Ahe) + + returns: tuple of size 2 + Afreq: Frequency, shape is (nWaveType, nEquivDepth, nPlanetaryWave) + Apzwn: Zonal savenumber, shape is (nWaveType, nEquivDepth, nPlanetaryWave) + + notes: + The outputs contain both symmetric and antisymmetric waves. In the case of + nWaveType == 6: + 0,1,2 are (ASYMMETRIC) "MRG", "IG", "EIG" (mixed rossby gravity, inertial gravity, equatorial inertial gravity) + 3,4,5 are (SYMMETRIC) "Kelvin", "ER", "IG" (Kelvin, equatorial rossby, inertial gravity) + """ + nEquivDepth = len(Ahe) # this was an input originally, but I don't know why. + pi = np.pi + radius = 6.37122e06 # [m] average radius of earth + g = 9.80665 # [m/s] gravity at 45 deg lat used by the WMO + omega = 7.292e-05 # [1/s] earth's angular vel + # U = 0.0 # NOT USED, so Commented + # Un = 0.0 # since Un = U*T/L # NOT USED, so Commented + ll = 2.*pi*radius*np.cos(np.abs(rlat)) + Beta = 2.*omega*np.cos(np.abs(rlat))/radius + fillval = 1e20 + + # NOTE: original code used a variable called del, + # I just replace that with `dell` because `del` is a python keyword. + + # Initialize the output arrays + Afreq = np.empty((nWaveType, nEquivDepth, nPlanetaryWave)) + Apzwn = np.empty((nWaveType, nEquivDepth, nPlanetaryWave)) + + for ww in range(1, nWaveType+1): + for ed, he in enumerate(Ahe): + # this loops through the specified equivalent depths + # ed provides index to fill in output array, while + # he is the current equivalent depth + # T = 1./np.sqrt(Beta)*(g*he)**(0.25) This is close to pre-factor of the dispersion relation, but is not used. + c = np.sqrt(g * he) # phase speed + L = np.sqrt(c/Beta) # was: (g*he)**(0.25)/np.sqrt(Beta), this is Rossby radius of deformation + + for wn in range(1, nPlanetaryWave+1): + s = -20.*(wn-1)*2./(nPlanetaryWave-1) + 20. + k = 2.0 * pi * s / ll + kn = k * L + + # Anti-symmetric curves + if (ww == 1): # MRG wave + if (k < 0): + dell = np.sqrt(1.0 + (4.0 * Beta)/(k**2 * c)) + deif = k * c * (0.5 - 0.5 * dell) + + if (k == 0): + deif = np.sqrt(c * Beta) + + if (k > 0): + deif = fillval + + + if (ww == 2): # n=0 IG wave + if (k < 0): + deif = fillval + + if (k == 0): + deif = np.sqrt( c * Beta) + + if (k > 0): + dell = np.sqrt(1.+(4.0*Beta)/(k**2 * c)) + deif = k * c * (0.5 + 0.5 * dell) + + + if (ww == 3): # n=2 IG wave + n=2. + dell = (Beta*c) + deif = np.sqrt((2.*n+1.)*dell + (g*he) * k**2) + # do some corrections to the above calculated frequency....... + for i in range(1,5+1): + deif = np.sqrt((2.*n+1.)*dell + (g*he) * k**2 + g*he*Beta*k/deif) + + + # symmetric curves + if (ww == 4): # n=1 ER wave + n=1. + if (k < 0.0): + dell = (Beta/c)*(2.*n+1.) + deif = -Beta*k/(k**2 + dell) + else: + deif = fillval + + if (ww == 5): # Kelvin wave + deif = k*c + + if (ww == 6): # n=1 IG wave + n=1. + dell = (Beta*c) + deif = np.sqrt((2. * n+1.) * dell + (g*he)*k**2) + # do some corrections to the above calculated frequency + for i in range(1,5+1): + deif = np.sqrt((2.*n+1.)*dell + (g*he)*k**2 + g*he*Beta*k/deif) + + eif = deif # + k*U since U=0.0 + P = 2.*pi/(eif*24.*60.*60.) # => PERIOD + # dps = deif/k # Does not seem to be used. + # R = L #<-- this seemed unnecessary, I just changed R to L in Rdeg + # Rdeg = (180.*L)/(pi*6.37e6) # And it doesn't get used. + + Apzwn[ww-1,ed,wn-1] = s + if (deif != fillval): + # P = 2.*pi/(eif*24.*60.*60.) # not sure why we would re-calculate now + Afreq[ww-1,ed,wn-1] = 1./P + else: + Afreq[ww-1,ed,wn-1] = fillval + return Afreq, Apzwn \ No newline at end of file diff --git a/auxiliary_tools/tropical_subseasonal_diags/zwf/zwf_plot.py b/auxiliary_tools/tropical_subseasonal_diags/zwf/zwf_plot.py new file mode 100755 index 000000000..5357b8ba0 --- /dev/null +++ b/auxiliary_tools/tropical_subseasonal_diags/zwf/zwf_plot.py @@ -0,0 +1,859 @@ +# Script to compute and plot spectral powers of a subseasonal tropical field in +# zonal wavenumber-frequency space. Both the plot files and files containing the +# associated numerical data shown in the plots are created. +# +# User-defined inputs can be entered near the beginning of the main script (the line +# if __name__ == "__main__":) +# +# To invoke on NERSC-Cori or LCRC: +# First, activate E3SM unified environment. E.g, for NERSC-Cori: +# > source /global/common/software/e3sm/anaconda_envs/load_latest_e3sm_unified_cori-knl.sh +# Then, simply run: +# > python zwf_plot.py + +import sys +import os +import math +import glob +import numpy as np +import xarray as xr +# our local module: +import zwf_functions as wf +#import matplotlib as mpl +import matplotlib.pyplot as plt +from matplotlib.colors import ListedColormap, BoundaryNorm + + +def find_nearest(array, value): + array = np.asarray(array) + idx = (np.abs(array - value)).argmin() + return idx,array[idx] + """Return index of [array] closest in value to [value] + Example: + array = [ 0.21069679 0.61290182 0.63425412 0.84635244 0.91599191 0.00213826 + 0.17104965 0.56874386 0.57319379 0.28719469] + print(find_nearest(array, value=0.5)) + # 0.568743859261 + + """ + + +def wf_analysis(x, **kwargs): + """Return zonal wavenumber-frequency power spectra of x. The returned spectra are: + spec_sym: Raw (non-normalized) power spectrum of the component of x that is symmetric about the equator. + spec_asym: Raw (non-normalized) power spectrum of the component of x that is antisymmetric about the equator. + nspec_sym: Normalized (by a smoothed red-noise background spectrum) power spectrum of the component of x that is symmetric about the equator. + nspec_asym: Normalized (by a smoothed red-noise background spectrum) power spectrum of the component of x that is antisymmetric about the equator. + + The NCL version of 'wkSpaceTime' smooths the symmetric and antisymmetric components + along the frequency dimension using a 1-2-1 filter once. + + """ + # Get the "raw" spectral power + # OPTIONAL kwargs: + # segsize, noverlap, spd, latitude_bounds (tuple: (south, north)), dosymmetries, rmvLowFrq + + z2 = wf.spacetime_power(x, **kwargs) + z2avg = z2.mean(dim='component') + z2.loc[{'frequency':0}] = np.nan # get rid of spurious power at \nu = 0 (mean) + + # Following NCL's wkSpaceTime, apply one pass of a 1-2-1 filter along the frequency + # domain to the raw (non-normalized) spectra/um. + # Do not use 0 frequency when smoothing here. + # Use weights that sum to 1 to ensure that smoothing is conservative. + z2s = wf.smoothFrq121(z2,1) + + # The background is supposed to be derived from both symmetric & antisymmetric + # Inputs to the background spectrum calculation should be z2avg + background = wf.smoothBackground_wavefreq(z2avg) + # separate components + spec_sym = z2s[0,...] + spec_asy = z2s[1,...] + # normalize: Following NCL's wkSpaceTime, use lightly smoothed version of spectra/um + # as numerator + nspec_sym = spec_sym / background + nspec_asy = spec_asy / background + return spec_sym, spec_asy, nspec_sym, nspec_asy, background + + +def plot_raw_symmetric_spectrum(s, ofil=None, dataDesc=None, clevs=None, cmapSpec='viridis', + varName=None, sourceID=None, do_zoom=False, + disp_col='black', disp_thk=1.5, disp_alpha=0.60, + perfrq_col='dimgray', perfrq_thk=1.0, perfrq_alpha=0.80, equivDepths=[50, 25, 12]): + """Basic plot of non-normalized (raw) symmetric power spectrum with shallow water curves.""" + + text_offset = 0.005 + fb = [0, .8] # frequency bounds for plot + wnb = [-15, 15] # zonal wavenumber bounds for plot + if(max(s['frequency'].values) == 0.5): + fb = [0, .5] + if(do_zoom): + fb = [0, .18] + wnb = [-7, 7] + # get data for dispersion curves: + swfreq,swwn = wf.genDispersionCurves(Ahe=equivDepths) + # swfreq.shape # -->(6, 3, 50) + swf = np.where(swfreq == 1e20, np.nan, swfreq) + swk = np.where(swwn == 1e20, np.nan, swwn) + + cmapSpecUse = ListedColormap(cmapSpec[1:-1]) # recall: range is NOT inclusive for final index + cmapSpecUse.set_under(cmapSpec[0]) + cmapSpecUse.set_over(cmapSpec[-1]) + normSpecUse = BoundaryNorm(clevs, cmapSpecUse.N) + + # Final data refinement: transpose and trim, set 0 freq to NaN, take log10, refine metadata + z = s.transpose().sel(frequency=slice(*fb), wavenumber=slice(*wnb)) + z.loc[{'frequency':0}] = np.nan + east_power = z.sel(frequency=slice((1./96.),(1./24.)), wavenumber=slice(1,3)).sum() + west_power = z.sel(frequency=slice((1./96.),(1./24.)), wavenumber=slice(-3,-1)).sum() + ew_ratio = east_power / west_power + print("\neast_power: %12.5f" % east_power) + print("west_power: %12.5f" % west_power) + print("ew_ratio: %12.5f\n" % ew_ratio) + + z = np.log10(z) + z.attrs["long_name"] = varName + ": log-base10 of lightly smoothed spectral power of component symmetric about equator" + z.attrs["method"] = "Follows Figure 1 methods of Wheeler and Kiladis (1999; https://doi.org/10.1175/1520-0469(1999)056<0374:CCEWAO>2.0.CO;2)" + z.attrs["ew_ratio_method"] = "Sum of raw (not log10) symmetric spectral power for ZWNs +/- 1-3, periods 24-96 days" + z.attrs["east_power"] = east_power.values + z.attrs["west_power"] = west_power.values + z.attrs["ew_ratio"] = ew_ratio.values + + + fig, ax = plt.subplots() + kmesh0, vmesh0 = np.meshgrid(z['wavenumber'], z['frequency']) + #img = ax.contourf(kmesh0, vmesh0, z, levels=np.linspace(0.2, 3.0, 16), cmap='Spectral_r', extend='both') + img = ax.contourf(kmesh0, vmesh0, z, levels=clevs, cmap=cmapSpecUse, norm=normSpecUse, extend='both') + img2 = ax.contour(kmesh0, vmesh0, z, levels=clevs, linewidths=1., linestyles='solid', colors='gray', alpha=0.7) + ax.axvline(0, linestyle='dashed', color=perfrq_col, linewidth=perfrq_thk, alpha=disp_alpha) + if( (1./30.) < fb[1] ): + ax.axhline((1./30.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./30.)+text_offset,'30 days',color=perfrq_col, alpha=perfrq_alpha) + if( (1./6.) < fb[1] ): + ax.axhline((1./6.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./6.)+text_offset,'6 days',color=perfrq_col, alpha=perfrq_alpha) + if( (1./3.) < fb[1] ): + ax.axhline((1./3.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./3.)+text_offset,'3 days',color=perfrq_col, alpha=perfrq_alpha) + for ii in range(3,6): + ax.plot(swk[ii, 0,:], swf[ii,0,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + ax.plot(swk[ii, 1,:], swf[ii,1,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + ax.plot(swk[ii, 2,:], swf[ii,2,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + ax.set_xlim(wnb) + ax.set_ylim(fb) + #ax.set_title(varName + ": Log ${\sum_{15^{\circ}S}^{15^{\circ}N} Power_{SYM}}$") # Version w/ LaTeX + ax.set_title(f"{varName}: Log{{Sum(Power) from 15°S-15°N}}\n") # Version w/o LaTeX + ax.set_title(sourceID, loc='left') + ax.set_title("Symmetric", loc='right') + plt.ylabel("Frequency (CPD)") + plt.xlabel("Zonal wavenumber") + plt.gcf().text(0.12, 0.03, "Westward", fontsize=11) + plt.gcf().text(0.64, 0.03, "Eastward", fontsize=11) + fig.colorbar(img) + if ofil is not None: + fig.savefig(ofil, bbox_inches='tight', dpi=300) + print("Plot file created: %s\n" % ofil) + + # Save plotted data z to file as xArray data array + if(not do_zoom): + z.to_netcdf(outDataDir + "/zwfData_raw_sym_" + dataDesc + ".nc") + + +def plot_normalized_symmetric_spectrum(s, ofil=None, dataDesc=None, clevs=None, cmapSpec='Spectral_r', + varName=None, sourceID=None, do_zoom=False, + disp_col='black', disp_thk=1.5, disp_alpha=0.60, + perfrq_col='dimgray', perfrq_thk=1.0, perfrq_alpha=0.80, equivDepths=[50, 25, 12]): + """Basic plot of normalized symmetric power spectrum with shallow water curves.""" + + text_offset = 0.005 + fb = [0, .8] # frequency bounds for plot + wnb = [-15, 15] # zonal wavenumber bounds for plot + if(max(s['frequency'].values) == 0.5): + fb = [0, .5] + if(do_zoom): + fb = [0, .18] + wnb = [-7, 7] + # get data for dispersion curves: + swfreq,swwn = wf.genDispersionCurves(Ahe=equivDepths) + # swfreq.shape # -->(6, 3, 50) + # For n=1 ER waves, allow dispersion curves to touch 0 -- this is for plot aesthetics only + for i in range(0,3): # loop 0-->2 for the assumed 3 shallow water dispersion curves for ER waves + indMinPosFrqER = np.where(swwn[3,i,:] >= 0., swwn[3,i,:], 1e20).argmin() # index of swwn for least positive wn + swwn[3,i,indMinPosFrqER],swfreq[3,i,indMinPosFrqER] = 0.,0. # this sets ER's frequencies to 0. at wavenumber 0. + swf = np.where(swfreq == 1e20, np.nan, swfreq) + swk = np.where(swwn == 1e20, np.nan, swwn) + +# for i in range(6): +# print(f'\nwaveType={i}') +# print(f"{'waveNum':12} {'frq (ih=0)':12} {'frq (ih=1)':12} {'frq (ih=2)':12}") +# for j in range(50): +# print(f'{swk[i,0,j]:12.4f} {swf[i,0,j]:12.4f} {swf[i,1,j]:12.4f} {swf[i,2,j]:12.4f}') +# sys.exit() + + + cmapSpecUse = ListedColormap(cmapSpec[1:-1]) # recall: range is NOT inclusive for final index + cmapSpecUse.set_under(cmapSpec[0]) + cmapSpecUse.set_over(cmapSpec[-1]) + normSpecUse = BoundaryNorm(clevs, cmapSpecUse.N) + + # Final data refinement: transpose and trim, set 0 freq to NaN (no log10 for + # normalized results), refine metadata + z = s.transpose().sel(frequency=slice(*fb), wavenumber=slice(-15,15)) + z.loc[{'frequency':0}] = np.nan + z.attrs["long_name"] = varName + ": lightly smoothed spectral power of component symmetric about equator, normalized by heavily smoothed background spectrum" + z.attrs["method"] = "Follows Figure 3 methods of Wheeler and Kiladis (1999; https://doi.org/10.1175/1520-0469(1999)056<0374:CCEWAO>2.0.CO;2)" + + fig, ax = plt.subplots() + kmesh0, vmesh0 = np.meshgrid(z['wavenumber'], z['frequency']) + #img = ax.contourf(kmesh0, vmesh0, z, levels=np.linspace(0.2, 3.0, 16), cmap='Spectral_r', extend='both') + img = ax.contourf(kmesh0, vmesh0, z, levels=clevs, cmap=cmapSpecUse, norm=normSpecUse, extend='both') + img2 = ax.contour(kmesh0, vmesh0, z, levels=clevs, linewidths=1., linestyles='solid', colors='gray', alpha=0.7) + ax.axvline(0, linestyle='dashed', color=perfrq_col, linewidth=perfrq_thk, alpha=disp_alpha) + if( (1./30.) < fb[1] ): + ax.axhline((1./30.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./30.)+text_offset,'30 days',color=perfrq_col, alpha=perfrq_alpha) + if( (1./6.) < fb[1] ): + ax.axhline((1./6.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./6.)+text_offset,'6 days',color=perfrq_col, alpha=perfrq_alpha) + if( (1./3.) < fb[1] ): + ax.axhline((1./3.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./3.)+text_offset,'3 days',color=perfrq_col, alpha=perfrq_alpha) + for ii in range(3,6): + ax.plot(swk[ii, 0,:], swf[ii,0,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + ax.plot(swk[ii, 1,:], swf[ii,1,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + ax.plot(swk[ii, 2,:], swf[ii,2,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + ax.set_xlim(wnb) + ax.set_ylim(fb) + #ax.set_title(varName + ": $\sum_{15^{\circ}S}^{15^{\circ}N} Power_{SYM}$ / Background") # Version w/ LaTeX + ax.set_title(f"{varName}: {{Sum(Power) from 15°S-15°N}}/Background\n") # Version w/o LaTeX + ax.set_title(sourceID, loc='left') + ax.set_title("Symmetric", loc='right') + + if(not do_zoom): # For now, only add equivalent depth and shallow water curve labels -NOT- to zoomed-in plots + # Shallow water dispersion curve line labels: See https://matplotlib.org/stable/tutorials/text/text_intro.html + # n=1 ER dispersion curve labels + iwave, ih = 3, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -11.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + iwave, ih = 3, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -9.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + iwave, ih = 3, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + ax.text(-7.,0.10,'n=1 ER',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + + # Kelvin dispersion curve labels + iwave, ih = 4, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + iwave, ih = 4, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 10.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + iwave, ih = 4, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 14.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + ax.text(6.,0.13,'Kelvin',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + + # IG dispersion curve labels + iwave, ih = 5, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + iwave, ih = 5, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + iwave, ih = 5, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + ax.text(-10.,0.48,'n=1 WIG',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + ax.text(5.,0.48,'n=1 EIG',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + + # MJO label + ax.text(6.,0.0333,'MJO',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + + plt.ylabel("Frequency (CPD)") + plt.xlabel("Zonal wavenumber") + plt.gcf().text(0.12, 0.03, "Westward", fontsize=11) + plt.gcf().text(0.64, 0.03, "Eastward", fontsize=11) + fig.colorbar(img) + if ofil is not None: + fig.savefig(ofil, bbox_inches='tight', dpi=300) + print("Plot file created: %s\n" % ofil) + + # Save plotted data z to file as xArray data array + if(not do_zoom): + z.to_netcdf(outDataDir + "/zwfData_norm_sym_" + dataDesc + ".nc") + + +def plot_raw_asymmetric_spectrum(s, ofil=None, dataDesc=None, clevs=None, cmapSpec='viridis', + varName=None, sourceID=None, do_zoom=False, + disp_col='black', disp_thk=1.5, disp_alpha=0.60, + perfrq_col='dimgray', perfrq_thk=1.0, perfrq_alpha=0.80, equivDepths=[50, 25, 12]): + """Basic plot of non-normalized (raw) antisymmetric power spectrum with shallow water curves.""" + + text_offset = 0.005 + fb = [0, .8] # frequency bounds for plot + wnb = [-15, 15] # zonal wavenumber bounds for plot + if(max(s['frequency'].values) == 0.5): + fb = [0, .5] + if(do_zoom): + fb = [0, .18] + wnb = [-7, 7] + # get data for dispersion curves: + swfreq,swwn = wf.genDispersionCurves(Ahe=equivDepths) + # swfreq.shape # -->(6, 3, 50) + swf = np.where(swfreq == 1e20, np.nan, swfreq) + swk = np.where(swwn == 1e20, np.nan, swwn) + + cmapSpecUse = ListedColormap(cmapSpec[1:-1]) # recall: range is NOT inclusive for final index + cmapSpecUse.set_under(cmapSpec[0]) + cmapSpecUse.set_over(cmapSpec[-1]) + normSpecUse = BoundaryNorm(clevs, cmapSpecUse.N) + + # Final data refinement: transpose and trim, set 0 freq to NaN, take log10, refine metadata + z = s.transpose().sel(frequency=slice(*fb), wavenumber=slice(-15,15)) + z.loc[{'frequency':0}] = np.nan + z = np.log10(z) + z.attrs["long_name"] = varName + ": log-base10 of lightly smoothed spectral power of component antisymmetric about equator" + z.attrs["method"] = "Follows Figure 1 methods of Wheeler and Kiladis (1999; https://doi.org/10.1175/1520-0469(1999)056<0374:CCEWAO>2.0.CO;2)" + + fig, ax = plt.subplots() + kmesh0, vmesh0 = np.meshgrid(z['wavenumber'], z['frequency']) + img = ax.contourf(kmesh0, vmesh0, z, levels=clevs, cmap=cmapSpecUse, norm=normSpecUse, extend='both') + img2 = ax.contour(kmesh0, vmesh0, z, levels=clevs, linewidths=1., linestyles='solid', colors='gray', alpha=0.7) + ax.axvline(0, linestyle='dashed', color=perfrq_col, linewidth=perfrq_thk, alpha=disp_alpha) + if( (1./30.) < fb[1] ): + ax.axhline((1./30.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./30.)+text_offset,'30 days',color=perfrq_col, alpha=perfrq_alpha) + if( (1./6.) < fb[1] ): + ax.axhline((1./6.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./6.)+text_offset,'6 days',color=perfrq_col, alpha=perfrq_alpha) + if( (1./3.) < fb[1] ): + ax.axhline((1./3.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./3.)+text_offset,'3 days',color=perfrq_col, alpha=perfrq_alpha) + for ii in range(0,3): + ax.plot(swk[ii, 0,:], swf[ii,0,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + ax.plot(swk[ii, 1,:], swf[ii,1,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + ax.plot(swk[ii, 2,:], swf[ii,2,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + ax.set_xlim(wnb) + ax.set_ylim(fb) + #ax.set_title(varName + ": Log10 $\sum_{15^{\circ}S}^{15^{\circ}N} Power_{ASYM}$") # Version w/ LaTeX + ax.set_title(f"{varName}: Log{{Sum(Power) from 15°S-15°N}}\n") # Version w/o LaTeX + ax.set_title(sourceID, loc='left') + ax.set_title("Antisymmetric", loc='right') + plt.ylabel("Frequency (CPD)") + plt.xlabel("Zonal wavenumber") + plt.gcf().text(0.12, 0.03, "Westward", fontsize=11) + plt.gcf().text(0.64, 0.03, "Eastward", fontsize=11) + fig.colorbar(img) + if ofil is not None: + fig.savefig(ofil, bbox_inches='tight', dpi=300) + print("Plot file created: %s\n" % ofil) + + # Save plotted data z to file as xArray data array + if(not do_zoom): + z.to_netcdf(outDataDir + "/zwfData_raw_asym_" + dataDesc + ".nc") + + +def plot_normalized_asymmetric_spectrum(s, ofil=None, dataDesc=None, clevs=None, cmapSpec='Spectral_r', + varName=None, sourceID=None, do_zoom=False, + disp_col='black', disp_thk=1.5, disp_alpha=0.60, + perfrq_col='dimgray', perfrq_thk=1.0, perfrq_alpha=0.80, equivDepths=[50, 25, 12]): + """Basic plot of normalized antisymmetric power spectrum with shallow water curves.""" + + text_offset = 0.005 + fb = [0, .8] # frequency bounds for plot + wnb = [-15, 15] # zonal wavenumber bounds for plot + if(max(s['frequency'].values) == 0.5): + fb = [0, .5] + if(do_zoom): + fb = [0, .18] + wnb = [-7, 7] + # get data for dispersion curves: + swfreq,swwn = wf.genDispersionCurves(Ahe=equivDepths) + # swfreq.shape # -->(6, 3, 50) + swf = np.where(swfreq == 1e20, np.nan, swfreq) + swk = np.where(swwn == 1e20, np.nan, swwn) + + cmapSpecUse = ListedColormap(cmapSpec[1:-1]) # recall: range is NOT inclusive for final index + cmapSpecUse.set_under(cmapSpec[0]) + cmapSpecUse.set_over(cmapSpec[-1]) + normSpecUse = BoundaryNorm(clevs, cmapSpecUse.N) + + # Final data refinement: transpose and trim, set 0 freq to NaN (no log10 for + # normalized results), refine metadata + z = s.transpose().sel(frequency=slice(*fb), wavenumber=slice(-15,15)) + z.loc[{'frequency':0}] = np.nan + z.attrs["long_name"] = varName + ": lightly smoothed spectral power of component antisymmetric about equator, normalized by heavily smoothed background spectrum" + z.attrs["method"] = "Follows Figure 3 methods of Wheeler and Kiladis (1999; https://doi.org/10.1175/1520-0469(1999)056<0374:CCEWAO>2.0.CO;2)" + + fig, ax = plt.subplots() + kmesh0, vmesh0 = np.meshgrid(z['wavenumber'], z['frequency']) + img = ax.contourf(kmesh0, vmesh0, z, levels=clevs, cmap=cmapSpecUse, norm=normSpecUse, extend='both') + img2 = ax.contour(kmesh0, vmesh0, z, levels=clevs, linewidths=1., linestyles='solid', colors='gray', alpha=0.7) + ax.axvline(0, linestyle='dashed', color=perfrq_col, linewidth=perfrq_thk, alpha=disp_alpha) + if( (1./30.) < fb[1] ): + ax.axhline((1./30.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./30.)+text_offset,'30 days',color=perfrq_col, alpha=perfrq_alpha) + if( (1./6.) < fb[1] ): + ax.axhline((1./6.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./6.)+text_offset,'6 days',color=perfrq_col, alpha=perfrq_alpha) + if( (1./3.) < fb[1] ): + ax.axhline((1./3.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./3.)+text_offset,'3 days',color=perfrq_col, alpha=perfrq_alpha) + for ii in range(0,3): + ax.plot(swk[ii, 0,:], swf[ii,0,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + ax.plot(swk[ii, 1,:], swf[ii,1,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + ax.plot(swk[ii, 2,:], swf[ii,2,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + ax.set_xlim(wnb) + ax.set_ylim(fb) + #ax.set_title(varName + ": $\sum_{15^{\circ}S}^{15^{\circ}N} Power_{SYM}$ / Background") # Version w/ LaTeX + ax.set_title(f"{varName}: {{Sum(Power) from 15°S-15°N}}/Background\n") # Version w/o LaTeX + ax.set_title(sourceID, loc='left') + ax.set_title("Antisymmetric", loc='right') + + if(not do_zoom): + # Shallow water dispersion curve line labels: See https://matplotlib.org/stable/tutorials/text/text_intro.html + # MRG dispersion curve labels -- SKIP LABELING EQUIVALENT DEPTHS FOR MRG WAVES AND ONLY LABEL FOR N=2 EIG WAVES, WHICH ARE POSTIVE-WAVENUMBER EXTENSIONS OF THE MRG CURVES +# iwave, ih = 0, 0 +# idxClose,valClose = find_nearest(swk[iwave,ih,:], 4.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] +# ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) +# iwave, ih = 0, 1 +# idxClose,valClose = find_nearest(swk[iwave,ih,:], 6.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] +# ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) +# iwave, ih = 0, 2 +# idxClose,valClose = find_nearest(swk[iwave,ih,:], 8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] +# ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + ax.text(-6.,0.18,'MRG',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + + # n=0 EIG dispersion curve labels + iwave, ih = 1, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 5.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + iwave, ih = 1, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + iwave, ih = 1, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + ax.text(9.,0.48,'n=0 EIG',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + + # n=2 IG dispersion curve labels + iwave, ih = 2, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -2.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + iwave, ih = 2, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -2.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + iwave, ih = 2, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -2.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + ax.text(-10.,0.65,'n=2 WIG',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + ax.text(8.,0.65,'n=2 EIG',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + + # MJO label + ax.text(3.,0.0333,'MJO',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + + + plt.ylabel("Frequency (CPD)") + plt.xlabel("Zonal wavenumber") + plt.gcf().text(0.12, 0.03, "Westward", fontsize=11) + plt.gcf().text(0.64, 0.03, "Eastward", fontsize=11) + fig.colorbar(img) + if ofil is not None: + fig.savefig(ofil, bbox_inches='tight', dpi=300) + print("Plot file created: %s\n" % ofil) + + # Save plotted data z to file as xArray data array + if(not do_zoom): + z.to_netcdf(outDataDir + "/zwfData_norm_asym_" + dataDesc + ".nc") + + +def plot_background_spectrum(s, ofil=None, dataDesc=None, clevs=None, cmapSpec='viridis', + varName=None, sourceID=None, do_zoom=False, + disp_col='black', disp_thk=1.5, disp_alpha=0.60, + perfrq_col='dimgray', perfrq_thk=1.0, perfrq_alpha=0.80, equivDepths=[50, 25, 12]): + """Basic plot of background power spectrum with shallow water curves.""" + + text_offset = 0.005 + fb = [0, .8] # frequency bounds for plot + wnb = [-15, 15] # zonal wavenumber bounds for plot + if(max(s['frequency'].values) == 0.5): + fb = [0, .5] + if(do_zoom): + fb = [0, .18] + wnb = [-7, 7] + # get data for dispersion curves: + swfreq,swwn = wf.genDispersionCurves(Ahe=equivDepths) + # swfreq.shape # -->(6, 3, 50) + # For n=1 ER waves, allow dispersion curves to touch 0 -- this is for plot aesthetics only + for i in range(0,3): # loop 0-->2 for the assumed 3 shallow water dispersion curves for ER waves + indMinPosFrqER = np.where(swwn[3,i,:] >= 0., swwn[3,i,:], 1e20).argmin() # index of swwn for least positive wn + swwn[3,i,indMinPosFrqER],swfreq[3,i,indMinPosFrqER] = 0.,0. # this sets ER's frequencies to 0. at wavenumber 0. + swf = np.where(swfreq == 1e20, np.nan, swfreq) + swk = np.where(swwn == 1e20, np.nan, swwn) + + cmapSpecUse = ListedColormap(cmapSpec[1:-1]) # recall: range is NOT inclusive for final index + cmapSpecUse.set_under(cmapSpec[0]) + cmapSpecUse.set_over(cmapSpec[-1]) + normSpecUse = BoundaryNorm(clevs, cmapSpecUse.N) + + # Final data refinement: transpose and trim, set 0 freq to NaN, take log10, refine metadata + z = s.transpose().sel(frequency=slice(*fb), wavenumber=slice(-15,15)) + z.loc[{'frequency':0}] = np.nan + z = np.log10(z) + z.attrs["long_name"] = varName + ": heavily smoothed version of the mean of spectral powers associated with the components symmetric and antisymmetric about equator" + z.attrs["method"] = "Follows Figure 2 methods of Wheeler and Kiladis (1999; https://doi.org/10.1175/1520-0469(1999)056<0374:CCEWAO>2.0.CO;2)" + + fig, ax = plt.subplots() + kmesh0, vmesh0 = np.meshgrid(z['wavenumber'], z['frequency']) + img = ax.contourf(kmesh0, vmesh0, z, levels=clevs, cmap=cmapSpecUse, norm=normSpecUse, extend='both') + img2 = ax.contour(kmesh0, vmesh0, z, levels=clevs, linewidths=1., linestyles='solid', colors='gray', alpha=0.7) + ax.axvline(0, linestyle='dashed', color=perfrq_col, linewidth=perfrq_thk, alpha=disp_alpha) + if( (1./30.) < fb[1] ): + ax.axhline((1./30.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./30.)+text_offset,'30 days',color=perfrq_col, alpha=perfrq_alpha) + if( (1./6.) < fb[1] ): + ax.axhline((1./6.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./6.)+text_offset,'6 days',color=perfrq_col, alpha=perfrq_alpha) + if( (1./3.) < fb[1] ): + ax.axhline((1./3.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./3.)+text_offset,'3 days',color=perfrq_col, alpha=perfrq_alpha) + ax.set_xlim(wnb) + ax.set_ylim(fb) + ax.set_title(f"{varName}: Log{{Smoothed Background Power}}\n") + ax.set_title(sourceID, loc='left') +# ax.set_title("", loc='right') + plt.ylabel("Frequency (CPD)") + plt.xlabel("Zonal wavenumber") + plt.gcf().text(0.12, 0.03, "Westward", fontsize=11) + plt.gcf().text(0.64, 0.03, "Eastward", fontsize=11) + fig.colorbar(img) + if ofil is not None: + fig.savefig(ofil, bbox_inches='tight', dpi=300) + print("Plot file created: %s\n" % ofil) + + # Save plotted data z to file as xArray data array + if(not do_zoom): + z.to_netcdf(outDataDir + "/zwfData_background_" + dataDesc + ".nc") + +# +# LOAD DATA, x = DataArray(time, lat, lon), e.g., daily mean precipitation +# +def get_data(filenames, variablename): + if(len(filenames) == 1): + print("\nOpening " + variablename + " from single file: " + filenames[0]) + try: + ds = xr.open_dataset(filenames[0]) + except ValueError: + ds = xr.open_dataset(filenames[0], decode_times=False) + else: + print("\nOpening " + variablename + " across multiple files: ") + for j in range(len(filenames)): + print(" " + str(j) + " " + filenames[j]) + try: +# ds = xr.open_mfdataset(filenames, combine="by_coords") + ds = xr.open_mfdataset(filenames) + except: + print("Error reading multi-file data set") + x = ds[variablename].compute() +# return ds[variablename] + return x + + +if __name__ == "__main__": +# swfreq,swwn = wf.genDispersionCurves() +# for i in range(6): +# print(f'\nwaveType={i}') +# print(f"{'waveNum':12} {'frq (ih=0)':12} {'frq (ih=1)':12} {'frq (ih=2)':12}") +# for j in range(50): +# print(f'{swwn[i,0,j]:12.4f} {swfreq[i,0,j]:12.4f} {swfreq[i,1,j]:12.4f} {swfreq[i,2,j]:12.4f}') +# sys.exit() + # + # ================== User parameters (below) ============================ + # + # input file -- could make this a CLI argument + # * fili could specify a single (pre-concatenated) file, or a directory containing + # a collection of files. If it's set to a directory, do -not- include trailing "/". + # If it's set to a directory, yrBeg and yrEnd will defined the + # (inclusive) year span from which to read multiple files and concatenate them + # along the time dimension. + # * vari: The name of the variable to read in, from fili + # * yrBeg, yrEnd: The beginning and ending years of the data + # + vari = "precipAvg" # "PRECT" +# fili = "/lcrc/group/e3sm/ac.golaz/E3SMv2/v2.LR.piControl/archive/atm/hist" + +# srcID = "TRMM" +# fili = "/global/cfs/cdirs/e3sm/benedict/obs/trmm_1dd.h2.20010101_to_20101231.PRECT.nc" +## fili = "/lcrc/group/acme/ac.benedict/test_data_in/trmm_1dd.h2.20010101_to_20101231.PRECT.nc" +# yrBeg = 2001 +# yrEnd = 2010 +# histStr = "" # Input file descriptors, where file name is [histStr].yyyy-mm-dd*.nc + + srcID = "IMERG_2.5deg" + fili = "/global/cfs/cdirs/e3sm/benedict/mjo_isv/obs_reanalysis/IMERG/daily_73x144" + yrBeg = 2002 + yrEnd = 2006 + histStr = "3B-DAY.MS.MRG.3IMERG.V06" # Input file descriptors, where file name is [histStr].yyyy-mm-dd*.nc + +# srcID = "E3SMv1_0.25deg" +# fili = "/global/cscratch1/sd/ruplanl9/e3sm_scratch/highRes_1980_2015/PRECT/remap_to_0.25deg" +# yrBeg = 2005 +# yrEnd = 2009 +# histStr = "202101027-maint-1.0-tro.A_WCYCL20TRS_CMIP6_HR.ne120_oRRS18v3_ICG.unc12.cam.h3" # Input file descriptors, where file name is [histStr].yyyy-mm-dd*.nc + +# srcID = "DECK.v1b.LR.CMIP6" +# fili = "/lcrc/group/acme/ac.benedict/test_data_in/20201125.DECKv1b_H1.cmip6_180x360.edison.daily.PRECT.nc" +# yrBeg = 2005 +# yrEnd = 2014 +# histStr = "" # Input file descriptors, where file name is [histStr].yyyy-mm-dd*.nc + +# srcID = "v2.LR.piControl" +# fili = "/lcrc/group/acme/ac.benedict/test_data_in/remapped" +# yrBeg = 491 +# yrEnd = 500 +# histStr = "v2.LR.piControl.eam.h1" # Input file descriptors, where file name is [vari]_[histStr].yyyy-mm-dd*.nc + +# srcID = "v2.LR.historical_0101" +# fili = "/lcrc/group/acme/ac.benedict/test_data_in/remapped" +# yrBeg = 2005 +# yrEnd = 2014 +# histStr = "v2.LR.historical_0101.eam.h1" # Input file descriptors, where file name is [vari]_[histStr].yyyy-mm-dd*.nc + + +### ------- cesm2.0.1 ------- +# srcID = "B1850_c201_CTL" +# fili = "/global/cfs/cdirs/e3sm/benedict/cesm2/B1850_c201_CTL" +# yrBeg = 1 +# yrEnd = 30 +# histStr = "B1850_c201_CTL.cam.h2" # Input file descriptors, where file name is [vari]_[histStr].yyyy-mm-dd*.nc + + +### ------- v1 ------- +# srcID = "DECKv1b_H1" +# fili = "/lcrc/group/acme/ac.benedict/e3sm_data/atm/20180215.DECKv1b_H1.ne30_oEC.edison/remapped" +# yrBeg = 1985 # 2001 +# yrEnd = 2014 # 2010 +# histStr = "20180215.DECKv1b_H1.ne30_oEC.edison.cam.h1" # Input file descriptors, where file name is [vari]_[histStr].yyyy-mm-dd*.nc + +# srcID = "DECKv1b_A1" +# fili = "/lcrc/group/acme/ac.benedict/e3sm_data/atm/20180316.DECKv1b_A1.ne30_oEC.edison/remapped" +# yrBeg = 1985 # 2001 +# yrEnd = 2014 # 2010 +# histStr = "20180316.DECKv1b_A1.ne30_oEC.edison.cam.h1" # Input file descriptors, where file name is [vari]_[histStr].yyyy-mm-dd*.nc + +# srcID = "DECKv1b_piControl" +# fili = "/lcrc/group/acme/ac.benedict/e3sm_data/atm/20180129.DECKv1b_piControl.ne30_oEC.edison/remapped" +# yrBeg = 250 +# yrEnd = 279 +# histStr = "20180129.DECKv1b_piControl.ne30_oEC.edison.cam.h1" # Input file descriptors, where file name is [vari]_[histStr].yyyy-mm-dd*.nc + + +### ------- v2 ------- +# srcID = "v2.LR.historical_0201" +# fili = "/lcrc/group/acme/ac.benedict/e3sm_data/atm/v2.LR.historical_0201/remapped" +# yrBeg = 1985 # 2001 +# yrEnd = 2014 # 2010 +# histStr = "v2.LR.historical_0201.eam.h1" # Input file descriptors, where file name is [vari]_[histStr].yyyy-mm-dd*.nc + +# srcID = "v2.LR.amip_0201" +# fili = "/lcrc/group/acme/ac.benedict/e3sm_data/atm/v2.LR.amip_0201/remapped" +# yrBeg = 1985 # 2001 +# yrEnd = 2014 # 2010 +# histStr = "v2.LR.amip_0201.eam.h1" # Input file descriptors, where file name is [vari]_[histStr].yyyy-mm-dd*.nc + +# srcID = "v2.LR.piControl" +# fili = "/global/cfs/cdirs/e3sm/benedict/e3sm_v2_output/v2.LR.piControl/remapped" +## fili = "/lcrc/group/acme/ac.benedict/e3sm_data/atm/v2.LR.piControl/remapped" +# yrBeg = 460 +# yrEnd = 489 +# histStr = "PRECT_v2.LR.piControl.eam.h1" # Input file descriptors, where file name is [vari]_[histStr].yyyy-mm-dd*.nc + + + # outputs + outDataDir = "/global/cfs/cdirs/e3sm/benedict/mjo_isv/data_E3SMdiags_TEST" + outPlotDir = "/global/cfs/cdirs/e3sm/benedict/mjo_isv/plot_E3SMdiags_TEST" +# outDataDir = "/home/ac.benedict/analysis/results/dataTrop" +# outPlotDir = "/home/ac.benedict/analysis/results/plotTrop" + + # + # Options ... right now these only go into wk.spacetime_power() + # + do_zooming = False # Set to True to also make plots to zoom into MJO spectral region, + # in addition to the default (larger) spectral region + latBound = (-15,15) # latitude bounds for analysis + spd = 1 # SAMPLES PER DAY + nDayWin = 96 # Wheeler-Kiladis [WK] temporal window length (days) + nDaySkip = -60 # time (days) between temporal windows [segments] + # negative means there will be overlapping temporal segments + twoMonthOverlap = -1*nDaySkip + + # Generic settings for spectra plots -- do not need to ba adjusted by user, but can be + # customized. The user can add additional key-value pairs as necessary to expand + # plotting customization. + contour_levs_raw_spec = (-1.4,-1.2,-1,-0.9,-0.8,-0.7,-0.6,-0.5,-0.4,-0.3,-0.2,-0.1,0,0.1,0.2) + #contour_levs_norm_spec = (0.9,1,1.1,1.2,1.3,1.4,1.5,1.7,1.9,2.1,2.4,2.7,3,3.5,4) + cmapSpecR = ["white", + "paleturquoise","lightblue","skyblue", + "lightgreen","limegreen","green","darkgreen", + "yellow","orange","orangered", + "red","maroon","magenta","orchid","pink", + "lavenderblush"] + contour_levs_norm_spec = (0.9,1.0,1.1,1.2,1.3,1.4,1.5,1.7,1.9,2.1,2.4,2.7,3.0) +# if(vari == "U850"): +# contour_levs_raw_spec = np.linspace(-2.6, 0.2, num=15) +# contour_levs_norm_spec = (0.7,0.75,0.8,0.85,0.9,0.95,1,1.1,1.2,1.3,1.4,1.5,2,2.5,3) + cmapSpecN = ["white", + "gainsboro","lightgray","silver", + "paleturquoise","skyblue", + "lightgreen","mediumseagreen","seagreen", + "yellow","orange", + "red","maroon","pink"] + + optPlot = {'varName': vari, + 'sourceID': srcID, + 'do_zoom' : False, + 'disp_col': 'black', + 'disp_thk': 1.5, + 'disp_alpha': 0.60, + 'perfrq_col': 'dimgray', + 'perfrq_thk': 1.0, + 'perfrq_alpha': 0.80, + 'equivDepths' : [50, 25, 12]} + # disp_col # Color for dispersion lines/labels + # disp_thk # Thickness for dispersion lines + # disp_alpha # Transparency for dispersion lines/labels (alpha=0 for opaque) + # perfrq_col # Color for period and frequency ref lines + # perfrq_thk # Thickness for period and frequency ref lines + # perfrq_alpha # Transparency for period and frequency ref lines (alpha=0 for opaque) + # clevs # Contour levels + # ================== User parameters (above) ============================ + + + + # Load data -- ORIGINAL SINGLE FILE +# data = get_data(fili, vari) # expected input is (time,lat,lon) + + # Load data: + # If fili is a single file, fileList will be a single-element list + # If fili is a directory, fileList will be a list containing multiple file names + # (contained within that directory). The file names will span a year range + # bounded by yrBeg and yrEnd (inclusive). ASSUMPTION: The input data files + # have the standard YYYY-MM-DD date structure. + if os.path.isfile(fili): + fileList = [fili] + data = get_data(fileList, vari) + elif os.path.isdir(fili): + yrList = [i for i in range(yrBeg,yrEnd+1)] + print(yrList) + yrListStr = [] + for y in yrList: + yrListStr.append("%0.4i" % y) + print(yrListStr) + fileList = [] + for y in yrListStr: +# fileList += sorted(glob.glob("{path}/*{hStr}.{year}*.nc".format(path=fili, hStr=histStr, year=y))) + print("{path}/{hStr}.{year}*.nc".format(path=fili, hStr=histStr, year=y)) +# fileList += sorted(glob.glob("{path}/{hStr}.{year}-??-??*.nc".format(path=fili, hStr=histStr, year=y))) + #fileList += sorted(glob.glob("{path}/{hStr}.{year}-??-??*.nc".format(path=fili, hStr=histStr, year=y))) + fileList += sorted(glob.glob("{path}/{hStr}.{year}*.remap_73x144.nc".format(path=fili, hStr=histStr, year=y))) +# print("\n\n\n JJB: fileList is:") +# print(fileList) + data = get_data(fileList, vari) + else: + sys.exit("\nFatal error: fili entry " + fili + " is neither a valid single file nor a valid directory. Exiting.") + + if not fileList: # In python, empty lists are boolean 'false' + sys.exit("\nFatal error: fileList is empty, no files matching the selected time range exist. Exiting.") + + print(data) + + # Assess/confirm time range of input data + xt = data.coords['time'] + yrBegData = xt[0].time.dt.year + yrEndData = xt[-1].time.dt.year + if(xt[-1].time.dt.dayofyear < 183): # if final time step is < halfway through year, subtract 1 from yrEndData + yrEndData = yrEndData - 1 + print("\nyrBegData (may not represent full year!): %0.4d" % yrBegData) + print("yrEndData (may not represent full year!): %0.4d" % yrEndData) + + # Unit conversion + if vari == "PRECT": + if data.attrs["units"] == "m/s" or data.attrs["units"] == "m s{-1}": + print("\nBEFORE unit conversion: Max/min of data: " + str(data.values.max()) + " " + str(data.values.min())) + data.values = data.values * 1000. * 86400. # convert m/s to mm/d, do not alter metadata (yet) + data.attrs["units"] = "mm/d" # adjust metadata to reflect change in units + print("\nAFTER unit conversion: Max/min of data: " + str(data.values.max()) + " " + str(data.values.min())) + print(data) + if vari == "precipAvg": + if data.attrs["units"] == "mm/hr": + print("\nBEFORE unit conversion: Max/min of data: " + str(data.values.max()) + " " + str(data.values.min())) + data.values = data.values * 24. # convert mm/hr to mm/d, do not alter metadata (yet) + data.attrs["units"] = "mm/d" # adjust metadata to reflect change in units + print("\nAFTER unit conversion: Max/min of data: " + str(data.values.max()) + " " + str(data.values.min())) + print(data) + + opt = {'segsize': nDayWin, + 'noverlap': twoMonthOverlap, + 'spd': spd, + 'latitude_bounds': latBound, + 'dosymmetries': True, + 'rmvLowFrq':True} + + spec_raw_sym, spec_raw_asym, spec_norm_sym, spec_norm_asym, spec_background = wf_analysis(data, **opt) + print("\nspec_raw_sym metadata:") + print(spec_raw_sym.dims) + print(spec_raw_sym.coords) + print(spec_raw_sym.attrs) + print(spec_raw_sym.max()) + + + # + # Plots ... sort of matching NCL, but not worrying much about customizing. + # + # This string goes into the output file name encapsulating the data that is plotted: + # Note that 'yrBegData' and 'yrEndData' might not represent the full year + plotFileDescriptor = "%s_%0.4d-%0.4d_%s" % (srcID, yrBegData, yrEndData, vari) + + # "Fig1": Log-10 of raw spectral power + outPlotName = outPlotDir + "/" + "zwfPlot_raw_sym_" + plotFileDescriptor + ".png" + plot_raw_symmetric_spectrum(spec_raw_sym, outPlotName, plotFileDescriptor, contour_levs_raw_spec, cmapSpecR, **optPlot) + + outPlotName = outPlotDir + "/" + "zwfPlot_raw_asym_" + plotFileDescriptor + ".png" + plot_raw_asymmetric_spectrum(spec_raw_asym, outPlotName, plotFileDescriptor, contour_levs_raw_spec, cmapSpecR, **optPlot) + + # "Fig2": Log-10 of smoothed background spectrum + outPlotName = outPlotDir + "/" + "zwfPlot_background_" + plotFileDescriptor + ".png" + plot_background_spectrum(spec_background, outPlotName, plotFileDescriptor, contour_levs_raw_spec, cmapSpecR, **optPlot) + + # "Fig3": Log-10 of normalized spectral power + outPlotName = outPlotDir + "/" + "zwfPlot_norm_sym_" + plotFileDescriptor + ".png" + plot_normalized_symmetric_spectrum(spec_norm_sym, outPlotName, plotFileDescriptor, contour_levs_norm_spec, cmapSpecN, **optPlot) + + outPlotName = outPlotDir + "/" + "zwfPlot_norm_asym_" + plotFileDescriptor + ".png" + plot_normalized_asymmetric_spectrum(spec_norm_asym, outPlotName, plotFileDescriptor, contour_levs_norm_spec, cmapSpecN, **optPlot) + + + # If also making plots to zoom into MJO spectral region + if(do_zooming): + + optPlot['do_zoom'] = do_zooming # Change do_zoom value in optPlot dictionary to True + + # "Fig1": Log-10 of raw spectral power + outPlotName = outPlotDir + "/" + "zwfPlot_raw_sym_ZOOM_" + plotFileDescriptor + ".png" + plot_raw_symmetric_spectrum(spec_raw_sym, outPlotName, plotFileDescriptor, contour_levs_raw_spec, cmapSpecR, **optPlot) + + outPlotName = outPlotDir + "/" + "zwfPlot_raw_asym_ZOOM_" + plotFileDescriptor + ".png" + plot_raw_asymmetric_spectrum(spec_raw_asym, outPlotName, plotFileDescriptor, contour_levs_raw_spec, cmapSpecR, **optPlot) + + # "Fig2": Log-10 of smoothed background spectrum + outPlotName = outPlotDir + "/" + "zwfPlot_background_ZOOM_" + plotFileDescriptor + ".png" + plot_background_spectrum(spec_background, outPlotName, plotFileDescriptor, contour_levs_raw_spec, cmapSpecR, **optPlot) + + # "Fig3": Log-10 of normalized spectral power + outPlotName = outPlotDir + "/" + "zwfPlot_norm_sym_ZOOM_" + plotFileDescriptor + ".png" + plot_normalized_symmetric_spectrum(spec_norm_sym, outPlotName, plotFileDescriptor, contour_levs_norm_spec, cmapSpecN, **optPlot) + + outPlotName = outPlotDir + "/" + "zwfPlot_norm_asym_ZOOM_" + plotFileDescriptor + ".png" + plot_normalized_asymmetric_spectrum(spec_norm_asym, outPlotName, plotFileDescriptor, contour_levs_norm_spec, cmapSpecN, **optPlot) diff --git a/auxiliary_tools/tropical_subseasonal_diags/zwf/zwf_plot_DIFF.py b/auxiliary_tools/tropical_subseasonal_diags/zwf/zwf_plot_DIFF.py new file mode 100755 index 000000000..d56fd3f1d --- /dev/null +++ b/auxiliary_tools/tropical_subseasonal_diags/zwf/zwf_plot_DIFF.py @@ -0,0 +1,321 @@ +import sys +import numpy as np +import xarray as xr +import matplotlib.pyplot as plt +# our local module: +import zwf_functions as wf + + + +def plot_diff_spectrum(z, ofil=None, clevs=None, do_zoom=False, + titleStr="Main title", leftStr="Left title", rightStr="Right title", cbar_label="cbar_label", + disp_col='black', disp_thk=1.5, disp_alpha=0.60, + perfrq_col='darkgray', perfrq_thk=1.0, perfrq_alpha=0.80): + """Basic plot of difference of either non-normalized (raw) or normalized antisymmetric power + spectra with shallow water curves.""" + + text_offset = 0.005 + fb = [0, .8] # frequency bounds for plot + wnb = [-15, 15] # zonal wavenumber bounds for plot + if(max(z['frequency'].values) == 0.5): + fb = [0, .5] + if(do_zoom): + fb = [0, .18] + wnb = [-7, 7] + # get data for dispersion curves: + swfreq,swwn = wf.genDispersionCurves() + # swfreq.shape # -->(6, 3, 50) + swf = np.where(swfreq == 1e20, np.nan, swfreq) + swk = np.where(swwn == 1e20, np.nan, swwn) + + fig, ax = plt.subplots() + kmesh0, vmesh0 = np.meshgrid(z['wavenumber'], z['frequency']) + #img = ax.contourf(kmesh0, vmesh0, z, levels=np.linspace(0.2, 3.0, 16), cmap='Spectral_r', extend='both') + img = ax.contourf(kmesh0, vmesh0, z, levels=clevs, cmap='seismic', extend='both') + img2 = ax.contour(kmesh0, vmesh0, z, levels=clevs, linewidths=1., linestyles='solid', colors='gray', alpha=0.7) + ax.axvline(0, linestyle='dashed', color=perfrq_col, linewidth=perfrq_thk, alpha=disp_alpha) + if( (1./30.) < fb[1] ): + ax.axhline((1./30.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./30.)+text_offset,'30 days',color=perfrq_col, alpha=perfrq_alpha) + if( (1./6.) < fb[1] ): + ax.axhline((1./6.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./6.)+text_offset,'6 days',color=perfrq_col, alpha=perfrq_alpha) + if( (1./3.) < fb[1] ): + ax.axhline((1./3.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./3.)+text_offset,'3 days',color=perfrq_col, alpha=perfrq_alpha) + if(rightStr == "Symmetric"): + for ii in range(3,6): + ax.plot(swk[ii, 0,:], swf[ii,0,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + ax.plot(swk[ii, 1,:], swf[ii,1,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + ax.plot(swk[ii, 2,:], swf[ii,2,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + if(rightStr == "Antisymmetric"): + for ii in range(0,3): + ax.plot(swk[ii, 0,:], swf[ii,0,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + ax.plot(swk[ii, 1,:], swf[ii,1,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + ax.plot(swk[ii, 2,:], swf[ii,2,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + ax.set_xlim(wnb) + ax.set_ylim(fb) + ax.set_title(titleStr) + ax.set_title(leftStr, loc='left') + ax.set_title(rightStr, loc='right') + plt.ylabel("Frequency (CPD)") + plt.xlabel("Zonal wavenumber") + plt.gcf().text(0.12, 0.03, "Westward", fontsize=11) + plt.gcf().text(0.64, 0.03, "Eastward", fontsize=11) + cabr = fig.colorbar(img, ticks=clevs, label=cbar_label) + if ofil is not None: + fig.savefig(ofil, bbox_inches='tight', dpi=300) + print("Plot file created: %s\n" % ofil) + + + +# +# Load spectral data [dims=(frequency,wavenumber)] +# +def get_power_data(fName): + print("\nRetrieving power data from file: " + fName) + try: + ds = xr.open_dataset(fName) + except: + sys.exit("Exiting: Error reading data set: %s" % fName) + # xPow = ds["power"].compute() + # return xPow + return ds["power"] + + + + + +if __name__ == "__main__": + + # + # ================== User parameters (below) ============================ + # + # * vari: Variable label in input filename, will also appear on plot + # * zwf_inData_dir: Path to directory containing pre-processed spectral data + # * srcID_[A,B]: Source ID descriptor for pre-processed spectral data + # * srcID_[A,B]_short: Abbreviated version of srcID_[A,B] for plot title and filename + # * yrSpanFile_[A,B]: Year span label in input file names, will also appear in + # plot filename + # * srcID_short_diffFile: Descriptor of spectral difference to be used in the plot filename + # * srcID_short_diffFile: Descriptor of spectral difference to be used in the plot title + # * outPlotDir: Directory in which plot file will be saved + # + vari = "PRECT" + + zwf_inData_dir = "/home/ac.benedict/analysis/results/dataTrop" + + # "A" and "B" data sources: Difference will be defined as A-B +# srcID_A = "v2.LR.historical_0101" +# srcID_A_short = "v2hist" +# yrSpanFile_A = "2005-2014" +# +# srcID_B = "DECK.v1b.LR.CMIP6" +# srcID_B_short = "v1hist" +# yrSpanFile_B = "2005-2014" +# +## srcID_B = "TRMM" +## srcID_B_short = "TRMM" +## yrSpanFile_B = "2001-2010" + +# srcID_A = "DECKv1b_A1" +# srcID_A_short = "v1amip" +# yrSpanFile_A = "2001-2010" + + srcID_A = "v2.LR.piControl" ; "v2.LR.amip_0201" + srcID_A_short = "v2piControl" ; "v2amip" + yrSpanFile_A = "0460-0489" ; "1985-2014" + + srcID_B = "DECKv1b_piControl" ; "DECKv1b_A1" + srcID_B_short = "v1piControl" ; "v1amip" + yrSpanFile_B = "0250-0279" ; "1985-2014" + +# srcID_B = "TRMM" +# srcID_B_short = "TRMM" +# yrSpanFile_B = "2001-2010" + + srcID_short_diffFile = "%s-%s" % (srcID_A_short, srcID_B_short) + srcID_short_diffPlot = "%s$-$%s" % (srcID_A_short, srcID_B_short) + + # outputs +# outDataDir = "/home/ac.benedict/analysis/diag_e3sm_py_20210709/testing/test_plots" + outPlotDir = "/home/ac.benedict/analysis/results/plotTrop" + + + do_zooming = True # Set to True to also make plots to zoom into MJO spectral region + + # Generic settings for spectra plots -- do not need to ba adjusted by user, but can be + # customized. The user can add additional key-value pairs as necessary to expand + # plotting customization. +# contour_levs_raw_diff = (-0.2,-0.15,-0.1,-0.05,-0.02,-0.01,0.01,0.02,0.05,0.1,0.15,0.2) +# contour_levs_norm_diff = (-0.2,-0.15,-0.1,-0.05,-0.02,-0.01,0.01,0.02,0.05,0.1,0.15,0.2) + contour_levs_raw_diffRatio = (-80.,-60.,-40.,-20.,-10.,-5.,5.,10.,20.,40.,60.,80.) + contour_levs_norm_diffRatio = (-60.,-30.,-20.,-15.,-10.,-5.,5.,10.,15.,20.,30.,60.) + + # Dictionary with additional plot resources -- NOTE: These are default settings, + # some of which are changed just below the call the the plotting routine. + optPlot = {'do_zoom': False, + 'titleStr': '', + 'leftStr': '', + 'rightStr': '', + 'cbar_label': '', + 'disp_col': 'black', + 'disp_thk': 1.5, + 'disp_alpha': 0.60, + 'perfrq_col': 'darkgray', + 'perfrq_thk': 1.0, + 'perfrq_alpha': 0.80} + # disp_col # Color for dispersion lines/labels + # disp_thk # Thickness for dispersion lines + # disp_alpha # Transparency for dispersion lines/labels (alpha=0 for opaque) + # perfrq_col # Color for period and frequency ref lines + # perfrq_thk # Thickness for period and frequency ref lines + # perfrq_alpha # Transparency for period and frequency ref lines (alpha=0 for opaque) + # ================== User parameters (above) ============================ + + + # Error trapping and warnings + + + + # ========================================= + # Load data + + # --- Source "A" --- + f = zwf_inData_dir + "/" + "zwfData_raw_sym_" + srcID_A + "_" + yrSpanFile_A + "_" + vari + ".nc" + xA_raw_sym = get_power_data(f) + + f = zwf_inData_dir + "/" + "zwfData_norm_sym_" + srcID_A + "_" + yrSpanFile_A + "_" + vari + ".nc" + xA_norm_sym = get_power_data(f) + + f = zwf_inData_dir + "/" + "zwfData_raw_asym_" + srcID_A + "_" + yrSpanFile_A + "_" + vari + ".nc" + xA_raw_asym = get_power_data(f) + + f = zwf_inData_dir + "/" + "zwfData_norm_asym_" + srcID_A + "_" + yrSpanFile_A + "_" + vari + ".nc" + xA_norm_asym = get_power_data(f) + + f = zwf_inData_dir + "/" + "zwfData_background_" + srcID_A + "_" + yrSpanFile_A + "_" + vari + ".nc" + xA_background = get_power_data(f) + + # --- Source "B" --- + f = zwf_inData_dir + "/" + "zwfData_raw_sym_" + srcID_B + "_" + yrSpanFile_B + "_" + vari + ".nc" + xB_raw_sym = get_power_data(f) + + f = zwf_inData_dir + "/" + "zwfData_norm_sym_" + srcID_B + "_" + yrSpanFile_B + "_" + vari + ".nc" + xB_norm_sym = get_power_data(f) + + f = zwf_inData_dir + "/" + "zwfData_raw_asym_" + srcID_B + "_" + yrSpanFile_B + "_" + vari + ".nc" + xB_raw_asym = get_power_data(f) + + f = zwf_inData_dir + "/" + "zwfData_norm_asym_" + srcID_B + "_" + yrSpanFile_B + "_" + vari + ".nc" + xB_norm_asym = get_power_data(f) + + f = zwf_inData_dir + "/" + "zwfData_background_" + srcID_B + "_" + yrSpanFile_B + "_" + vari + ".nc" + xB_background = get_power_data(f) + + + # ========================================= + # Unit conversion: "undo" log-10 for raw and background spectra + xA_raw_sym = 10.**xA_raw_sym # this will clobber attributes, but this is okay + xA_raw_asym = 10.**xA_raw_asym + xA_background = 10.**xA_background + xB_raw_sym = 10.**xB_raw_sym # this will clobber attributes, but this is okay + xB_raw_asym = 10.**xB_raw_asym + xB_background = 10.**xB_background + + + # ========================================= + # Compute difference spectra + rawDiff_sym = xA_raw_sym - xB_raw_sym + rawDiffRatio_sym = 100. * (xA_raw_sym - xB_raw_sym) / xB_raw_sym + + normDiff_sym = xA_norm_sym - xB_norm_sym + normDiffRatio_sym = 100. * (xA_norm_sym - xB_norm_sym) / xB_norm_sym + + rawDiff_asym = xA_raw_asym - xB_raw_asym + rawDiffRatio_asym = 100. * (xA_raw_asym - xB_raw_asym) / xB_raw_asym + + normDiff_asym = xA_norm_asym - xB_norm_asym + normDiffRatio_asym = 100. * (xA_norm_asym - xB_norm_asym) / xB_norm_asym + + backgroundDiff = xA_background - xB_background + backgroundDiffRatio = 100. * (xA_background - xB_background) / xB_background + + + # ========================================= + # Plot spectra + + # The 'plotFileDescriptor' string goes into the output file name encapsulating the + # data that is plotted: 'yrBegData_A' and 'yrEndData_A' are used for simplicity, + # assuming that the time windows from the A and B data source mostly overlap + plotFileDescriptor = srcID_short_diffFile + "_" + yrSpanFile_A + "_" + vari + + # --- Ratio of differences of raw spectra --- + outPlotName = outPlotDir + "/" + "zwfPlot_diffRatio_raw_sym_" + plotFileDescriptor + ".png" + print("\nPlotting: %s" % outPlotName) + optPlot.update({'do_zoom': False}) + optPlot.update({'titleStr': "%s: $\Delta$(Raw power)\n" % vari}) + optPlot.update({'leftStr': "(%s$-$%s)/%s" % (srcID_A_short, srcID_B_short, srcID_B_short)}) + optPlot.update({'rightStr': 'Symmetric'}) + optPlot.update({'cbar_label': '%'}) + plot_diff_spectrum(rawDiffRatio_sym, outPlotName, contour_levs_raw_diffRatio, **optPlot) + if(do_zooming): + outPlotName = outPlotDir + "/" + "zwfPlot_diffRatio_raw_sym_ZOOM_" + plotFileDescriptor + ".png" + optPlot.update({'do_zoom': True}) + plot_diff_spectrum(rawDiffRatio_sym, outPlotName, contour_levs_raw_diffRatio, **optPlot) + + + outPlotName = outPlotDir + "/" + "zwfPlot_diffRatio_raw_asym_" + plotFileDescriptor + ".png" + print("Plotting: %s" % outPlotName) + optPlot.update({'do_zoom': False}) + optPlot.update({'titleStr': "%s: $\Delta$(Raw power)\n" % vari}) + optPlot.update({'leftStr': "(%s$-$%s)/%s" % (srcID_A_short, srcID_B_short, srcID_B_short)}) + optPlot.update({'rightStr': 'Antisymmetric'}) + optPlot.update({'cbar_label': '%'}) + plot_diff_spectrum(rawDiffRatio_asym, outPlotName, contour_levs_raw_diffRatio, **optPlot) + if(do_zooming): + outPlotName = outPlotDir + "/" + "zwfPlot_diffRatio_raw_asym_ZOOM_" + plotFileDescriptor + ".png" + optPlot.update({'do_zoom': True}) + plot_diff_spectrum(rawDiffRatio_asym, outPlotName, contour_levs_raw_diffRatio, **optPlot) + + outPlotName = outPlotDir + "/" + "zwfPlot_diffRatio_background_" + plotFileDescriptor + ".png" + print("Plotting: %s" % outPlotName) + optPlot.update({'do_zoom': False}) + optPlot.update({'titleStr': "%s: $\Delta$(Raw power)\n" % vari}) + optPlot.update({'leftStr': "(%s$-$%s)/%s" % (srcID_A_short, srcID_B_short, srcID_B_short)}) + optPlot.update({'rightStr': 'Background'}) + optPlot.update({'cbar_label': '%'}) + plot_diff_spectrum(backgroundDiffRatio, outPlotName, contour_levs_raw_diffRatio, **optPlot) + if(do_zooming): + outPlotName = outPlotDir + "/" + "zwfPlot_diffRatio_background_ZOOM_" + plotFileDescriptor + ".png" + optPlot.update({'do_zoom': True}) + plot_diff_spectrum(backgroundDiffRatio, outPlotName, contour_levs_raw_diffRatio, **optPlot) + + + # --- Ratio of differences of normalized spectra --- + outPlotName = outPlotDir + "/" + "zwfPlot_diffRatio_norm_sym_" + plotFileDescriptor + ".png" + print("Plotting: %s" % outPlotName) + optPlot.update({'do_zoom': False}) + optPlot.update({'titleStr': "%s: $\Delta$(Normalized power)\n" % vari}) + optPlot.update({'leftStr': "(%s$-$%s)/%s" % (srcID_A_short, srcID_B_short, srcID_B_short)}) + optPlot.update({'rightStr': 'Symmetric'}) + optPlot.update({'cbar_label': '%'}) + plot_diff_spectrum(normDiffRatio_sym, outPlotName, contour_levs_norm_diffRatio, **optPlot) + if(do_zooming): + outPlotName = outPlotDir + "/" + "zwfPlot_diffRatio_norm_sym_ZOOM_" + plotFileDescriptor + ".png" + optPlot.update({'do_zoom': True}) + plot_diff_spectrum(normDiffRatio_sym, outPlotName, contour_levs_norm_diffRatio, **optPlot) + + outPlotName = outPlotDir + "/" + "zwfPlot_diffRatio_norm_asym_" + plotFileDescriptor + ".png" + print("Plotting: %s" % outPlotName) + optPlot.update({'do_zoom': False}) + optPlot.update({'titleStr': "%s: $\Delta$(Normalized power)\n" % vari}) + optPlot.update({'leftStr': "(%s$-$%s)/%s" % (srcID_A_short, srcID_B_short, srcID_B_short)}) + optPlot.update({'rightStr': 'Antisymmetric'}) + optPlot.update({'cbar_label': '%'}) + plot_diff_spectrum(normDiffRatio_asym, outPlotName, contour_levs_norm_diffRatio, **optPlot) + if(do_zooming): + outPlotName = outPlotDir + "/" + "zwfPlot_diffRatio_norm_asym_ZOOM_" + plotFileDescriptor + ".png" + optPlot.update({'do_zoom': True}) + plot_diff_spectrum(normDiffRatio_asym, outPlotName, contour_levs_norm_diffRatio, **optPlot) + From 88e588462a278374f5d1b30753d04fef60953bbf Mon Sep 17 00:00:00 2001 From: ChengzhuZhang Date: Fri, 22 Sep 2023 15:44:20 -0700 Subject: [PATCH 02/46] add stand alone driver to reproduce Jim's results --- .../tropical_subseasonal_driver.py | 337 ++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_driver.py diff --git a/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_driver.py b/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_driver.py new file mode 100644 index 000000000..982d335a0 --- /dev/null +++ b/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_driver.py @@ -0,0 +1,337 @@ +from __future__ import annotations + +import glob +import json +import os +from typing import TYPE_CHECKING # , Optional + +import numpy as np +import xarray as xr + +import e3sm_diags +from e3sm_diags.driver import utils +from e3sm_diags.logger import custom_logger +from e3sm_diags.plot.cartopy.mp_partition_plot import plot +import matplotlib.pyplot as plt +from matplotlib.colors import ListedColormap, BoundaryNorm +from zwf import zwf_functions as wf + +logger = custom_logger(__name__) + +# Script to compute and plot spectral powers of a subseasonal tropical field in +# zonal wavenumber-frequency space. Both the plot files and files containing the +# associated numerical data shown in the plots are created. + +# Authors: Jim Benedict and Brian Medeiros +# Modified by Jill Zhang to integrate into E3SM Diags. + +def find_nearest(array, value): + array = np.asarray(array) + idx = (np.abs(array - value)).argmin() + return idx,array[idx] + """Return index of [array] closest in value to [value] + Example: + array = [ 0.21069679 0.61290182 0.63425412 0.84635244 0.91599191 0.00213826 + 0.17104965 0.56874386 0.57319379 0.28719469] + print(find_nearest(array, value=0.5)) + # 0.568743859261 + + """ + +def wf_analysis(x, **kwargs): + """Return zonal wavenumber-frequency power spectra of x. The returned spectra are: + spec_sym: Raw (non-normalized) power spectrum of the component of x that is symmetric about the equator. + spec_asym: Raw (non-normalized) power spectrum of the component of x that is antisymmetric about the equator. + nspec_sym: Normalized (by a smoothed red-noise background spectrum) power spectrum of the component of x that is symmetric about the equator. + nspec_asym: Normalized (by a smoothed red-noise background spectrum) power spectrum of the component of x that is antisymmetric about the equator. + + The NCL version of 'wkSpaceTime' smooths the symmetric and antisymmetric components + along the frequency dimension using a 1-2-1 filter once. + + """ + # Get the "raw" spectral power + # OPTIONAL kwargs: + # segsize, noverlap, spd, latitude_bounds (tuple: (south, north)), dosymmetries, rmvLowFrq + + z2 = wf.spacetime_power(x, **kwargs) + z2avg = z2.mean(dim='component') + z2.loc[{'frequency':0}] = np.nan # get rid of spurious power at \nu = 0 (mean) + + # Following NCL's wkSpaceTime, apply one pass of a 1-2-1 filter along the frequency + # domain to the raw (non-normalized) spectra/um. + # Do not use 0 frequency when smoothing here. + # Use weights that sum to 1 to ensure that smoothing is conservative. + z2s = wf.smoothFrq121(z2,1) + + # The background is supposed to be derived from both symmetric & antisymmetric + # Inputs to the background spectrum calculation should be z2avg + background = wf.smoothBackground_wavefreq(z2avg) + # separate components + spec_sym = z2s[0,...] + spec_asy = z2s[1,...] + # normalize: Following NCL's wkSpaceTime, use lightly smoothed version of spectra/um + # as numerator + nspec_sym = spec_sym / background + nspec_asy = spec_asy / background + + #spec_sym.rename('spec_raw_sym') + #spec_asy.rename('spec_raw_asy') + #nspec_sym.rename('spec_norm_sym') + #nspec_asy.rename('spec_norm_asy') + #background.rename('spec_background') + #return spec_sym, spec_asy, nspec_sym, nspec_asy, background + return spec_sym.rename('spec_raw_sym'), spec_asy.rename('spec_raw_asy'), nspec_sym.rename('spec_norm_sym'), nspec_asy.rename('spec_norm_asy'), background.rename('spec_background') + +def plot_spectrum(s, ofil=None, clevs=None, cmapSpec='viridis', component=None, spec_type = None, + varName=None, sourceID=None, do_zoom=False, + disp_col='black', disp_thk=1.5, disp_alpha=0.60, + perfrq_col='dimgray', perfrq_thk=1.0, perfrq_alpha=0.80, equivDepths=[50, 25, 12]): + """Basic plot of non-normalized (raw) symmetric power spectrum with shallow water curves.""" + + PlotDesc = {} + PlotDesc['spec_raw_sym'] = {"long_name_desc": f"{varName}: log-base10 of lightly smoothed spectral power of component symmetric about equator", "ref_fig_num": "Figure 1"} # Figure number from Wheeler and Kiladis (1999) + PlotDesc['spec_raw_asy'] = {"long_name_desc": f"{varName}: log-base10 of lightly smoothed spectral power of component antisymmetric about equator", "ref_fig_num": "Figure 1"} + PlotDesc['spec_norm_sym'] = {"long_name_desc": f"{varName}: lightly smoothed spectral power of component symmetric about equator, normalized by heavily smoothed background spectrum", "ref_fig_num": "Figure 3"} + PlotDesc['spec_norm_asy'] = {"long_name_desc": f"{varName}: lightly smoothed spectral power of component antisymmetric about equator, normalized by heavily smoothed background spectrum", "ref_fig_num": "Figure 3"} + PlotDesc['spec_background'] = {"long_name_desc": f"{varName}: heavily smoothed version of the mean of spectral powers associated with the components symmetric and antisymmetric about equator", "ref_fig_num": "Figure 2"} + print(s) + print('xxxxx', s.name, PlotDesc[s.name]) + + text_offset = 0.005 + fb = [0, .8] # frequency bounds for plot + wnb = [-15, 15] # zonal wavenumber bounds for plot + if(max(s['frequency'].values) == 0.5): + fb = [0, .5] + if(do_zoom): + fb = [0, .18] + wnb = [-7, 7] + # get data for dispersion curves: + swfreq,swwn = wf.genDispersionCurves(Ahe=equivDepths) + # swfreq.shape # -->(6, 3, 50) + if spec_type == "normalized": # with dispersion curves to plot + # For n=1 ER waves, allow dispersion curves to touch 0 -- this is for plot aesthetics only + for i in range(0,3): # loop 0-->2 for the assumed 3 shallow water dispersion curves for ER waves + indMinPosFrqER = np.where(swwn[3,i,:] >= 0., swwn[3,i,:], 1e20).argmin() # index of swwn for least positive wn + swwn[3,i,indMinPosFrqER],swfreq[3,i,indMinPosFrqER] = 0.,0. # this sets ER's frequencies to 0. at wavenumber 0. + + swf = np.where(swfreq == 1e20, np.nan, swfreq) + swk = np.where(swwn == 1e20, np.nan, swwn) + + cmapSpecUse = ListedColormap(cmapSpec[1:-1]) # recall: range is NOT inclusive for final index + cmapSpecUse.set_under(cmapSpec[0]) + cmapSpecUse.set_over(cmapSpec[-1]) + normSpecUse = BoundaryNorm(clevs, cmapSpecUse.N) + + # Final data refinement: transpose and trim, set 0 freq to NaN, take log10 for raw, refine metadata + z = s.transpose().sel(frequency=slice(*fb), wavenumber=slice(*wnb)) + z.loc[{'frequency':0}] = np.nan + + if 'spec_raw' in s.name: + + east_power = z.sel(frequency=slice((1./96.),(1./24.)), wavenumber=slice(1,3)).sum() + west_power = z.sel(frequency=slice((1./96.),(1./24.)), wavenumber=slice(-3,-1)).sum() + ew_ratio = east_power / west_power + print("\neast_power: %12.5f" % east_power) + print("west_power: %12.5f" % west_power) + print("ew_ratio: %12.5f\n" % ew_ratio) + + z = np.log10(z) + + z.attrs["long_name"] = PlotDesc[s.name]["long_name_desc"] + z.attrs["method"] = f"Follows {PlotDesc[s.name]['ref_fig_num']} methods of Wheeler and Kiladis (1999; https://doi.org/10.1175/1520-0469(1999)056<0374:CCEWAO>2.0.CO;2)" + + if 'spec_raw' in s.name: + + z.attrs["ew_ratio_method"] = "Sum of raw (not log10) symmetric spectral power for ZWNs +/- 1-3, periods 24-96 days" + z.attrs["east_power"] = east_power.values + z.attrs["west_power"] = west_power.values + z.attrs["ew_ratio"] = ew_ratio.values + + # Save plotted data z to file as xArray data array + dataDesc = f"spectral_power_{srcID}_{vari}_{spec_type}_{component}_200101_201412" + z.to_netcdf(outDataDir + "/"+ dataDesc + ".nc") + + fig, ax = plt.subplots() + kmesh0, vmesh0 = np.meshgrid(z['wavenumber'], z['frequency']) + #img = ax.contourf(kmesh0, vmesh0, z, levels=np.linspace(0.2, 3.0, 16), cmap='Spectral_r', extend='both') + img = ax.contourf(kmesh0, vmesh0, z, levels=clevs, cmap=cmapSpecUse, norm=normSpecUse, extend='both') + img2 = ax.contour(kmesh0, vmesh0, z, levels=clevs, linewidths=1., linestyles='solid', colors='gray', alpha=0.7) + ax.axvline(0, linestyle='dashed', color=perfrq_col, linewidth=perfrq_thk, alpha=disp_alpha) + if( (1./30.) < fb[1] ): + ax.axhline((1./30.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./30.)+text_offset,'30 days',color=perfrq_col, alpha=perfrq_alpha) + if( (1./6.) < fb[1] ): + ax.axhline((1./6.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./6.)+text_offset,'6 days',color=perfrq_col, alpha=perfrq_alpha) + if( (1./3.) < fb[1] ): + ax.axhline((1./3.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) + ax.text(wnb[0]+1,(1./3.)+text_offset,'3 days',color=perfrq_col, alpha=perfrq_alpha) + for ii in range(3,6): + ax.plot(swk[ii, 0,:], swf[ii,0,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + ax.plot(swk[ii, 1,:], swf[ii,1,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + ax.plot(swk[ii, 2,:], swf[ii,2,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) + ax.set_xlim(wnb) + ax.set_ylim(fb) + #ax.set_title(varName + ": Log ${\sum_{15^{\circ}S}^{15^{\circ}N} Power_{SYM}}$") # Version w/ LaTeX + if spec_type == "raw": + ax.set_title(f"{varName}: Log{{Sum(Power) from 15°S-15°N}}\n") # Version w/o LaTeX + elif spec_type == "normalized": + ax.set_title(f"{varName}: {{Sum(Power) from 15°S-15°N}}/Background\n") # Version w/o LaTeX + else: + ax.set_title(f"{varName}: Log{{Smoothed Background Power}}\n") + + ax.set_title(sourceID, loc='left') + ax.set_title(f"{component}", loc='right') + + if spec_type == "normalized": + # Shallow water dispersion curve line labels: See https://matplotlib.org/stable/tutorials/text/text_intro.html + # n=1 ER dispersion curve labels + text_opt = {'fontsize': 9,'verticalalignment': 'center','horizontalalignment': 'center','clip_on': True,'bbox': {'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}} + iwave, ih = 3, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -11.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 3, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -9.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 3, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + ax.text(-7.,0.10,'n=1 ER',text_opt) + + # Kelvin dispersion curve labels + iwave, ih = 4, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 4, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 10.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 4, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 14.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + ax.text(6.,0.13,'Kelvin',text_opt) + + # IG dispersion curve labels + iwave, ih = 5, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 5, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 5, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + ax.text(-10.,0.48,'n=1 WIG',text_opt) + ax.text(5.,0.48,'n=1 EIG',text_opt) + + # MJO label + ax.text(6.,0.0333,'MJO',text_opt) + + + plt.ylabel("Frequency (CPD)") + plt.xlabel("Zonal wavenumber") + plt.gcf().text(0.12, 0.03, "Westward", fontsize=11) + plt.gcf().text(0.64, 0.03, "Eastward", fontsize=11) + fig.colorbar(img) + # Save fig + fig.savefig(outDataDir + "/"+ dataDesc + "_plot.png", bbox_inches='tight', dpi=300) + + print("Plot file created: %s\n" % outDataDir + "/"+ dataDesc + "_plot.png") + + + +# +# Options ... right now these only go into wk.spacetime_power() +# +do_zooming = False # Set to True to also make plots to zoom into MJO spectral region, + # in addition to the default (larger) spectral region +latBound = (-15,15) # latitude bounds for analysis +spd = 1 # SAMPLES PER DAY +nDayWin = 96 # Wheeler-Kiladis [WK] temporal window length (days) +nDaySkip = -60 # time (days) between temporal windows [segments] + # negative means there will be overlapping temporal segments +twoMonthOverlap = -1*nDaySkip + +vari = "PRECT" +srcID = "model" +outDataDir = "/global/cfs/cdirs/e3sm/www/chengzhu/tests/tropical_diags" + +opt = {'segsize': nDayWin, + 'noverlap': twoMonthOverlap, + 'spd': spd, + 'latitude_bounds': latBound, + 'dosymmetries': True, + 'rmvLowFrq':True} + +# Generic settings for spectra plots -- do not need to ba adjusted by user, but can be +# customized. The user can add additional key-value pairs as necessary to expand +# plotting customization. +contour_levs_raw_spec = (-1.4,-1.2,-1,-0.9,-0.8,-0.7,-0.6,-0.5,-0.4,-0.3,-0.2,-0.1,0,0.1,0.2) +#contour_levs_norm_spec = (0.9,1,1.1,1.2,1.3,1.4,1.5,1.7,1.9,2.1,2.4,2.7,3,3.5,4) +cmapSpecR = ["white", + "paleturquoise","lightblue","skyblue", + "lightgreen","limegreen","green","darkgreen", + "yellow","orange","orangered", + "red","maroon","magenta","orchid","pink", + "lavenderblush"] +contour_levs_norm_spec = (0.9,1.0,1.1,1.2,1.3,1.4,1.5,1.7,1.9,2.1,2.4,2.7,3.0) + +cmapSpecN = ["white", + "gainsboro","lightgray","silver", + "paleturquoise","skyblue", + "lightgreen","mediumseagreen","seagreen", + "yellow","orange", + "red","maroon","pink"] + +optPlot = {'varName': vari, + 'sourceID': srcID, + 'do_zoom' : False, + 'disp_col': 'black', + 'disp_thk': 1.5, + 'disp_alpha': 0.60, + 'perfrq_col': 'dimgray', + 'perfrq_thk': 1.0, + 'perfrq_alpha': 0.80, + 'equivDepths' : [50, 25, 12]} + + + +datapath = '/global/cfs/cdirs/e3sm/forsyth/E3SMv2/v2.LR.historical_0201/post/atm/180x360_aave/ts/daily/5yr' +data = xr.open_mfdataset(glob.glob(f"{datapath}/PRECT_201001_201412.nc")).sel( + lat=slice(-15, 15))['PRECT'] +# TODO: subset time + +# Unit conversion +if vari == "PRECT": + if data.attrs["units"] == "m/s" or data.attrs["units"] == "m s{-1}": + print("\nBEFORE unit conversion: Max/min of data: " + str(data.values.max()) + " " + str(data.values.min())) + data.values = data.values * 1000. * 86400. # convert m/s to mm/d, do not alter metadata (yet) + data.attrs["units"] = "mm/d" # adjust metadata to reflect change in units + print("\nAFTER unit conversion: Max/min of data: " + str(data.values.max()) + " " + str(data.values.min())) + print(data) +if vari == "precipAvg": + if data.attrs["units"] == "mm/hr": + print("\nBEFORE unit conversion: Max/min of data: " + str(data.values.max()) + " " + str(data.values.min())) + data.values = data.values * 24. # convert mm/hr to mm/d, do not alter metadata (yet) + data.attrs["units"] = "mm/d" # adjust metadata to reflect change in units + print("\nAFTER unit conversion: Max/min of data: " + str(data.values.max()) + " " + str(data.values.min())) + print(data) + + +# Wavenumber Frequency Analysis +spec_raw_sym, spec_raw_asym, spec_norm_sym, spec_norm_asym, spec_background = wf_analysis(data, **opt) +print("\nspec_raw_sym metadata:") +print(spec_raw_sym.dims) +print(spec_raw_sym.coords) +print(spec_raw_sym.attrs) +print(spec_raw_sym.max()) + + +outPlotName = outDataDir +plot_spectrum(spec_raw_sym, outPlotName, contour_levs_raw_spec, cmapSpecR,component="symmetric", spec_type = "raw", **optPlot) +plot_spectrum(spec_raw_asym, outPlotName, contour_levs_raw_spec, cmapSpecR,component="antisymmetric", spec_type = "raw", **optPlot) +plot_spectrum(spec_background, outPlotName, contour_levs_raw_spec, cmapSpecR,component="background", spec_type = "background", **optPlot) +plot_spectrum(spec_norm_sym, outPlotName, contour_levs_norm_spec, cmapSpecN,component="symmetric", spec_type = "normalized", **optPlot) +plot_spectrum(spec_norm_asym, outPlotName, contour_levs_norm_spec, cmapSpecN,component="antisymmetric", spec_type = "normalized", **optPlot) + + + From 58d0b5a73b7da805435fc297bf7e8765f61d094d Mon Sep 17 00:00:00 2001 From: chengzhuzhang Date: Mon, 20 Nov 2023 17:03:51 -0800 Subject: [PATCH 03/46] add plotting; clean up driver --- .../tropical_subseasonal_driver.py | 313 ++++---------- .../tropical_subseasonal_plot.py | 395 ++++++++++++++++++ 2 files changed, 476 insertions(+), 232 deletions(-) create mode 100644 auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_plot.py diff --git a/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_driver.py b/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_driver.py index 982d335a0..80387a4cc 100644 --- a/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_driver.py +++ b/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_driver.py @@ -14,6 +14,8 @@ from e3sm_diags.plot.cartopy.mp_partition_plot import plot import matplotlib.pyplot as plt from matplotlib.colors import ListedColormap, BoundaryNorm + +from tropical_subseasonal_plot import plot from zwf import zwf_functions as wf logger = custom_logger(__name__) @@ -73,171 +75,43 @@ def wf_analysis(x, **kwargs): # as numerator nspec_sym = spec_sym / background nspec_asy = spec_asy / background - - #spec_sym.rename('spec_raw_sym') - #spec_asy.rename('spec_raw_asy') - #nspec_sym.rename('spec_norm_sym') - #nspec_asy.rename('spec_norm_asy') - #background.rename('spec_background') - #return spec_sym, spec_asy, nspec_sym, nspec_asy, background - return spec_sym.rename('spec_raw_sym'), spec_asy.rename('spec_raw_asy'), nspec_sym.rename('spec_norm_sym'), nspec_asy.rename('spec_norm_asy'), background.rename('spec_background') - -def plot_spectrum(s, ofil=None, clevs=None, cmapSpec='viridis', component=None, spec_type = None, - varName=None, sourceID=None, do_zoom=False, - disp_col='black', disp_thk=1.5, disp_alpha=0.60, - perfrq_col='dimgray', perfrq_thk=1.0, perfrq_alpha=0.80, equivDepths=[50, 25, 12]): - """Basic plot of non-normalized (raw) symmetric power spectrum with shallow water curves.""" - - PlotDesc = {} - PlotDesc['spec_raw_sym'] = {"long_name_desc": f"{varName}: log-base10 of lightly smoothed spectral power of component symmetric about equator", "ref_fig_num": "Figure 1"} # Figure number from Wheeler and Kiladis (1999) - PlotDesc['spec_raw_asy'] = {"long_name_desc": f"{varName}: log-base10 of lightly smoothed spectral power of component antisymmetric about equator", "ref_fig_num": "Figure 1"} - PlotDesc['spec_norm_sym'] = {"long_name_desc": f"{varName}: lightly smoothed spectral power of component symmetric about equator, normalized by heavily smoothed background spectrum", "ref_fig_num": "Figure 3"} - PlotDesc['spec_norm_asy'] = {"long_name_desc": f"{varName}: lightly smoothed spectral power of component antisymmetric about equator, normalized by heavily smoothed background spectrum", "ref_fig_num": "Figure 3"} - PlotDesc['spec_background'] = {"long_name_desc": f"{varName}: heavily smoothed version of the mean of spectral powers associated with the components symmetric and antisymmetric about equator", "ref_fig_num": "Figure 2"} - print(s) - print('xxxxx', s.name, PlotDesc[s.name]) - text_offset = 0.005 - fb = [0, .8] # frequency bounds for plot - wnb = [-15, 15] # zonal wavenumber bounds for plot - if(max(s['frequency'].values) == 0.5): - fb = [0, .5] - if(do_zoom): - fb = [0, .18] - wnb = [-7, 7] - # get data for dispersion curves: - swfreq,swwn = wf.genDispersionCurves(Ahe=equivDepths) - # swfreq.shape # -->(6, 3, 50) - if spec_type == "normalized": # with dispersion curves to plot - # For n=1 ER waves, allow dispersion curves to touch 0 -- this is for plot aesthetics only - for i in range(0,3): # loop 0-->2 for the assumed 3 shallow water dispersion curves for ER waves - indMinPosFrqER = np.where(swwn[3,i,:] >= 0., swwn[3,i,:], 1e20).argmin() # index of swwn for least positive wn - swwn[3,i,indMinPosFrqER],swfreq[3,i,indMinPosFrqER] = 0.,0. # this sets ER's frequencies to 0. at wavenumber 0. - - swf = np.where(swfreq == 1e20, np.nan, swfreq) - swk = np.where(swwn == 1e20, np.nan, swwn) - - cmapSpecUse = ListedColormap(cmapSpec[1:-1]) # recall: range is NOT inclusive for final index - cmapSpecUse.set_under(cmapSpec[0]) - cmapSpecUse.set_over(cmapSpec[-1]) - normSpecUse = BoundaryNorm(clevs, cmapSpecUse.N) - - # Final data refinement: transpose and trim, set 0 freq to NaN, take log10 for raw, refine metadata - z = s.transpose().sel(frequency=slice(*fb), wavenumber=slice(*wnb)) - z.loc[{'frequency':0}] = np.nan - - if 'spec_raw' in s.name: - - east_power = z.sel(frequency=slice((1./96.),(1./24.)), wavenumber=slice(1,3)).sum() - west_power = z.sel(frequency=slice((1./96.),(1./24.)), wavenumber=slice(-3,-1)).sum() - ew_ratio = east_power / west_power - print("\neast_power: %12.5f" % east_power) - print("west_power: %12.5f" % west_power) - print("ew_ratio: %12.5f\n" % ew_ratio) - - z = np.log10(z) - - z.attrs["long_name"] = PlotDesc[s.name]["long_name_desc"] - z.attrs["method"] = f"Follows {PlotDesc[s.name]['ref_fig_num']} methods of Wheeler and Kiladis (1999; https://doi.org/10.1175/1520-0469(1999)056<0374:CCEWAO>2.0.CO;2)" - - if 'spec_raw' in s.name: - - z.attrs["ew_ratio_method"] = "Sum of raw (not log10) symmetric spectral power for ZWNs +/- 1-3, periods 24-96 days" - z.attrs["east_power"] = east_power.values - z.attrs["west_power"] = west_power.values - z.attrs["ew_ratio"] = ew_ratio.values - - # Save plotted data z to file as xArray data array - dataDesc = f"spectral_power_{srcID}_{vari}_{spec_type}_{component}_200101_201412" - z.to_netcdf(outDataDir + "/"+ dataDesc + ".nc") - - fig, ax = plt.subplots() - kmesh0, vmesh0 = np.meshgrid(z['wavenumber'], z['frequency']) - #img = ax.contourf(kmesh0, vmesh0, z, levels=np.linspace(0.2, 3.0, 16), cmap='Spectral_r', extend='both') - img = ax.contourf(kmesh0, vmesh0, z, levels=clevs, cmap=cmapSpecUse, norm=normSpecUse, extend='both') - img2 = ax.contour(kmesh0, vmesh0, z, levels=clevs, linewidths=1., linestyles='solid', colors='gray', alpha=0.7) - ax.axvline(0, linestyle='dashed', color=perfrq_col, linewidth=perfrq_thk, alpha=disp_alpha) - if( (1./30.) < fb[1] ): - ax.axhline((1./30.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) - ax.text(wnb[0]+1,(1./30.)+text_offset,'30 days',color=perfrq_col, alpha=perfrq_alpha) - if( (1./6.) < fb[1] ): - ax.axhline((1./6.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) - ax.text(wnb[0]+1,(1./6.)+text_offset,'6 days',color=perfrq_col, alpha=perfrq_alpha) - if( (1./3.) < fb[1] ): - ax.axhline((1./3.), linestyle='dashed', color=perfrq_col, alpha=perfrq_alpha) - ax.text(wnb[0]+1,(1./3.)+text_offset,'3 days',color=perfrq_col, alpha=perfrq_alpha) - for ii in range(3,6): - ax.plot(swk[ii, 0,:], swf[ii,0,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) - ax.plot(swk[ii, 1,:], swf[ii,1,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) - ax.plot(swk[ii, 2,:], swf[ii,2,:], color=disp_col, linewidth=disp_thk, alpha=perfrq_alpha) - ax.set_xlim(wnb) - ax.set_ylim(fb) - #ax.set_title(varName + ": Log ${\sum_{15^{\circ}S}^{15^{\circ}N} Power_{SYM}}$") # Version w/ LaTeX - if spec_type == "raw": - ax.set_title(f"{varName}: Log{{Sum(Power) from 15°S-15°N}}\n") # Version w/o LaTeX - elif spec_type == "normalized": - ax.set_title(f"{varName}: {{Sum(Power) from 15°S-15°N}}/Background\n") # Version w/o LaTeX - else: - ax.set_title(f"{varName}: Log{{Smoothed Background Power}}\n") - - ax.set_title(sourceID, loc='left') - ax.set_title(f"{component}", loc='right') - - if spec_type == "normalized": - # Shallow water dispersion curve line labels: See https://matplotlib.org/stable/tutorials/text/text_intro.html - # n=1 ER dispersion curve labels - text_opt = {'fontsize': 9,'verticalalignment': 'center','horizontalalignment': 'center','clip_on': True,'bbox': {'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}} - iwave, ih = 3, 0 - idxClose,valClose = find_nearest(swk[iwave,ih,:], -11.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) - iwave, ih = 3, 1 - idxClose,valClose = find_nearest(swk[iwave,ih,:], -9.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) - iwave, ih = 3, 2 - idxClose,valClose = find_nearest(swk[iwave,ih,:], -8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) - ax.text(-7.,0.10,'n=1 ER',text_opt) - - # Kelvin dispersion curve labels - iwave, ih = 4, 0 - idxClose,valClose = find_nearest(swk[iwave,ih,:], 8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) - iwave, ih = 4, 1 - idxClose,valClose = find_nearest(swk[iwave,ih,:], 10.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) - iwave, ih = 4, 2 - idxClose,valClose = find_nearest(swk[iwave,ih,:], 14.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) - ax.text(6.,0.13,'Kelvin',text_opt) - - # IG dispersion curve labels - iwave, ih = 5, 0 - idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) - iwave, ih = 5, 1 - idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) - iwave, ih = 5, 2 - idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) - ax.text(-10.,0.48,'n=1 WIG',text_opt) - ax.text(5.,0.48,'n=1 EIG',text_opt) - - # MJO label - ax.text(6.,0.0333,'MJO',text_opt) - - - plt.ylabel("Frequency (CPD)") - plt.xlabel("Zonal wavenumber") - plt.gcf().text(0.12, 0.03, "Westward", fontsize=11) - plt.gcf().text(0.64, 0.03, "Eastward", fontsize=11) - fig.colorbar(img) - # Save fig - fig.savefig(outDataDir + "/"+ dataDesc + "_plot.png", bbox_inches='tight', dpi=300) - - print("Plot file created: %s\n" % outDataDir + "/"+ dataDesc + "_plot.png") + spec = xr.merge([spec_sym.rename('spec_raw_sym'), spec_asy.rename('spec_raw_asy'), nspec_sym.rename('spec_norm_sym'), nspec_asy.rename('spec_norm_asy'), background.rename('spec_background')], compat='override') + spec_all = spec.drop('component') + spec_all['spec_raw_sym'].attrs = {"component": "symmetric", "type": "raw"} + spec_all['spec_raw_asy'].attrs = {"component": "antisymmetric", "type": "raw"} + spec_all['spec_norm_sym'].attrs = {"component": "symmetric", "type": "normalized"} + spec_all['spec_norm_asy'].attrs = {"component": "antisymmetric", "type": "normalized"} + spec_all['spec_background'].attrs = {"component": "", "type": "background"} + + return spec_all + +def calculate_spectrum(path, variable): + var = xr.open_mfdataset(glob.glob(f"{test_data_path}/{variable}_*.nc")).sel( + lat=slice(-15, 15))[variable] + + # TODO: subset time + + # Unit conversion + if var.name == "PRECT": + if var.attrs["units"] == "m/s" or var.attrs["units"] == "m s{-1}": + print("\nBEFORE unit conversion: Max/min of data: " + str(var.values.max()) + " " + str(var.values.min())) + var.values = var.values * 1000. * 86400. # convert m/s to mm/d, do not alter metadata (yet) + var.attrs["units"] = "mm/d" # adjust metadata to reflect change in units + print("\nAFTER unit conversion: Max/min of data: " + str(var.values.max()) + " " + str(var.values.min())) + if var.name == "precipAvg": + if var.attrs["units"] == "mm/hr": + print("\nBEFORE unit conversion: Max/min of data: " + str(var.values.max()) + " " + str(var.values.min())) + var.values = data.values * 24. # convert mm/hr to mm/d, do not alter metadata (yet) + var.attrs["units"] = "mm/d" # adjust metadata to reflect change in units + print("\nAFTER unit conversion: Max/min of data: " + str(var.values.max()) + " " + str(var.values.min())) + + # Wavenumber Frequency Analysis + spec_all = wf_analysis(var, **opt) + #spec_all.to_netcdf(outDataDir + "/full_spec.nc") + return spec_all # # Options ... right now these only go into wk.spacetime_power() @@ -254,6 +128,7 @@ def plot_spectrum(s, ofil=None, clevs=None, cmapSpec='viridis', component=None, vari = "PRECT" srcID = "model" outDataDir = "/global/cfs/cdirs/e3sm/www/chengzhu/tests/tropical_diags" +outDataDir = "/Users/zhang40/Documents/repos/e3sm_diags/auxiliary_tools/tropical_subseasonal_diags/data" opt = {'segsize': nDayWin, 'noverlap': twoMonthOverlap, @@ -262,76 +137,50 @@ def plot_spectrum(s, ofil=None, clevs=None, cmapSpec='viridis', component=None, 'dosymmetries': True, 'rmvLowFrq':True} -# Generic settings for spectra plots -- do not need to ba adjusted by user, but can be -# customized. The user can add additional key-value pairs as necessary to expand -# plotting customization. -contour_levs_raw_spec = (-1.4,-1.2,-1,-0.9,-0.8,-0.7,-0.6,-0.5,-0.4,-0.3,-0.2,-0.1,0,0.1,0.2) -#contour_levs_norm_spec = (0.9,1,1.1,1.2,1.3,1.4,1.5,1.7,1.9,2.1,2.4,2.7,3,3.5,4) -cmapSpecR = ["white", - "paleturquoise","lightblue","skyblue", - "lightgreen","limegreen","green","darkgreen", - "yellow","orange","orangered", - "red","maroon","magenta","orchid","pink", - "lavenderblush"] -contour_levs_norm_spec = (0.9,1.0,1.1,1.2,1.3,1.4,1.5,1.7,1.9,2.1,2.4,2.7,3.0) - -cmapSpecN = ["white", - "gainsboro","lightgray","silver", - "paleturquoise","skyblue", - "lightgreen","mediumseagreen","seagreen", - "yellow","orange", - "red","maroon","pink"] - -optPlot = {'varName': vari, - 'sourceID': srcID, - 'do_zoom' : False, - 'disp_col': 'black', - 'disp_thk': 1.5, - 'disp_alpha': 0.60, - 'perfrq_col': 'dimgray', - 'perfrq_thk': 1.0, - 'perfrq_alpha': 0.80, - 'equivDepths' : [50, 25, 12]} - - - -datapath = '/global/cfs/cdirs/e3sm/forsyth/E3SMv2/v2.LR.historical_0201/post/atm/180x360_aave/ts/daily/5yr' -data = xr.open_mfdataset(glob.glob(f"{datapath}/PRECT_201001_201412.nc")).sel( - lat=slice(-15, 15))['PRECT'] -# TODO: subset time - -# Unit conversion -if vari == "PRECT": - if data.attrs["units"] == "m/s" or data.attrs["units"] == "m s{-1}": - print("\nBEFORE unit conversion: Max/min of data: " + str(data.values.max()) + " " + str(data.values.min())) - data.values = data.values * 1000. * 86400. # convert m/s to mm/d, do not alter metadata (yet) - data.attrs["units"] = "mm/d" # adjust metadata to reflect change in units - print("\nAFTER unit conversion: Max/min of data: " + str(data.values.max()) + " " + str(data.values.min())) - print(data) -if vari == "precipAvg": - if data.attrs["units"] == "mm/hr": - print("\nBEFORE unit conversion: Max/min of data: " + str(data.values.max()) + " " + str(data.values.min())) - data.values = data.values * 24. # convert mm/hr to mm/d, do not alter metadata (yet) - data.attrs["units"] = "mm/d" # adjust metadata to reflect change in units - print("\nAFTER unit conversion: Max/min of data: " + str(data.values.max()) + " " + str(data.values.min())) - print(data) - - -# Wavenumber Frequency Analysis -spec_raw_sym, spec_raw_asym, spec_norm_sym, spec_norm_asym, spec_background = wf_analysis(data, **opt) -print("\nspec_raw_sym metadata:") -print(spec_raw_sym.dims) -print(spec_raw_sym.coords) -print(spec_raw_sym.attrs) -print(spec_raw_sym.max()) - - -outPlotName = outDataDir -plot_spectrum(spec_raw_sym, outPlotName, contour_levs_raw_spec, cmapSpecR,component="symmetric", spec_type = "raw", **optPlot) -plot_spectrum(spec_raw_asym, outPlotName, contour_levs_raw_spec, cmapSpecR,component="antisymmetric", spec_type = "raw", **optPlot) -plot_spectrum(spec_background, outPlotName, contour_levs_raw_spec, cmapSpecR,component="background", spec_type = "background", **optPlot) -plot_spectrum(spec_norm_sym, outPlotName, contour_levs_norm_spec, cmapSpecN,component="symmetric", spec_type = "normalized", **optPlot) -plot_spectrum(spec_norm_asym, outPlotName, contour_levs_norm_spec, cmapSpecN,component="antisymmetric", spec_type = "normalized", **optPlot) + +#datapath = '/global/cfs/cdirs/e3sm/forsyth/E3SMv2/v2.LR.historical_0201/post/atm/180x360_aave/ts/daily/5yr' +datapath = '/Users/zhang40/Documents/e3sm_diags_data/e3sm_diags_test_data/E3SM_v2_daily' + +from e3sm_diags.parameter.core_parameter import CoreParameter +parameter = CoreParameter() + +test_data_path = '/Users/zhang40/Documents/e3sm_diags_data/e3sm_diags_test_data/E3SM_v2_daily' +parameter.test_data_path = test_data_path +parameter.test_timeseries_input = True +parameter.test_start_yr = '2000' +parameter.test_end_yr = '2014' +parameter.ref_data_path = test_data_path +parameter.ref_timeseries_input = True +parameter.ref_start_yr = '2000' +parameter.ref_end_yr = '2014' +parameter.variables = ['PRECT'] +season = "ANN" + +test_data = utils.dataset.Dataset(parameter, test=True) +parameter.test_name_yrs = utils.general.get_name_and_yrs( + parameter, test_data, season +) + +ref_data = utils.dataset.Dataset(parameter, ref=True) +parameter.ref_name_yrs = utils.general.get_name_and_yrs( + parameter, ref_data, season +) + +for variable in parameter.variables: + #test = calculate_spectrum(parameter.test_data_path, variable) + #test.to_netcdf("data/full_spec_test.nc") + #ref = calculate_spectrum(parameter.ref_data_path, variable) + #ref.to_netcdf("data/full_spec_ref.nc") + test = xr.open_dataset("/Users/zhang40/Documents/repos/e3sm_diags/auxiliary_tools/tropical_subseasonal_diags/data/full_spec_ref.nc").load() + ref = xr.open_dataset("/Users/zhang40/Documents/repos/e3sm_diags/auxiliary_tools/tropical_subseasonal_diags/data/full_spec_ref.nc").load() + + for diff_name in ["raw_sym", "raw_asy", "norm_sym", "norm_asy", "background"]: + diff = test[f"spec_{diff_name}"]-ref[f"spec_{diff_name}"] + diff.name = f"spec_{diff_name}" + diff.attrs.update(test[f"spec_{diff_name}"].attrs) + parameter.spec_type = diff_name + plot(parameter, test[f"spec_{diff_name}"], ref[f"spec_{diff_name}"], diff) + diff --git a/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_plot.py b/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_plot.py new file mode 100644 index 000000000..ccf2199b6 --- /dev/null +++ b/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_plot.py @@ -0,0 +1,395 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import matplotlib +import xarray as xr + +from e3sm_diags.logger import custom_logger +from e3sm_diags.parameter.core_parameter import CoreParameter +import matplotlib.pyplot as plt +from matplotlib.colors import ListedColormap, BoundaryNorm +import numpy as np +import os +#from e3sm_diags.plot.utils import _add_colormap, _save_plot +from zwf import zwf_functions as wf + + +if TYPE_CHECKING: + from e3sm_diags.driver.lat_lon_driver import MetricsDict + + +matplotlib.use("Agg") +import matplotlib.pyplot as plt # isort:skip # noqa: E402 + +logger = custom_logger(__name__) + +# Plot title and side title configurations. +PLOT_TITLE = {"fontsize": 11.5} +PLOT_SIDE_TITLE = {"fontsize": 9.5} + +# Position and sizes of subplot axes in page coordinates (0 to 1) +PANEL = [ + (0.17, 0.70, 0.50, 0.25), + (0.17, 0.37, 0.50, 0.25), + (0.17, 0.04, 0.50, 0.25), +] + +# Border padding relative to subplot axes for saving individual panels +# (left, bottom, right, top) in page coordinates +BORDER_PADDING = (-0.06, -0.03, 0.13, 0.03) + +CONTOUR_LEVS_SPEC_RAW = (-1.4,-1.2,-1,-0.9,-0.8,-0.7,-0.6,-0.5,-0.4,-0.3,-0.2,-0.1,0,0.1,0.2) + +CMAP_SPEC_RAW = ["white", + "paleturquoise","lightblue","skyblue", + "lightgreen","limegreen","green","darkgreen", + "yellow","orange","orangered", + "red","maroon","magenta","orchid","pink", + "lavenderblush"] + +CONTOUR_LEVS_SPEC_NORM = (0.9,1.0,1.1,1.2,1.3,1.4,1.5,1.7,1.9,2.1,2.4,2.7,3.0) + +CMAP_SPEC_NORM = ["white", + "gainsboro","lightgray","silver", + "paleturquoise","skyblue", + "lightgreen","mediumseagreen","seagreen", + "yellow","orange", + "red","maroon","pink"] + +def find_nearest(array, value): + array = np.asarray(array) + idx = (np.abs(array - value)).argmin() + return idx,array[idx] + """Return index of [array] closest in value to [value] + Example: + array = [ 0.21069679 0.61290182 0.63425412 0.84635244 0.91599191 0.00213826 + 0.17104965 0.56874386 0.57319379 0.28719469] + print(find_nearest(array, value=0.5)) + # 0.568743859261 + + """ + +def create_colormap_clevs(cmapSpec, clevs): + cmapSpecUse = ListedColormap(cmapSpec[1:-1]) # recall: range is NOT inclusive for final index + cmapSpecUse.set_under(cmapSpec[0]) + cmapSpecUse.set_over(cmapSpec[-1]) + normSpecUse = BoundaryNorm(clevs, cmapSpecUse.N) + + return cmapSpecUse, normSpecUse + + + +def _save_plot(fig: plt.figure, parameter: CoreParameter): + """Save the plot using the figure object and parameter configs. + + This function creates the output filename to save the plot. It also + saves each individual subplot if the reference name is an empty string (""). + + Parameters + ---------- + fig : plt.figure + The plot figure. + parameter : CoreParameter + The CoreParameter with file configurations. + """ + for f in parameter.output_format: + f = f.lower().split(".")[-1] + #fnm = os.path.join( + # f'{parameter.spec_type}', + # #get_output_dir(parameter.current_set, parameter), + # parameter.output_file + "." + f, + #) + fnm = f'{parameter.spec_type}.png' + plt.savefig(fnm) + logger.info(f"Plot saved in: {fnm}") + + # Save individual subplots + if parameter.ref_name == "": + panels = [PANEL[0]] + else: + panels = PANEL + + for f in parameter.output_format_subplot: + fnm = os.path.join( + get_output_dir(parameter.current_set, parameter), + parameter.output_file, + ) + page = fig.get_size_inches() + + for idx, panel in enumerate(panels): + # Extent of subplot + subpage = np.array(panel).reshape(2, 2) + subpage[1, :] = subpage[0, :] + subpage[1, :] + subpage = subpage + np.array(BORDER_PADDING).reshape(2, 2) + subpage = list(((subpage) * page).flatten()) # type: ignore + extent = Bbox.from_extents(*subpage) + + # Save subplot + fname = fnm + ".%i." % idx + f + plt.savefig(fname, bbox_inches=extent) + + orig_fnm = os.path.join( + './', + #get_output_dir(parameter.current_set, parameter), + parameter.output_file, + ) + fname = orig_fnm + ".%i." % idx + f + logger.info(f"Sub-plot saved in: {fname}") + + + +def _wave_frequency_plot( + subplot_num: int, + var: xr.DataArray, + fig: plt.figure, + parameter: CoreParameter, + title: Tuple[str | None, str, str], + do_zoom: Boolean, +): + """Create wave frequency plot. + + Parameters + ---------- + subplot_num : int + The subplot number. + var : xr.DataArray + The variable to plot. + fig : plt.figure + The figure object to add the subplot to. + parameter : CoreParameter + The CoreParameter object containing plot configurations. + title : Tuple[str | None, str, str] + A tuple of strings to form the title of the colormap, in the format + ( years, title, units). + do_zoom: Boolean + """ + #TODO link var_id + #varName = parameter.var_id + varName = 'PRECT' + PlotDesc = {} + PlotDesc['spec_raw_sym'] = {"long_name_desc": f"{varName}: log-base10 of lightly smoothed spectral power of component symmetric about equator", "ref_fig_num": "Figure 1"} # Figure number from Wheeler and Kiladis (1999) + PlotDesc['spec_raw_asy'] = {"long_name_desc": f"{varName}: log-base10 of lightly smoothed spectral power of component antisymmetric about equator", "ref_fig_num": "Figure 1"} + PlotDesc['spec_norm_sym'] = {"long_name_desc": f"{varName}: lightly smoothed spectral power of component symmetric about equator, normalized by heavily smoothed background spectrum", "ref_fig_num": "Figure 3"} + PlotDesc['spec_norm_asy'] = {"long_name_desc": f"{varName}: lightly smoothed spectral power of component antisymmetric about equator, normalized by heavily smoothed background spectrum", "ref_fig_num": "Figure 3"} + PlotDesc['spec_background'] = {"long_name_desc": f"{varName}: heavily smoothed version of the mean of spectral powers associated with the components symmetric and antisymmetric about equator", "ref_fig_num": "Figure 2"} + + text_offset = 0.005 + fb = [0, .8] # frequency bounds for plot + wnb = [-15, 15] # zonal wavenumber bounds for plot + + if(max(var['frequency'].values) == 0.5): + fb = [0, .5] + + if(do_zoom): + fb = [0, .18] + wnb = [-7, 7] + + # get data for dispersion curves: + equivDepths=[50, 25, 12] + swfreq,swwn = wf.genDispersionCurves(Ahe=equivDepths) + # swfreq.shape # -->(6, 3, 50) + + if 'spec_norm' in var.name: # with dispersion curves to plot + # For n=1 ER waves, allow dispersion curves to touch 0 -- this is for plot aesthetics only + for i in range(0,3): # loop 0-->2 for the assumed 3 shallow water dispersion curves for ER waves + indMinPosFrqER = np.where(swwn[3,i,:] >= 0., swwn[3,i,:], 1e20).argmin() # index of swwn for least positive wn + swwn[3,i,indMinPosFrqER],swfreq[3,i,indMinPosFrqER] = 0.,0. # this sets ER's frequencies to 0. at wavenumber 0. + + swf = np.where(swfreq == 1e20, np.nan, swfreq) + swk = np.where(swwn == 1e20, np.nan, swwn) + + + # Final data refinement: transpose and trim, set 0 freq to NaN, take log10 for raw, refine metadata + z = var.transpose().sel(frequency=slice(*fb), wavenumber=slice(*wnb)) + z.loc[{'frequency':0}] = np.nan + + if 'spec_raw' in var.name: + + east_power = z.sel(frequency=slice((1./96.),(1./24.)), wavenumber=slice(1,3)).sum() + west_power = z.sel(frequency=slice((1./96.),(1./24.)), wavenumber=slice(-3,-1)).sum() + ew_ratio = east_power / west_power + print("\neast_power: %12.5f" % east_power) + print("west_power: %12.5f" % west_power) + print("ew_ratio: %12.5f\n" % ew_ratio) + + z = np.log10(z) + + if 'spec_background' in var.name: + z = np.log10(z) + + z.attrs["long_name"] = PlotDesc[var.name]["long_name_desc"] + z.attrs["method"] = f"Follows {PlotDesc[var.name]['ref_fig_num']} methods of Wheeler and Kiladis (1999; https://doi.org/10.1175/1520-0469(1999)056<0374:CCEWAO>2.0.CO;2)" + + if 'spec_raw' in var.name: + + z.attrs["ew_ratio_method"] = "Sum of raw (not log10) symmetric spectral power for ZWNs +/- 1-3, periods 24-96 days" + z.attrs["east_power"] = east_power.values + z.attrs["west_power"] = west_power.values + z.attrs["ew_ratio"] = ew_ratio.values + +# # TODO Save plotted data z to file as xArray data array +# dataDesc = f"spectral_power_{srcID}_{vari}_{spec_type}_{component}_200101_201412" +# z.to_netcdf(outDataDir + "/"+ dataDesc + ".nc") + + #fig, ax = plt.subplots() + ax = fig.add_axes(PANEL[subplot_num]) + + kmesh0, vmesh0 = np.meshgrid(z['wavenumber'], z['frequency']) + #img = ax.contourf(kmesh0, vmesh0, z, levels=np.linspace(0.2, 3.0, 16), cmap='Spectral_r', extend='both') + + if 'spec_norm' in var.name: + contour_level_spec = CONTOUR_LEVS_SPEC_NORM + cmapSpecUse, normSpecUse = create_colormap_clevs(CMAP_SPEC_NORM, CONTOUR_LEVS_SPEC_NORM) + else: + contour_level_spec = CONTOUR_LEVS_SPEC_RAW + cmapSpecUse, normSpecUse = create_colormap_clevs(CMAP_SPEC_RAW, CONTOUR_LEVS_SPEC_RAW) + + img = ax.contourf(kmesh0, vmesh0, z, levels=contour_level_spec, cmap=cmapSpecUse, norm=normSpecUse, extend='both') + img2 = ax.contour(kmesh0, vmesh0, z, levels=contour_level_spec, linewidths=1., linestyles='solid', colors='gray', alpha=0.7) + ax.axvline(0, linestyle='dashed', color='dimgray', linewidth=1.0, alpha=0.60) + if( (1./30.) < fb[1] ): + ax.axhline((1./30.), linestyle='dashed', color='dimgray', alpha=0.80) + ax.text(wnb[0]+1,(1./30.)+text_offset,'30 days',color='dimgray', alpha=0.80) + if( (1./6.) < fb[1] ): + ax.axhline((1./6.), linestyle='dashed', color='dimgray', alpha=0.80) + ax.text(wnb[0]+1,(1./6.)+text_offset,'6 days',color='dimgray', alpha=0.80) + if( (1./3.) < fb[1] ): + ax.axhline((1./3.), linestyle='dashed', color='dimgray', alpha=0.80) + ax.text(wnb[0]+1,(1./3.)+text_offset,'3 days',color='dimgray', alpha=0.80) + for ii in range(3,6): + ax.plot(swk[ii, 0,:], swf[ii,0,:], color='black', linewidth=1.5, alpha=0.80) + ax.plot(swk[ii, 1,:], swf[ii,1,:], color='black', linewidth=1.5, alpha=0.80) + ax.plot(swk[ii, 2,:], swf[ii,2,:], color='black', linewidth=1.5, alpha=0.80) + ax.set_xlim(wnb) + ax.set_ylim(fb) + #ax.set_title(varName + ": Log ${\sum_{15^{\circ}S}^{15^{\circ}N} Power_{SYM}}$") # Version w/ LaTeX + if 'spec_raw' in var.name: + ax.set_title(f"{varName}: Log{{Sum(Power) from 15°S-15°N}}\n") # Version w/o LaTeX + elif 'spec_norm' in var.name: + ax.set_title(f"{varName}: {{Sum(Power) from 15°S-15°N}}/Background\n") # Version w/o LaTeX + else: + ax.set_title(f"{varName}: Log{{Smoothed Background Power}}\n") + + ax.set_title('model', loc='left') + print('*****',var) + ax.set_title(f"{var.component}", loc='right') + + if 'spec_norm' in var.name: + # Shallow water dispersion curve line labels: See https://matplotlib.org/stable/tutorials/text/text_intro.html + # n=1 ER dispersion curve labels + text_opt = {'fontsize': 9,'verticalalignment': 'center','horizontalalignment': 'center','clip_on': True,'bbox': {'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}} + iwave, ih = 3, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -11.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 3, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -9.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 3, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + ax.text(-7.,0.10,'n=1 ER',text_opt) + + # Kelvin dispersion curve labels + iwave, ih = 4, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 4, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 10.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 4, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 14.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + ax.text(6.,0.13,'Kelvin',text_opt) + + # IG dispersion curve labels + iwave, ih = 5, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 5, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 5, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + ax.text(-10.,0.48,'n=1 WIG',text_opt) + ax.text(5.,0.48,'n=1 EIG',text_opt) + + # MJO label + ax.text(6.,0.0333,'MJO',text_opt) + + + plt.ylabel("Frequency (CPD)") + plt.xlabel("Zonal wavenumber") + fig.text( + PANEL[subplot_num][0] - 0.03 , + PANEL[subplot_num][1] - 0.03, "Westward", fontsize=11) + fig.text( + PANEL[subplot_num][0] + 0.35 , + PANEL[subplot_num][1] -0.03 , "Eastward", fontsize=11) + fig.colorbar(img) +# # Save fig +# fig.savefig(outDataDir + "/"+ dataDesc + "_plot.png", bbox_inches='tight', dpi=300) +# +# print("Plot file created: %s\n" % outDataDir + "/"+ dataDesc + "_plot.png") + + + + +def plot( + parameter: CoreParameter, + da_test: xr.DataArray, + da_ref: xr.DataArray | None, + da_diff: xr.DataArray | None, +): + """Plot the variable's metrics generated for the lat_lon set. + + Parameters + ---------- + parameter : CoreParameter + The CoreParameter object containing plot configurations. + da_test : xr.DataArray + The test data. + da_ref : xr.DataArray | None + The optional reference data. + ds_diff : xr.DataArray | None + The difference between ``ds_test`` and ``ds_ref``. + """ + #fig = plt.figure(figsize=parameter.figsize, dpi=parameter.dpi) + fig = plt.figure(figsize=[8.5, 12.0], dpi=300) + #fig.suptitle(parameter.main_title, x=0.5, y=0.96, fontsize=18) + + + _wave_frequency_plot( + 0, + da_test, + fig, + parameter, + title=(parameter.test_name_yrs, parameter.test_title), # type: ignore + do_zoom=False, + ) + + + _wave_frequency_plot( + 1, + da_ref, + fig, + parameter, + title=(parameter.ref_name_yrs, parameter.reference_title), # type: ignore + do_zoom=False, + ) + + + _wave_frequency_plot( + 2, + da_diff, + fig, + parameter, + title=(None, parameter.diff_title), # type: ignore + do_zoom=False, + ) + +#TODO: save plot:NameError: name 'get_output_dir' is not defined + _save_plot(fig, parameter) + + plt.close() From f40d46aa73f8b45501e3d43899e08ce5ae755133 Mon Sep 17 00:00:00 2001 From: chengzhuzhang Date: Tue, 21 Nov 2023 11:14:14 -0800 Subject: [PATCH 04/46] add viwer --- .../tropical_subseasonal_driver.py | 18 +++--- .../tropical_subseasonal_plot.py | 6 +- .../tropical_subseasonal_viewer.py | 58 +++++++++++++++++++ 3 files changed, 72 insertions(+), 10 deletions(-) mode change 100644 => 100755 auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_driver.py mode change 100644 => 100755 auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_plot.py create mode 100755 auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_viewer.py diff --git a/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_driver.py b/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_driver.py old mode 100644 new mode 100755 index 80387a4cc..8c10a0c15 --- a/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_driver.py +++ b/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_driver.py @@ -16,6 +16,7 @@ from matplotlib.colors import ListedColormap, BoundaryNorm from tropical_subseasonal_plot import plot +from tropical_subseasonal_viewer import create_viewer from zwf import zwf_functions as wf logger = custom_logger(__name__) @@ -167,12 +168,14 @@ def calculate_spectrum(path, variable): ) for variable in parameter.variables: - #test = calculate_spectrum(parameter.test_data_path, variable) - #test.to_netcdf("data/full_spec_test.nc") - #ref = calculate_spectrum(parameter.ref_data_path, variable) - #ref.to_netcdf("data/full_spec_ref.nc") - test = xr.open_dataset("/Users/zhang40/Documents/repos/e3sm_diags/auxiliary_tools/tropical_subseasonal_diags/data/full_spec_ref.nc").load() - ref = xr.open_dataset("/Users/zhang40/Documents/repos/e3sm_diags/auxiliary_tools/tropical_subseasonal_diags/data/full_spec_ref.nc").load() + test = calculate_spectrum(parameter.test_data_path, variable) + test.to_netcdf("data/full_spec_test.nc") + ref = calculate_spectrum(parameter.ref_data_path, variable) + ref.to_netcdf("data/full_spec_ref.nc") + # Below uses intermediate saved files for development + #test = xr.open_dataset("/Users/zhang40/Documents/repos/e3sm_diags/auxiliary_tools/tropical_subseasonal_diags/data/full_spec_ref.nc").load() + #ref = xr.open_dataset("/Users/zhang40/Documents/repos/e3sm_diags/auxiliary_tools/tropical_subseasonal_diags/data/full_spec_ref.nc").load() + parameter.var_id = variable for diff_name in ["raw_sym", "raw_asy", "norm_sym", "norm_asy", "background"]: diff = test[f"spec_{diff_name}"]-ref[f"spec_{diff_name}"] @@ -182,5 +185,6 @@ def calculate_spectrum(path, variable): plot(parameter, test[f"spec_{diff_name}"], ref[f"spec_{diff_name}"], diff) - +display_name, url = create_viewer('.', parameter) +print("Viewer Created: ", url) diff --git a/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_plot.py b/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_plot.py old mode 100644 new mode 100755 index ccf2199b6..6f2feb729 --- a/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_plot.py +++ b/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_plot.py @@ -100,7 +100,7 @@ def _save_plot(fig: plt.figure, parameter: CoreParameter): # #get_output_dir(parameter.current_set, parameter), # parameter.output_file + "." + f, #) - fnm = f'{parameter.spec_type}.png' + fnm = f'{parameter.var_id}_{parameter.spec_type}_15N-15N.png' plt.savefig(fnm) logger.info(f"Plot saved in: {fnm}") @@ -165,8 +165,8 @@ def _wave_frequency_plot( do_zoom: Boolean """ #TODO link var_id - #varName = parameter.var_id - varName = 'PRECT' + varName = parameter.var_id + #varName = 'PRECT' PlotDesc = {} PlotDesc['spec_raw_sym'] = {"long_name_desc": f"{varName}: log-base10 of lightly smoothed spectral power of component symmetric about equator", "ref_fig_num": "Figure 1"} # Figure number from Wheeler and Kiladis (1999) PlotDesc['spec_raw_asy'] = {"long_name_desc": f"{varName}: log-base10 of lightly smoothed spectral power of component antisymmetric about equator", "ref_fig_num": "Figure 1"} diff --git a/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_viewer.py b/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_viewer.py new file mode 100755 index 000000000..ce17f6a07 --- /dev/null +++ b/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_viewer.py @@ -0,0 +1,58 @@ +import os +from typing import Dict, List + +from cdp.cdp_viewer import OutputViewer + +from e3sm_diags.logger import custom_logger + + +logger = custom_logger(__name__) + + +def create_viewer(root_dir, parameters): + """ + Given a set of parameters for the enso_diags set, + create a single webpage. + + Return the title and url for this page. + """ + viewer = OutputViewer(path=root_dir) + + # The name that's displayed on the viewer. + display_name = "Tropical Subseasonal Variability Diagnostics" + set_name = "tropical_subseasonal_diags" + # The title of the colums on the webpage. + # Appears in the second and third columns of the bolded rows. + cols = ["Description", "Plot"] + viewer.add_page(display_name, short_name=set_name, columns=cols) + for plot_type in ["Wavenumber Frequency", "Lag correlation"]: + # Appears in the first column of the bolded rows. + viewer.add_group(plot_type.capitalize()) + + for var in parameters.variables: + # Appears in the first column of the non-bolded rows. + # This should use param.case_id to match the output_dir determined by + # get_output_dir in e3sm_diags/plot/cartopy/enso_diags_plot.py. + # Otherwise, the plot image and the plot HTML file will have URLs + # differing in the final directory name. + for spec_type in ["norm_sym", "norm_asy", "raw_sym", "raw_asy", "background"]: + viewer.add_row(f'{var} {spec_type} ref_name') + # Adding the description for this var to the current row. + # This was obtained and stored in the driver for this plotset. + # Appears in the second column of the non-bolded rows. + #viewer.add_col(param.viewer_descr[var]) + viewer.add_col(f'Long description for var') + # Link to an html version of the plot png file. + # Appears in the third column of the non-bolded rows. + image_relative_path = f'{var}_{spec_type}_15N-15N.png' + viewer.add_col( + image_relative_path, + is_file=True, + title="Plot", + #other_files=formatted_files, + #meta=create_metadata(param), + ) + + url = viewer.generate_page() + + return display_name, url From 4ac2e66f2e4beaa10a61f255be4b66f4e41feb7d Mon Sep 17 00:00:00 2001 From: chengzhuzhang Date: Tue, 21 Nov 2023 17:36:00 -0800 Subject: [PATCH 05/46] zoom in MJO;use diff ratio --- .../tropical_subseasonal_driver.py | 20 ++++++--- .../tropical_subseasonal_plot.py | 43 +++++++++++++------ .../tropical_subseasonal_viewer.py | 2 +- 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_driver.py b/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_driver.py index 8c10a0c15..4e9b209a0 100755 --- a/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_driver.py +++ b/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_driver.py @@ -168,21 +168,27 @@ def calculate_spectrum(path, variable): ) for variable in parameter.variables: - test = calculate_spectrum(parameter.test_data_path, variable) - test.to_netcdf("data/full_spec_test.nc") - ref = calculate_spectrum(parameter.ref_data_path, variable) - ref.to_netcdf("data/full_spec_ref.nc") + #test = calculate_spectrum(parameter.test_data_path, variable) + #test.to_netcdf("data/full_spec_test.nc") + #ref = calculate_spectrum(parameter.ref_data_path, variable) + #ref.to_netcdf("data/full_spec_ref.nc") # Below uses intermediate saved files for development - #test = xr.open_dataset("/Users/zhang40/Documents/repos/e3sm_diags/auxiliary_tools/tropical_subseasonal_diags/data/full_spec_ref.nc").load() - #ref = xr.open_dataset("/Users/zhang40/Documents/repos/e3sm_diags/auxiliary_tools/tropical_subseasonal_diags/data/full_spec_ref.nc").load() + test = xr.open_dataset("/Users/zhang40/Documents/repos/e3sm_diags/auxiliary_tools/tropical_subseasonal_diags/data/full_spec_ref.nc").load() + ref = xr.open_dataset("/Users/zhang40/Documents/repos/e3sm_diags/auxiliary_tools/tropical_subseasonal_diags/data/full_spec_ref.nc").load() parameter.var_id = variable for diff_name in ["raw_sym", "raw_asy", "norm_sym", "norm_asy", "background"]: - diff = test[f"spec_{diff_name}"]-ref[f"spec_{diff_name}"] + + # Compute percentage difference + diff = 100 * (test[f"spec_{diff_name}"]-ref[f"spec_{diff_name}"])/ref[f"spec_{diff_name}"] diff.name = f"spec_{diff_name}" diff.attrs.update(test[f"spec_{diff_name}"].attrs) parameter.spec_type = diff_name plot(parameter, test[f"spec_{diff_name}"], ref[f"spec_{diff_name}"], diff) + if "norm" in diff_name: + parameter.spec_type = f"{diff_name}_zoom" + plot(parameter, test[f"spec_{diff_name}"], ref[f"spec_{diff_name}"], diff, do_zoom = True) + display_name, url = create_viewer('.', parameter) diff --git a/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_plot.py b/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_plot.py index 6f2feb729..7a096684a 100755 --- a/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_plot.py +++ b/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_plot.py @@ -57,6 +57,10 @@ "yellow","orange", "red","maroon","pink"] +CONTOUR_LEVS_SPEC_RAW_DIFF = (-80.,-60.,-40.,-20.,-10.,-5.,5.,10.,20.,40.,60.,80.) +CONTOUR_LEVS_SPEC_NORM_DIFF = (-60.,-30.,-20.,-15.,-10.,-5.,5.,10.,15.,20.,30.,60.) + + def find_nearest(array, value): array = np.asarray(array) idx = (np.abs(array - value)).argmin() @@ -145,7 +149,7 @@ def _wave_frequency_plot( fig: plt.figure, parameter: CoreParameter, title: Tuple[str | None, str, str], - do_zoom: Boolean, + do_zoom: Boolean = False, ): """Create wave frequency plot. @@ -238,15 +242,29 @@ def _wave_frequency_plot( kmesh0, vmesh0 = np.meshgrid(z['wavenumber'], z['frequency']) #img = ax.contourf(kmesh0, vmesh0, z, levels=np.linspace(0.2, 3.0, 16), cmap='Spectral_r', extend='both') - if 'spec_norm' in var.name: - contour_level_spec = CONTOUR_LEVS_SPEC_NORM - cmapSpecUse, normSpecUse = create_colormap_clevs(CMAP_SPEC_NORM, CONTOUR_LEVS_SPEC_NORM) - else: - contour_level_spec = CONTOUR_LEVS_SPEC_RAW - cmapSpecUse, normSpecUse = create_colormap_clevs(CMAP_SPEC_RAW, CONTOUR_LEVS_SPEC_RAW) + # for test and ref: + if subplot_num < 2: + if 'spec_norm' in var.name: + contour_level_spec = CONTOUR_LEVS_SPEC_NORM + cmapSpecUse, normSpecUse = create_colormap_clevs(CMAP_SPEC_NORM, CONTOUR_LEVS_SPEC_NORM) + else: + contour_level_spec = CONTOUR_LEVS_SPEC_RAW + cmapSpecUse, normSpecUse = create_colormap_clevs(CMAP_SPEC_RAW, CONTOUR_LEVS_SPEC_RAW) + img = ax.contourf(kmesh0, vmesh0, z, levels=contour_level_spec, cmap=cmapSpecUse, norm=normSpecUse, extend='both') + img2 = ax.contour(kmesh0, vmesh0, z, levels=contour_level_spec, linewidths=1., linestyles='solid', colors='gray', alpha=0.7) + + # for diff ratio + if subplot_num == 2: + # TODO refine color bar + if 'spec_norm' in var.name: + contour_level_spec = CONTOUR_LEVS_SPEC_NORM_DIFF + else: + contour_level_spec = CONTOUR_LEVS_SPEC_RAW_DIFF + cmapSpecUse = 'seismic' + + img = ax.contourf(kmesh0, vmesh0, z, levels=contour_level_spec, cmap=cmapSpecUse, extend='both') + img2 = ax.contour(kmesh0, vmesh0, z, levels=contour_level_spec, linewidths=1., linestyles='solid', colors='gray', alpha=0.7) - img = ax.contourf(kmesh0, vmesh0, z, levels=contour_level_spec, cmap=cmapSpecUse, norm=normSpecUse, extend='both') - img2 = ax.contour(kmesh0, vmesh0, z, levels=contour_level_spec, linewidths=1., linestyles='solid', colors='gray', alpha=0.7) ax.axvline(0, linestyle='dashed', color='dimgray', linewidth=1.0, alpha=0.60) if( (1./30.) < fb[1] ): ax.axhline((1./30.), linestyle='dashed', color='dimgray', alpha=0.80) @@ -341,6 +359,7 @@ def plot( da_test: xr.DataArray, da_ref: xr.DataArray | None, da_diff: xr.DataArray | None, + do_zoom: Boolean = False, ): """Plot the variable's metrics generated for the lat_lon set. @@ -366,7 +385,7 @@ def plot( fig, parameter, title=(parameter.test_name_yrs, parameter.test_title), # type: ignore - do_zoom=False, + do_zoom=do_zoom, ) @@ -376,7 +395,7 @@ def plot( fig, parameter, title=(parameter.ref_name_yrs, parameter.reference_title), # type: ignore - do_zoom=False, + do_zoom=do_zoom, ) @@ -386,7 +405,7 @@ def plot( fig, parameter, title=(None, parameter.diff_title), # type: ignore - do_zoom=False, + do_zoom=do_zoom, ) #TODO: save plot:NameError: name 'get_output_dir' is not defined diff --git a/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_viewer.py b/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_viewer.py index ce17f6a07..30475c1c0 100755 --- a/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_viewer.py +++ b/auxiliary_tools/tropical_subseasonal_diags/tropical_subseasonal_viewer.py @@ -35,7 +35,7 @@ def create_viewer(root_dir, parameters): # get_output_dir in e3sm_diags/plot/cartopy/enso_diags_plot.py. # Otherwise, the plot image and the plot HTML file will have URLs # differing in the final directory name. - for spec_type in ["norm_sym", "norm_asy", "raw_sym", "raw_asy", "background"]: + for spec_type in ["norm_sym", "norm_sym_zoom", "norm_asy", "norm_asy_zoom", "raw_sym", "raw_asy", "background"]: viewer.add_row(f'{var} {spec_type} ref_name') # Adding the description for this var to the current row. # This was obtained and stored in the driver for this plotset. From 1166fd2d2c464b9f27bb62f41036f72ef0e90c5d Mon Sep 17 00:00:00 2001 From: Tom Vo Date: Fri, 13 Oct 2023 12:35:22 -0700 Subject: [PATCH 06/46] [Refactor]: Update `e3sm_diags_driver.run_diags` as `CoreParameter` method (#742) - Update VSCode workspace settings with extension and debugger configs --- .vscode/e3sm_diags.code-workspace | 31 +++++++++-- e3sm_diags/e3sm_diags_driver.py | 64 +++------------------- e3sm_diags/parameter/core_parameter.py | 53 +++++++++++++++++- tests/e3sm_diags/test_e3sm_diags_driver.py | 40 +------------- tests/e3sm_diags/test_parameters.py | 38 +++++++++++++ 5 files changed, 123 insertions(+), 103 deletions(-) diff --git a/.vscode/e3sm_diags.code-workspace b/.vscode/e3sm_diags.code-workspace index 31c07ce29..c7d968567 100644 --- a/.vscode/e3sm_diags.code-workspace +++ b/.vscode/e3sm_diags.code-workspace @@ -8,6 +8,9 @@ "path": ".." } ], + // =========================== + // VS Code Workspace Settings. + // =========================== "settings": { // =================== // Editor settings @@ -22,17 +25,14 @@ "editor.rulers": [80, 88, 120], "editor.wordWrap": "wordWrapColumn", "editor.wordWrapColumn": 120, - "editor.defaultFormatter": "ms-python.python" + "editor.defaultFormatter": "ms-python.black-formatter" }, // Code Formatting and Linting // --------------------------- - "python.formatting.provider": "black", - "python.linting.flake8Enabled": true, - "python.linting.flake8Args": ["--config=setup.cfg"], + "flake8.args": ["--config=setup.cfg"], // Type checking // --------------------------- - "python.linting.mypyEnabled": true, - "python.linting.mypyArgs": ["--config=setup.cfg"], + "mypy-type-checker.args": ["--config=pyproject.toml"], // Testing // --------------------------- // NOTE: Debugger doesn't work if pytest-cov is enabled, so set "--no-cov" @@ -49,5 +49,24 @@ "editor.wordWrap": "wordWrapColumn", "editor.wordWrapColumn": 120 } + }, + // ===================================== + // VS Code Python Debugger Configuration + // ===================================== + "launch": { + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Current File", + "type": "python", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": true, + "env": { + "PYTHONPATH": "${workspaceFolder}" + } + } + ] } } diff --git a/e3sm_diags/e3sm_diags_driver.py b/e3sm_diags/e3sm_diags_driver.py index 53fe24623..111d20f0d 100644 --- a/e3sm_diags/e3sm_diags_driver.py +++ b/e3sm_diags/e3sm_diags_driver.py @@ -1,8 +1,6 @@ #!/usr/bin/env python # The above line is needed for `test_all_sets.test_all_sets_mpl`. # Otherwise, OSError: [Errno 8] Exec format error: 'e3sm_diags_driver.py'. - -import importlib import os import subprocess import sys @@ -251,56 +249,6 @@ def create_parameter_dict(parameters): return d -def run_diag(parameter: CoreParameter) -> List[CoreParameter]: - """Run the corresponding diagnostics for a set of parameters. - - Additional CoreParameter (or CoreParameter sub-class) objects are derived - from the CoreParameter `sets` attribute, hence this function returns a - list of CoreParameter objects. - - This function loops over the specified diagnostic sets and runs the - diagnostic using the parameters. - - Parameters - ---------- - parameter : CoreParameter - The CoreParameter object to run diagnostics on. - - Returns - ------- - List[CoreParameter] - The list of CoreParameter objects with results from the diagnostic run. - """ - results = [] - - for set_name in parameter.sets: - # FIXME: The parameter and driver for a diagnostic should be mapped - # together. If this is done, the `run_diag` function should be - # accessible by the parameter class without needing to perform a static - # string reference for the module name. - parameter.current_set = set_name - mod_str = "e3sm_diags.driver.{}_driver".format(set_name) - - # Check if there is a matching driver module for the `set_name`. - try: - module = importlib.import_module(mod_str) - except ModuleNotFoundError as e: - logger.error(f"'Error with set name {set_name}'", exc_info=e) - continue - - # If the module exists, call the driver module's `run_diag` function. - try: - single_result = module.run_diag(parameter) - results.append(single_result) - except Exception: - logger.exception(f"Error in {mod_str}", exc_info=True) - - if parameter.debug: - sys.exit() - - return results - - def _run_serially(parameters: List[CoreParameter]) -> List[CoreParameter]: """Run diagnostics with the parameters serially. @@ -314,14 +262,16 @@ def _run_serially(parameters: List[CoreParameter]) -> List[CoreParameter]: List[CoreParameter] The list of CoreParameter objects with results from the diagnostic run. """ - results = [] + # A nested list of lists, where a sub-list represents the results of + # the sets related to the CoreParameter object. + nested_results: List[List[CoreParameter]] = [] - for p in parameters: - results.append(run_diag(p)) + for parameter in parameters: + nested_results.append(parameter._run_diag()) # `results` becomes a list of lists of parameters so it needs to be # collapsed a level. - collapsed_results = _collapse_results(results) + collapsed_results = _collapse_results(nested_results) return collapsed_results @@ -362,7 +312,7 @@ def _run_with_dask(parameters: List[CoreParameter]) -> List[CoreParameter]: ) with dask.config.set(config): - results = bag.map(run_diag).compute(num_workers=num_workers) + results = bag.map(CoreParameter._run_diag).compute(num_workers=num_workers) # `results` becomes a list of lists of parameters so it needs to be # collapsed a level. diff --git a/e3sm_diags/parameter/core_parameter.py b/e3sm_diags/parameter/core_parameter.py index e2caba989..4b96c13ab 100644 --- a/e3sm_diags/parameter/core_parameter.py +++ b/e3sm_diags/parameter/core_parameter.py @@ -1,5 +1,11 @@ import copy -from typing import Dict, List +import importlib +import sys +from typing import Any, Dict, List + +from e3sm_diags.logger import custom_logger + +logger = custom_logger(__name__) class CoreParameter: @@ -213,3 +219,48 @@ def check_values(self): ): msg = "You need to define both the 'test_start_yr' and 'test_end_yr' parameter." raise RuntimeError(msg) + + def _run_diag(self) -> List[Any]: + """Run the diagnostics for each set in the parameter. + + Additional CoreParameter (or CoreParameter sub-class) objects are derived + from the CoreParameter `sets` attribute, hence this function returns a + list of CoreParameter objects. + + This method loops over the parameter's diagnostic sets and attempts to + import and call the related `run_diags()` function. + + Returns + ------- + List[Any] + The list of CoreParameter objects with results from the diagnostic run. + NOTE: `Self` type is not yet supported by mypy. + """ + results = [] + + for set_name in self.sets: + self.current_set = set_name + # FIXME: This is a shortcut to importing `run_diag`, but can break + # easily because the module driver is statically imported via string. + # Instead, the import should be done more progammatically via + # direct Python import. + mod_str = "e3sm_diags.driver.{}_driver".format(set_name) + + # Check if there is a matching driver module for the `set_name`. + try: + module = importlib.import_module(mod_str) + except ModuleNotFoundError as e: + logger.error(f"'Error with set name {set_name}'", exc_info=e) + continue + + # If the module exists, call the driver module's `run_diag` function. + try: + single_result = module.run_diag(self) + results.append(single_result) + except Exception: + logger.exception(f"Error in {mod_str}", exc_info=True) + + if self.debug: + sys.exit() + + return results diff --git a/tests/e3sm_diags/test_e3sm_diags_driver.py b/tests/e3sm_diags/test_e3sm_diags_driver.py index 9aa2e2b6b..a06220a35 100644 --- a/tests/e3sm_diags/test_e3sm_diags_driver.py +++ b/tests/e3sm_diags/test_e3sm_diags_driver.py @@ -1,6 +1,6 @@ import pytest -from e3sm_diags.e3sm_diags_driver import _run_serially, _run_with_dask, run_diag +from e3sm_diags.e3sm_diags_driver import _run_serially, _run_with_dask from e3sm_diags.logger import custom_logger from e3sm_diags.parameter.core_parameter import CoreParameter @@ -8,44 +8,6 @@ class TestRunDiag: - def test_returns_parameter_with_results(self): - parameter = CoreParameter() - parameter.sets = ["lat_lon"] - - results = run_diag(parameter) - expected = [parameter] - - # NOTE: We are only testing that the function returns a list of - # parameter objects, not the results themselves. There are integration - # tests validates the results. - assert results == expected - - def test_logs_error_if_driver_module_for_set_not_found(self, caplog): - parameter = CoreParameter() - parameter.sets = ["invalid_set"] - - run_diag(parameter) - - assert ( - "ModuleNotFoundError: No module named 'e3sm_diags.driver.invalid_set_driver'" - in caplog.text - ) - - @pytest.mark.xfail - def test_logs_exception_if_driver_run_diag_function_fails(self, caplog): - # TODO: Need to implement this test by raising an exception through - # the driver's `run_diag` function - parameter = CoreParameter() - parameter.sets = ["lat_lon"] - - # Make this attribute an invalid value to test exception is thrown. - parameter.seasons = None # type: ignore - - # NOTE: Comment out temporarily to avoid polluting the test output log. - # run_diag(parameter) - - assert "TypeError: 'NoneType' object is not iterable" in caplog.text - def test_run_diag_serially_returns_parameters_with_results(self): parameter = CoreParameter() parameter.sets = ["lat_lon"] diff --git a/tests/e3sm_diags/test_parameters.py b/tests/e3sm_diags/test_parameters.py index 9b30fa3d3..83162ad8b 100644 --- a/tests/e3sm_diags/test_parameters.py +++ b/tests/e3sm_diags/test_parameters.py @@ -79,6 +79,44 @@ def test_check_values_raises_error_if_test_timeseries_input_and_no_test_start_an with pytest.raises(RuntimeError): param.check_values() + def test_returns_parameter_with_results(self): + parameter = CoreParameter() + parameter.sets = ["lat_lon"] + + results = parameter._run_diag() + expected = [parameter] + + # NOTE: We are only testing that the function returns a list of + # parameter objects, not the results themselves. There are integration + # tests validates the results. + assert results == expected + + def test_logs_error_if_driver_module_for_set_not_found(self, caplog): + parameter = CoreParameter() + parameter.sets = ["invalid_set"] + + parameter._run_diag() + + assert ( + "ModuleNotFoundError: No module named 'e3sm_diags.driver.invalid_set_driver'" + in caplog.text + ) + + @pytest.mark.xfail + def test_logs_exception_if_driver_run_diag_function_fails(self, caplog): + # TODO: Need to implement this test by raising an exception through + # the driver's `run_diag` function + parameter = CoreParameter() + parameter.sets = ["lat_lon"] + + # Make this attribute an invalid value to test exception is thrown. + parameter.seasons = None # type: ignore + + # NOTE: Comment out temporarily to avoid polluting the test output log. + # parameter._run_diag() + + assert "TypeError: 'NoneType' object is not iterable" in caplog.text + def test_ac_zonal_mean_parameter(): param = ACzonalmeanParameter() From 091d9eea90cf9bea73aa19ba9457d77bfb6f81ad Mon Sep 17 00:00:00 2001 From: Tom Vo Date: Mon, 23 Oct 2023 11:21:57 -0700 Subject: [PATCH 07/46] [Doc]: Add GitHub issues and pull request templates (#741) --- .github/ISSUE_TEMPLATE/bug_report.yml | 61 ++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 10 ++++ .github/ISSUE_TEMPLATE/documentation.yml | 15 ++++++ .github/ISSUE_TEMPLATE/feature_request.yml | 42 +++++++++++++++ .github/pull_request_template.md | 24 +++++++++ .github/workflows/build_workflow.yml | 2 +- 6 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/documentation.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/pull_request_template.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..bc5c85ce9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,61 @@ +name: Bug Report +description: File a bug report to help us improve e3sm_diags +title: "[Bug]: " +labels: ["bug"] +assignees: [] +body: + - type: textarea + id: what-happened + attributes: + label: What happened? + description: | + Thanks for reporting a bug! Please describe what you were trying to get done. + Tell us what happened, what went wrong. + validations: + required: true + + - type: textarea + id: what-did-you-expect-to-happen + attributes: + label: What did you expect to happen? Are there are possible answers you came across? + description: | + Describe what you expected to happen. Include links to pages you've researched (e.g., software docs, Stack Overflow posts). + validations: + required: false + + - type: textarea + id: sample-code + attributes: + label: Minimal Complete Verifiable Example (MVCE) + description: | + Minimal, self-contained copy-pastable example that generates the issue if possible. Please be concise with code posted (e.g., module imports, publicly accessible files). + Bug reports that follow these guidelines are easier to diagnose, and so are often handled much more quickly. This section will be automatically formatted into code, so no need for markdown backticks. + + See guidelines below on how to provide a good MCVE: + + - [Minimal Complete Verifiable Examples](https://stackoverflow.com/help/mcve) + - [Craft Minimal Bug Reports](http://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports) + render: python + + - type: textarea + id: log-output + attributes: + label: Relevant log output + description: Please copy and paste any relevant output. This will be automatically formatted into code, so no need for markdown backticks. + render: python + + - type: textarea + id: extra + attributes: + label: Anything else we need to know? + description: | + Please describe any other information you want to share. + + - type: textarea + id: show-versions + attributes: + label: Environment + description: | + Paste your conda environment here (`conda info`). + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..f6d25a7a0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,10 @@ +blank_issues_enabled: true +contact_links: + - name: Questions + url: https://github.com/E3SM-Project/e3sm_diags/discussions + about: | + Ask questions and discuss with other e3sm_diags community members here. Please + browse the e3sm_diags Discussions Forum or e3sm_diags documentation first before asking a + question to make sure it is not already answered. If you can't find an + answer, please include a self-contained reproducible example with your + question if possible. Thanks! diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml new file mode 100644 index 000000000..5166f5098 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -0,0 +1,15 @@ +name: Documentation Update +description: Update e3sm_diags documentation +title: "[Doc]: " +labels: ["documentation"] +assignees: [] +body: + - type: textarea + id: description + attributes: + label: Describe your documentation update + description: | + Concise description of why the documentation is being updated (e.g., missing content for new feature, typo) + If this is related to an issue or PR, please mention it. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..f8e97ad36 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,42 @@ +name: Feature Request +description: Suggest an idea for e3sm_diags +title: "[Feature]: " +labels: ["enhancement"] +assignees: [] +body: + - type: textarea + id: description + attributes: + label: Is your feature request related to a problem? + description: | + Please do a quick search of existing issues to make sure that this has not been asked before. + Please provide a clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Describe the solution you'd like + description: | + A clear and concise description of what you want to happen. + validations: + required: false + + - type: textarea + id: alternatives + attributes: + label: Describe alternatives you've considered + description: | + A clear and concise description of any alternative solutions or features you've considered. + validations: + required: false + + - type: textarea + id: additional-context + attributes: + label: Additional context + description: | + Add any other context about the feature request here. + validations: + required: false diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..9868a618e --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,24 @@ +## Description + + + +- Closes # + +## Checklist + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] My changes generate no new warnings +- [ ] Any dependent changes have been merged and published in downstream modules + +If applicable: + +- [ ] New and existing unit tests pass with my changes (locally and CI/CD build) +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] I have noted that this is a breaking change for a major release (fix or feature that would cause existing functionality to not work as expected) diff --git a/.github/workflows/build_workflow.yml b/.github/workflows/build_workflow.yml index df255151a..e69d82fff 100644 --- a/.github/workflows/build_workflow.yml +++ b/.github/workflows/build_workflow.yml @@ -11,7 +11,7 @@ on: env: CANCEL_OTHERS: true - PATHS_IGNORE: '["**/README.md", "**/docs/**", "**/examples/**", "**/misc/**", "**/.vscode/**"]' + PATHS_IGNORE: '["**/README.md", "**/docs/**", "**/examples/**", "**/misc/**", "**/.vscode/**", "**/.github/pull_request_template.md", "**/.github/ISSUE_TEMPLATE"]' jobs: pre-commit-hooks: From 336be9bcfd594ed84c01f72d0f05580eaccaed90 Mon Sep 17 00:00:00 2001 From: Jill Chengzhu Zhang Date: Fri, 27 Oct 2023 16:55:52 -0700 Subject: [PATCH 08/46] fix typo (#753) --- analysis_data_preprocess/create_OMI-MLS_climo.sh | 2 +- docs/source/index.rst | 2 +- e3sm_diags/driver/annual_cycle_zonal_mean_driver.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/analysis_data_preprocess/create_OMI-MLS_climo.sh b/analysis_data_preprocess/create_OMI-MLS_climo.sh index eccb5c404..849080d2e 100644 --- a/analysis_data_preprocess/create_OMI-MLS_climo.sh +++ b/analysis_data_preprocess/create_OMI-MLS_climo.sh @@ -18,7 +18,7 @@ mkdir $climo_output_path mkdir $tmp -#Add lon dimention to zonel mean +#Add lon dimention to zonal mean ncap2 -s 'SCO=O3strat' ${original_data_path}O3strat_ZMK.nc ${time_series_output_path}SCO_200410_201712.nc #cp ${original_data_path}O3strat_ZMK.nc ${time_series_output_path}SCO_200410_201712.nc cdo splityear ${time_series_output_path}SCO_200410_201712.nc ${tmp}sco diff --git a/docs/source/index.rst b/docs/source/index.rst index 24222d264..54297ee35 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -143,7 +143,7 @@ Additional back-ends could be implemented if the need arose. | :align: center | :align: center | | :target: _static/index/fig21.png | :target: _static/index/fig22.png | | | | -| Tropical Cyclone Track Density | Annual Cycle Zonel Mean plot | +| Tropical Cyclone Track Density | Annual Cycle Zonal Mean plot | +--------------------------------------------------------+------------------------------------------------------+ | .. figure:: _static/index/fig23.png | .. figure:: _static/index/fig24.png | | :align: center | :align: center | diff --git a/e3sm_diags/driver/annual_cycle_zonal_mean_driver.py b/e3sm_diags/driver/annual_cycle_zonal_mean_driver.py index 9b5f2c775..1fcb75743 100755 --- a/e3sm_diags/driver/annual_cycle_zonal_mean_driver.py +++ b/e3sm_diags/driver/annual_cycle_zonal_mean_driver.py @@ -101,7 +101,7 @@ def run_diag(parameter: ACzonalmeanParameter) -> ACzonalmeanParameter: parameter.var_id = var parameter.output_file = "-".join([ref_name, var, "Annual-Cycle"]) - parameter.main_title = str(" ".join([var, "Zonel Mean Annual Cycle"])) + parameter.main_title = str(" ".join([var, "Zonal Mean Annual Cycle"])) parameter.viewer_descr[var] = ( test_ac.long_name From 9cc56f34d5783b7cff43b4e9bbae3706cc5731c5 Mon Sep 17 00:00:00 2001 From: Tom Vo Date: Tue, 28 Nov 2023 16:48:21 -0800 Subject: [PATCH 09/46] Update DevOps configs (#755) - Update conda env dependencies - Add `dev-nompi.yml` which includes the nompi version of esmf to avoid the yaksa leak error that breaks VS Code testing API - Update pre-commit dependency versions and fix new issues for `black`, `flake8`, and `mypy` - Move most of `setup.cfg` to `pyproject.toml` - Add `Makefile` with convenient developer commands - Update `test_diags.py` and `test_allsets.py` to use `pytest` --- .pre-commit-config.yaml | 16 +- Makefile | 82 ++++ conda-env/ci.yml | 1 - conda-env/dev-nompi.yml | 56 +++ conda-env/dev.yml | 31 +- e3sm_diags/derivations/acme.py | 3 +- e3sm_diags/driver/arm_diags_driver.py | 3 - e3sm_diags/driver/enso_diags_driver.py | 6 +- e3sm_diags/driver/lat_lon_driver.py | 8 +- e3sm_diags/driver/polar_driver.py | 8 +- e3sm_diags/driver/streamflow_driver.py | 6 +- e3sm_diags/driver/tc_analysis_driver.py | 9 +- e3sm_diags/driver/utils/general.py | 2 +- e3sm_diags/driver/zonal_mean_xy_driver.py | 8 +- .../plot/cartopy/aerosol_aeronet_plot.py | 2 +- .../cartopy/annual_cycle_zonal_mean_plot.py | 3 +- .../cartopy/area_mean_time_series_plot.py | 2 +- .../plot/cartopy/cosp_histogram_plot.py | 4 +- e3sm_diags/plot/cartopy/diurnal_cycle_plot.py | 5 +- e3sm_diags/plot/cartopy/enso_diags_plot.py | 3 +- e3sm_diags/plot/cartopy/lat_lon_land_plot.py | 1 - e3sm_diags/plot/cartopy/lat_lon_plot.py | 4 +- e3sm_diags/plot/cartopy/lat_lon_river_plot.py | 1 - .../plot/cartopy/meridional_mean_2d_plot.py | 4 +- e3sm_diags/plot/cartopy/polar_plot.py | 4 +- e3sm_diags/plot/cartopy/qbo_plot.py | 2 +- e3sm_diags/plot/cartopy/streamflow_plot.py | 4 +- e3sm_diags/plot/cartopy/taylor_diagram.py | 2 +- e3sm_diags/plot/cartopy/tc_analysis_plot.py | 1 - e3sm_diags/plot/cartopy/zonal_mean_2d_plot.py | 4 +- .../zonal_mean_2d_stratosphere_plot.py | 1 - e3sm_diags/plot/cartopy/zonal_mean_xy_plot.py | 3 +- e3sm_diags/run.py | 2 +- e3sm_diags/viewer/lat_lon_viewer.py | 3 - e3sm_diags/viewer/mean_2d_viewer.py | 1 - pyproject.toml | 36 +- setup.cfg | 48 +- tests/complete_run.py | 31 +- tests/integration/test_all_sets.py | 136 ++++-- tests/integration/test_diags.py | 415 ++++++++++-------- 40 files changed, 577 insertions(+), 384 deletions(-) create mode 100644 Makefile create mode 100644 conda-env/dev-nompi.yml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 140dce44f..05513b719 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,14 +4,14 @@ fail_fast: true repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 23.9.1 hooks: - id: black @@ -23,15 +23,15 @@ repos: # Need to use flake8 GitHub mirror due to CentOS git issue with GitLab # https://github.com/pre-commit/pre-commit/issues/1206 - repo: https://github.com/pycqa/flake8 - rev: 6.0.0 + rev: 6.1.0 hooks: - id: flake8 - args: ["--config=setup.cfg"] - additional_dependencies: [flake8-isort] + args: [--config=setup.cfg] + additional_dependencies: [flake8-isort==6.1.0] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.991 + rev: v1.5.1 hooks: - id: mypy - args: ["--config=setup.cfg"] - additional_dependencies: [types-pyYAML==6.0.12.6] + args: [--config=pyproject.toml] + additional_dependencies: [dask, numpy>=1.23.0, types-PyYAML] diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..eb6445974 --- /dev/null +++ b/Makefile @@ -0,0 +1,82 @@ +.PHONY: clean clean-test clean-pyc clean-build docs help +.DEFAULT_GOAL := help + +define BROWSER_PYSCRIPT +import os, webbrowser, sys + +from urllib.request import pathname2url + +webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) +endef +export BROWSER_PYSCRIPT + +define PRINT_HELP_PYSCRIPT +import re, sys + +for line in sys.stdin: + match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) + if match: + target, help = match.groups() + print("%-20s %s" % (target, help)) +endef +export PRINT_HELP_PYSCRIPT + +BROWSER := python -c "$$BROWSER_PYSCRIPT" + +# To run these commands: make +# ================================================== + +help: + @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) + +# Clean local repository +# ---------------------- +clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts + +clean-build: ## remove build artifacts + rm -fr build/ + rm -fr conda-build/ + rm -fr dist/ + rm -fr .eggs/ + find . -name '*.egg-info' -exec rm -fr {} + + find . -name '*.egg' -exec rm -f {} + + +clean-pyc: ## remove Python file artifacts + find . -name '*.pyc' -exec rm -f {} + + find . -name '*.pyo' -exec rm -f {} + + find . -name '*~' -exec rm -f {} + + find . -name '__pycache__' -exec rm -fr {} + + +clean-test: ## remove test and coverage artifacts + rm -fr tests_coverage_reports/ + rm -f .coverage + rm -fr htmlcov/ + rm -f coverage.xml + rm -fr .pytest_cache + rm -rf .mypy_cache + +# Quality Assurance +# ---------------------- +pre-commit: # run pre-commit quality assurance checks + pre-commit run --all-files + +lint: ## check style with flake8 + flake8 e3sm_diags tests + +test: ## run tests quickly with the default Python and produces code coverage report + pytest + $(BROWSER) tests_coverage_reports/htmlcov/index.html + +# Documentation +# ---------------------- +docs: ## generate Sphinx HTML documentation, including API docs + rm -rf docs/generated + cd docs && make html + $(MAKE) -C docs clean + $(MAKE) -C docs html + $(BROWSER) docs/_build/html/index.html + +# Build +# ---------------------- +install: clean ## install the package to the active Python's site-packages + python -m pip install . diff --git a/conda-env/ci.yml b/conda-env/ci.yml index cfb172672..5a17600e7 100644 --- a/conda-env/ci.yml +++ b/conda-env/ci.yml @@ -36,4 +36,3 @@ dependencies: - sphinx - sphinx_rtd_theme - sphinx-multiversion -prefix: /opt/miniconda3/envs/e3sm_diags_ci diff --git a/conda-env/dev-nompi.yml b/conda-env/dev-nompi.yml new file mode 100644 index 000000000..5f7edfd0c --- /dev/null +++ b/conda-env/dev-nompi.yml @@ -0,0 +1,56 @@ +# Conda development environment for testing local source code changes to `e3sm_diags` before merging them to production (`master` branch). +# This version contains the no MPI version of `esmf` as a workaround for allowing VS Code's testing API to work. +# The MPI version of `esmf` is usually installed by default, but it breaks VS Code's testing API because it throws a mysterious +# `yaksa` warning. +# More info: https://github.com/E3SM-Project/e3sm_diags/issues/737 +name: e3sm_diags_dev_nompi +channels: + - conda-forge + - defaults +dependencies: + # Base + # ================= + - python >=3.9 + - pip + - beautifulsoup4 + - cartopy >=0.17.0 + - cartopy_offlinedata + - cdp 1.7.0 + - cdms2 3.1.5 + - cdutil 8.2.1 + - dask + - esmf >=8.4.0 nompi* + - esmpy >=8.4.0 + - genutil 8.2.1 + - lxml + - mache >=0.15.0 + - matplotlib-base + - netcdf4 + - numpy >=1.23.0 + - shapely >=2.0.0,<3.0.0 + - xarray >=2023.02.0 + # Testing + # ======================= + - scipy + - pytest + - pytest-cov + # Documentation + # ======================= + - sphinx + - sphinx_rtd_theme + - sphinx-multiversion + # Quality Assurance Tools + # ======================= + # Run `pre-commit autoupdate` to get the latest pinned versions of 'rev' in + # `.pre-commit.config.yaml`, then update the pinned versions here. + - black=23.9.1 + - flake8=6.1.0 + - flake8-isort=6.1.0 + - isort=5.12.0 + - mypy=1.5.1 + - pre-commit >=3.0.0 + - types-PyYAML >=6.0.0 + # Developer Tools + # ======================= + - tbump=6.9.0 + - ipykernel diff --git a/conda-env/dev.yml b/conda-env/dev.yml index 002b858df..bbdf5f46a 100644 --- a/conda-env/dev.yml +++ b/conda-env/dev.yml @@ -25,28 +25,27 @@ dependencies: - shapely >=2.0.0,<3.0.0 - xarray >=2023.02.0 # Testing - # ================== + # ======================= - scipy - pytest - pytest-cov # Documentation - # ================= + # ======================= - sphinx - sphinx_rtd_theme - sphinx-multiversion + # Quality Assurance Tools + # ======================= + # Run `pre-commit autoupdate` to get the latest pinned versions of 'rev' in + # `.pre-commit.config.yaml`, then update the pinned versions here. + - black=23.9.1 + - flake8=6.1.0 + - flake8-isort=6.1.0 + - isort=5.12.0 + - mypy=1.5.1 + - pre-commit >=3.0.0 + - types-PyYAML >=6.0.0 # Developer Tools - # ================= - # If versions are updated, also update 'rev' in `.pre-commit.config.yaml` - - black=22.10.0 - - flake8=6.0.0 - - flake8-isort=5.0.3 - - isort=5.11.3 - - mypy=0.991 - - pre-commit=2.20.0 - - pytest=7.2.0 - - pytest-cov=4.0.0 - - types-PyYAML=6.0.12.6 - # Developer Tools - # ================= + # ======================= - tbump=6.9.0 -prefix: /opt/miniconda3/envs/e3sm_diags_dev + - ipykernel diff --git a/e3sm_diags/derivations/acme.py b/e3sm_diags/derivations/acme.py index b7c4f020a..eca698af2 100644 --- a/e3sm_diags/derivations/acme.py +++ b/e3sm_diags/derivations/acme.py @@ -465,7 +465,8 @@ def cosp_bin_sum( tau_high0: Optional[float], ): """sum of cosp bins to calculate cloud fraction in specified cloud top pressure / height and - cloud thickness bins, input variable has dimension (cosp_prs,cosp_tau,lat,lon)/(cosp_ht,cosp_tau,lat,lon)""" + cloud thickness bins, input variable has dimension (cosp_prs,cosp_tau,lat,lon)/(cosp_ht,cosp_tau,lat,lon) + """ prs: FileAxis = cld.getAxis(0) tau: FileAxis = cld.getAxis(1) diff --git a/e3sm_diags/driver/arm_diags_driver.py b/e3sm_diags/driver/arm_diags_driver.py index 633c23dcd..56d5f6399 100644 --- a/e3sm_diags/driver/arm_diags_driver.py +++ b/e3sm_diags/driver/arm_diags_driver.py @@ -423,7 +423,6 @@ def run_diag_aerosol_activation(parameter: ARMDiagsParameter) -> ARMDiagsParamet logger.info("Selected region: {}".format(region)) # Possible variables are ccn01, ccn02, ccn05 for variable in variables: - test_data = utils.dataset.Dataset(parameter, test=True) test_a_num = test_data.get_timeseries_variable("a_num", single_point=True)[ @@ -441,7 +440,6 @@ def run_diag_aerosol_activation(parameter: ARMDiagsParameter) -> ARMDiagsParamet ) if "armdiags" in ref_name: - ref_file = os.path.join( ref_path, region[:3] + "armdiagsaciactivate" + region[3:5].upper() + ".c1.nc", @@ -568,7 +566,6 @@ def run_diag_pdf_daily(parameter: ARMDiagsParameter): def run_diag(parameter: ARMDiagsParameter) -> ARMDiagsParameter: - if parameter.diags_set == "annual_cycle": return run_diag_annual_cycle(parameter) elif parameter.diags_set == "diurnal_cycle": diff --git a/e3sm_diags/driver/enso_diags_driver.py b/e3sm_diags/driver/enso_diags_driver.py index e216b87ae..ff060110a 100644 --- a/e3sm_diags/driver/enso_diags_driver.py +++ b/e3sm_diags/driver/enso_diags_driver.py @@ -272,7 +272,11 @@ def run_diag_map(parameter: EnsoDiagsParameter) -> EnsoDiagsParameter: ) # Reference - (ref_domain, ref_reg_coe, ref_confidence_levels,) = perform_regression( + ( + ref_domain, + ref_reg_coe, + ref_confidence_levels, + ) = perform_regression( ref_data, parameter, var, diff --git a/e3sm_diags/driver/lat_lon_driver.py b/e3sm_diags/driver/lat_lon_driver.py index efc92c386..2b5e63c50 100755 --- a/e3sm_diags/driver/lat_lon_driver.py +++ b/e3sm_diags/driver/lat_lon_driver.py @@ -175,12 +175,8 @@ def run_diag(parameter: CoreParameter) -> CoreParameter: # noqa: C901 # Select plev. for ilev in range(len(plev)): - mv1 = mv1_p[ - ilev, - ] - mv2 = mv2_p[ - ilev, - ] + mv1 = mv1_p[ilev,] + mv2 = mv2_p[ilev,] for region in regions: parameter.var_region = region diff --git a/e3sm_diags/driver/polar_driver.py b/e3sm_diags/driver/polar_driver.py index 75930e7ea..32a8dfc19 100755 --- a/e3sm_diags/driver/polar_driver.py +++ b/e3sm_diags/driver/polar_driver.py @@ -133,12 +133,8 @@ def run_diag(parameter: CoreParameter) -> CoreParameter: # Select plev. for ilev in range(len(plev)): - mv1 = mv1_p[ - ilev, - ] - mv2 = mv2_p[ - ilev, - ] + mv1 = mv1_p[ilev,] + mv2 = mv2_p[ilev,] for region in regions: logger.info("Selected region: {}".format(region)) diff --git a/e3sm_diags/driver/streamflow_driver.py b/e3sm_diags/driver/streamflow_driver.py index 2cf14288c..13810780b 100644 --- a/e3sm_diags/driver/streamflow_driver.py +++ b/e3sm_diags/driver/streamflow_driver.py @@ -285,7 +285,7 @@ def setup_test(parameter, var, using_test_mat_file): if parameter.print_statements: # For edison: 720x360x600 logger.info("test_array.shape={}".format(test_array.shape)) - if type(area_upstream) == cdms2.tvariable.TransientVariable: + if isinstance(area_upstream, cdms2.tvariable.TransientVariable): area_upstream = area_upstream.getValue() return area_upstream, test_array @@ -489,7 +489,7 @@ def generate_export( else: monthly = mmat - if type(monthly) == cdms2.tvariable.TransientVariable: + if isinstance(monthly, cdms2.tvariable.TransientVariable): monthly = monthly.getValue() seasonality_index_ref, peak_month_ref = get_seasonality(monthly) @@ -515,7 +515,7 @@ def generate_export( else: monthly = mmat - if type(monthly) == cdms2.tvariable.TransientVariable: + if isinstance(monthly, cdms2.tvariable.TransientVariable): monthly = monthly.getValue() seasonality_index_test, peak_month_test = get_seasonality(monthly) diff --git a/e3sm_diags/driver/tc_analysis_driver.py b/e3sm_diags/driver/tc_analysis_driver.py index 9db1c06ed..de82b2c1b 100644 --- a/e3sm_diags/driver/tc_analysis_driver.py +++ b/e3sm_diags/driver/tc_analysis_driver.py @@ -265,9 +265,9 @@ def _get_vars_from_te_stitch( vars_dict["yearmc"][k - 1, index - 1] = float(line_split[6]) vars_dict["monthmc"][k - 1, index - 1] = float(line_split[7]) - vars_dict["year_start"] = year_start - vars_dict["year_end"] = year_end - vars_dict["num_years"] = year_end - year_start + 1 + vars_dict["year_start"] = year_start # type: ignore + vars_dict["year_end"] = year_end # type: ignore + vars_dict["num_years"] = year_end - year_start + 1 # type: ignore logger.info( f"TE Start Year: {vars_dict['year_start']}, TE End Year: {vars_dict['year_end']}, Total Years: {vars_dict['num_years']}" ) @@ -340,7 +340,6 @@ def _derive_metrics_per_basin( and lat[0] > basin_info[3] and lat[0] < basin_info[4] ): - mod_num_ocn = mod_num_ocn + 1 mod_mon.append(mon[0]) mod_wnd.append(np.max(wind)) @@ -491,7 +490,7 @@ def _calc_mean_ace(vsmc: "MaskedArray", yearic: np.ndarray, num_rows: int) -> fl wind_ts = wind[wind >= 35] ace[i] = ace[i] + np.sum(wind_ts**2) / 1e4 - return np.mean(ace) + return np.mean(ace) # type: ignore def _calc_ts_intensity_dist(wind_speeds: List[int]) -> np.ndarray: diff --git a/e3sm_diags/driver/utils/general.py b/e3sm_diags/driver/utils/general.py index c051d93c8..b4d5b07fd 100644 --- a/e3sm_diags/driver/utils/general.py +++ b/e3sm_diags/driver/utils/general.py @@ -293,7 +293,7 @@ def save_transient_variables_to_netcdf(set_num, variables_dict, label, parameter Save the transient variables to nc file. """ if parameter.save_netcdf: - for (variable_name, variable) in variables_dict.items(): + for variable_name, variable in variables_dict.items(): # Set cdms preferences - no compression, no shuffling, no complaining cdms2.setNetcdfDeflateFlag(1) # 1-9, min to max - Comes at heavy IO (read/write time cost) diff --git a/e3sm_diags/driver/zonal_mean_xy_driver.py b/e3sm_diags/driver/zonal_mean_xy_driver.py index 2069d8c28..195922f05 100755 --- a/e3sm_diags/driver/zonal_mean_xy_driver.py +++ b/e3sm_diags/driver/zonal_mean_xy_driver.py @@ -171,12 +171,8 @@ def run_diag(parameter: CoreParameter) -> CoreParameter: # Select plev. for ilev in range(len(plev)): - mv1 = mv1_p[ - ilev, - ] - mv2 = mv2_p[ - ilev, - ] + mv1 = mv1_p[ilev,] + mv2 = mv2_p[ilev,] for region in regions: logger.info(f"Selected region: {region}") diff --git a/e3sm_diags/plot/cartopy/aerosol_aeronet_plot.py b/e3sm_diags/plot/cartopy/aerosol_aeronet_plot.py index 3e75918b6..9f43dc8df 100644 --- a/e3sm_diags/plot/cartopy/aerosol_aeronet_plot.py +++ b/e3sm_diags/plot/cartopy/aerosol_aeronet_plot.py @@ -114,7 +114,7 @@ def plot(test, test_site, ref_site, parameter): subpage = np.array(p).reshape(2, 2) subpage[1, :] = subpage[0, :] + subpage[1, :] subpage = subpage + np.array(border).reshape(2, 2) - subpage = list(((subpage) * page).flatten()) + subpage = list(((subpage) * page).flatten()) # type: ignore extent = matplotlib.transforms.Bbox.from_extents(*subpage) # Save subplot fname = fnm + ".%i." % (i) + f diff --git a/e3sm_diags/plot/cartopy/annual_cycle_zonal_mean_plot.py b/e3sm_diags/plot/cartopy/annual_cycle_zonal_mean_plot.py index 498b8c4fc..ab72288b4 100644 --- a/e3sm_diags/plot/cartopy/annual_cycle_zonal_mean_plot.py +++ b/e3sm_diags/plot/cartopy/annual_cycle_zonal_mean_plot.py @@ -40,7 +40,6 @@ def get_ax_size(fig, ax): def plot_panel(n, fig, var, clevels, cmap, title, parameters, stats=None): - mon = var.getTime() lat = var.getLatitude() var = np.transpose(var) @@ -172,7 +171,7 @@ def plot(reference, test, diff, metrics_dict, parameter): subpage = np.array(p).reshape(2, 2) subpage[1, :] = subpage[0, :] + subpage[1, :] subpage = subpage + np.array(border).reshape(2, 2) - subpage = list(((subpage) * page).flatten()) + subpage = list(((subpage) * page).flatten()) # type: ignore extent = matplotlib.transforms.Bbox.from_extents(*subpage) # Save subplot fname = fnm + ".%i." % (i) + f diff --git a/e3sm_diags/plot/cartopy/area_mean_time_series_plot.py b/e3sm_diags/plot/cartopy/area_mean_time_series_plot.py index 7e6941656..eebafc0d4 100644 --- a/e3sm_diags/plot/cartopy/area_mean_time_series_plot.py +++ b/e3sm_diags/plot/cartopy/area_mean_time_series_plot.py @@ -126,7 +126,7 @@ def plot(var, regions_to_data, parameter): subpage = np.array(p).reshape(2, 2) subpage[1, :] = subpage[0, :] + subpage[1, :] subpage = subpage + np.array(border).reshape(2, 2) - subpage = list(((subpage) * page).flatten()) + subpage = list(((subpage) * page).flatten()) # type: ignore extent = matplotlib.transforms.Bbox.from_extents(*subpage) # Save subplot fname = fnm + ".%i." % (i) + f diff --git a/e3sm_diags/plot/cartopy/cosp_histogram_plot.py b/e3sm_diags/plot/cartopy/cosp_histogram_plot.py index b4982275f..eab7cd328 100644 --- a/e3sm_diags/plot/cartopy/cosp_histogram_plot.py +++ b/e3sm_diags/plot/cartopy/cosp_histogram_plot.py @@ -39,7 +39,6 @@ def get_ax_size(fig, ax): def plot_panel(n, fig, _, var, clevels, cmap, title, parameters, stats=None): - # Contour levels levels = None norm = None @@ -170,7 +169,6 @@ def plot_panel(n, fig, _, var, clevels, cmap, title, parameters, stats=None): def plot(reference, test, diff, _, parameter): - # Create figure, projection fig = plt.figure(figsize=parameter.figsize, dpi=parameter.dpi) @@ -251,7 +249,7 @@ def plot(reference, test, diff, _, parameter): subpage = np.array(p).reshape(2, 2) subpage[1, :] = subpage[0, :] + subpage[1, :] subpage = subpage + np.array(border).reshape(2, 2) - subpage = list(((subpage) * page).flatten()) + subpage = list(((subpage) * page).flatten()) # type: ignore extent = matplotlib.transforms.Bbox.from_extents(*subpage) # Save subplot fname = fnm + ".%i." % (i) + f diff --git a/e3sm_diags/plot/cartopy/diurnal_cycle_plot.py b/e3sm_diags/plot/cartopy/diurnal_cycle_plot.py index 44560bb2d..b1073af88 100644 --- a/e3sm_diags/plot/cartopy/diurnal_cycle_plot.py +++ b/e3sm_diags/plot/cartopy/diurnal_cycle_plot.py @@ -65,7 +65,6 @@ def determine_tick_step(degrees_covered): def plot_panel(n, fig, proj, var, amp, amp_ref, title, parameter): - normalize_test_amp = parameter.normalize_test_amp specified_max_amp = parameter.normalize_amp_int @@ -129,7 +128,7 @@ def plot_panel(n, fig, proj, var, amp, amp_ref, title, parameter): # If less than 0.50 is subtracted, then 0 W will overlap 0 E on the left side of the plot. # If a number is added, then the value won't show up at all. if global_domain or full_lon: - xticks = [0, 60, 120, 180, 240, 300, 359.99] + xticks = [0, 60, 120, 180, 240, 300, 359.99] # type: ignore else: xticks = np.append(xticks, lon_east) proj = ccrs.PlateCarree() @@ -309,7 +308,7 @@ def plot(test_tmax, test_amp, ref_tmax, ref_amp, parameter): subpage = np.array(p).reshape(2, 2) subpage[1, :] = subpage[0, :] + subpage[1, :] subpage = subpage + np.array(border).reshape(2, 2) - subpage = list(((subpage) * page).flatten()) + subpage = list(((subpage) * page).flatten()) # type: ignore extent = matplotlib.transforms.Bbox.from_extents(*subpage) # Save subplot subplot_suffix = ".%i." % (i) + f diff --git a/e3sm_diags/plot/cartopy/enso_diags_plot.py b/e3sm_diags/plot/cartopy/enso_diags_plot.py index 02c4c3795..457c8a1dc 100644 --- a/e3sm_diags/plot/cartopy/enso_diags_plot.py +++ b/e3sm_diags/plot/cartopy/enso_diags_plot.py @@ -63,7 +63,6 @@ def determine_tick_step(degrees_covered): def plot_panel_map( n, fig, proj, var, clevels, cmap, title, parameter, conf=None, stats={} ): - var = add_cyclic(var) lon = var.getLongitude() lat = var.getLatitude() @@ -330,7 +329,7 @@ def plot_map( subpage = np.array(p).reshape(2, 2) subpage[1, :] = subpage[0, :] + subpage[1, :] subpage = subpage + np.array(border).reshape(2, 2) - subpage = list(((subpage) * page).flatten()) + subpage = list(((subpage) * page).flatten()) # type: ignore extent = matplotlib.transforms.Bbox.from_extents(*subpage) # Save subplot subplot_suffix = ".%i." % (i) + f diff --git a/e3sm_diags/plot/cartopy/lat_lon_land_plot.py b/e3sm_diags/plot/cartopy/lat_lon_land_plot.py index 1ce44a16d..c7da4782d 100644 --- a/e3sm_diags/plot/cartopy/lat_lon_land_plot.py +++ b/e3sm_diags/plot/cartopy/lat_lon_land_plot.py @@ -4,5 +4,4 @@ def plot(reference, test, diff, metrics_dict, parameter): - return base_plot(reference, test, diff, metrics_dict, parameter) diff --git a/e3sm_diags/plot/cartopy/lat_lon_plot.py b/e3sm_diags/plot/cartopy/lat_lon_plot.py index 1a20ae783..117be5889 100644 --- a/e3sm_diags/plot/cartopy/lat_lon_plot.py +++ b/e3sm_diags/plot/cartopy/lat_lon_plot.py @@ -65,7 +65,6 @@ def determine_tick_step(degrees_covered): def plot_panel( # noqa: C901 n, fig, proj, var, clevels, cmap, title, parameters, stats=None ): - var = add_cyclic(var) lon = var.getLongitude() lat = var.getLatitude() @@ -249,7 +248,6 @@ def plot_panel( # noqa: C901 def plot(reference, test, diff, metrics_dict, parameter): - # Create figure, projection fig = plt.figure(figsize=parameter.figsize, dpi=parameter.dpi) proj = ccrs.PlateCarree() @@ -337,7 +335,7 @@ def plot(reference, test, diff, metrics_dict, parameter): subpage = np.array(p).reshape(2, 2) subpage[1, :] = subpage[0, :] + subpage[1, :] subpage = subpage + np.array(border).reshape(2, 2) - subpage = list(((subpage) * page).flatten()) + subpage = list(((subpage) * page).flatten()) # type: ignore extent = matplotlib.transforms.Bbox.from_extents(*subpage) # Save subplot fname = fnm + ".%i." % (i) + f diff --git a/e3sm_diags/plot/cartopy/lat_lon_river_plot.py b/e3sm_diags/plot/cartopy/lat_lon_river_plot.py index 1ce44a16d..c7da4782d 100644 --- a/e3sm_diags/plot/cartopy/lat_lon_river_plot.py +++ b/e3sm_diags/plot/cartopy/lat_lon_river_plot.py @@ -4,5 +4,4 @@ def plot(reference, test, diff, metrics_dict, parameter): - return base_plot(reference, test, diff, metrics_dict, parameter) diff --git a/e3sm_diags/plot/cartopy/meridional_mean_2d_plot.py b/e3sm_diags/plot/cartopy/meridional_mean_2d_plot.py index 855c35317..09d3ed258 100644 --- a/e3sm_diags/plot/cartopy/meridional_mean_2d_plot.py +++ b/e3sm_diags/plot/cartopy/meridional_mean_2d_plot.py @@ -45,7 +45,6 @@ def get_ax_size(fig, ax): def plot_panel(n, fig, proj, var, clevels, cmap, title, parameters, stats=None): - # var_min = float(var.min()) # var_max = float(var.max()) # var_mean = cdutil.averager(var, axis='xy', weights='generate') @@ -167,7 +166,6 @@ def plot_panel(n, fig, proj, var, clevels, cmap, title, parameters, stats=None): def plot(reference, test, diff, metrics_dict, parameter): - # Create figure, projection fig = plt.figure(figsize=parameter.figsize, dpi=parameter.dpi) # proj = ccrs.PlateCarree(central_longitude=180) @@ -255,7 +253,7 @@ def plot(reference, test, diff, metrics_dict, parameter): subpage = np.array(p).reshape(2, 2) subpage[1, :] = subpage[0, :] + subpage[1, :] subpage = subpage + np.array(border).reshape(2, 2) - subpage = list(((subpage) * page).flatten()) + subpage = list(((subpage) * page).flatten()) # type: ignore extent = matplotlib.transforms.Bbox.from_extents(*subpage) # Save subplot fname = fnm + ".%i." % (i) + f diff --git a/e3sm_diags/plot/cartopy/polar_plot.py b/e3sm_diags/plot/cartopy/polar_plot.py index b7439e184..7ec12ad80 100644 --- a/e3sm_diags/plot/cartopy/polar_plot.py +++ b/e3sm_diags/plot/cartopy/polar_plot.py @@ -47,7 +47,6 @@ def get_ax_size(fig, ax): def plot_panel(n, fig, proj, pole, var, clevels, cmap, title, parameters, stats=None): - var = add_cyclic(var) lon = var.getLongitude() lat = var.getLatitude() @@ -158,7 +157,6 @@ def plot_panel(n, fig, proj, pole, var, clevels, cmap, title, parameters, stats= def plot(reference, test, diff, metrics_dict, parameter): - # Create figure, projection fig = plt.figure(figsize=parameter.figsize, dpi=parameter.dpi) @@ -262,7 +260,7 @@ def plot(reference, test, diff, metrics_dict, parameter): subpage = np.array(p).reshape(2, 2) subpage[1, :] = subpage[0, :] + subpage[1, :] subpage = subpage + np.array(border).reshape(2, 2) - subpage = list(((subpage) * page).flatten()) + subpage = list(((subpage) * page).flatten()) # type: ignore extent = matplotlib.transforms.Bbox.from_extents(*subpage) # Save subplot fname = fnm + ".%i." % (i) + f diff --git a/e3sm_diags/plot/cartopy/qbo_plot.py b/e3sm_diags/plot/cartopy/qbo_plot.py index e7a591d49..773a21a17 100644 --- a/e3sm_diags/plot/cartopy/qbo_plot.py +++ b/e3sm_diags/plot/cartopy/qbo_plot.py @@ -231,7 +231,7 @@ def plot(parameter, test, ref): subpage = np.array(p).reshape(2, 2) subpage[1, :] = subpage[0, :] + subpage[1, :] subpage = subpage + np.array(border).reshape(2, 2) - subpage = list(((subpage) * page).flatten()) + subpage = list(((subpage) * page).flatten()) # type: ignore extent = matplotlib.transforms.Bbox.from_extents(*subpage) # Save subplot subplot_suffix = (".%i." % i) + f diff --git a/e3sm_diags/plot/cartopy/streamflow_plot.py b/e3sm_diags/plot/cartopy/streamflow_plot.py index 2c0a18649..a1aad2240 100644 --- a/e3sm_diags/plot/cartopy/streamflow_plot.py +++ b/e3sm_diags/plot/cartopy/streamflow_plot.py @@ -341,7 +341,7 @@ def plot_seasonality_map(export, parameter): subpage = np.array(p).reshape(2, 2) subpage[1, :] = subpage[0, :] + subpage[1, :] subpage = subpage + np.array(border).reshape(2, 2) - subpage = list((subpage * page).flatten()) + subpage = list((subpage * page).flatten()) # type: ignore extent = matplotlib.transforms.Bbox.from_extents(*subpage) # Save subplot subplot_suffix = ".%i." % i + f @@ -623,7 +623,7 @@ def plot_annual_map(export, bias, parameter): subpage = np.array(p).reshape(2, 2) subpage[1, :] = subpage[0, :] + subpage[1, :] subpage = subpage + np.array(border).reshape(2, 2) - subpage = list((subpage * page).flatten()) + subpage = list((subpage * page).flatten()) # type: ignore extent = matplotlib.transforms.Bbox.from_extents(*subpage) # Save subplot subplot_suffix = ".%i." % i + f diff --git a/e3sm_diags/plot/cartopy/taylor_diagram.py b/e3sm_diags/plot/cartopy/taylor_diagram.py index 6feee0b2b..3a87658b7 100644 --- a/e3sm_diags/plot/cartopy/taylor_diagram.py +++ b/e3sm_diags/plot/cartopy/taylor_diagram.py @@ -35,7 +35,7 @@ def __init__(self, refstd, fig=None, rect=111, label="_"): tr = PolarAxes.PolarTransform() # Correlation labels - rlocs = np.concatenate((np.arange(10) / 10.0, [0.95, 0.99])) + rlocs = np.concatenate((np.arange(10) / 10.0, [0.95, 0.99])) # type: ignore tlocs = np.arccos(rlocs) # Conversion to polar angles gl1 = GF.FixedLocator(tlocs) # Positions gl2_num = np.linspace(0, 1.5, 7) diff --git a/e3sm_diags/plot/cartopy/tc_analysis_plot.py b/e3sm_diags/plot/cartopy/tc_analysis_plot.py index a9d90a08a..2b3a6b357 100644 --- a/e3sm_diags/plot/cartopy/tc_analysis_plot.py +++ b/e3sm_diags/plot/cartopy/tc_analysis_plot.py @@ -63,7 +63,6 @@ def get_ax_size(fig, ax): def plot_panel(n, fig, proj, var, var_num_years, region, title): - ax = fig.add_axes(panel[n], projection=proj) ax.set_extent(plot_info[region][0], ccrs.PlateCarree()) diff --git a/e3sm_diags/plot/cartopy/zonal_mean_2d_plot.py b/e3sm_diags/plot/cartopy/zonal_mean_2d_plot.py index 785caf15d..83a9b48a8 100644 --- a/e3sm_diags/plot/cartopy/zonal_mean_2d_plot.py +++ b/e3sm_diags/plot/cartopy/zonal_mean_2d_plot.py @@ -46,7 +46,6 @@ def get_ax_size(fig, ax): def plot_panel(n, fig, proj, var, clevels, cmap, title, parameters, stats=None): - # var_min = float(var.min()) # var_max = float(var.max()) # var_mean = cdutil.averager(var, axis='xy', weights='generate') @@ -187,7 +186,6 @@ def plot_panel(n, fig, proj, var, clevels, cmap, title, parameters, stats=None): def plot(reference, test, diff, metrics_dict, parameter): - # Create figure, projection fig = plt.figure(figsize=parameter.figsize, dpi=parameter.dpi) # proj = ccrs.PlateCarree(central_longitude=180) @@ -277,7 +275,7 @@ def plot(reference, test, diff, metrics_dict, parameter): subpage = np.array(p).reshape(2, 2) subpage[1, :] = subpage[0, :] + subpage[1, :] subpage = subpage + np.array(border).reshape(2, 2) - subpage = list(((subpage) * page).flatten()) + subpage = list(((subpage) * page).flatten()) # type: ignore extent = matplotlib.transforms.Bbox.from_extents(*subpage) # Save subplot fname = fnm + ".%i." % (i) + f diff --git a/e3sm_diags/plot/cartopy/zonal_mean_2d_stratosphere_plot.py b/e3sm_diags/plot/cartopy/zonal_mean_2d_stratosphere_plot.py index b600cb021..e9bab3713 100644 --- a/e3sm_diags/plot/cartopy/zonal_mean_2d_stratosphere_plot.py +++ b/e3sm_diags/plot/cartopy/zonal_mean_2d_stratosphere_plot.py @@ -4,5 +4,4 @@ def plot(reference, test, diff, metrics_dict, parameter): - return base_plot(reference, test, diff, metrics_dict, parameter) diff --git a/e3sm_diags/plot/cartopy/zonal_mean_xy_plot.py b/e3sm_diags/plot/cartopy/zonal_mean_xy_plot.py index 1bff35dd7..938faab48 100644 --- a/e3sm_diags/plot/cartopy/zonal_mean_xy_plot.py +++ b/e3sm_diags/plot/cartopy/zonal_mean_xy_plot.py @@ -37,7 +37,6 @@ def get_ax_size(fig, ax): def plot(reference, test, diff, metrics_dict, parameter): - # Create figure fig = plt.figure(figsize=parameter.figsize, dpi=parameter.dpi) @@ -121,7 +120,7 @@ def plot(reference, test, diff, metrics_dict, parameter): subpage = np.array(p).reshape(2, 2) subpage[1, :] = subpage[0, :] + subpage[1, :] subpage = subpage + np.array(border).reshape(2, 2) - subpage = list(((subpage) * page).flatten()) + subpage = list(((subpage) * page).flatten()) # type: ignore extent = matplotlib.transforms.Bbox.from_extents(*subpage) # Save subplot fname = fnm + ".%i." % (i) + f diff --git a/e3sm_diags/run.py b/e3sm_diags/run.py index 89fac612d..d7a6ba870 100644 --- a/e3sm_diags/run.py +++ b/e3sm_diags/run.py @@ -227,7 +227,7 @@ def _get_instance_of_param_class(self, cls, parameters): for cls_type in class_types: for p in parameters: - if type(p) == cls_type: + if isinstance(p, cls_type): return p msg = "There's weren't any class of types {} in your parameters." diff --git a/e3sm_diags/viewer/lat_lon_viewer.py b/e3sm_diags/viewer/lat_lon_viewer.py index 28ef759a4..678c68748 100644 --- a/e3sm_diags/viewer/lat_lon_viewer.py +++ b/e3sm_diags/viewer/lat_lon_viewer.py @@ -253,7 +253,6 @@ def _create_lat_lon_table_index( # --- Function to read CMIP6 model metrics --- def read_cmip6_metrics_from_csv(path, variables, seasons): - models = [] with open(path, "r") as fin: @@ -292,7 +291,6 @@ def read_cmip6_metrics_from_csv(path, variables, seasons): # --- Function to read E3SM Diags metrics --- def read_e3sm_diags_metrics(path, variables, seasons, names=None): - # List of available models models = [] paths = [] @@ -674,7 +672,6 @@ def _create_csv_from_dict_taylor_diag( # Add samples for baseline simulation. if run_type == "model_vs_obs": - # Read the control run data. # Example base line csv file name: JJA_metrics_table_taylor_diag_historical_1985-2014_E3SMv1.csv for ibase, base_line_csv_path in enumerate(base_line_csv_paths): diff --git a/e3sm_diags/viewer/mean_2d_viewer.py b/e3sm_diags/viewer/mean_2d_viewer.py index 2ba6ba0e5..e9a2332be 100644 --- a/e3sm_diags/viewer/mean_2d_viewer.py +++ b/e3sm_diags/viewer/mean_2d_viewer.py @@ -39,7 +39,6 @@ def create_viewer(root_dir, parameters): for var in param.variables: for season in param.seasons: for region in param.regions: - try: viewer.set_group(param.case_id) except RuntimeError: diff --git a/pyproject.toml b/pyproject.toml index 6835efe21..50df363ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,7 @@ -# This config file is required for Black. -# Unforunately, black does not support setup.cfg (refer to link). -# https://github.com/psf/black/issues/683#issuecomment-490236406 - [tool.black] +# Docs: https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html line-length = 88 -target-version = ['py36'] +target-version = ["py39", "py310"] include = '\.pyi?$' exclude = ''' /( @@ -25,3 +22,32 @@ exclude = ''' | analysis_data_preprocess )/ ''' + +[tool.isort] +# Docs: https://pycqa.github.io/isort/docs/configuration/options.html#example-pyprojecttoml_4 +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +line_length = 88 +skip = "e3sm_diags/e3sm_diags_driver.py" + +[tool.pytest.ini_options] +# Docs: https://docs.pytest.org/en/7.2.x/reference/customize.html#configuration +junit_family = "xunit2" +addopts = "--cov=e3sm_diags --cov-report term --cov-report html:tests_coverage_reports/htmlcov --cov-report xml:tests_coverage_reports/coverage.xml -s" +python_files = ["tests.py", "test_*.py"] +testpaths = "tests/e3sm_diags" + +[tool.mypy] +# Docs: https://mypy.readthedocs.io/en/stable/config_file.html +python_version = "3.10" +check_untyped_defs = true +ignore_missing_imports = true +warn_unused_ignores = true +warn_redundant_casts = true +warn_unused_configs = true + +[[tool.mypy.overrides]] +module = ["analysis_data_preprocess.*", "model_data_preprocess.*"] +ignore_errors = true diff --git a/setup.cfg b/setup.cfg index 036c04f66..50b647306 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,5 @@ +# This file is used for configuring flake8, which does not currently support pyproject.toml + [flake8] # https://pep8.readthedocs.io/en/latest/intro.html#error-codes ignore = @@ -26,49 +28,3 @@ exclude = venv, analysis_data_preprocess model_data_preprocess - -[isort] -multi_line_output=3 -include_trailing_comma=True -force_grid_wrap=0 -use_parentheses=True -line_length=88 -skip= - e3sm_diags/e3sm_diags_driver.py - -[pycodestyle] -max-line-length = 119 -exclude = - .tox - .git - */migrations/* - */static/CACHE/* - docs - node_modules - .idea - .mypy_cache - .pytest_cache - *__init__.py - venv - analysis_data_preprocess - model_data_preprocess - -[mypy] -python_version = 3.10 -check_untyped_defs = True -ignore_missing_imports = True -warn_unused_ignores = True -warn_redundant_casts = True -warn_unused_configs = True - -[mypy-analysis_data_preprocess.*] -ignore_errors = True - -[mypy-model_data_preprocess.*] -ignore_errors = True - -[tool:pytest] -junit_family=xunit2 -addopts = --cov=e3sm_diags --cov-report term --cov-report html:tests_coverage_reports/htmlcov --cov-report xml:tests_coverage_reports/coverage.xml -s --ignore=tests/integration -python_files = tests.py test_*.py -testpaths = tests/e3sm_diags diff --git a/tests/complete_run.py b/tests/complete_run.py index 68b6627f9..00e490615 100644 --- a/tests/complete_run.py +++ b/tests/complete_run.py @@ -1,15 +1,21 @@ +"""The complete E3SM Diagnostic run. + +Due to the large amount of data required to run, this test will be run manually +on Anvil (rather than as part of the CI tests). + +Run the following first: + - srun --pty --nodes=1 --time=01:00:00 /bin/bash + - source /lcrc/soft/climate/e3sm-unified/load_latest_e3sm_unified_chrysalis.sh + - Or: source /lcrc/soft/climate/e3sm-unified/load_latest_e3sm_unified_anvil.sh +""" import os -import unittest # This test should be run with the latest E3SM Diags tutorial code. from examples.run_v2_6_0_all_sets_E3SM_machines import run_lcrc -from tests.integration.test_diags import compare_images - -# Due to the large amount of data required to run, this test will be run manually on Anvil -# (rather than as part of the CI tests). +from tests.integration.test_diags import _compare_images -class TestCompleteRun(unittest.TestCase): +class TestCompleteRun: def test_complete_run(self): actual_images_dir = run_lcrc(".") @@ -25,20 +31,11 @@ def test_complete_run(self): path_to_actual_png = os.path.join(actual_images_dir, image_name) path_to_expected_png = os.path.join(expected_images_dir, image_name) - compare_images( - self, + mismatched_images = _compare_images( mismatched_images, image_name, path_to_actual_png, path_to_expected_png, ) - self.assertEqual(mismatched_images, []) - - -if __name__ == "__main__": - # Run the following first: - # srun --pty --nodes=1 --time=01:00:00 /bin/bash - # source /lcrc/soft/climate/e3sm-unified/load_latest_e3sm_unified_chrysalis.sh - # Or: source /lcrc/soft/climate/e3sm-unified/load_latest_e3sm_unified_anvil.sh - unittest.main() + assert len(mismatched_images) == 0 diff --git a/tests/integration/test_all_sets.py b/tests/integration/test_all_sets.py index 6a06c54d2..f23d736a9 100644 --- a/tests/integration/test_all_sets.py +++ b/tests/integration/test_all_sets.py @@ -1,61 +1,117 @@ +import ast +import configparser import os import re import shutil -import unittest +from typing import List +import pytest + +from e3sm_diags.parameter import SET_TO_PARAMETERS +from e3sm_diags.parameter.core_parameter import CoreParameter +from e3sm_diags.run import runner from tests.integration.config import TEST_DATA_DIR from tests.integration.utils import run_cmd_and_pipe_stderr +# The path to the integration test data, which needs to be downloaded +# prior to running this test file. +MODULE_PATH = os.path.dirname(__file__) +TEST_DATA_PATH = os.path.join(MODULE_PATH, TEST_DATA_DIR) -def count_images(directory, file_type="png"): - """Count the number of images of type file_type in directory""" - count = 0 - for _, __, files in os.walk(directory): - for f in files: - if f.endswith(file_type): - count += 1 - return count +# The path to the integration test diagnostics .cfg file. +CFG_PATH = os.path.join(MODULE_PATH, "all_sets_modified.cfg") +CFG_PATH = os.path.abspath(CFG_PATH) -class TestAllSets(unittest.TestCase): - def get_results_dir(self, output): - """Given output from e3sm_diags_driver, extract the path to results_dir.""" - for line in output: - match = re.search("Viewer HTML generated at (.*)viewer.*.html", line) - if match: - results_dir = match.group(1) - return results_dir - self.fail("No viewer directory listed in output: {}".format(output)) - - def run_test(self, backend): - pth = os.path.dirname(__file__) - test_pth = os.path.join(pth, TEST_DATA_DIR) - cfg_pth = os.path.join(pth, "all_sets_modified.cfg") - cfg_pth = os.path.abspath(cfg_pth) - - if backend == "mpl": - backend_option = "" - # Note: remember to increase expected_num_diags as adding new images. - expected_num_diags = 12 - else: - raise RuntimeError("Invalid backend: {}".format(backend)) +class TestAllSets: + def test_all_sets(self): + expected_num_diags = 12 + # *_data_path needs to be added b/c the tests runs the diags from a different location cmd = ( - "e3sm_diags_driver.py -d {}{} --reference_data_path {} --test_data_path {}" + f"e3sm_diags_driver.py -d {CFG_PATH} " + f"--reference_data_path {TEST_DATA_PATH} " + f"--test_data_path {TEST_DATA_PATH}" ) - cmd = cmd.format(cfg_pth, backend_option, test_pth, test_pth) + stderr = run_cmd_and_pipe_stderr(cmd) + # count the number of pngs in viewer_dir - results_dir = self.get_results_dir(stderr) - count = count_images(results_dir) + results_dir = self._get_results_dir(stderr) + count = self._count_images(results_dir) + # -1 is needed because of the E3SM logo in the viewer html - self.assertEqual(count - 1, expected_num_diags) + assert count - 1 == expected_num_diags shutil.rmtree(results_dir) # remove all generated results from the diags - def test_all_sets_mpl(self): - self.run_test("mpl") + def _get_results_dir(self, output: List[str]): + """Given output from e3sm_diags_driver, extract the path to results_dir.""" + for line in output: + match = re.search("Viewer HTML generated at (.*)viewer.*.html", line) + if match: + results_dir = match.group(1) + return results_dir + + raise RuntimeError("No viewer directory listed in output: {}".format(output)) + + def _count_images(self, directory: str): + """Count the number of images of type file_type in directory""" + count = 0 + + for _, __, files in os.walk(directory): + for f in files: + if f.endswith("png"): + count += 1 + + return count + + @pytest.mark.xfail + def test_all_sets_directly(self): + # TODO: This test is meant to replace `test_all_sets`. It should create + # CoreParameter objects per diagnostic set defined in + # `all_sets_modified.cfg`. These CoreParameter objects should then be + # passed to `runner.run_diags()`. The benefit with this approach is + # that we don't need to run the command using subprocess, which means + # immediate unit testing feedback rather than waiting to pipe the + # complete stderr. We can also step through the code for debugging using + # an interactive console such as VS Code's Python debugger. + params = self._convert_cfg_to_param_objs() + + for param in params: + runner.run_diags([param]) + + def _convert_cfg_to_param_objs(self) -> List[CoreParameter]: + """Convert diagnostic cfg entries to parameter objects. + + NOTE: ast.literal_eval is not considered "safe" on untrusted data. + The reason why it is used is because `configparser.ConfigParser` + doesn't work well with parsing Python types from strings in + `.cfg` files, resulting in things such as nested strings or string + representation of lists. Since we are only calling literal_eval on + `.cfg` files hosted in this repo, there is minimal risk here. + + Returns + ------- + List[CoreParameter] + A list of CoreParameter objects, one for each diagnotic set. + """ + config = configparser.ConfigParser() + config.read(CFG_PATH) + params = [] + + for set_name in config.sections(): + param = SET_TO_PARAMETERS[set_name]() + + for option in config.options(set_name): + val = config.get(set_name, option) + val = ast.literal_eval(val) + + setattr(param, option, val) + + param.reference_data_path = TEST_DATA_PATH + param.test_data_path = TEST_DATA_PATH + params.append(param) -if __name__ == "__main__": - unittest.main() + return params diff --git a/tests/integration/test_diags.py b/tests/integration/test_diags.py index 47bde7f16..2a33f1e7a 100644 --- a/tests/integration/test_diags.py +++ b/tests/integration/test_diags.py @@ -2,10 +2,12 @@ import re import shutil import subprocess -import unittest +from typing import List +import pytest from PIL import Image, ImageChops, ImageDraw +from e3sm_diags.logger import custom_logger from tests.integration.config import TEST_IMAGES_PATH, TEST_ROOT_PATH from tests.integration.utils import run_cmd_and_pipe_stderr @@ -22,19 +24,40 @@ # Set to False to place the results directory in tests/system CORI_WEB = False +logger = custom_logger(__name__) -def get_results_dir(output_list): + +@pytest.fixture(scope="module") +def get_results_dir(): + command = f"python {TEST_ROOT_PATH}/all_sets.py -d {TEST_ROOT_PATH}/all_sets.cfg" + stderr = run_cmd_and_pipe_stderr(command) + + results_dir = _get_results_dir(stderr) + logger.info("results_dir={}".format(results_dir)) + + if CORI_WEB: + results_dir = _move_to_NERSC_webserver( + "/global/u1/f/(.*)/e3sm_diags", + "/global/cfs/cdirs/acme/www/{}", + results_dir, + ) + + return results_dir + + +def _get_results_dir(stderr): """Given output from e3sm_diags_driver, extract the path to results_dir.""" - for line in output_list: + for line in stderr: match = re.search("Viewer HTML generated at (.*)viewer.*.html", line) if match: results_dir = match.group(1) return results_dir - message = "No viewer directory listed in output: {}".format(output_list) + + message = "No viewer directory listed in output: {}".format(stderr) raise RuntimeError(message) -def move_to_web(machine_path_re_str, html_prefix_format_str, results_dir): +def _move_to_NERSC_webserver(machine_path_re_str, html_prefix_format_str, results_dir): command = "git rev-parse --show-toplevel" top_level = subprocess.check_output(command.split()).decode("utf-8").splitlines()[0] match = re.search(machine_path_re_str, top_level) @@ -43,10 +66,11 @@ def move_to_web(machine_path_re_str, html_prefix_format_str, results_dir): else: message = "Username could not be extracted from top_level={}".format(top_level) raise RuntimeError(message) + html_prefix = html_prefix_format_str.format(username) - print("html_prefix={}".format(html_prefix)) + logger.info("html_prefix={}".format(html_prefix)) new_results_dir = "{}/{}".format(html_prefix, results_dir) - print("new_results_dir={}".format(new_results_dir)) + logger.info("new_results_dir={}".format(new_results_dir)) if os.path.exists(new_results_dir): command = "rm -r {}".format(new_results_dir) subprocess.check_output(command.split()) @@ -54,28 +78,16 @@ def move_to_web(machine_path_re_str, html_prefix_format_str, results_dir): subprocess.check_output(command.split()) command = "chmod -R 755 {}".format(new_results_dir) subprocess.check_output(command.split()) + return new_results_dir -def count_images(directory): - images = [] - for root, _, filenames in os.walk(directory): - # download_data.py won't download files in the viewer directory - # because the webpage is more than a simple page of links. - if "viewer" not in root: - for file in filenames: - if file.endswith(".png"): - images.append(file) - return len(images), images - - -def compare_images( - test, - mismatched_images, - image_name, - path_to_actual_png, - path_to_expected_png, -): +def _compare_images( + mismatched_images: List[str], + image_name: str, + path_to_actual_png: str, + path_to_expected_png: str, +) -> List[str]: # https://stackoverflow.com/questions/35176639/compare-images-python-pil actual_png = Image.open(path_to_actual_png).convert("RGB") @@ -87,9 +99,9 @@ def compare_images( os.mkdir(diff_dir) bbox = diff.getbbox() - if not bbox: - # If `diff.getbbox()` is None, then the images are in theory equal - test.assertIsNone(diff.getbbox()) + # If `diff.getbbox()` is None, then the images are in theory equal + if bbox is None: + pass else: # Sometimes, a few pixels will differ, but the two images appear identical. # https://codereview.stackexchange.com/questions/55902/fastest-way-to-count-non-zero-pixels-using-python-and-pillow @@ -101,14 +113,15 @@ def compare_images( .getdata() ) num_nonzero_pixels = sum(nonzero_pixels) - print("\npath_to_actual_png={}".format(path_to_actual_png)) - print("path_to_expected_png={}".format(path_to_expected_png)) - print("diff has {} nonzero pixels.".format(num_nonzero_pixels)) + logger.info("\npath_to_actual_png={}".format(path_to_actual_png)) + logger.info("path_to_expected_png={}".format(path_to_expected_png)) + logger.info("diff has {} nonzero pixels.".format(num_nonzero_pixels)) width, height = expected_png.size num_pixels = width * height - print("total number of pixels={}".format(num_pixels)) + logger.info("total number of pixels={}".format(num_pixels)) fraction = num_nonzero_pixels / num_pixels - print("num_nonzero_pixels/num_pixels fraction={}".format(fraction)) + logger.info("num_nonzero_pixels/num_pixels fraction={}".format(fraction)) + # Fraction of mismatched pixels should be less than 0.02% if fraction >= 0.0002: mismatched_images.append(image_name) @@ -131,25 +144,135 @@ def compare_images( "PNG", ) + return mismatched_images + -class TestAllSets(unittest.TestCase): - @classmethod - def setUpClass(cls): - command = ( - f"python {TEST_ROOT_PATH}/all_sets.py -d {TEST_ROOT_PATH}/all_sets.cfg" +class TestAllSets: + @pytest.fixture(autouse=True) + def setup(self, get_results_dir): + self.results_dir = get_results_dir + + def test_results_directory_ends_with_specific_directory(self): + assert self.results_dir.endswith("all_sets_results_test/") + + def test_actual_images_produced_is_the_same_as_the_expected(self): + actual_num_images, actual_images = self._count_images_in_dir( + f"{TEST_ROOT_PATH}/all_sets_results_test" + ) + expected_num_images, expected_images = self._count_images_in_dir( + TEST_IMAGES_PATH ) - stderr = run_cmd_and_pipe_stderr(command) - # FIXME: "Type[TestAllSets]" has no attribute "results_dir" - TestAllSets.results_dir = get_results_dir(stderr) # type: ignore - print("TestAllSets.results_dir={}".format(TestAllSets.results_dir)) # type: ignore - if CORI_WEB: - TestAllSets.results_dir = move_to_web( # type: ignore - "/global/u1/f/(.*)/e3sm_diags", - "/global/cfs/cdirs/acme/www/{}", - TestAllSets.results_dir, # type: ignore + + assert actual_images == expected_images + assert actual_num_images == expected_num_images + + def test_area_mean_time_series_plot_diffs(self): + set_name = "area_mean_time_series" + variables = ["TREFHT"] + for variable in variables: + variable_lower = variable.lower() + + # Check PNG path is the same as the expected. + png_path = "{}/{}.png".format(set_name, variable) + full_png_path = "{}{}".format(self.results_dir, png_path) + path_exists = os.path.exists(full_png_path) + + assert path_exists + + # Check full HTML path is the same as the expected. + html_path = "{}viewer/{}/variable/{}/plot.html".format( + self.results_dir, set_name, variable_lower ) + self._check_html_image(html_path, png_path, full_png_path) + + def test_cosp_histogram_plot_diffs(self): + self._check_plots_generic( + set_name="cosp_histogram", + case_id="MISR-COSP", + ref_name="MISRCOSP", + variables=["COSP_HISTOGRAM_MISR"], + region="global", + ) + + def test_enso_diags_map_diffs(self): + case_id = "TREFHT-response-map" + self._check_enso_map_plots(case_id) + + def test_enso_diags_map_with_start_yrs_diffs(self): + case_id = "TREFHT-response-map-start-yrs" + self._check_enso_map_plots(case_id) + + def test_enso_diags_map_test_with_ref_yrs_diffs(self): + case_id = "TREFHT-response-map-test-ref-yrs" + self._check_enso_map_plots(case_id) + + def test_enso_diags_scatter_plot_diffs(self): + case_id = "TREFHT-response-scatter" + self._check_enso_scatter_plots(case_id) + + def test_enso_diags_scatter_with_start_yrs_plot_diffs(self): + case_id = "TREFHT-response-scatter-start-yrs" + self._check_enso_scatter_plots(case_id) + + def test_enso_diags_scatter_with_test_ref_yrs_plot_diffs(self): + case_id = "TREFHT-response-scatter-test-ref-yrs" + self._check_enso_scatter_plots(case_id) + + def test_lat_lon_plot_diffs(self): + self._check_plots_plevs("lat_lon", "global", [850.0]) - def check_html_image(self, html_path, png_path, full_png_path): + def test_lat_lon_regional_plot_diffs(self): + self._check_plots_plevs("lat_lon", "CONUS_RRM", [850.0]) + + def test_meridional_mean_2d_plot_diffs(self): + self._check_plots_2d("meridional_mean_2d") + + def test_polar_plot_diffs(self): + self._check_plots_plevs("polar", "polar_S", [850.0]) + + def test_qbo_plot_diffs(self): + case_id = "qbo-test" + case_id_lower = case_id.lower() + set_name = "qbo" + + # Check PNG path is the same as the expected. + png_path = "{}/{}/qbo_diags.png".format(set_name, case_id) + full_png_path = "{}{}".format(self.results_dir, png_path) + path_exists = os.path.exists(full_png_path) + + assert path_exists + + # Check full HTML path is the same as the expected. + # viewer/qbo/variable/era-interim/plot.html + html_path = "{}viewer/{}/variable/{}/plot.html".format( + self.results_dir, set_name, case_id_lower + ) + self._check_html_image(html_path, png_path, full_png_path) + + def test_streamflow_plot_diffs(self): + self._check_streamflow_plots() + + def test_zonal_mean_2d_plot_diffs(self): + self._check_plots_2d("zonal_mean_2d") + + def test_zonal_mean_xy_plot_diffs(self): + self._check_plots_plevs("zonal_mean_xy", "global", [200.0]) + + # Utility methods. + # -------------------------------------------------------------------------- + def _count_images_in_dir(self, directory): + images = [] + for root, _, filenames in os.walk(directory): + # download_data.py won't download files in the viewer directory + # because the webpage is more than a simple page of links. + if "viewer" not in root: + for file in filenames: + if file.endswith(".png"): + images.append(file) + return len(images), images + + def _check_html_image(self, html_path, png_path, full_png_path): + # Check HTML image tags exist. img_src = None option_value = None href = None @@ -167,9 +290,10 @@ def check_html_image(self, html_path, png_path, full_png_path): if not href: re_str = 'href="../../../../{}">'.format(png_path) href = re.search(re_str, line) - self.assertIsNotNone(img_src) - self.assertIsNotNone(option_value) - self.assertIsNotNone(href) + + assert img_src is not None + assert option_value is not None + assert href is not None image_name = os.path.split(png_path)[-1] path_to_actual_png = full_png_path @@ -188,17 +312,16 @@ def check_html_image(self, html_path, png_path, full_png_path): check_images = True if check_images: - mismatched_images = [] # type: ignore - compare_images( - self, + mismatched_images: List[str] = [] + _compare_images( mismatched_images, image_name, path_to_actual_png, path_to_expected_png, ) - self.assertEqual(mismatched_images, []) + assert len(mismatched_images) == 0 - def check_plots_generic( + def _check_plots_generic( self, set_name, case_id, ref_name, variables, region, plev=None ): case_id_lower = case_id.lower() @@ -209,6 +332,9 @@ def check_plots_generic( variable_lower = variable.lower() for season in seasons: season_lower = season.lower() + + # Check PNG path is the same as the expected. + if plev: # 200.9 would just show up as 200 in the file paths. plev_str = "%.0f" % plev @@ -226,10 +352,15 @@ def check_plots_generic( season, region, ) - full_png_path = "{}{}".format(TestAllSets.results_dir, png_path) # type: ignore - self.assertTrue(os.path.exists(full_png_path)) + + full_png_path = "{}{}".format(self.results_dir, png_path) + path_exists = os.path.exists(full_png_path) + + assert path_exists + + # Check full HTML path is the same as the expected. html_path = "{}viewer/{}/{}/{}-{}{}-{}/{}.html".format( - TestAllSets.results_dir, # type: ignore + self.results_dir, set_name, case_id_lower, variable_lower, @@ -238,10 +369,10 @@ def check_plots_generic( ref_name_lower, season_lower, ) - self.check_html_image(html_path, png_path, full_png_path) + self._check_html_image(html_path, png_path, full_png_path) - def check_plots_2d(self, set_name): - self.check_plots_generic( + def _check_plots_2d(self, set_name): + self._check_plots_generic( set_name=set_name, case_id="ERA-Interim", ref_name="ERA-Interim", @@ -249,9 +380,9 @@ def check_plots_2d(self, set_name): region="global", ) - def check_plots_plevs(self, set_name, region, plevs): + def _check_plots_plevs(self, set_name, region, plevs): for plev in plevs: - self.check_plots_generic( + self._check_plots_generic( set_name=set_name, case_id="ERA-Interim", ref_name="ERA-Interim", @@ -260,40 +391,54 @@ def check_plots_plevs(self, set_name, region, plevs): plev=plev, ) - def check_enso_map_plots(self, case_id): + def _check_enso_map_plots(self, case_id): case_id_lower = case_id.lower() nino_region_lower = "NINO34".lower() set_name = "enso_diags" variables = ["TREFHT"] + for variable in variables: variable_lower = variable.lower() + + # Check PNG path is the same as the expected. png_path = "{}/{}/regression-coefficient-{}-over-{}.png".format( set_name, case_id, variable_lower, nino_region_lower ) - full_png_path = "{}{}".format(TestAllSets.results_dir, png_path) # type: ignore - self.assertTrue(os.path.exists(full_png_path)) + full_png_path = "{}{}".format(self.results_dir, png_path) + path_exists = os.path.exists(full_png_path) + + assert path_exists + + # Check full HTML path is the same as the expected. html_path = "{}viewer/{}/map/{}/plot.html".format( - TestAllSets.results_dir, set_name, case_id_lower # type: ignore + self.results_dir, set_name, case_id_lower ) - self.check_html_image(html_path, png_path, full_png_path) + self._check_html_image(html_path, png_path, full_png_path) - def check_enso_scatter_plots(self, case_id): + def _check_enso_scatter_plots(self, case_id): case_id_lower = case_id.lower() set_name = "enso_diags" variables = ["TREFHT"] + for variable in variables: region = "NINO3" + + # Check PNG path is the same as the expected. png_path = "{}/{}/feedback-{}-{}-TS-NINO3.png".format( set_name, case_id, variable, region ) - full_png_path = "{}{}".format(TestAllSets.results_dir, png_path) # type: ignore - self.assertTrue(os.path.exists(full_png_path)) + full_png_path = "{}{}".format(self.results_dir, png_path) + path_exists = os.path.exists(full_png_path) + + assert path_exists + + # Check full HTML path is the same as the expected. html_path = "{}viewer/{}/scatter/{}/plot.html".format( - TestAllSets.results_dir, set_name, case_id_lower # type: ignore + self.results_dir, set_name, case_id_lower ) - self.check_html_image(html_path, png_path, full_png_path) + self._check_html_image(html_path, png_path, full_png_path) - def check_streamflow_plots(self): + def _check_streamflow_plots(self): case_id = "RIVER_DISCHARGE_OVER_LAND_LIQ_GSIM" case_id_lower = case_id.lower() set_name = "streamflow" @@ -304,15 +449,22 @@ def check_streamflow_plots(self): "annual_map", "annual_scatter", ]: + # Check PNG path is the same as the expected. png_path = "{}/{}/{}.png".format(set_name, case_id, plot_type) expected = ( "streamflow/RIVER_DISCHARGE_OVER_LAND_LIQ_GSIM/{}.png".format( plot_type ) ) - self.assertEqual(png_path, expected) - full_png_path = "{}{}".format(TestAllSets.results_dir, png_path) # type: ignore - self.assertTrue(os.path.exists(full_png_path)) + assert png_path == expected + + # Check path exists + full_png_path = "{}{}".format(self.results_dir, png_path) + path_exists = os.path.exists(full_png_path) + + assert path_exists + + # Check HTML path is the same as the expected. if plot_type == "seasonality_map": plot_label = "seasonality-map" elif plot_type == "annual_map": @@ -327,105 +479,8 @@ def check_streamflow_plots(self): expected = "viewer/streamflow/{}/river_discharge_over_land_liq_gsim-{}/plot.html".format( plot_label, plot_type ) - self.assertEqual(html_path, expected) - full_html_path = "{}{}".format(TestAllSets.results_dir, html_path) # type: ignore - self.check_html_image(full_html_path, png_path, full_png_path) - - # Test results_dir - def test_results_dir(self): - self.assertTrue(TestAllSets.results_dir.endswith("all_sets_results_test/")) # type: ignore - - # Test the image count - def test_image_count(self): - actual_num_images, actual_images = count_images( - f"{TEST_ROOT_PATH}/all_sets_results_test" - ) - expected_num_images, expected_images = count_images(TEST_IMAGES_PATH) - self.assertEqual(actual_images, expected_images) - self.assertEqual(actual_num_images, expected_num_images) - - # Test sets - def test_area_mean_time_series(self): - set_name = "area_mean_time_series" - variables = ["TREFHT"] - for variable in variables: - variable_lower = variable.lower() - png_path = "{}/{}.png".format(set_name, variable) - full_png_path = "{}{}".format(TestAllSets.results_dir, png_path) # type: ignore - self.assertTrue(os.path.exists(full_png_path)) - html_path = "{}viewer/{}/variable/{}/plot.html".format( - TestAllSets.results_dir, set_name, variable_lower # type: ignore - ) - self.check_html_image(html_path, png_path, full_png_path) - - def test_cosp_histogram(self): - self.check_plots_generic( - set_name="cosp_histogram", - case_id="MISR-COSP", - ref_name="MISRCOSP", - variables=["COSP_HISTOGRAM_MISR"], - region="global", - ) - - def test_enso_diags_map(self): - case_id = "TREFHT-response-map" - self.check_enso_map_plots(case_id) - - def test_enso_diags_map_start_yrs(self): - case_id = "TREFHT-response-map-start-yrs" - self.check_enso_map_plots(case_id) - - def test_enso_diags_map_test_ref_yrs(self): - case_id = "TREFHT-response-map-test-ref-yrs" - self.check_enso_map_plots(case_id) - - def test_enso_diags_scatter(self): - case_id = "TREFHT-response-scatter" - self.check_enso_scatter_plots(case_id) - - def test_enso_diags_scatter_start_yrs(self): - case_id = "TREFHT-response-scatter-start-yrs" - self.check_enso_scatter_plots(case_id) - - def test_enso_diags_scatter_test_ref_yrs(self): - case_id = "TREFHT-response-scatter-test-ref-yrs" - self.check_enso_scatter_plots(case_id) - - def test_lat_lon(self): - self.check_plots_plevs("lat_lon", "global", [850.0]) - - def test_lat_lon_regional(self): - self.check_plots_plevs("lat_lon", "CONUS_RRM", [850.0]) - - def test_meridional_mean_2d(self): - self.check_plots_2d("meridional_mean_2d") - - def test_polar(self): - self.check_plots_plevs("polar", "polar_S", [850.0]) - - def test_qbo(self): - case_id = "qbo-test" - case_id_lower = case_id.lower() - set_name = "qbo" - png_path = "{}/{}/qbo_diags.png".format(set_name, case_id) - full_png_path = "{}{}".format(TestAllSets.results_dir, png_path) # type: ignore - print(full_png_path) - self.assertTrue(os.path.exists(full_png_path)) - # viewer/qbo/variable/era-interim/plot.html - html_path = "{}viewer/{}/variable/{}/plot.html".format( - TestAllSets.results_dir, set_name, case_id_lower # type: ignore - ) - self.check_html_image(html_path, png_path, full_png_path) - - def test_streamflow(self): - self.check_streamflow_plots() - - def test_zonal_mean_2d(self): - self.check_plots_2d("zonal_mean_2d") - - def test_zonal_mean_xy(self): - self.check_plots_plevs("zonal_mean_xy", "global", [200.0]) - + assert html_path == expected -if __name__ == "__main__": - unittest.main() + # Check the full HTML path is the same as the expected. + full_html_path = "{}{}".format(self.results_dir, html_path) + self._check_html_image(full_html_path, png_path, full_png_path) From 28ea590acdf85ec8bc8a67d5a517e5e77a736cf0 Mon Sep 17 00:00:00 2001 From: Tom Vo Date: Tue, 12 Dec 2023 12:36:34 -0800 Subject: [PATCH 10/46] Replace `parser.readfp()` with `parser.read_file()` for Python 3.12 support (#765) --- e3sm_diags/parser/core_parser.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/e3sm_diags/parser/core_parser.py b/e3sm_diags/parser/core_parser.py index 2d1defd23..d823de253 100644 --- a/e3sm_diags/parser/core_parser.py +++ b/e3sm_diags/parser/core_parser.py @@ -815,21 +815,21 @@ def _get_cfg_parameters( parameters = [] cfg_file_obj = self._create_cfg_hash_titles(cfg_file) - kwargs = ( - {"strict": False} if sys.version_info[0] >= 3 else {} - ) # 'strict' keyword doesn't work in Python 2. - config = configparser.ConfigParser( # type: ignore - **kwargs - ) # Allow for two lines to be the same. - config.readfp(cfg_file_obj) - - for section in config.sections(): + + # Setting `strict=False` enables the parser to allow for any section + # or duplicates while reading from a single source. This is required + # because .cfg diagnostic files might contain duplicate sections with + # slight tweaks based on the set. + parser = configparser.ConfigParser(strict=False) + parser.read_file(cfg_file_obj) + + for section in parser.sections(): p = self._parameter_cls() # Remove all of the variables. p.__dict__.clear() - for k, v in config.items(section): + for k, v in parser.items(section): v = yaml.safe_load(v) setattr(p, k, v) From 14a6b972d8f0b43337a141464ba148b94e9d8871 Mon Sep 17 00:00:00 2001 From: Naser Mahfouz Date: Wed, 13 Dec 2023 09:51:17 -0800 Subject: [PATCH 11/46] Add more aerosol metrics (#763) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add aburdens to lat-lon * Add some ERF calculations * Add mostly auto-generated docs to erf calc * Remove tag entry in lat-lon set * Fix contour levels in lat-lon for burdens * Add more aerosol variables and specs * Add specs for cdnc/lwp vars Co-authored-by: Johannes Mülmenstädt Co-authored-by: Susannah Burrows Co-authored-by: Tom Vo --- e3sm_diags/derivations/acme.py | 389 ++++++++++++++++++ ...annual_cycle_zonal_mean_model_vs_model.cfg | 187 +++++++++ .../annual_cycle_zonal_mean_model_vs_obs.cfg | 170 ++++++++ .../default_diags/lat_lon_model_vs_model.cfg | 294 +++++++++++++ .../default_diags/lat_lon_model_vs_obs.cfg | 277 +++++++++++++ .../default_diags/polar_model_vs_model.cfg | 381 +++++++++++++++++ .../default_diags/polar_model_vs_obs.cfg | 361 ++++++++++++++++ .../zonal_mean_xy_model_vs_model.cfg | 38 ++ .../zonal_mean_xy_model_vs_obs.cfg | 32 ++ 9 files changed, 2129 insertions(+) diff --git a/e3sm_diags/derivations/acme.py b/e3sm_diags/derivations/acme.py index eca698af2..52e602e64 100644 --- a/e3sm_diags/derivations/acme.py +++ b/e3sm_diags/derivations/acme.py @@ -2074,3 +2074,392 @@ def cosp_histogram_standardize(cld: "FileVariable"): "sitemptop": OrderedDict([(("sitemptop",), rename)]), "siv": OrderedDict([(("siv",), rename)]), } + +# Names of 2D aerosol burdens, including cloud-borne aerosols +aero_burden_list = [ + "ABURDENDUST", + "ABURDENSO4", + "ABURDENSO4_STR", + "ABURDENSO4_TRO", + "ABURDENPOM", + "ABURDENMOM", + "ABURDENSOA", + "ABURDENBC", + "ABURDENSEASALT", +] + + +def aero_burden_fxn(var): + """ + Scale the aerosol burden by 1e6. + + Parameters: + var (cdms2.TransientVariable): The input burden in kg/m2. + + Returns: + burden (cdms2.TransientVariable): The output burden in 1e-6 kg/m2. + """ + burden = var * 1e6 + burden.units = "1e-6 kg/m2" + return burden + + +# Add burden vars to derived_variables +for aero_burden_item in aero_burden_list: + derived_variables[aero_burden_item] = OrderedDict( + [((aero_burden_item,), aero_burden_fxn)] + ) + + +# Names of 2D mass slices of aerosol species +# Also add 3D masses while at it (if available) +aero_mass_list = [] +for aero_name in ["dst", "mom", "pom", "so4", "soa", "ncl", "bc"]: + for aero_lev in ["_srf", "_200", "_330", "_500", "_850", ""]: + # Note that the empty string (last entry) will get the 3D mass fields + aero_mass_list.append(f"Mass_{aero_name}{aero_lev}") + + +def aero_mass_fxn(var): + """ + Scale the given mass by 1e12. + + Parameters: + var (cdms2.TransientVariable): The input mass in kg/kg. + + Returns: + cdms2.TransientVariable: The aerosol mass concentration in 1e-12 kg/kg units. + """ + mass = var * 1e12 + mass.units = "1e-12 kg/kg" + return mass + + +# Add burden vars to derived_variables +for aero_mass_item in aero_mass_list: + derived_variables[aero_mass_item] = OrderedDict( + [((aero_mass_item,), aero_mass_fxn)] + ) + +# Add all the output_aerocom_aie.F90 variables to aero_rename_list +# components/eam/src/physics/cam/output_aerocom_aie.F90 +aero_aerocom_list = [ + "angstrm", + "aerindex", + "cdr", + "cdnc", + "cdnum", + "icnum", + "clt", + "lcc", + "lwp", + "iwp", + "icr", + "icc", + "cod", + "ccn", + "ttop", + "htop", + "ptop", + "autoconv", + "accretn", + "icnc", + "rh700", + "rwp", + "intccn", + "colrv", + "lwp2", + "iwp2", + "lwpbf", + "iwpbf", + "cdnumbf", + "icnumbf", + "aod400", + "aod700", + "colccn.1", + "colccn.3", + "ccn.1bl", + "ccn.3bl", +] + +# Add aerocom vars to derived_variables +for aero_aerocom_item in aero_aerocom_list: + derived_variables[aero_aerocom_item] = OrderedDict([((aero_aerocom_item,), rename)]) + + +def incldtop_cdnc(cdnc, lcc): + """ + Return the in-cloud cloud droplet number concentration at cloud top. + + Parameters: + cdnc (cdms2.TransientVariable): Cloud droplet number concentration in 1/m3. + lcc (cdms2.TransientVariable): Liquid cloud fraction. + + Returns: + var (cdms2.TransientVariable): In-cloud cdnc at cloud top in 1/cm3. + """ + var = cdnc * 1e-6 / lcc + var.units = "1/cm3" + var.long_name = "In-cloud-top CDNC" + return var + + +def cldtop_cdnc(cdnc): + """ + Return the in-grid cloud droplet number concentration at cloud top. + + Args: + cdnc (cdms2.TransientVariable): Cloud droplet number concentration in 1/m3. + + Returns: + var (cdms2.TransientVariable): In-grid cdnc at cloud top in 1/cm3. + """ + var = cdnc * 1e-6 + var.units = "1/cm3" + var.long_name = "In-grid cloud-top CDNC" + return var + + +def incldtop_icnc(icnc, icc): + """ + Return the in-cloud ice crystal number concentration at cloud top. + + Parameters: + icnc (cdms2.TransientVariable): ice crystal number concentration in 1/m3. + icc (cdms2.TransientVariable): ice cloud fraction. + + Returns: + var (cdms2.TransientVariable): In-cloud cdnc at cloud top in 1/cm3. + """ + var = icnc * 1e-6 / icc + var.units = "1/cm3" + var.long_name = "In-cloud-top ICNC" + return var + + +def cldtop_icnc(icnc): + """ + Return the in-grid ice crystal number concentration at cloud top. + + Args: + icnc (cdms2.TransientVariable): Cloud crystal number concentration in 1/m3. + + Returns: + var (cdms2.TransientVariable): In-grid icnc at cloud top in 1/cm3. + """ + var = icnc * 1e-6 + var.units = "1/cm3" + var.long_name = "In-grid cloud-top ICNC" + return var + + +def incld_lwp(lwp, lcc): + """ + Return the in-cloud liquid water path (LWP). + + Parameters: + lwp (cdms2.TransientVariable): Liquid water path in kg/m2. + lcc (cdms2.TransientVariable): Liquid cloud fraction. + + Returns: + cdms2.TransientVariable: In-cloud liquid water path in g/cm3. + """ + var = 1e3 * lwp / lcc + var.units = "g/cm3" + var.long_name = "In-cloud LWP" + return var + + +def cld_lwp(lwp): + """ + Return the grid-mean-cloud LWP in g/cm3. + + Parameters: + lwp (cdms2.TransientVariable): Liquid Water Path (LWP) value. + + Returns: + cdms2.TransientVariable: Grid-mean-cloud LWP in g/cm3. + """ + var = 1e3 * lwp + var.units = "g/cm3" + var.long_name = "In-grid LWP" + return var + + +def incld_iwp(iwp, icc): + """ + Return the in-cloud ice water path (IWP). + + Parameters: + iwp (cdms2.TransientVariable): Ice water path in kg/m2. + icc (cdms2.TransientVariable): Ice cloud fraction. + + Returns: + cdms2.TransientVariable: In-cloud IWP in g/cm3. + """ + var = 1e3 * iwp / icc + var.units = "g/cm3" + var.long_name = "In-cloud IWP" + return var + + +def cld_iwp(iwp): + """ + Return the in-grid ice water path (IWP). + + Parameters: + iwp (cdms2.TransientVariable): Ice water path in kg/m2. + + Returns: + cdms2.TransientVariable: In-grid IWP in g/cm3. + """ + var = 1e3 * iwp + var.units = "g/cm3" + var.long_name = "In-grid IWP" + return var + + +# add cdnc, icnc, lwp, iwp to derived_variables +derived_variables.update( + { + "in_cloud_cdnc": OrderedDict([(("cdnc", "lcc"), incldtop_cdnc)]), + "in_grid_cdnc": OrderedDict([(("cdnc",), cldtop_cdnc)]), + "in_cloud_icnc": OrderedDict([(("icnc", "icc"), incldtop_icnc)]), + "in_grid_icnc": OrderedDict([(("icnc",), cldtop_icnc)]), + "in_cloud_lwp": OrderedDict([(("lwp", "lcc"), incld_lwp)]), + "in_grid_lwp": OrderedDict([(("lwp",), cld_lwp)]), + "in_cloud_iwp": OrderedDict([(("iwp", "icc"), incld_iwp)]), + "in_grid_iwp": OrderedDict([(("iwp",), cld_iwp)]), + } +) + + +def erf_tot(fsnt, flnt): + """ + Calculate the total effective radiative forcing (ERFtot). + + Args: + fsnt (cdms2.TransientVariable): The incoming sw radiation at the top of the atmosphere. + flnt (cdms2.TransientVariable): The outgoing lw radiation at the top of the atmosphere. + + Returns: + var (cdms2.TransientVariable): The ERFtot which represents the total erf. + + See Ghan 2013 for derivation of ERF decomposition: https://doi.org/10.5194/acp-13-9971-2013 + """ + var = fsnt - flnt + var.units = "W/m2" + var.long_name = "ERFtot: total effect" + return var + + +def erf_ari(fsnt, flnt, fsnt_d1, flnt_d1): + """ + Calculate aerosol--radiation interactions (ARI) part of effective radiative forcing (ERF). + + Parameters: + fsnt (cdms2.TransientVariable): Net solar flux at the top of the atmosphere. + flnt (cdms2.TransientVariable): Net longwave flux at the top of the atmosphere. + fsnt_d1 (cdms2.TransientVariable): fsnt without aerosols. + flnt_d1 (cdms2.TransientVariable): flnt without aerosols. + + Returns: + var (cdms2.TransientVariable): ERFari (aka, direct effect) in W/m2. + + See Ghan 2013 for derivation of ERF decomposition: https://doi.org/10.5194/acp-13-9971-2013 + """ + var = (fsnt - flnt) - (fsnt_d1 - flnt_d1) + var.units = "W/m2" + var.long_name = "ERFari: direct effect" + return var + + +def erf_aci(fsnt_d1, flnt_d1, fsntc_d1, flntc_d1): + """ + Calculate aerosol--cloud interactions (ACI) part of effectie radiative forcing (ERF) + + Parameters: + fsnt_d1 (cdms2.TransientVariable): Downward shortwave radiation toa without aerosols. + flnt_d1 (cdms2.TransientVariable): Upward longwave radiation toa without aerosols. + fsntc_d1 (cdms2.TransientVariable): fsnt_d1 without clouds. + flntc_d1 (cdms2.TransientVariable): flnt_d1 without clouds. + + Returns: + var (cdms2.TransientVariable): ERFaci (aka, indirect effect) in W/m2. + + See Ghan 2013 for derivation of ERF decomposition: https://doi.org/10.5194/acp-13-9971-2013 + """ + var = (fsnt_d1 - flnt_d1) - (fsntc_d1 - flntc_d1) + var.units = "W/m2" + var.long_name = "ERFaci: indirect effect" + return var + + +def erf_res(fsntc_d1, flntc_d1): + """ + Calculate the residual effect (RES) part of effective radiative forcin g. + + Parameters: + fsntc_d1 (cdms2.TransientVariable): Downward solar radiation at the top of the atmosphere + with neither clouds nor aerosols. + flntc_d1 (cdms2.TransientVariable): Upward longwave radiation at the top of the atmosphere + with neither clouds nor aerosols. + + Returns: + var (cdms2.TransientVariable): ERFres (aka, surface effect) in W/m2. + + See Ghan 2013 for derivation of ERF decomposition: https://doi.org/10.5194/acp-13-9971-2013 + """ + var = fsntc_d1 - flntc_d1 + var.units = "W/m2" + var.long_name = "ERFres: residual effect" + return var + + +derived_variables.update( + { + "ERFtot": OrderedDict([(("FSNT", "FLNT"), erf_tot)]), + "ERFari": OrderedDict([(("FSNT", "FLNT", "FSNT_d1", "FLNT_d1"), erf_ari)]), + "ERFaci": OrderedDict( + [(("FSNT_d1", "FLNT_d1", "FSNTC_d1", "FLNTC_d1"), erf_aci)] + ), + "ERFres": OrderedDict([(("FSNTC_d1", "FLNTC_d1"), erf_res)]), + } +) + +# Add more AOD terms +# Note that AODVIS and AODDUST are already added elsewhere +aero_aod_list = [ + "AODBC", + "AODPOM", + "AODMOM", + "AODSO4", + "AODSO4_STR", + "AODSO4_TRO", + "AODSS", + "AODSOA", +] + +# Add aod vars to derived_variables +for aero_aod_item in aero_aod_list: + derived_variables[aero_aod_item] = OrderedDict([((aero_aod_item,), rename)]) + +# Add 3D variables related to aerosols and chemistry +# Note that O3 is already added above +# Note that 3D mass vars are already added by the empty string above "" +# Note that it is possible to create on-the-fly slices from these variables with +# a function of the form: +# def aero_3d_slice(var, lev): +# return var[lev, :, :] +aero_chem_list = [ + "DMS", + "H2O2", + "H2SO4", + "NO3", + "OH", + "SO2", +] + +# Add aero/chem vars to derived_variables +for aero_chem_item in aero_chem_list: + derived_variables[aero_chem_item] = OrderedDict([((aero_chem_item,), rename)]) diff --git a/e3sm_diags/driver/default_diags/annual_cycle_zonal_mean_model_vs_model.cfg b/e3sm_diags/driver/default_diags/annual_cycle_zonal_mean_model_vs_model.cfg index d0ea991a7..d79624e6a 100644 --- a/e3sm_diags/driver/default_diags/annual_cycle_zonal_mean_model_vs_model.cfg +++ b/e3sm_diags/driver/default_diags/annual_cycle_zonal_mean_model_vs_model.cfg @@ -253,6 +253,16 @@ diff_colormap = "BrBG_r" contour_levels = [0., 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2] diff_levels = [-0.5, -0.4, -0.3, -0.2, -0.1, -0.05, -0.02, 0.02, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5] +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["AODDUST", "AODBC", "AODSS", "AODPOM", "AODMOM", "AODSOA", "AODSO4", "AODSO4_STR", "AODSO4_TRO"] +test_colormap = "Oranges" +reference_colormap = "Oranges" +diff_colormap = "BrBG_r" +contour_levels = [0, 0.01, 0.02, 0.03, 0.06, 0.09, 0.12, 0.15, 0.18, 0.21, 0.24, 0.27, 0.30] +diff_levels = [-0.10, -0.08, -0.06, -0.04, -0.02, -0.01, -0.005, -0.0025, 0.0025, 0.005, 0.01, 0.02, 0.04, 0.06, 0.08, 0.10] + [#] sets = ["annual_cycle_zonal_mean"] case_id = "model_vs_model" @@ -290,3 +300,180 @@ reference_colormap = "WhiteBlueGreenYellowRed.rgb" diff_colormap = "diverging_bwr.rgb" contour_levels = [12,16,20,24,28,32,36,40,44] diff_levels = [-20,-15,-10,-5,-2,2,5,10,15,20] + + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["ERFari"] +contour_levels = [-5, -4, -3, -2, -1, -0.5, -0.25, 0.25, 0.5, 1, 2, 3, 4, 5] +diff_levels = [-2.5, -2, -1.5, -1, -0.5, -0.25, -0.10, 0.10, 0.25, 0.5, 1, 1.5, 2, 2.5] + + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["ERFaci", "ERFres", "ERFtot"] +contour_levels = [-120, -100, -80, -60, -40, -20, -10, -5, -2.5, 2.5, 5, 10, 20, 40, 60, 80, 100, 120] +diff_levels = [-10, -8, -6, -4, -2, -1, -0.5, -0.25, 0.25, 0.5, 1, 2, 4, 6, 8, 10] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_bc_srf"] +contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550] +diff_levels = [-600, -400, -200, -100, -50, -25, -10, -5, 5, 10, 25, 50, 100, 200, 400, 600] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_ncl_srf"] +contour_levels = [0, 50, 100, 200, 500, 1000, 5000, 10000, 20000] +diff_levels = [-1500, -1000, -500, -250, -100, -50, -25, -10, 10, 25, 50, 100, 250, 500, 1000, 1500] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_so4_srf"] +contour_levels = [0, 50, 100, 200, 400, 600, 800, 1000, 1500, 2000, 2500] +diff_levels = [-2000, -1500, -1000, -500, -250, -100, -50, -25, -10, 10, 25, 50, 100, 250, 500, 1000, 1500, 2000] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_dst_srf"] +contour_levels = [0, 1000, 2000, 5000, 10000, 20000, 40000, 60000, 80000, 100000] +diff_levels = [-15000, -10000, -5000, -2500, -1000, -500, -250, -100, -50, -25, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 15000] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_pom_srf"] +contour_levels = [0, 50, 100, 200, 400, 600, 800, 1000, 1500, 2000, 2500] +diff_levels = [-2000, -1500, -1000, -500, -250, -100, -50, -25, -10, 10, 25, 50, 100, 250, 500, 1000, 1500, 2000] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_soa_srf"] +contour_levels = [0, 50, 100, 200, 400, 600, 800, 1000, 1500, 2000, 2500, 3000, 4000] +diff_levels = [-3000, -2000, -1500, -1000, -500, -250, -100, -50, -25, -10, 10, 25, 50, 100, 250, 500, 1000, 1500, 2000, 3000] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_mom_srf"] +contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 650, 750] +diff_levels = [-100, -50, -25, -10, -5, 5, 10, 25, 50, 100] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_bc_850"] +contour_levels = [0, 5, 10, 20, 40, 60, 80, 100, 120, 140, 160, 180, 200] +diff_levels = [-150, -100, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 100, 150] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_ncl_850"] +contour_levels = [0, 50, 100, 200, 500, 1000, 2000, 3000, 4000, 5000, 7000] +diff_levels = [-500, -400, -300, -200, -100, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_so4_850"] +contour_levels = [0, 10, 20, 50, 100, 150, 200, 250, 500, 750, 1000, 1500, 2000] +diff_levels = [-1500, -1000, -500, -400, -300, -200, -100, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500, 1000, 1500] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_dst_850"] +contour_levels = [0, 1000, 2000, 5000, 10000, 20000, 30000, 40000, 50000, 70000, 90000, 120000] +diff_levels = [-10000, -8000, -6000, -4000, -2000, -1000, -500, -300, -200, -100, -50, -20, -10, 10, 20,50, 100, 200, 300, 500, 1000, 2000, 4000, 6000, 8000, 10000] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_pom_850"] +contour_levels = [0, 10, 20, 50, 100, 150, 200, 250, 500, 750, 1000, 1500, 2000] +diff_levels = [-900, -600, -400, -200, -100, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 600, 900] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_soa_850"] +contour_levels = [0, 50, 100, 200, 500, 1000, 2000, 3000, 4000, 5000, 7000] +diff_levels = [-3000, -1500, -500, -400, -300, -200, -100, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500, 1500, 3000] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_mom_850"] +contour_levels = [0, 1, 2, 5, 10, 20, 30, 40, 50, 100, 150, 200, 300] +diff_levels = [-30, -20, -10, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 10, 20, 30] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_bc_330"] +contour_levels = [0, 1, 2, 5, 10, 20, 30, 40, 50, 60] +diff_levels = [-50, -40, -30, -20, -10, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 10, 20, 30, 40, 50] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_ncl_330"] +contour_levels = [0, 1, 2, 5, 10, 20, 30, 40, 50, 100, 200, 300, 500] +diff_levels = [-50, -40, -30, -20, -10, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 10, 20, 30, 40, 50] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_so4_330"] +contour_levels = [0, 10, 20, 50, 100, 200, 300, 400, 500, 700, 1000] +diff_levels = [-700, -500, -400, -300, -200, -100, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500, 700] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_dst_330"] +contour_levels = [0, 100, 200, 500, 1000, 2000, 3000, 4000, 5000, 7000, 10000, 13000] +diff_levels = [-1000, -700, -500, -400, -300, -200, -100, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500, 700, 1000] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_pom_330"] +contour_levels = [0, 1, 2, 5, 10, 20, 30, 40, 50, 100, 150, 200, 300] +diff_levels = [-200, -150, -100, -50, -40, -30, -20, -10, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100, 150, 200] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_soa_330"] +contour_levels = [0, 10, 20, 50, 100, 200, 300, 400, 500, 700, 1000, 1200] +diff_levels = [-700, -500, -400, -300, -200, -100, -50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500, 700] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["Mass_mom_330"] +contour_levels = [0, 0.1, 0.2, 0.5, 1, 2, 3, 4, 5, 7, 10] +diff_levels = [-1, -0.7, -0.5, -0.4, -0.3, -0.2, -0.1, -0.05, -0.02, -0.01, -0.005, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.7, 1] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["in_cloud_cdnc", "in_cloud_lwp"] +contour_levels = [0, 20, 40, 60, 80, 100, 125, 150, 175, 200, 250] +diff_levels = [-50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "model_vs_model" +variables = ["in_grid_cdnc", "in_grid_lwp"] +contour_levels = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100] +diff_levels = [-50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50] diff --git a/e3sm_diags/driver/default_diags/annual_cycle_zonal_mean_model_vs_obs.cfg b/e3sm_diags/driver/default_diags/annual_cycle_zonal_mean_model_vs_obs.cfg index 110e35513..ea44f5272 100644 --- a/e3sm_diags/driver/default_diags/annual_cycle_zonal_mean_model_vs_obs.cfg +++ b/e3sm_diags/driver/default_diags/annual_cycle_zonal_mean_model_vs_obs.cfg @@ -394,6 +394,15 @@ diff_colormap = "BrBG_r" contour_levels = [0., 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2] diff_levels = [-0.5, -0.4, -0.3, -0.2, -0.1, -0.05, -0.02, 0.02, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5] +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["AODDUST", "AODBC", "AODSS", "AODPOM", "AODMOM", "AODSOA", "AODSO4", "AODSO4_STR", "AODSO4_TRO"] +test_colormap = "Oranges" +reference_colormap = "Oranges" +diff_colormap = "BrBG_r" +contour_levels = [0, 0.01, 0.02, 0.03, 0.06, 0.09, 0.12, 0.15, 0.18, 0.21, 0.24, 0.27, 0.30] +diff_levels = [-0.10, -0.08, -0.06, -0.04, -0.02, -0.01, -0.005, -0.0025, 0.0025, 0.005, 0.01, 0.02, 0.04, 0.06, 0.08, 0.10] [#] sets = ["annual_cycle_zonal_mean"] @@ -461,3 +470,164 @@ reference_colormap = "WhiteBlueGreenYellowRed.rgb" diff_colormap = "diverging_bwr.rgb" contour_levels = [12,16,20,24,28,32,36,40,44] diff_levels = [-20,-15,-10,-5,-2,2,5,10,15,20] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_bc_srf"] +contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550] +diff_levels = [-600, -400, -200, -100, -50, -25, -10, -5, 5, 10, 25, 50, 100, 200, 400, 600] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_ncl_srf"] +contour_levels = [0, 50, 100, 200, 500, 1000, 5000, 10000, 20000] +diff_levels = [-1500, -1000, -500, -250, -100, -50, -25, -10, 10, 25, 50, 100, 250, 500, 1000, 1500] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_so4_srf"] +contour_levels = [0, 50, 100, 200, 400, 600, 800, 1000, 1500, 2000, 2500] +diff_levels = [-2000, -1500, -1000, -500, -250, -100, -50, -25, -10, 10, 25, 50, 100, 250, 500, 1000, 1500, 2000] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_dst_srf"] +contour_levels = [0, 1000, 2000, 5000, 10000, 20000, 40000, 60000, 80000, 100000] +diff_levels = [-15000, -10000, -5000, -2500, -1000, -500, -250, -100, -50, -25, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 15000] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_pom_srf"] +contour_levels = [0, 50, 100, 200, 400, 600, 800, 1000, 1500, 2000, 2500] +diff_levels = [-2000, -1500, -1000, -500, -250, -100, -50, -25, -10, 10, 25, 50, 100, 250, 500, 1000, 1500, 2000] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_soa_srf"] +contour_levels = [0, 50, 100, 200, 400, 600, 800, 1000, 1500, 2000, 2500, 3000, 4000] +diff_levels = [-3000, -2000, -1500, -1000, -500, -250, -100, -50, -25, -10, 10, 25, 50, 100, 250, 500, 1000, 1500, 2000, 3000] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_mom_srf"] +contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 650, 750] +diff_levels = [-100, -50, -25, -10, -5, 5, 10, 25, 50, 100] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_bc_850"] +contour_levels = [0, 5, 10, 20, 40, 60, 80, 100, 120, 140, 160, 180, 200] +diff_levels = [-150, -100, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 100, 150] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_ncl_850"] +contour_levels = [0, 50, 100, 200, 500, 1000, 2000, 3000, 4000, 5000, 7000] +diff_levels = [-500, -400, -300, -200, -100, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_so4_850"] +contour_levels = [0, 10, 20, 50, 100, 150, 200, 250, 500, 750, 1000, 1500, 2000] +diff_levels = [-1500, -1000, -500, -400, -300, -200, -100, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500, 1000, 1500] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_dst_850"] +contour_levels = [0, 1000, 2000, 5000, 10000, 20000, 30000, 40000, 50000, 70000, 90000, 120000] +diff_levels = [-10000, -8000, -6000, -4000, -2000, -1000, -500, -300, -200, -100, -50, -20, -10, 10, 20,50, 100, 200, 300, 500, 1000, 2000, 4000, 6000, 8000, 10000] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_pom_850"] +contour_levels = [0, 10, 20, 50, 100, 150, 200, 250, 500, 750, 1000, 1500, 2000] +diff_levels = [-900, -600, -400, -200, -100, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 600, 900] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_soa_850"] +contour_levels = [0, 50, 100, 200, 500, 1000, 2000, 3000, 4000, 5000, 7000] +diff_levels = [-3000, -1500, -500, -400, -300, -200, -100, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500, 1500, 3000] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_mom_850"] +contour_levels = [0, 1, 2, 5, 10, 20, 30, 40, 50, 100, 150, 200, 300] +diff_levels = [-30, -20, -10, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 10, 20, 30] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_bc_330"] +contour_levels = [0, 1, 2, 5, 10, 20, 30, 40, 50, 60] +diff_levels = [-50, -40, -30, -20, -10, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 10, 20, 30, 40, 50] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_ncl_330"] +contour_levels = [0, 1, 2, 5, 10, 20, 30, 40, 50, 100, 200, 300, 500] +diff_levels = [-50, -40, -30, -20, -10, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 10, 20, 30, 40, 50] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_so4_330"] +contour_levels = [0, 10, 20, 50, 100, 200, 300, 400, 500, 700, 1000] +diff_levels = [-700, -500, -400, -300, -200, -100, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500, 700] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_dst_330"] +contour_levels = [0, 100, 200, 500, 1000, 2000, 3000, 4000, 5000, 7000, 10000, 13000] +diff_levels = [-1000, -700, -500, -400, -300, -200, -100, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500, 700, 1000] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_pom_330"] +contour_levels = [0, 1, 2, 5, 10, 20, 30, 40, 50, 100, 150, 200, 300] +diff_levels = [-200, -150, -100, -50, -40, -30, -20, -10, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100, 150, 200] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_soa_330"] +contour_levels = [0, 10, 20, 50, 100, 200, 300, 400, 500, 700, 1000, 1200] +diff_levels = [-700, -500, -400, -300, -200, -100, -50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500, 700] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["Mass_mom_330"] +contour_levels = [0, 0.1, 0.2, 0.5, 1, 2, 3, 4, 5, 7, 10] +diff_levels = [-1, -0.7, -0.5, -0.4, -0.3, -0.2, -0.1, -0.05, -0.02, -0.01, -0.005, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.7, 1] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["in_cloud_cdnc", "in_cloud_lwp"] +contour_levels = [0, 20, 40, 60, 80, 100, 125, 150, 175, 200, 250] +diff_levels = [-50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50] + +[#] +sets = ["annual_cycle_zonal_mean"] +case_id = "aero-no-ref-data" +variables = ["in_grid_cdnc", "in_grid_lwp"] +contour_levels = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100] +diff_levels = [-50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50] diff --git a/e3sm_diags/driver/default_diags/lat_lon_model_vs_model.cfg b/e3sm_diags/driver/default_diags/lat_lon_model_vs_model.cfg index d0ecd47a8..d0e883b04 100644 --- a/e3sm_diags/driver/default_diags/lat_lon_model_vs_model.cfg +++ b/e3sm_diags/driver/default_diags/lat_lon_model_vs_model.cfg @@ -734,3 +734,297 @@ reference_colormap = "WhiteBlueGreenYellowRed.rgb" diff_colormap = "diverging_bwr.rgb" contour_levels = [12,16,20,24,28,32,36,40,44] diff_levels = [-20,-15,-10,-5,-2,2,5,10,15,20] + +[#] +sets = ["lat_lon"] +case_id = "model_vs_model" +variables = ['ABURDENDUST'] +seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +test_colormap = "WhiteBlueGreenYellowRed.rgb" +reference_colormap = "WhiteBlueGreenYellowRed.rgb" +diff_colormap = "diverging_bwr.rgb" +contour_levels = [0, 10, 50, 100, 200, 500, 1000] +diff_levels = [-1000, -500, -200, -100, -50, -10, 10, 50, 100, 200, 500, 1000] + +[#] +sets = ["lat_lon"] +case_id = "model_vs_model" +variables = ['ABURDENBC', 'ABURDENSOA'] +seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +test_colormap = "WhiteBlueGreenYellowRed.rgb" +reference_colormap = "WhiteBlueGreenYellowRed.rgb" +diff_colormap = "diverging_bwr.rgb" +contour_levels = [0, 1, 2, 4, 8] +diff_levels = [-8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8] + +[#] +sets = ["lat_lon"] +case_id = "model_vs_model" +variables = ['ABURDENPOM', 'ABURDENSEASALT'] +seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +test_colormap = "WhiteBlueGreenYellowRed.rgb" +reference_colormap = "WhiteBlueGreenYellowRed.rgb" +diff_colormap = "diverging_bwr.rgb" +contour_levels = [0, 1, 2, 4, 8, 16, 32] +diff_levels = [-8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8] + +[#] +sets = ["lat_lon"] +case_id = "model_vs_model" +variables = ['ABURDENSO4', 'ABURDENSO4_STR', 'ABURDENSO4_TRO'] +seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +test_colormap = "WhiteBlueGreenYellowRed.rgb" +reference_colormap = "WhiteBlueGreenYellowRed.rgb" +diff_colormap = "diverging_bwr.rgb" +contour_levels = [0, 1, 2, 4, 8, 16, 32] +diff_levels = [-32, -16, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 16, 32] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["ERFari"] +contour_levels = [-10, -8, -6, -4, -2, -1, 1, 2, 4, 6, 8, 10] +diff_levels = [-3, -2.5, -2, -1.5, -1, -0.5, 0.5, 1, 1.5, 2, 2.5, 3] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["ERFaci", "ERFres", "ERFtot"] +contour_levels = [-100, -80, -60, -40, -20, -10, 10, 20, 40, 60, 80, 100] +diff_levels = [-3, -2.5, -2, -1.5, -1, -0.5, 0.5, 1, 1.5, 2, 2.5, 3] + + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_bc_srf"] +contour_levels = [0, 100, 500, 1000, 2000, 3000, 4000, 5000, 6000] +diff_levels = [-5000, -4000, -3000, -2000, -1000, -500, -100, -50, -25, -10, 10, 25, 50, 100, 500, 1000, 2000, 3000, 4000, 5000] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_ncl_srf"] +contour_levels = [0, 1000, 2000, 5000, 10000, 15000, 20000, 25000] +diff_levels = [-1200, -1000, -500, -250, -100, -50, -25, -10, 10, 25, 50, 100, 250, 500, 1000, 1200] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_so4_srf"] +contour_levels = [0, 100, 200, 500, 1000, 2000, 3000, 4000, 5000, 7000] +diff_levels = [-7000, -5000, -4000, -2000, -1000, -500, -100, -50, 50, 100, 500, 1000, 2000, 4000, 5000, 7000] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_dst_srf"] +contour_levels = [0, 1000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000] +diff_levels = [-40000, -20000, -10000, -5000, -1000, -500, -100, -50, -25, -10, 10, 25, 50, 100, 500, 1000, 5000, 10000, 20000, 40000] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_pom_srf"] +contour_levels = [0, 1000, 2000, 5000, 10000, 15000, 20000, 25000] +diff_levels = [-15000, -10000, -5000, -1000, -500, -100, -50, -25, -10, 10, 25, 50, 100, 500, 1000, 5000, 10000, 15000] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_soa_srf"] +contour_levels = [0, 1000, 2000, 5000, 10000, 15000, 20000, 25000, 30000] +diff_levels = [-30000, -25000, -20000, -15000, -10000, -5000, -1000, -500, -100, -50, -25, -10, 10, 25, 50, 100, 500, 1000, 5000, 10000, 15000, 20000, 25000, 30000] + +[srfmom_mass_diags_lat_lon] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_mom_srf"] +contour_levels = [0, 100, 200, 300, 400, 500, 600, 700, 800] +diff_levels = [-50, -25, -10, -5, -2, -1, 1, 2, 5, 10, 25, 50] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_bc_850"] +contour_levels = [0, 500, 1000, 2000, 4000, 6000, 8000, 10000, 12000, 14000] +diff_levels = [-1200, -1000, -800, -600, -400, -200, -100, -50, -25, -10, 10, 25, 50, 100, 200, 400, 600, 800, 1000, 1200] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_ncl_850"] +contour_levels = [0, 100, 500, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000] +diff_levels = [-600, -500, -400, -300, -200, -100, -50, -25, -10, 10, 25, 50, 100, 200, 300, 400, 500, 600] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_so4_850"] +contour_levels = [0, 100, 200, 500, 1000, 2000, 3000, 5000] +diff_levels = [-5000, -3000, -1000, -500, -300, -100, -50, 50, 100, 300, 500, 1000, 3000, 5000] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_dst_850"] +contour_levels = [0, 10000, 50000, 100000, 200000, 300000, 400000, 500000] +diff_levels = [-23000, -20000, -15000, -10000, -5000, -4000, -3000, -2000, -1000, -500, -400, -300, -200, -100, -50, -25, -10, 10, 25, 50, 100, 200, 300, 400, 500, 1000, 2000, 3000, 4000, 5000, 10000, 20000, 23000] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_pom_850"] +contour_levels = [0, 100, 500, 1000, 2000, 3000, 4000, 5000, 6000, 7000] +diff_levels = [-3500, -3000, -2500, -2000, -1500, -1000, -500, -400, -300, -200, -100, -50, -25, -10, 10, 25, 50, 100, 200, 300, 400, 500, 1000, 1500, 2000, 2500, 3000, 3500] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_soa_850"] +contour_levels = [0, 1000, 5000, 10000, 15000, 20000, 25000, 30000] +diff_levels = [-15000, -10000, -5000, -4000, -3000, -2000, -1000, -500, -400, -300, -200, -100, -50, -25, -10, 10, 25, 50, 100, 200, 300, 400, 500, 1000, 4000, 5000, 10000, 15000] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_mom_850"] +contour_levels = [0, 5, 10, 20, 50, 100, 150, 200] +diff_levels = [-15, -10, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 10, 15] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_bc_330"] +contour_levels = [0, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100] +diff_levels = [-100, -50, -40, -30, -20, -10, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_ncl_330"] +contour_levels = [0, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 350] +diff_levels = [-30, -20, -10, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 10, 20, 30] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_so4_330"] +contour_levels = [0, 10, 50, 100, 200, 300, 400, 500, 600, 700] +diff_levels = [-600, -500, -400, -300, -200, -100, -50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500, 600] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_dst_330"] +contour_levels = [0, 500, 1000, 2000, 5000, 10000, 15000] +diff_levels = [-2000, -1000, -500, -400, -300, -200, -100, -50, -40, -30, -20, -10, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100, 500, 1000, 2000] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_pom_330"] +contour_levels = [0, 5, 10, 20, 30, 40, 50, 60, 70, 300] +diff_levels = [-250, -200, -150, -100, -50, -40, -30, -20, -10, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 10, 20, 30, 40, 50] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_soa_330"] +contour_levels = [0, 5, 10, 20, 30, 40, 50, 60, 70, 2500] +diff_levels = [-1500, -1000, -500, -400, -300, -200, -100, -50, -40, -30, -20, -10, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100, 500, 1000, 1500] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_mom_330"] +contour_levels = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 10] +diff_levels = [-0.5, -0.4, -0.3, -0.2, -0.1, -0.05, -0.04, -0.03, -0.02, -0.01, -0.005, 0.005, 0.01, 0.02, 0.03, 0.04, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +variables = ["AODBC"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +contour_levels = [0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09] +diff_levels = [-0.08, -0.06, -0.04, -0.02, -0.01, -0.005, 0.005, 0.01, 0.02, 0.04, 0.06, 0.08] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +variables = ["AODSS"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +contour_levels = [0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.10, 0.11] +diff_levels = [-0.01, -0.008, -0.006, -0.004, -0.002, -0.001, 0.001, 0.002, 0.004, 0.006, 0.008, 0.01] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +variables = ["AODSOA"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +contour_levels = [0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.50, 0.55] +diff_levels = [-0.45, -0.35, -0.25, -0.15, -0.05, -0.025, 0.025, 0.05, 0.15, 0.25, 0.35, 0.45] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +variables = ["AODSO4", "AODSO4_STR", "AODSO4_TRO"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +contour_levels = [0, 0.02, 0.04, 0.06, 0.08, 0.10, 0.12, 0.14, 0.16] +diff_levels = [-0.15, -0.12, -0.09, -0.06, -0.03, -0.015, -0.005, 0.005, 0.015, 0.03, 0.06, 0.09, 0.12, 0.15] + + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +variables = ["AODPOM"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +contour_levels = [0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09] +diff_levels = [-0.06, -0.04, -0.02, -0.01, -0.005, -0.0025, 0.0025, 0.005, 0.01, 0.02, 0.04, 0.06] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +variables = ["AODMOM"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +contour_levels = [0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09] +diff_levels = [-0.06, -0.04, -0.02, -0.01, -0.005, -0.0025, 0.0025, 0.005, 0.01, 0.02, 0.04, 0.06] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +variables = ["in_cloud_cdnc", "in_cloud_lwp"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +contour_levels = [0, 20, 40, 60, 80, 100, 125, 150, 175, 200, 250] +diff_levels = [-50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50] + +[#] +sets = ['lat_lon'] +case_id = "model_vs_model" +variables = ["in_grid_cdnc", "in_grid_lwp"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +contour_levels = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100] +diff_levels = [-50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50] diff --git a/e3sm_diags/driver/default_diags/lat_lon_model_vs_obs.cfg b/e3sm_diags/driver/default_diags/lat_lon_model_vs_obs.cfg index d9e1c40e3..f9d303075 100644 --- a/e3sm_diags/driver/default_diags/lat_lon_model_vs_obs.cfg +++ b/e3sm_diags/driver/default_diags/lat_lon_model_vs_obs.cfg @@ -1453,3 +1453,280 @@ reference_colormap = "WhiteBlueGreenYellowRed.rgb" diff_colormap = "diverging_bwr.rgb" contour_levels = [12,16,20,24,28,32,36,40,44] diff_levels = [-20,-15,-10,-5,-2,2,5,10,15,20] + +[#] +sets = ["lat_lon"] +case_id = "aero-no-ref-data" +variables = ['ABURDENDUST'] +seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +test_colormap = "WhiteBlueGreenYellowRed.rgb" +reference_colormap = "WhiteBlueGreenYellowRed.rgb" +diff_colormap = "diverging_bwr.rgb" +contour_levels = [0, 10, 50, 100, 200, 500, 1000] +diff_levels = [-1000, -500, -200, -100, -50, -10, 10, 50, 100, 200, 500, 1000] + +[#] +sets = ["lat_lon"] +case_id = "aero-no-ref-data" +variables = ['ABURDENBC', 'ABURDENSOA'] +seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +test_colormap = "WhiteBlueGreenYellowRed.rgb" +reference_colormap = "WhiteBlueGreenYellowRed.rgb" +diff_colormap = "diverging_bwr.rgb" +contour_levels = [0, 1, 2, 4, 8] +diff_levels = [-8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8] + +[#] +sets = ["lat_lon"] +case_id = "aero-no-ref-data" +variables = ['ABURDENPOM', 'ABURDENSEASALT'] +seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +test_colormap = "WhiteBlueGreenYellowRed.rgb" +reference_colormap = "WhiteBlueGreenYellowRed.rgb" +diff_colormap = "diverging_bwr.rgb" +contour_levels = [0, 1, 2, 4, 8, 16, 32] +diff_levels = [-8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8] + +[#] +sets = ["lat_lon"] +case_id = "aero-no-ref-data" +variables = ['ABURDENSO4', 'ABURDENSO4_STR', 'ABURDENSO4_TRO'] +seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +test_colormap = "WhiteBlueGreenYellowRed.rgb" +reference_colormap = "WhiteBlueGreenYellowRed.rgb" +diff_colormap = "diverging_bwr.rgb" +contour_levels = [0, 1, 2, 4, 8, 16, 32] +diff_levels = [-32, -16, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 16, 32] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_bc_srf"] +contour_levels = [0, 100, 500, 1000, 2000, 3000, 4000, 5000, 6000] +diff_levels = [-5000, -4000, -3000, -2000, -1000, -500, -100, -50, -25, -10, 10, 25, 50, 100, 500, 1000, 2000, 3000, 4000, 5000] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_ncl_srf"] +contour_levels = [0, 1000, 2000, 5000, 10000, 15000, 20000, 25000] +diff_levels = [-1200, -1000, -500, -250, -100, -50, -25, -10, 10, 25, 50, 100, 250, 500, 1000, 1200] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_so4_srf"] +contour_levels = [0, 100, 200, 500, 1000, 2000, 3000, 4000, 5000, 7000] +diff_levels = [-7000, -5000, -4000, -2000, -1000, -500, -100, -50, 50, 100, 500, 1000, 2000, 4000, 5000, 7000] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_dst_srf"] +contour_levels = [0, 1000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000] +diff_levels = [-40000, -20000, -10000, -5000, -1000, -500, -100, -50, -25, -10, 10, 25, 50, 100, 500, 1000, 5000, 10000, 20000, 40000] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_pom_srf"] +contour_levels = [0, 1000, 2000, 5000, 10000, 15000, 20000, 25000] +diff_levels = [-15000, -10000, -5000, -1000, -500, -100, -50, -25, -10, 10, 25, 50, 100, 500, 1000, 5000, 10000, 15000] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_soa_srf"] +contour_levels = [0, 1000, 2000, 5000, 10000, 15000, 20000, 25000, 30000] +diff_levels = [-30000, -25000, -20000, -15000, -10000, -5000, -1000, -500, -100, -50, -25, -10, 10, 25, 50, 100, 500, 1000, 5000, 10000, 15000, 20000, 25000, 30000] + +[srfmom_mass_diags_lat_lon] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_mom_srf"] +contour_levels = [0, 100, 200, 300, 400, 500, 600, 700, 800] +diff_levels = [-50, -25, -10, -5, -2, -1, 1, 2, 5, 10, 25, 50] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_bc_850"] +contour_levels = [0, 500, 1000, 2000, 4000, 6000, 8000, 10000, 12000, 14000] +diff_levels = [-1200, -1000, -800, -600, -400, -200, -100, -50, -25, -10, 10, 25, 50, 100, 200, 400, 600, 800, 1000, 1200] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_ncl_850"] +contour_levels = [0, 100, 500, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000] +diff_levels = [-600, -500, -400, -300, -200, -100, -50, -25, -10, 10, 25, 50, 100, 200, 300, 400, 500, 600] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_so4_850"] +contour_levels = [0, 100, 200, 500, 1000, 2000, 3000, 5000] +diff_levels = [-5000, -3000, -1000, -500, -300, -100, -50, 50, 100, 300, 500, 1000, 3000, 5000] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_dst_850"] +contour_levels = [0, 10000, 50000, 100000, 200000, 300000, 400000, 500000] +diff_levels = [-23000, -20000, -15000, -10000, -5000, -4000, -3000, -2000, -1000, -500, -400, -300, -200, -100, -50, -25, -10, 10, 25, 50, 100, 200, 300, 400, 500, 1000, 2000, 3000, 4000, 5000, 10000, 20000, 23000] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_pom_850"] +contour_levels = [0, 100, 500, 1000, 2000, 3000, 4000, 5000, 6000, 7000] +diff_levels = [-3500, -3000, -2500, -2000, -1500, -1000, -500, -400, -300, -200, -100, -50, -25, -10, 10, 25, 50, 100, 200, 300, 400, 500, 1000, 1500, 2000, 2500, 3000, 3500] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_soa_850"] +contour_levels = [0, 1000, 5000, 10000, 15000, 20000, 25000, 30000] +diff_levels = [-15000, -10000, -5000, -4000, -3000, -2000, -1000, -500, -400, -300, -200, -100, -50, -25, -10, 10, 25, 50, 100, 200, 300, 400, 500, 1000, 4000, 5000, 10000, 15000] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_mom_850"] +contour_levels = [0, 5, 10, 20, 50, 100, 150, 200] +diff_levels = [-15, -10, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 10, 15] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_bc_330"] +contour_levels = [0, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100] +diff_levels = [-100, -50, -40, -30, -20, -10, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_ncl_330"] +contour_levels = [0, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 350] +diff_levels = [-30, -20, -10, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 10, 20, 30] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_so4_330"] +contour_levels = [0, 10, 50, 100, 200, 300, 400, 500, 600, 700] +diff_levels = [-600, -500, -400, -300, -200, -100, -50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500, 600] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_dst_330"] +contour_levels = [0, 500, 1000, 2000, 5000, 10000, 15000] +diff_levels = [-2000, -1000, -500, -400, -300, -200, -100, -50, -40, -30, -20, -10, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100, 500, 1000, 2000] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_pom_330"] +contour_levels = [0, 5, 10, 20, 30, 40, 50, 60, 70, 300] +diff_levels = [-250, -200, -150, -100, -50, -40, -30, -20, -10, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 10, 20, 30, 40, 50] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_soa_330"] +contour_levels = [0, 5, 10, 20, 30, 40, 50, 60, 70, 2500] +diff_levels = [-1500, -1000, -500, -400, -300, -200, -100, -50, -40, -30, -20, -10, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100, 500, 1000, 1500] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +variables = ["Mass_mom_330"] +contour_levels = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 10] +diff_levels = [-0.5, -0.4, -0.3, -0.2, -0.1, -0.05, -0.04, -0.03, -0.02, -0.01, -0.005, 0.005, 0.01, 0.02, 0.03, 0.04, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +variables = ["AODBC"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +contour_levels = [0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09] +diff_levels = [-0.08, -0.06, -0.04, -0.02, -0.01, -0.005, 0.005, 0.01, 0.02, 0.04, 0.06, 0.08] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +variables = ["AODSS"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +contour_levels = [0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.10, 0.11] +diff_levels = [-0.01, -0.008, -0.006, -0.004, -0.002, -0.001, 0.001, 0.002, 0.004, 0.006, 0.008, 0.01] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +variables = ["AODSOA"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +contour_levels = [0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.50, 0.55] +diff_levels = [-0.45, -0.35, -0.25, -0.15, -0.05, -0.025, 0.025, 0.05, 0.15, 0.25, 0.35, 0.45] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +variables = ["AODSO4", "AODSO4_STR", "AODSO4_TRO"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +contour_levels = [0, 0.02, 0.04, 0.06, 0.08, 0.10, 0.12, 0.14, 0.16] +diff_levels = [-0.15, -0.12, -0.09, -0.06, -0.03, -0.015, -0.005, 0.005, 0.015, 0.03, 0.06, 0.09, 0.12, 0.15] + + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +variables = ["AODPOM"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +contour_levels = [0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09] +diff_levels = [-0.06, -0.04, -0.02, -0.01, -0.005, -0.0025, 0.0025, 0.005, 0.01, 0.02, 0.04, 0.06] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +variables = ["AODMOM"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +contour_levels = [0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09] +diff_levels = [-0.06, -0.04, -0.02, -0.01, -0.005, -0.0025, 0.0025, 0.005, 0.01, 0.02, 0.04, 0.06] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +variables = ["in_cloud_cdnc", "in_cloud_lwp"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +contour_levels = [0, 20, 40, 60, 80, 100, 125, 150, 175, 200, 250] +diff_levels = [-50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50] + +[#] +sets = ['lat_lon'] +case_id = "aero-no-ref-data" +variables = ["in_grid_cdnc", "in_grid_lwp"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +contour_levels = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100] +diff_levels = [-50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50] diff --git a/e3sm_diags/driver/default_diags/polar_model_vs_model.cfg b/e3sm_diags/driver/default_diags/polar_model_vs_model.cfg index 730ec93c2..72a2b1ac9 100644 --- a/e3sm_diags/driver/default_diags/polar_model_vs_model.cfg +++ b/e3sm_diags/driver/default_diags/polar_model_vs_model.cfg @@ -394,3 +394,384 @@ regions = ["polar_S", "polar_N"] plevs = [200.0] contour_levels = [200, 205, 210, 215, 220, 225, 230, 235, 240] diff_levels = [-10, -7.5, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 7.5, 10] + + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["ERFari"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S", "polar_N"] +contour_levels = [-2, -1.5, -1, -0.75, -0.5, -0.25, -0.10, 0.10, 0.25, 0.5, 0.75, 1, 1.5, 2.0] +diff_levels = [-1.5, -1, -0.75, -0.5, -0.25, -0.10, -0.05, 0.05, 0.10, 0.25, 0.5, 0.75, 1, 1.5] + + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["ERFaci", "ERFres", "ERFtot"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S", "polar_N"] +contour_levels = [-60, -50, -40, -30, -20, -10, -5, -2.5, 2.5, 5, 10, 20, 30, 40, 50, 60] +diff_levels = [-3, -2.5, -2, -1.5, -1, -0.5, -0.25, 0.25, 0.5, 1, 1.5, 2, 2.5, 3] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_bc_srf"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 50, 100, 200, 300, 400, 500, 600, 700] +diff_levels = [-700, -600, -500, -400, -300, -200, -100, -50, -25, -10, 10, 25, 50, 100, 200, 300, 400, 500, 600, 700] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_bc_srf"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 0.5, 1, 2, 3, 4, 5] +diff_levels = [-5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_ncl_srf"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N", "polar_S"] +contour_levels = [0, 100, 500, 1000, 2000, 5000, 10000, 15000, 20000] +diff_levels = [-800, -600, -500, -400, -300, -200, -100, -50, -25, -10, 10, 25, 50, 100, 200, 300, 400, 500, 600, 800] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_so4_srf"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 50, 100, 200, 400, 600, 800, 1000, 1200, 1400, 1600] +diff_levels = [-1200, -1000, -800, -600, -400, -200, -100, -50, -25, -10, 10, 25, 50, 100, 200, 400, 600, 800, 1000, 1200] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_so4_srf"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 10, 20, 40, 60, 80, 100, 120, 140, 160, 180, 200] +diff_levels = [-20, -10, -5, -2, -1, 1, 2, 5, 10, 20] + + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_dst_srf"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N", "polar_S"] +contour_levels = [0, 100, 500, 1000, 2000, 5000, 10000, 15000, 20000, 30000] +diff_levels = [-6000, -5000, -4000, -3000, -2000, -1000, -500, -100, -50, -25, -10, 10, 25, 50, 100, 500, 1000, 2000, 3000, 4000, 5000, 6000] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_pom_srf"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 50, 100, 200, 400, 600, 800, 1000, 1200, 1400, 1600, 1800, 2000, 2400] +diff_levels = [-1500, -1200, -1000, -800, -600, -400, -200, -100, -50, -25, -10, 10, 25, 50, 100, 200, 400, 600, 800, 1000, 1200, 1500] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_pom_srf"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +diff_levels = [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_mom_srf"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 1, 2, 4, 6, 8, 10, 12, 14, 20, 30, 40, 50] +diff_levels = [-25, -20, -15, -10, -5, -2, -1, 1, 2, 5, 10, 15, 20, 25] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_mom_srf"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 25, 50, 100, 150, 200, 300, 400, 500] +diff_levels = [-40, -30, -20, -10, -5, -2, -1, 1, 2, 5, 10, 20, 30, 40] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_bc_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 0.1, 0.2, 0.5, 1, 2, 3, 3.5] +diff_levels = [-3, -2, -1, -0.5, -0.2, -0.1, -0.05, -0.02, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 3] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_ncl_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 100, 200, 500, 1000, 2000, 3000, 4000, 5000, 7000] +diff_levels = [-250, -200, -150, -100, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 100, 150, 200, 250] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_so4_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 1, 2, 5, 10, 20, 30, 40, 50, 100, 150] +diff_levels = [-25, -20, -15, -10, -5, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 5, 10, 15, 20, 25] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_dst_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 10, 20, 50, 100, 200, 300, 400, 500, 700, 1000] +diff_levels = [-100, -80, -60, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 60, 80, 100] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_pom_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 0.5, 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20] +diff_levels = [-10, -7, -5, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 5, 7, 10] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_soa_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 1, 2, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100] +diff_levels = [-40, -30, -20, -10, -5, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 5, 10, 20, 30, 40] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_mom_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 0.5, 1, 2, 4, 6, 8, 10, 20, 40, 60, 80] +diff_levels = [-8, -6, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 6, 8] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_bc_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 0.1, 0.2, 0.5, 1, 2, 4, 6, 8, 10, 12, 15] +diff_levels = [-10, -8, -6, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 6, 8, 10] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_ncl_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 5, 10, 20, 40, 60, 80, 100, 150, 200, 250, 300, 350] +diff_levels = [-30, -25, -20, -15, -10, -8, -6, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 6, 8, 10, 15, 20, 25, 30] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_so4_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 10, 20, 40, 60, 80, 100, 150, 200, 250, 300] +diff_levels = [-150, -100, -80, -60, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 60, 80, 100, 150] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_dst_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 10, 20, 40, 60, 80, 100, 150, 200, 300, 400, 500, 600, 800, 1000, 1200] +diff_levels = [-200, -150, -100, -80, -60, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 60, 80, 100, 150, 200] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_pom_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 2, 4, 6, 8, 10, 20, 30, 40, 50, 60, 80, 100] +diff_levels = [-50, -40, -30, -20, -10, -5, -2, -1, -0.5, 0.5, 1, 2, 5, 10, 20, 30, 40, 50] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_soa_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 10, 20, 40, 60, 80, 100, 150, 200, 250, 300, 350] +diff_levels = [-150, -100, -80, -60, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 60, 80, 100, 150] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_mom_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 0.1, 0.2, 0.5, 1, 2, 4, 6, 8, 10] +diff_levels = [-0.5, -0.4, -0.3, -0.2, -0.1, -0.05, -0.02, -0.01, -0.005, -0.002, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_bc_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 10, 20, 40, 60, 80, 100, 150] +diff_levels = [-100, -80, -60, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 60, 80, 100] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_ncl_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 100, 200, 400, 600, 800, 1000, 1500, 2000, 2500, 3000, 4000, 5000] +diff_levels = [-250, -200, -150, -100, -80, -60, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 60, 80, 100, 150, 200, 250] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_so4_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 20, 40, 60, 80, 100, 150, 200, 300, 400, 500, 600, 700, 800, 1000] +diff_levels = [-900, -700, -500, -400, -300, -200, -150, -100, -80, -60, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 60, 80, 100, 150, 200, 300, 400, 500, 700, 800, 900] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_dst_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 1000, 2000, 4000, 6000, 8000, 10000, 15000, 20000] +diff_levels = [-2000, -1500, -1000, -800, -600, -400, -300, -200, -150, -100, -80, -60, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 60, 80, 100, 150, 200, 300, 400, 600, 800, 1000, 1500, 2000] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_pom_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 50, 100, 200, 300, 400, 600, 800, 1000, 1200] +diff_levels = [-300, -200, -150, -100, -80, -60, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 60, 80, 100, 150, 200, 300] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_soa_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 100, 200, 400, 600, 800, 1000, 1500, 2000, 2500, 3000, 3600] +diff_levels = [-700, -500, -400, -300, -200, -150, -100, -80, -60, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 60, 80, 100, 150, 200, 300, 400, 500, 700] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_mom_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 2, 4, 6, 8, 10, 20, 30, 40, 50, 60, 80, 100] +diff_levels = [-10, -8, -6, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 6, 8, 10] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_bc_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 1, 2, 4, 6, 8, 10, 20, 30, 40, 50] +diff_levels = [-50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_ncl_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 5, 10, 20, 30, 40, 50, 60, 80, 100, 120, 150] +diff_levels = [-30, -20, -15, -10, -8, -6, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 6, 8, 10, 15, 20, 30] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_so4_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 50, 100, 200, 300, 400, 500, 600, 700] +diff_levels = [-600, -500, -400, -300, -200, -150, -100, -80, -60, -40, -30, -20, -10, 10, 20, 30, 40, 60, 80, 100, 150, 200, 300, 400, 500, 600] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_dst_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 500, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 9000] +diff_levels = [-1000, -700, -500, -400, -300, -200, -150, -100, -80, -60, -40, -30, -20, -10, 10, 20, 30, 40, 60, 80, 100, 150, 200, 300, 400, 500, 700, 1000] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_pom_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 10, 20, 30, 40, 50, 60, 80, 100, 120, 150, 200] +diff_levels = [-120, -100, -80, -60, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 60, 80, 100, 120] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_soa_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 50, 100, 200, 300, 400, 500, 600, 700, 1000] +diff_levels = [-400, -300, -200, -150, -100, -80, -60, -40, -30, -20, -10, 10, 20, 30, 40, 60, 80, 100, 150, 200, 300, 400] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["Mass_mom_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 0.1, 0.2, 0.4, 0.6, 0.8, 1, 2, 3, 4] +diff_levels = [-0.5, -0.4, -0.3, -0.2, -0.1, -0.05, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["AODDUST", "AODBC", "AODSS", "AODPOM", "AODMOM", "AODSOA", "AODSO4", "AODSO4_STR", "AODSO4_TRO"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S", "polar_N"] +contour_levels = [0, 0.02, 0.04, 0.06, 0.08, 0.10, 0.12] +diff_levels = [-0.02, -0.01, -0.005, -0.0025, -0.0010, -0.0005, 0.0005, 0.0010, 0.0025, 0.005, 0.01, 0.02] + +[#] +sets = ["polar"] +case_id = "model_vs_model" +variables = ["AODVIS"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S", "polar_N"] +contour_levels = [0, 0.03, 0.06, 0.09, 0.12, 0.15, 0.18] +diff_levels = [-0.05, -0.04, -0.03, -0.02, -0.01, -0.005, -0.0025, 0.0025, 0.005, 0.01, 0.02, 0.03, 0.04, 0.05] diff --git a/e3sm_diags/driver/default_diags/polar_model_vs_obs.cfg b/e3sm_diags/driver/default_diags/polar_model_vs_obs.cfg index 13adc3616..bcec76a04 100644 --- a/e3sm_diags/driver/default_diags/polar_model_vs_obs.cfg +++ b/e3sm_diags/driver/default_diags/polar_model_vs_obs.cfg @@ -692,3 +692,364 @@ regions = ["polar_S", "polar_N"] seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] contour_levels = [955, 965, 975,980, 985, 990, 995, 1000, 1005, 1010, 1015, 1020, 1025, 1035] diff_levels = [ -16, -12, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 12, 16] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_bc_srf"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 50, 100, 200, 300, 400, 500, 600, 700] +diff_levels = [-700, -600, -500, -400, -300, -200, -100, -50, -25, -10, 10, 25, 50, 100, 200, 300, 400, 500, 600, 700] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_bc_srf"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 0.5, 1, 2, 3, 4, 5] +diff_levels = [-5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_ncl_srf"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N", "polar_S"] +contour_levels = [0, 100, 500, 1000, 2000, 5000, 10000, 15000, 20000] +diff_levels = [-800, -600, -500, -400, -300, -200, -100, -50, -25, -10, 10, 25, 50, 100, 200, 300, 400, 500, 600, 800] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_so4_srf"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 50, 100, 200, 400, 600, 800, 1000, 1200, 1400, 1600] +diff_levels = [-1200, -1000, -800, -600, -400, -200, -100, -50, -25, -10, 10, 25, 50, 100, 200, 400, 600, 800, 1000, 1200] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_so4_srf"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 10, 20, 40, 60, 80, 100, 120, 140, 160, 180, 200] +diff_levels = [-20, -10, -5, -2, -1, 1, 2, 5, 10, 20] + + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_dst_srf"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N", "polar_S"] +contour_levels = [0, 100, 500, 1000, 2000, 5000, 10000, 15000, 20000, 30000] +diff_levels = [-6000, -5000, -4000, -3000, -2000, -1000, -500, -100, -50, -25, -10, 10, 25, 50, 100, 500, 1000, 2000, 3000, 4000, 5000, 6000] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_pom_srf"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 50, 100, 200, 400, 600, 800, 1000, 1200, 1400, 1600, 1800, 2000, 2400] +diff_levels = [-1500, -1200, -1000, -800, -600, -400, -200, -100, -50, -25, -10, 10, 25, 50, 100, 200, 400, 600, 800, 1000, 1200, 1500] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_pom_srf"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +diff_levels = [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_mom_srf"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 1, 2, 4, 6, 8, 10, 12, 14, 20, 30, 40, 50] +diff_levels = [-25, -20, -15, -10, -5, -2, -1, 1, 2, 5, 10, 15, 20, 25] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_mom_srf"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 25, 50, 100, 150, 200, 300, 400, 500] +diff_levels = [-40, -30, -20, -10, -5, -2, -1, 1, 2, 5, 10, 20, 30, 40] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_bc_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 0.1, 0.2, 0.5, 1, 2, 3, 3.5] +diff_levels = [-3, -2, -1, -0.5, -0.2, -0.1, -0.05, -0.02, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 3] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_ncl_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 100, 200, 500, 1000, 2000, 3000, 4000, 5000, 7000] +diff_levels = [-250, -200, -150, -100, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 100, 150, 200, 250] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_so4_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 1, 2, 5, 10, 20, 30, 40, 50, 100, 150] +diff_levels = [-25, -20, -15, -10, -5, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 5, 10, 15, 20, 25] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_dst_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 10, 20, 50, 100, 200, 300, 400, 500, 700, 1000] +diff_levels = [-100, -80, -60, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 60, 80, 100] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_pom_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 0.5, 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20] +diff_levels = [-10, -7, -5, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 5, 7, 10] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_soa_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 1, 2, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100] +diff_levels = [-40, -30, -20, -10, -5, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 5, 10, 20, 30, 40] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_mom_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 0.5, 1, 2, 4, 6, 8, 10, 20, 40, 60, 80] +diff_levels = [-8, -6, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 6, 8] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_bc_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 0.1, 0.2, 0.5, 1, 2, 4, 6, 8, 10, 12, 15] +diff_levels = [-10, -8, -6, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 6, 8, 10] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_ncl_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 5, 10, 20, 40, 60, 80, 100, 150, 200, 250, 300, 350] +diff_levels = [-30, -25, -20, -15, -10, -8, -6, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 6, 8, 10, 15, 20, 25, 30] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_so4_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 10, 20, 40, 60, 80, 100, 150, 200, 250, 300] +diff_levels = [-150, -100, -80, -60, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 60, 80, 100, 150] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_dst_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 10, 20, 40, 60, 80, 100, 150, 200, 300, 400, 500, 600, 800, 1000, 1200] +diff_levels = [-200, -150, -100, -80, -60, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 60, 80, 100, 150, 200] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_pom_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 2, 4, 6, 8, 10, 20, 30, 40, 50, 60, 80, 100] +diff_levels = [-50, -40, -30, -20, -10, -5, -2, -1, -0.5, 0.5, 1, 2, 5, 10, 20, 30, 40, 50] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_soa_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 10, 20, 40, 60, 80, 100, 150, 200, 250, 300, 350] +diff_levels = [-150, -100, -80, -60, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 60, 80, 100, 150] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_mom_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S"] +contour_levels = [0, 0.1, 0.2, 0.5, 1, 2, 4, 6, 8, 10] +diff_levels = [-0.5, -0.4, -0.3, -0.2, -0.1, -0.05, -0.02, -0.01, -0.005, -0.002, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_bc_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 10, 20, 40, 60, 80, 100, 150] +diff_levels = [-100, -80, -60, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 60, 80, 100] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_ncl_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 100, 200, 400, 600, 800, 1000, 1500, 2000, 2500, 3000, 4000, 5000] +diff_levels = [-250, -200, -150, -100, -80, -60, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 60, 80, 100, 150, 200, 250] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_so4_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 20, 40, 60, 80, 100, 150, 200, 300, 400, 500, 600, 700, 800, 1000] +diff_levels = [-900, -700, -500, -400, -300, -200, -150, -100, -80, -60, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 60, 80, 100, 150, 200, 300, 400, 500, 700, 800, 900] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_dst_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 1000, 2000, 4000, 6000, 8000, 10000, 15000, 20000] +diff_levels = [-2000, -1500, -1000, -800, -600, -400, -300, -200, -150, -100, -80, -60, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 60, 80, 100, 150, 200, 300, 400, 600, 800, 1000, 1500, 2000] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_pom_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 50, 100, 200, 300, 400, 600, 800, 1000, 1200] +diff_levels = [-300, -200, -150, -100, -80, -60, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 60, 80, 100, 150, 200, 300] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_soa_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 100, 200, 400, 600, 800, 1000, 1500, 2000, 2500, 3000, 3600] +diff_levels = [-700, -500, -400, -300, -200, -150, -100, -80, -60, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 60, 80, 100, 150, 200, 300, 400, 500, 700] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_mom_850"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 2, 4, 6, 8, 10, 20, 30, 40, 50, 60, 80, 100] +diff_levels = [-10, -8, -6, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 6, 8, 10] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_bc_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 1, 2, 4, 6, 8, 10, 20, 30, 40, 50] +diff_levels = [-50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_ncl_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 5, 10, 20, 30, 40, 50, 60, 80, 100, 120, 150] +diff_levels = [-30, -20, -15, -10, -8, -6, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 6, 8, 10, 15, 20, 30] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_so4_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 50, 100, 200, 300, 400, 500, 600, 700] +diff_levels = [-600, -500, -400, -300, -200, -150, -100, -80, -60, -40, -30, -20, -10, 10, 20, 30, 40, 60, 80, 100, 150, 200, 300, 400, 500, 600] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_dst_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 500, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 9000] +diff_levels = [-1000, -700, -500, -400, -300, -200, -150, -100, -80, -60, -40, -30, -20, -10, 10, 20, 30, 40, 60, 80, 100, 150, 200, 300, 400, 500, 700, 1000] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_pom_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 10, 20, 30, 40, 50, 60, 80, 100, 120, 150, 200] +diff_levels = [-120, -100, -80, -60, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 60, 80, 100, 120] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_soa_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 50, 100, 200, 300, 400, 500, 600, 700, 1000] +diff_levels = [-400, -300, -200, -150, -100, -80, -60, -40, -30, -20, -10, 10, 20, 30, 40, 60, 80, 100, 150, 200, 300, 400] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["Mass_mom_330"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_N"] +contour_levels = [0, 0.1, 0.2, 0.4, 0.6, 0.8, 1, 2, 3, 4] +diff_levels = [-0.5, -0.4, -0.3, -0.2, -0.1, -0.05, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["AODDUST", "AODBC", "AODSS", "AODPOM", "AODMOM", "AODSOA", "AODSO4", "AODSO4_STR", "AODSO4_TRO"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S", "polar_N"] +contour_levels = [0, 0.02, 0.04, 0.06, 0.08, 0.10, 0.12] +diff_levels = [-0.02, -0.01, -0.005, -0.0025, -0.0010, -0.0005, 0.0005, 0.0010, 0.0025, 0.005, 0.01, 0.02] + +[#] +sets = ["polar"] +case_id = "aero-no-ref-data" +variables = ["AODVIS"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +regions = ["polar_S", "polar_N"] +contour_levels = [0, 0.03, 0.06, 0.09, 0.12, 0.15, 0.18] +diff_levels = [-0.05, -0.04, -0.03, -0.02, -0.01, -0.005, -0.0025, 0.0025, 0.005, 0.01, 0.02, 0.03, 0.04, 0.05] diff --git a/e3sm_diags/driver/default_diags/zonal_mean_xy_model_vs_model.cfg b/e3sm_diags/driver/default_diags/zonal_mean_xy_model_vs_model.cfg index 844d93c46..e91583974 100644 --- a/e3sm_diags/driver/default_diags/zonal_mean_xy_model_vs_model.cfg +++ b/e3sm_diags/driver/default_diags/zonal_mean_xy_model_vs_model.cfg @@ -296,3 +296,41 @@ sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["TGCLDLWP_OCN"] seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] + +[#] +sets = ["zonal_mean_xy"] +case_id = "model_vs_model" +variables = ["ERFtot", "ERFari", "ERFaci", "ERFres"] +seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] + +[#] +sets = ["zonal_mean_xy"] +case_id = "model_vs_model" +variables = ['Mass_bc_200', 'Mass_ncl_200', 'Mass_so4_200', 'Mass_dst_200', 'Mass_pom_200', 'Mass_soa_200', 'Mass_mom_200'] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] + +[#] +sets = ["zonal_mean_xy"] +case_id = "model_vs_model" +variables = ['Mass_bc_srf', 'Mass_ncl_srf', 'Mass_so4_srf', 'Mass_dst_srf', 'Mass_pom_srf', 'Mass_soa_srf', 'Mass_mom_srf'] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] + +[#] +sets = ["zonal_mean_xy"] +case_id = "model_vs_model" +variables = ['Mass_bc_850', 'Mass_ncl_850', 'Mass_so4_850', 'Mass_dst_850', 'Mass_pom_850', 'Mass_soa_850', 'Mass_mom_850'] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] + + +[#] +sets = ["zonal_mean_xy"] +case_id = "model_vs_model" +variables = ['Mass_bc_330', 'Mass_ncl_330', 'Mass_so4_330', 'Mass_dst_330', 'Mass_pom_330', 'Mass_soa_330', 'Mass_mom_330'] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] + + +[#] +sets = ["zonal_mean_xy"] +case_id = "model_vs_model" +variables = ["in_grid_cdnc", "in_grid_lwp", "in_cloud_cdnc", "in_cloud_lwp"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] diff --git a/e3sm_diags/driver/default_diags/zonal_mean_xy_model_vs_obs.cfg b/e3sm_diags/driver/default_diags/zonal_mean_xy_model_vs_obs.cfg index a9f0af873..bdf58e278 100644 --- a/e3sm_diags/driver/default_diags/zonal_mean_xy_model_vs_obs.cfg +++ b/e3sm_diags/driver/default_diags/zonal_mean_xy_model_vs_obs.cfg @@ -535,3 +535,35 @@ variables = ["PminusE"] ref_name = "COREv2_Flux" reference_name = "COREv2_Flux" seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] + +[#] +sets = ["zonal_mean_xy"] +case_id = "aero-no-ref-data" +variables = ['Mass_bc_200', 'Mass_ncl_200', 'Mass_so4_200', 'Mass_dst_200', 'Mass_pom_200', 'Mass_soa_200', 'Mass_mom_200'] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] + +[#] +sets = ["zonal_mean_xy"] +case_id = "aero-no-ref-data" +variables = ['Mass_bc_srf', 'Mass_ncl_srf', 'Mass_so4_srf', 'Mass_dst_srf', 'Mass_pom_srf', 'Mass_soa_srf', 'Mass_mom_srf'] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] + +[#] +sets = ["zonal_mean_xy"] +case_id = "aero-no-ref-data" +variables = ['Mass_bc_850', 'Mass_ncl_850', 'Mass_so4_850', 'Mass_dst_850', 'Mass_pom_850', 'Mass_soa_850', 'Mass_mom_850'] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] + + +[#] +sets = ["zonal_mean_xy"] +case_id = "aero-no-ref-data" +variables = ['Mass_bc_330', 'Mass_ncl_330', 'Mass_so4_330', 'Mass_dst_330', 'Mass_pom_330', 'Mass_soa_330', 'Mass_mom_330'] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] + + +[#] +sets = ["zonal_mean_xy"] +case_id = "aero-no-ref-data" +variables = ["in_grid_cdnc", "in_grid_lwp", "in_cloud_cdnc", "in_cloud_lwp"] +seasons = ["ANN","DJF", "MAM", "JJA", "SON"] From 059c71c8b6bc62043a5ecd8063a3bb726fcc7933 Mon Sep 17 00:00:00 2001 From: Tom Vo Date: Wed, 13 Dec 2023 10:24:24 -0800 Subject: [PATCH 12/46] Bump to 2.10.0 (#766) --- e3sm_diags/__init__.py | 2 +- setup.py | 2 +- tbump.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/e3sm_diags/__init__.py b/e3sm_diags/__init__.py index 442dbbdc3..5ceb7ceb2 100644 --- a/e3sm_diags/__init__.py +++ b/e3sm_diags/__init__.py @@ -6,7 +6,7 @@ # issue with dask when using ESMF with system compilers. import shapely -__version__ = "v2.9.0" +__version__ = "v2.10.0" INSTALL_PATH = os.path.join(sys.prefix, "share/e3sm_diags/") # Disable MPI in cdms2, which is not currently supported by E3SM-unified diff --git a/setup.py b/setup.py index b534a1ac9..e80997495 100644 --- a/setup.py +++ b/setup.py @@ -152,7 +152,7 @@ def get_all_files_in_dir(directory, pattern): "Programming Language :: Python :: 3.10", ], name="e3sm_diags", - version="2.9.0", + version="2.10.0", author="Chengzhu (Jill) Zhang, Tom Vo, Ryan Forsyth, Chris Golaz and Zeshawn Shaheen", author_email="zhang40@llnl.gov", description="E3SM Diagnostics", diff --git a/tbump.toml b/tbump.toml index 405460a3b..824648f02 100644 --- a/tbump.toml +++ b/tbump.toml @@ -2,7 +2,7 @@ github_url = "https://github.com/E3SM-Project/e3sm_diags" [version] -current = "2.9.0" +current = "2.10.0" # Example of a semver regexp. # Make sure this matches current_version before From ecc5f2af1c3633e78c9b35e4f5efa6f19295a976 Mon Sep 17 00:00:00 2001 From: Tom Vo Date: Wed, 13 Dec 2023 10:58:31 -0800 Subject: [PATCH 13/46] [Refactor]: Refactor integration tests and add `use_cfg` flag to `run_diags()` (#747) * Refactor integration tests (_tests/integration_) * Delete `test_dataset.py` because it is a really old and incomplete test file for the legacy `Dataset` class based on CDAT * Delete `test_all_sets.py` and `all_sets_modified.cfg` because it tests for expected image counts which is redundant (`test_all_sets_image_diffs.py` does this already) * Replace `subprocess` calls with direct call to Python API for real-time test results and easier debugging * Move `complete_run.py` to `/test/integration` * Move `test_run.py` to `tests/e3sm_diags` since it is more of a unit test file * Refactor `Run` class (_run.py_) * Closes #735 * Add `use_cfg` boolean argument to `run_diags()` and `get_run_parameters()` * Add `self.cfg_path` attribute, which is set if `.cfg` file(s) are used for diagnostic runs * Add `is_cfg_file_arg_set()` property to check parser for `-d/--diags` * Rename `get_final_parameters()` to `get_run_parameters()` and refactored to smaller methods * Update CI/CD build workflow (_build_workflow.yml_) * Split up testing step into: 1) run unit tests 2) download integration test data 3) run integration tests * Easier to keep track of runtime and results --- .github/workflows/build_workflow.yml | 12 +- Makefile | 8 + docs/source/dev_guide/testing.rst | 10 +- e3sm_diags/e3sm_diags_driver.py | 7 +- e3sm_diags/parameter/core_parameter.py | 50 +-- e3sm_diags/plot/cartopy/arm_diags_plot.py | 1 + e3sm_diags/run.py | 328 ++++++++++++++---- pyproject.toml | 2 + tests/{integration => e3sm_diags}/test_run.py | 24 +- tests/integration/all_sets.cfg | 15 - tests/integration/all_sets.py | 30 -- tests/integration/all_sets_modified.cfg | 187 ---------- tests/{ => integration}/complete_run.py | 2 +- tests/integration/config.py | 4 +- tests/integration/test_all_sets.py | 117 ------- ..._diags.py => test_all_sets_image_diffs.py} | 199 +++-------- tests/integration/test_dataset.py | 198 ----------- tests/integration/utils.py | 155 +++++++++ 18 files changed, 536 insertions(+), 813 deletions(-) rename tests/{integration => e3sm_diags}/test_run.py (88%) delete mode 100644 tests/integration/all_sets.py delete mode 100644 tests/integration/all_sets_modified.cfg rename tests/{ => integration}/complete_run.py (96%) delete mode 100644 tests/integration/test_all_sets.py rename tests/integration/{test_diags.py => test_all_sets_image_diffs.py} (63%) delete mode 100644 tests/integration/test_dataset.py diff --git a/.github/workflows/build_workflow.yml b/.github/workflows/build_workflow.yml index e69d82fff..cbe3de14e 100644 --- a/.github/workflows/build_workflow.yml +++ b/.github/workflows/build_workflow.yml @@ -99,10 +99,18 @@ jobs: mamba info - if: ${{ steps.skip_check.outputs.should_skip != 'true' }} - name: Run Tests + name: Run Unit Tests + run: pytest tests/e3sm_diags + + - if: ${{ steps.skip_check.outputs.should_skip != 'true' }} + name: Download Integration Test Data + run: python -m tests.integration.download_data + + - if: ${{ steps.skip_check.outputs.should_skip != 'true' }} + name: Run Integration Tests env: CHECK_IMAGES: False - run: bash tests/test.sh + run: pytest tests/integration publish-docs: if: ${{ github.event_name == 'push' }} diff --git a/Makefile b/Makefile index eb6445974..9736f7704 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,14 @@ clean-test: ## remove test and coverage artifacts rm -fr .pytest_cache rm -rf .mypy_cache +clean-test-int-res: ## remove integration test results and image check failures + rm -rf tests/integration/all_sets_results_test + rm -rf tests/integration/image_check_failures + +clean-test-int-data: # remove integration test data and images (expected) -- useful when they are updated + rm -rf tests/integration/integration_test_data + rm -rf tests/integration/integration_test_images + # Quality Assurance # ---------------------- pre-commit: # run pre-commit quality assurance checks diff --git a/docs/source/dev_guide/testing.rst b/docs/source/dev_guide/testing.rst index c61a41042..aa21ed3e4 100644 --- a/docs/source/dev_guide/testing.rst +++ b/docs/source/dev_guide/testing.rst @@ -56,7 +56,7 @@ The unit and integration tests are run automatically as part of this. Complete run test ----------------- -``tests/complete_run.py`` checks the images generated by all diagnostics to +``tests/integration/complete_run.py`` checks the images generated by all diagnostics to see if any differ from expected. This test is not run as part of the unit test suite, because it relies on a large quantity of data found on LCRC (Anvil/Chrysalis). @@ -78,7 +78,7 @@ Now that you have your changes on LCRC, enter your development environment. Then .. code:: pip install . # Install your changes - python -m unittest tests/complete_run.py + pytest tests/integration/complete_run.py If this test passes, you're done. If it fails however, that means your code has changed what the output looks like. @@ -108,11 +108,11 @@ then you need to update the expected images. find . -type f -name '*.png' > ../image_list_all_sets.txt cd .. -Run ``python -m unittest tests/complete_run.py`` again. Now, the test should pass. +Run ``pytest tests/integration/complete_run.py`` again. Now, the test should pass. After merging your pull request, edit ``README.md``. The version should be the version of E3SM Diags you ran -``python -m unittest tests/complete_run.py`` with, -the date should be the date you ran ``python -m unittest tests/complete_run.py`` on, +``pytest tests/integration/complete_run.py`` with, +the date should be the date you ran ``pytest tests/integration/complete_run.py`` on, and the hash should be for the top commit shown by ``git log`` or on https://github.com/E3SM-Project/e3sm_diags/commits/main. diff --git a/e3sm_diags/e3sm_diags_driver.py b/e3sm_diags/e3sm_diags_driver.py index 111d20f0d..2c5336102 100644 --- a/e3sm_diags/e3sm_diags_driver.py +++ b/e3sm_diags/e3sm_diags_driver.py @@ -347,15 +347,16 @@ def _collapse_results(parameters: List[List[CoreParameter]]) -> List[CoreParamet return output_parameters -def main(parameters=[]): +def main(parameters=[]) -> List[CoreParameter]: # Get the diagnostic run parameters # --------------------------------- parser = CoreParser() # If no parameters are passed, use the parser args as defaults. Otherwise, # create the dictionary of expected parameters. - if not parameters: + if len(parameters) == 0: parameters = get_parameters(parser) + expected_parameters = create_parameter_dict(parameters) if not os.path.exists(parameters[0].results_dir): @@ -408,6 +409,8 @@ def main(parameters=[]): ) raise Exception(message) + return parameters_results + if __name__ == "__main__": main() diff --git a/e3sm_diags/parameter/core_parameter.py b/e3sm_diags/parameter/core_parameter.py index 4b96c13ab..5382c74c1 100644 --- a/e3sm_diags/parameter/core_parameter.py +++ b/e3sm_diags/parameter/core_parameter.py @@ -7,6 +7,32 @@ logger = custom_logger(__name__) +# FIXME: There is probably a better way of defining default sets because most of +# this is repeated in SETS_TO_PARAMETERS and SETS_TO_PARSERS. +# Also integration tests will break if "mp_partition" is included because +# we did not take it into account yet. +DEFAULT_SETS = [ + "zonal_mean_xy", + "zonal_mean_2d", + "zonal_mean_2d_stratosphere", + "meridional_mean_2d", + "lat_lon", + "polar", + "area_mean_time_series", + "cosp_histogram", + "enso_diags", + "qbo", + "streamflow", + "diurnal_cycle", + "arm_diags", + "tc_analysis", + "annual_cycle_zonal_mean", + "lat_lon_land", + "lat_lon_river", + "aerosol_aeronet", + "aerosol_budget", +] + class CoreParameter: def __init__(self): @@ -58,28 +84,8 @@ def __init__(self): # 'model_vs_obs' (by default), 'model_vs_model', or 'obs_vs_obs'. self.run_type: str = "model_vs_obs" - # A list of the sets to be run. Default is all sets - self.sets: List[str] = [ - "zonal_mean_xy", - "zonal_mean_2d", - "zonal_mean_2d_stratosphere", - "meridional_mean_2d", - "lat_lon", - "polar", - "area_mean_time_series", - "cosp_histogram", - "enso_diags", - "qbo", - "streamflow", - "diurnal_cycle", - "arm_diags", - "tc_analysis", - "annual_cycle_zonal_mean", - "lat_lon_land", - "lat_lon_river", - "aerosol_aeronet", - "aerosol_budget", - ] + # A list of the sets to be run, by default all sets. + self.sets: List[str] = DEFAULT_SETS # The current set that is being ran when looping over sets in # `e3sm_diags_driver.run_diag()`. diff --git a/e3sm_diags/plot/cartopy/arm_diags_plot.py b/e3sm_diags/plot/cartopy/arm_diags_plot.py index 4837eab60..cf485dd06 100644 --- a/e3sm_diags/plot/cartopy/arm_diags_plot.py +++ b/e3sm_diags/plot/cartopy/arm_diags_plot.py @@ -174,6 +174,7 @@ def plot_convection_onset_statistics( var_time_absolute = cwv.getTime().asComponentTime() time_interval = int(var_time_absolute[1].hour - var_time_absolute[0].hour) + # FIXME: UnboundLocalError: local variable 'cwv_max' referenced before assignment number_of_bins = int(np.ceil((cwv_max - cwv_min) / bin_width)) bin_center = np.arange( (cwv_min + (bin_width / 2)), diff --git a/e3sm_diags/run.py b/e3sm_diags/run.py index d7a6ba870..c92f5ee83 100644 --- a/e3sm_diags/run.py +++ b/e3sm_diags/run.py @@ -1,10 +1,12 @@ import copy +from itertools import chain +from typing import List import e3sm_diags # noqa: F401 from e3sm_diags.e3sm_diags_driver import get_default_diags_path, main from e3sm_diags.logger import custom_logger, move_log_to_prov_dir from e3sm_diags.parameter import SET_TO_PARAMETERS -from e3sm_diags.parameter.core_parameter import CoreParameter +from e3sm_diags.parameter.core_parameter import DEFAULT_SETS, CoreParameter from e3sm_diags.parser.core_parser import CoreParser logger = custom_logger(__name__) @@ -18,79 +20,299 @@ class Run: """ def __init__(self): - self.sets_to_run = CoreParameter().sets self.parser = CoreParser() - def run_diags(self, parameters): + # The list of sets to run based on diagnostic parameters. + self.sets_to_run = [] + + # The path to the user-specified `.cfg` file using `-d/--diags` or + # the default diagnostics `.cfg` file. + self.cfg_path = None + + @property + def is_cfg_file_arg_set(self): + """A property to check if `-d/--diags` was set to a `.cfg` filepath. + + Returns + ------- + bool + True if list contains more than one path, else False. """ - Based on sets_to_run, run the diags with the list of parameters. + args = self.parser.view_args() + self.cfg_path = args.other_parameters + + is_set = len(self.cfg_path) > 0 + + return is_set + + def run_diags( + self, parameters: List[CoreParameter], use_cfg: bool = True + ) -> List[CoreParameter]: + """Run a set of diagnostics with a list of parameters. + + Parameters + ---------- + parameters : List[CoreParameter] + A list of parameters defined through the Python API. + use_cfg : bool, optional + Also run diagnostics using a `.cfg` file, by default True. + + * If True, run all sets using the list of parameters passed in + this function and parameters defined in a .cfg file (if + defined), or use the .cfg file(s) for default diagnostics. This + is the default option. + * If False, only the parameters passed via ``parameters`` will be + run. The sets to run are based on the sets defined by the + parameters. This makes it easy to debug a few sets instead of + all of the debug sets too. + + Returns + ------- + List[CoreParameter] + A list of parameter objects with their results. + + Raises + ------ + RuntimeError + If a diagnostic run using a parameter fails for any reason. """ - final_params = self.get_final_parameters(parameters) - if not final_params: - msg = "No parameters we able to be extracted." - msg += " Please check the parameters you defined." - raise RuntimeError(msg) + params = self.get_run_parameters(parameters, use_cfg) + + if params is None or len(params) == 0: + raise RuntimeError( + "No parameters we able to be extracted. Please " + "check the parameters you defined." + ) try: - main(final_params) + params_results = main(params) except Exception: logger.exception("Error traceback:", exc_info=True) - move_log_to_prov_dir(final_params[0].results_dir) - def get_final_parameters(self, parameters): - """ - Based on sets_to_run and the list of parameters, - get the final list of paremeters to run the diags on. - """ - if not parameters or not isinstance(parameters, list): - msg = "You must pass in a list of parameter objects." - raise RuntimeError(msg) + move_log_to_prov_dir(params_results[0].results_dir) + + return params_results + + def get_run_parameters( + self, parameters: List[CoreParameter], use_cfg: bool = True + ) -> List[CoreParameter]: + """Get the run parameters. - # For each of the passed in parameters, we can only have one of - # each type. - types = set([p.__class__ for p in parameters]) - if len(types) != len(parameters): - msg = "You passed in two or more parameters of the same type." - raise RuntimeError(msg) + Parameters + ---------- + parameters : List[CoreParameter] + A list of parameters defined through the Python API. + use_cfg : bool, optional + Use parameters defined in a .cfg file too, by default True. + Returns + ------- + List[CoreParameter] + A list of run parameters. + """ + self._validate_parameters(parameters) + + # FIXME: This line produces some unintended side-effects. For example, + # let's say we have two objects: 1. CoreParameter, 2. ZonalMean2DParameter. + # If object 1 has `plevs=[200]`, this value will get copied to object 2. + # Object 2 has a check to make sure plevs has more than 1 value. This + # breaks the diagnostic run as a result. The workaround is to loop + # over `run_diags()` function and run one parameter at a time. self._add_parent_attrs_to_children(parameters) - final_params = [] + if use_cfg: + run_params = self._get_parameters_with_api_and_cfg(parameters) + else: + run_params = self._get_parameters_with_api_only(parameters) - for set_name in self.sets_to_run: - other_params = self._get_other_diags(parameters[0].run_type) + self.parser.check_values_of_params(run_params) - # For each of the set_names, get the corresponding parameter. + return run_params + + def _validate_parameters(self, parameters: List[CoreParameter]): + if parameters is None or not isinstance(parameters, list): + raise RuntimeError("You must pass in a list of parameter objects.") + + param_types_list = [ + p.__class__ for p in parameters if p.__class__ != CoreParameter + ] + param_types_set = set(param_types_list) + + if len(param_types_set) != len(param_types_list): + raise RuntimeError( + "You passed in two or more non-CoreParameter objects of the same type." + ) + + def _get_parameters_with_api_and_cfg( + self, parameters: List[CoreParameter] + ) -> List[CoreParameter]: + """Get the run parameters using the Python API and .cfg file. + + Parameters + ---------- + parameters : List[CoreParameter] + A list of parameter objects defined by the Python API. + + Returns + ------- + List[CoreParameter] + A list of run parameters, including ones defined in a .cfg file. + Any non-CoreParameter objects will be replaced by a sub-class + based on the set (``SETS_TO_PARAMETERS``). + """ + run_params = [] + + if self.is_cfg_file_arg_set: + cfg_params = self._get_custom_params_from_cfg_file() + else: + run_type = parameters[0].run_type + cfg_params = self._get_default_params_from_cfg_file(run_type) + + if len(self.sets_to_run) == 0: + self.sets_to_run = DEFAULT_SETS + + for set_name in self.sets_to_run: param = self._get_instance_of_param_class( SET_TO_PARAMETERS[set_name], parameters ) - # Since each parameter will have lots of default values, we want to remove them. - # Otherwise when calling get_parameters(), these default values - # will take precedence over values defined in other_params. + # Since each parameter will have lots of default values, we want to + # remove them. Otherwise when calling get_parameters(), these + # default values will take precedence over values defined in + # other_params. self._remove_attrs_with_default_values(param) param.sets = [set_name] + # # FIXME: Make a deep copy of cfg_params because there is some + # buggy code in this method that changes parameter attributes in + # place, which affects downstream operations. The original + # cfg_params needs to be perserved for each iteration of this + # for loop. params = self.parser.get_parameters( orig_parameters=param, - other_parameters=other_params, + other_parameters=copy.deepcopy(cfg_params), cmd_default_vars=False, argparse_vals_only=False, ) - # Makes sure that any parameters that are selectors - # will be in param. + # Makes sure that any parameters that are selectors will be in param. self._add_attrs_with_default_values(param) + # The select() call in get_parameters() was made for the original - # command-line way of using CDP. - # We just call it manually with the parameter object param. + # command-line way of using CDP. We just call it manually with the + # parameter object param. params = self.parser.select(param, params) - final_params.extend(params) + run_params.extend(params) + + return run_params + + def _get_custom_params_from_cfg_file(self) -> List[CoreParameter]: + """Get parameters using the cfg file set by `-d`/`--diags`. + + Returns + ------- + List[CoreParameter] + A list of parameter objects. + """ + params = self.parser.get_cfg_parameters(argparse_vals_only=False) + + params_final = self._convert_params_to_subclass(params) + + return params_final + + def _get_default_params_from_cfg_file(self, run_type: str) -> List[CoreParameter]: + """Get parameters using the default diagnostic .cfg file(s). + + Parameters + ---------- + run_type : str + The run type used to check for which .cfg file(s) to reference. + + Returns + ------- + List[CoreParameter] + A list of parameter objects. + """ + # Get the paths to the default diags .cfg file(s). + paths = [] + for set_name in self.sets_to_run: + path = get_default_diags_path(set_name, run_type, False) + paths.append(path) + + self.cfg_path = paths + + # Convert the .cfg file(s) to parameter objects. + params = self.parser.get_cfg_parameters( + files_to_open=paths, argparse_vals_only=False + ) + + # Update parameter objects using subclass with default values. + params_final = self._convert_params_to_subclass(params) + + return params_final + + def _convert_params_to_subclass( + self, parameters: List[CoreParameter] + ) -> List[CoreParameter]: + new_params: List[CoreParameter] = [] + + # For each of the params, add in the default values using the parameter + # classes in SET_TO_PARAMETERS. + for param in parameters: + set_key = param.sets[0] + new_param = SET_TO_PARAMETERS[set_key]() + param + + new_params.append(new_param) + + return new_params + + def _get_parameters_with_api_only( + self, parameters: List[CoreParameter] + ) -> List[CoreParameter]: + """Get the parameters defined through the Python API only. + + This method replaces CoreParameter objects with the related sub-class + for the specified set. + + Parameters + ---------- + parameters : List[CoreParameter] + A list of parameter objects. + + Returns + ------- + List[CoreParameter] + A list of parameter objects defined through the Python API only. + Any non-CoreParameter objects will be replaced by a sub-class based + on the set (``SETS_TO_PARAMETERS``). + """ + params = [] + + if len(self.sets_to_run) == 0: + sets_to_run = [param.sets for param in parameters] + self.sets_to_run = list(chain.from_iterable(sets_to_run)) + + for set_name in self.sets_to_run: + # For each of the set_names, get the corresponding parameter. + param = self._get_instance_of_param_class( + SET_TO_PARAMETERS[set_name], parameters + ) + + # Since each parameter will have lots of default values, we want to remove them. + # Otherwise when calling get_parameters(), these default values + # will take precedence over values defined in other_params. + self._remove_attrs_with_default_values(param) + param.sets = [set_name] + + # Makes sure that any parameters that are selectors will be in param. + self._add_attrs_with_default_values(param) - self.parser.check_values_of_params(final_params) + params.append(param) - return final_params + self.parser.check_values_of_params(params) + + return params def _add_parent_attrs_to_children(self, parameters): """ @@ -233,31 +455,5 @@ def _get_instance_of_param_class(self, cls, parameters): msg = "There's weren't any class of types {} in your parameters." raise RuntimeError(msg.format(class_types)) - def _get_other_diags(self, run_type): - """ - If the user has ran the script with a -d, get the diags for that. - If not, load the default diags based on sets_to_run and run_type. - """ - args = self.parser.view_args() - - # If the user has passed in args with -d. - if args.other_parameters: - params = self.parser.get_cfg_parameters(argparse_vals_only=False) - else: - default_diags_paths = [ - get_default_diags_path(set_name, run_type, False) - for set_name in self.sets_to_run - ] - params = self.parser.get_cfg_parameters( - files_to_open=default_diags_paths, argparse_vals_only=False - ) - - # For each of the params, add in the default values - # using the parameter classes in SET_TO_PARAMETERS. - for i in range(len(params)): - params[i] = SET_TO_PARAMETERS[params[i].sets[0]]() + params[i] - - return params - runner = Run() diff --git a/pyproject.toml b/pyproject.toml index 50df363ba..09d466ab5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,8 @@ skip = "e3sm_diags/e3sm_diags_driver.py" junit_family = "xunit2" addopts = "--cov=e3sm_diags --cov-report term --cov-report html:tests_coverage_reports/htmlcov --cov-report xml:tests_coverage_reports/coverage.xml -s" python_files = ["tests.py", "test_*.py"] +# Only run the unit tests because integration tests take a long time. +# Integration tests can be executed manually with `test.sh` or `pytest tests/integration`. testpaths = "tests/e3sm_diags" [tool.mypy] diff --git a/tests/integration/test_run.py b/tests/e3sm_diags/test_run.py similarity index 88% rename from tests/integration/test_run.py rename to tests/e3sm_diags/test_run.py index f1ea689de..3421b0aaf 100644 --- a/tests/integration/test_run.py +++ b/tests/e3sm_diags/test_run.py @@ -35,7 +35,7 @@ def setUp(self): def test_lat_lon_ann(self): self.core_param.seasons = ["ANN"] self.runner.sets_to_run = ["lat_lon"] - parameters = self.runner.get_final_parameters([self.core_param]) + parameters = self.runner.get_run_parameters([self.core_param]) for param in parameters: bad_seasons = ["DJF", "MAM", "JJA", "SON"] @@ -64,8 +64,8 @@ def test_all_sets_and_all_seasons(self): "streamflow", ] - parameters = self.runner.get_final_parameters( - [self.core_param, ts_param, enso_param, streamflow_param] + parameters = self.runner.get_run_parameters( + [self.core_param, ts_param, enso_param, streamflow_param], use_cfg=True ) # Counts the number of each set and each seasons to run the diags on. set_counter, season_counter = ( @@ -88,23 +88,25 @@ def test_all_sets_and_all_seasons(self): # So, reduce the ANN count by the number of times these appear season_counter["ANN"] -= set_counter["enso_diags"] season_counter["ANN"] -= set_counter["streamflow"] - if not all(season_counter["ANN"] == count for count in season_counter.values()): - self.fail( - "In .cfg files, at least one season does not match the count for ANN: {}".format( - season_counter + + for season, count in season_counter.items(): + if count != season_counter["ANN"]: + self.fail( + "In .cfg files, at least one season does not match the count for ANN: {}".format( + season_counter + ) ) - ) def test_zonal_mean_2d(self): # Running zonal_mean_2d with the core param only. self.runner.sets_to_run = ["zonal_mean_2d"] - core_only_results = self.runner.get_final_parameters([self.core_param]) + core_only_results = self.runner.get_run_parameters([self.core_param]) # Running zonal_mean_2d with a set-specific param. # We pass in both the core and this parameter. zonal_mean_2d_param = ZonalMean2dParameter() zonal_mean_2d_param.plevs = [10.0, 20.0, 30.0] - both_results = self.runner.get_final_parameters( + both_results = self.runner.get_run_parameters( [self.core_param, zonal_mean_2d_param] ) @@ -121,7 +123,7 @@ def test_zonal_mean_2d(self): another_zonal_mean_2d_param.reference_data_path = "/something" another_zonal_mean_2d_param.test_data_path = "/something/else" another_zonal_mean_2d_param.results_dir = "/something/else/too" - zm_2d_only_results = self.runner.get_final_parameters( + zm_2d_only_results = self.runner.get_run_parameters( [another_zonal_mean_2d_param] ) diff --git a/tests/integration/all_sets.cfg b/tests/integration/all_sets.cfg index 059155714..06277d1e0 100644 --- a/tests/integration/all_sets.cfg +++ b/tests/integration/all_sets.cfg @@ -17,7 +17,6 @@ test_file = "T_20161118.beta0.FC5COSP.ne30_ne30.edison_ANN_climo.nc" results_dir = "tests/integration/all_sets_results_test" debug = True - [zonal_mean_2d] sets = ["zonal_mean_2d"] case_id = "ERA-Interim" @@ -38,7 +37,6 @@ test_file = "T_20161118.beta0.FC5COSP.ne30_ne30.edison_ANN_climo.nc" results_dir = "tests/integration/all_sets_results_test" debug = True - [meridional_mean_2d] sets = ["meridional_mean_2d"] case_id = "ERA-Interim" @@ -59,7 +57,6 @@ test_file = "T_20161118.beta0.FC5COSP.ne30_ne30.edison_ANN_climo.nc" results_dir = "tests/integration/all_sets_results_test" debug = True - [lat_lon] sets = ["lat_lon"] case_id = "ERA-Interim" @@ -82,8 +79,6 @@ results_dir = "tests/integration/all_sets_results_test" debug = True regions=["CONUS_RRM","global"] - - [polar] sets = ["polar"] case_id = "ERA-Interim" @@ -106,7 +101,6 @@ test_file = "T_20161118.beta0.FC5COSP.ne30_ne30.edison_ANN_climo.nc" results_dir = "tests/integration/all_sets_results_test" debug = True - [cosp_histogram] sets = ["cosp_histogram"] case_id = "MISR-COSP" @@ -127,7 +121,6 @@ test_file = "CLD_MISR_20161118.beta0.FC5COSP.ne30_ne30.edison_ANN_climo.nc" results_dir = "tests/integration/all_sets_results_test" debug = True - [area_mean_time_series] sets = ["area_mean_time_series"] variables = ["TREFHT"] @@ -158,7 +151,6 @@ reference_data_path = 'tests/integration/integration_test_data' ref_file = 'TREFHT_201201_201312.nc' test_name = "system tests" variables = ["TREFHT"] -print_statements = True [#] sets = ["enso_diags"] @@ -177,7 +169,6 @@ reference_data_path = 'tests/integration/integration_test_data' ref_file = 'TREFHT_201201_201312.nc' test_name = "system tests" variables = ["TREFHT"] -print_statements = True [#] sets = ["enso_diags"] @@ -197,7 +188,6 @@ reference_data_path = 'tests/integration/integration_test_data' ref_file = 'TREFHT_201201_201312.nc' test_name = "system tests" variables = ["TREFHT"] -print_statements = True [#] sets = ["enso_diags"] @@ -214,7 +204,6 @@ reference_data_path = 'tests/integration/integration_test_data' ref_file = 'TREFHT_201201_201312.nc' test_name = "system tests" variables = ["TREFHT"] -print_statements = True [#] sets = ["enso_diags"] @@ -232,7 +221,6 @@ reference_data_path = 'tests/integration/integration_test_data' ref_file = 'TREFHT_201201_201312.nc' test_name = "system tests" variables = ["TREFHT"] -print_statements = True [#] sets = ["enso_diags"] @@ -251,7 +239,6 @@ reference_data_path = 'tests/integration/integration_test_data' ref_file = 'TREFHT_201201_201312.nc' test_name = "system tests" variables = ["TREFHT"] -print_statements = True [qbo] sets = ["qbo"] @@ -283,7 +270,6 @@ test_start_yr = '1959' test_end_yr = '1961' results_dir = "tests/integration/all_sets_results_test" test_name = "system tests" -print_statements = True [diurnal_cycle] sets = ["diurnal_cycle"] @@ -304,7 +290,6 @@ test_file = "20180215.DECKv1b_H1.ne30_oEC.edison.cam.h4_JJA_200006_200908_climo. results_dir = "tests/integration/all_sets_results_test" debug = True - [arm_diags1] sets = ["arm_diags"] diags_set = "annual_cycle" diff --git a/tests/integration/all_sets.py b/tests/integration/all_sets.py deleted file mode 100644 index 943de7f89..000000000 --- a/tests/integration/all_sets.py +++ /dev/null @@ -1,30 +0,0 @@ -# Running the software with the API: -# python all_sets.py -d all_sets.py -from e3sm_diags.parameter.area_mean_time_series_parameter import ( - AreaMeanTimeSeriesParameter, -) -from e3sm_diags.parameter.core_parameter import CoreParameter -from e3sm_diags.parameter.enso_diags_parameter import EnsoDiagsParameter -from e3sm_diags.parameter.meridional_mean_2d_parameter import MeridionalMean2dParameter -from e3sm_diags.parameter.zonal_mean_2d_parameter import ZonalMean2dParameter -from e3sm_diags.run import Run - -run_object = Run() -param = CoreParameter() -ts_param = AreaMeanTimeSeriesParameter() - -m2d_param = MeridionalMean2dParameter() -m2d_param.plevs = [ - 200.0, - 500.0, -] -z2d_param = ZonalMean2dParameter() -z2d_param.plevs = [ - 200.0, - 300.0, -] - -enso_param = EnsoDiagsParameter() -enso_param.test_name = "e3sm_v1" - -run_object.run_diags([param, ts_param, m2d_param, z2d_param, enso_param]) diff --git a/tests/integration/all_sets_modified.cfg b/tests/integration/all_sets_modified.cfg deleted file mode 100644 index b4fc5e472..000000000 --- a/tests/integration/all_sets_modified.cfg +++ /dev/null @@ -1,187 +0,0 @@ -[zonal_mean_xy] -sets = ["zonal_mean_xy"] -case_id = "ERA-Interim" -variables = ["T"] -seasons = ["ANN"] -plevs = [200.0] - -test_name = "system tests" -short_test_name = "short_system tests" -ref_name = "ERA-Interim" -reference_name = "ERA-Interim Reanalysis 1979-2015" -reference_data_path = "tests/integration" -ref_file = "ta_ERA-Interim_ANN_198001_201401_climo.nc" -test_data_path = "tests/integration" -test_file = "T_20161118.beta0.FC5COSP.ne30_ne30.edison_ANN_climo.nc" - -backend = "mpl" -results_dir = "tests/integration/all_sets_results" -debug = True - - -[zonal_mean_2d] -sets = ["zonal_mean_2d"] -case_id = "ERA-Interim" -variables = ["T"] -seasons = ["ANN"] -contour_levels = [180,185,190,200,210,220,230,240,250,260,270,280,290,295,300] -diff_levels = [-7,-6,-5,-4,-3,-2,-1,1,2,3,4,5,6,7] - -test_name = "system tests" -short_test_name = "short_system tests" -ref_name = "ERA-Interim" -reference_name = "ERA-Interim Reanalysis 1989-2005" -reference_data_path = "tests/integration" -ref_file = "ta_ERA-Interim_ANN_198001_201401_climo.nc" -test_data_path = "tests/integration" -test_file = "T_20161118.beta0.FC5COSP.ne30_ne30.edison_ANN_climo.nc" - -backend = "mpl" -results_dir = "tests/integration/all_sets_results" -debug = True -plot_log_plevs = False -plot_plevs = False - - -[meridional_mean_2d] -sets = ["meridional_mean_2d"] -case_id = "ERA-Interim" -variables = ["T"] -seasons = ["ANN"] -contour_levels = [180,185,190,200,210,220,230,240,250,260,270,280,290,295,300] -diff_levels = [-7,-6,-5,-4,-3,-2,-1,1,2,3,4,5,6,7] - -test_name = "system tests" -short_test_name = "short_system tests" -ref_name = "ERA-Interim" -reference_name = "ERA-Interim Reanalysis 1989-2005" -reference_data_path = "tests/integration" -ref_file = "ta_ERA-Interim_ANN_198001_201401_climo.nc" -test_data_path = "tests/integration" -test_file = "T_20161118.beta0.FC5COSP.ne30_ne30.edison_ANN_climo.nc" - -backend = "mpl" -results_dir = "tests/integration/all_sets_results" -debug = True -plot_log_plevs = False -plot_plevs = False - - -[lat_lon] -sets = ["lat_lon"] -case_id = "ERA-Interim" -variables = ["T"] -seasons = ["ANN"] -plevs = [850.0] -contour_levels = [240, 245, 250, 255, 260, 265, 270, 275, 280, 285, 290, 295] -diff_levels = [-10, -7.5, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 7.5, 10] - -test_name = "system tests" -short_test_name = "short_system tests" -ref_name = "ERA-Interim" -reference_name = "ERA-Interim Reanalysis 1979-2015" -reference_data_path = "tests/integration" -ref_file = "ta_ERA-Interim_ANN_198001_201401_climo.nc" -test_data_path = "tests/integration" -test_file = "T_20161118.beta0.FC5COSP.ne30_ne30.edison_ANN_climo.nc" - -backend = "mpl" -results_dir = "tests/integration/all_sets_results" -debug = True - - -[polar] -sets = ["polar"] -case_id = "ERA-Interim" -variables = ["T"] -seasons = ["ANN"] -regions = ["polar_S"] -plevs = [850.0] -contour_levels = [240, 244, 248, 252, 256, 260, 264, 268, 272] -diff_levels = [-15, -10, -7.5, -5, -2.5, -1, 1, 2.5, 5, 7.5, 10, 15] - -test_name = "system tests" -short_test_name = "short_system tests" -ref_name = "ERA-Interim" -reference_name = "ERA-Interim Reanalysis 1979-2015" -reference_data_path = "tests/integration" -ref_file = "ta_ERA-Interim_ANN_198001_201401_climo.nc" -test_data_path = "tests/integration" -test_file = "T_20161118.beta0.FC5COSP.ne30_ne30.edison_ANN_climo.nc" - -backend = "mpl" -results_dir = "tests/integration/all_sets_results" -debug = True - - -[cosp_histogram] -sets = ["cosp_histogram"] -case_id = "MISR-COSP" -variables = ["COSP_HISTOGRAM_MISR"] -seasons = ["ANN"] -contour_levels = [0,0.5,1.0,1.5,2.0,2.5,3.0,3.5,4.0,4.5,5.0,5.5] -diff_levels = [-3.0,-2.5,-2.0,-1.5,-1.0,-0.5,0,0.5,1.0,1.5,2.0,2.5,3.0] - -test_name = "system tests" -short_test_name = "short_system tests" -ref_name = "MISRCOSP" -reference_name = "MISR COSP (2000-2009)" -reference_data_path = "tests/integration" -ref_file = "CLDMISR_ERA-Interim_ANN_198001_201401_climo.nc" -test_data_path = "tests/integration" -test_file = "CLD_MISR_20161118.beta0.FC5COSP.ne30_ne30.edison_ANN_climo.nc" - -backend = "mpl" -results_dir = "tests/integration/all_sets_results" -debug = True - - -[area_mean_time_series] -sets = ["area_mean_time_series"] -variables = ["TREFHT"] - -test_name = "system tests" -short_test_name = "short_system tests" -test_data_path = "tests/integration" -test_file = "TREFHT_201201_201312.nc" -start_yr = '2012' -end_yr = '2013' - -backend = "mpl" -results_dir = "tests/integration/all_sets_results" -debug = True -ref_names = [] -ref_timeseries_input = True -test_timeseries_input = True -ref_start_yr='2012' -ref_end_yr='2013' -test_start_yr='2012' -test_end_yr='2013' - - -[enso_diags] -sets = ["enso_diags"] -case_id = 'TREFHT-response' -debug = True -diff_colormap = "BrBG" -regions = ["20S20N"] -results_dir = "tests/integration/all_sets_results" -seasons = ["ANN"] -start_yr = '2012' -end_yr = '2013' -test_data_path = '.' -test_file = 'TREFHT_201201_201312.nc' -reference_data_path = '.' -ref_file = 'TREFHT_201201_201312.nc' -test_name = "system tests" -variables = ["TREFHT"] -print_statements = True -nino_region = "NINO34" -ref_timeseries_input = True -test_timeseries_input = True -ref_start_yr='2012' -ref_end_yr='2013' -test_start_yr='2012' -test_end_yr='2013' -backend = "mpl" -plot_type="map" diff --git a/tests/complete_run.py b/tests/integration/complete_run.py similarity index 96% rename from tests/complete_run.py rename to tests/integration/complete_run.py index 00e490615..cd8af9f7e 100644 --- a/tests/complete_run.py +++ b/tests/integration/complete_run.py @@ -12,7 +12,7 @@ # This test should be run with the latest E3SM Diags tutorial code. from examples.run_v2_6_0_all_sets_E3SM_machines import run_lcrc -from tests.integration.test_diags import _compare_images +from tests.integration.utils import _compare_images class TestCompleteRun: diff --git a/tests/integration/config.py b/tests/integration/config.py index f90f8cb51..7e26cc667 100644 --- a/tests/integration/config.py +++ b/tests/integration/config.py @@ -1,5 +1,5 @@ -# Paths and directories used in the integration test. Configurations in -# `all_sets.cfg` and `all_sets_modified.cfg` should match these paths. +# Paths and directories used in the integration test. All `.cfg` paths +# should align with these (e.g., `all_sets.cfg`). TEST_ROOT_PATH = "tests/integration/" TEST_DATA_DIR = "integration_test_data" TEST_IMAGES_DIR = "integration_test_images" diff --git a/tests/integration/test_all_sets.py b/tests/integration/test_all_sets.py deleted file mode 100644 index f23d736a9..000000000 --- a/tests/integration/test_all_sets.py +++ /dev/null @@ -1,117 +0,0 @@ -import ast -import configparser -import os -import re -import shutil -from typing import List - -import pytest - -from e3sm_diags.parameter import SET_TO_PARAMETERS -from e3sm_diags.parameter.core_parameter import CoreParameter -from e3sm_diags.run import runner -from tests.integration.config import TEST_DATA_DIR -from tests.integration.utils import run_cmd_and_pipe_stderr - -# The path to the integration test data, which needs to be downloaded -# prior to running this test file. -MODULE_PATH = os.path.dirname(__file__) -TEST_DATA_PATH = os.path.join(MODULE_PATH, TEST_DATA_DIR) - -# The path to the integration test diagnostics .cfg file. -CFG_PATH = os.path.join(MODULE_PATH, "all_sets_modified.cfg") -CFG_PATH = os.path.abspath(CFG_PATH) - - -class TestAllSets: - def test_all_sets(self): - expected_num_diags = 12 - - # *_data_path needs to be added b/c the tests runs the diags from a different location - cmd = ( - f"e3sm_diags_driver.py -d {CFG_PATH} " - f"--reference_data_path {TEST_DATA_PATH} " - f"--test_data_path {TEST_DATA_PATH}" - ) - - stderr = run_cmd_and_pipe_stderr(cmd) - - # count the number of pngs in viewer_dir - results_dir = self._get_results_dir(stderr) - count = self._count_images(results_dir) - - # -1 is needed because of the E3SM logo in the viewer html - assert count - 1 == expected_num_diags - - shutil.rmtree(results_dir) # remove all generated results from the diags - - def _get_results_dir(self, output: List[str]): - """Given output from e3sm_diags_driver, extract the path to results_dir.""" - for line in output: - match = re.search("Viewer HTML generated at (.*)viewer.*.html", line) - if match: - results_dir = match.group(1) - return results_dir - - raise RuntimeError("No viewer directory listed in output: {}".format(output)) - - def _count_images(self, directory: str): - """Count the number of images of type file_type in directory""" - count = 0 - - for _, __, files in os.walk(directory): - for f in files: - if f.endswith("png"): - count += 1 - - return count - - @pytest.mark.xfail - def test_all_sets_directly(self): - # TODO: This test is meant to replace `test_all_sets`. It should create - # CoreParameter objects per diagnostic set defined in - # `all_sets_modified.cfg`. These CoreParameter objects should then be - # passed to `runner.run_diags()`. The benefit with this approach is - # that we don't need to run the command using subprocess, which means - # immediate unit testing feedback rather than waiting to pipe the - # complete stderr. We can also step through the code for debugging using - # an interactive console such as VS Code's Python debugger. - params = self._convert_cfg_to_param_objs() - - for param in params: - runner.run_diags([param]) - - def _convert_cfg_to_param_objs(self) -> List[CoreParameter]: - """Convert diagnostic cfg entries to parameter objects. - - NOTE: ast.literal_eval is not considered "safe" on untrusted data. - The reason why it is used is because `configparser.ConfigParser` - doesn't work well with parsing Python types from strings in - `.cfg` files, resulting in things such as nested strings or string - representation of lists. Since we are only calling literal_eval on - `.cfg` files hosted in this repo, there is minimal risk here. - - Returns - ------- - List[CoreParameter] - A list of CoreParameter objects, one for each diagnotic set. - """ - config = configparser.ConfigParser() - config.read(CFG_PATH) - params = [] - - for set_name in config.sections(): - param = SET_TO_PARAMETERS[set_name]() - - for option in config.options(set_name): - val = config.get(set_name, option) - val = ast.literal_eval(val) - - setattr(param, option, val) - - param.reference_data_path = TEST_DATA_PATH - param.test_data_path = TEST_DATA_PATH - - params.append(param) - - return params diff --git a/tests/integration/test_diags.py b/tests/integration/test_all_sets_image_diffs.py similarity index 63% rename from tests/integration/test_diags.py rename to tests/integration/test_all_sets_image_diffs.py index 2a33f1e7a..2373d2ad4 100644 --- a/tests/integration/test_diags.py +++ b/tests/integration/test_all_sets_image_diffs.py @@ -1,161 +1,52 @@ import os import re -import shutil -import subprocess +import sys from typing import List import pytest -from PIL import Image, ImageChops, ImageDraw from e3sm_diags.logger import custom_logger +from e3sm_diags.run import runner from tests.integration.config import TEST_IMAGES_PATH, TEST_ROOT_PATH -from tests.integration.utils import run_cmd_and_pipe_stderr +from tests.integration.utils import _compare_images, _get_test_params -# Run these tetsts on Cori by doing the following: -# cd tests/system -# module load python/2.7-anaconda-4.4 -# source activate e3sm_diags_env_dev -# If code in e3sm_diags has been changed: -# pip install /global/homes/f//e3sm_diags/ -# python test_diags.py +CFG_PATH = os.path.join(TEST_ROOT_PATH, "all_sets.cfg") -# Set to True to place the results directory on Cori's web server -# Set to False to place the results directory in tests/system -CORI_WEB = False - logger = custom_logger(__name__) @pytest.fixture(scope="module") -def get_results_dir(): - command = f"python {TEST_ROOT_PATH}/all_sets.py -d {TEST_ROOT_PATH}/all_sets.cfg" - stderr = run_cmd_and_pipe_stderr(command) - - results_dir = _get_results_dir(stderr) - logger.info("results_dir={}".format(results_dir)) - - if CORI_WEB: - results_dir = _move_to_NERSC_webserver( - "/global/u1/f/(.*)/e3sm_diags", - "/global/cfs/cdirs/acme/www/{}", - results_dir, - ) +def run_diags_and_get_results_dir() -> str: + """Run the diagnostics and get the results directory containing the images. - return results_dir + The scope of this fixture is at the module level so that it only runs + once, then each individual test can reference the result directory. + Returns + ------- + str + The path to the results directory. + """ + # Set -d flag to use the .cfg file for running additional diagnostic sets. + sys.argv.extend(["-d", CFG_PATH]) -def _get_results_dir(stderr): - """Given output from e3sm_diags_driver, extract the path to results_dir.""" - for line in stderr: - match = re.search("Viewer HTML generated at (.*)viewer.*.html", line) - if match: - results_dir = match.group(1) - return results_dir - - message = "No viewer directory listed in output: {}".format(stderr) - raise RuntimeError(message) - - -def _move_to_NERSC_webserver(machine_path_re_str, html_prefix_format_str, results_dir): - command = "git rev-parse --show-toplevel" - top_level = subprocess.check_output(command.split()).decode("utf-8").splitlines()[0] - match = re.search(machine_path_re_str, top_level) - if match: - username = match.group(1) - else: - message = "Username could not be extracted from top_level={}".format(top_level) - raise RuntimeError(message) - - html_prefix = html_prefix_format_str.format(username) - logger.info("html_prefix={}".format(html_prefix)) - new_results_dir = "{}/{}".format(html_prefix, results_dir) - logger.info("new_results_dir={}".format(new_results_dir)) - if os.path.exists(new_results_dir): - command = "rm -r {}".format(new_results_dir) - subprocess.check_output(command.split()) - command = "mv {} {}".format(results_dir, new_results_dir) - subprocess.check_output(command.split()) - command = "chmod -R 755 {}".format(new_results_dir) - subprocess.check_output(command.split()) - - return new_results_dir - - -def _compare_images( - mismatched_images: List[str], - image_name: str, - path_to_actual_png: str, - path_to_expected_png: str, -) -> List[str]: - # https://stackoverflow.com/questions/35176639/compare-images-python-pil - - actual_png = Image.open(path_to_actual_png).convert("RGB") - expected_png = Image.open(path_to_expected_png).convert("RGB") - diff = ImageChops.difference(actual_png, expected_png) - - diff_dir = f"{TEST_ROOT_PATH}image_check_failures" - if not os.path.isdir(diff_dir): - os.mkdir(diff_dir) - - bbox = diff.getbbox() - # If `diff.getbbox()` is None, then the images are in theory equal - if bbox is None: - pass - else: - # Sometimes, a few pixels will differ, but the two images appear identical. - # https://codereview.stackexchange.com/questions/55902/fastest-way-to-count-non-zero-pixels-using-python-and-pillow - nonzero_pixels = ( - diff.crop(bbox) - .point(lambda x: 255 if x else 0) - .convert("L") - .point(bool) - .getdata() - ) - num_nonzero_pixels = sum(nonzero_pixels) - logger.info("\npath_to_actual_png={}".format(path_to_actual_png)) - logger.info("path_to_expected_png={}".format(path_to_expected_png)) - logger.info("diff has {} nonzero pixels.".format(num_nonzero_pixels)) - width, height = expected_png.size - num_pixels = width * height - logger.info("total number of pixels={}".format(num_pixels)) - fraction = num_nonzero_pixels / num_pixels - logger.info("num_nonzero_pixels/num_pixels fraction={}".format(fraction)) - - # Fraction of mismatched pixels should be less than 0.02% - if fraction >= 0.0002: - mismatched_images.append(image_name) - - simple_image_name = image_name.split("/")[-1].split(".")[0] - shutil.copy( - path_to_actual_png, - os.path.join(diff_dir, "{}_actual.png".format(simple_image_name)), - ) - shutil.copy( - path_to_expected_png, - os.path.join(diff_dir, "{}_expected.png".format(simple_image_name)), - ) - # https://stackoverflow.com/questions/41405632/draw-a-rectangle-and-a-text-in-it-using-pil - draw = ImageDraw.Draw(diff) - (left, upper, right, lower) = diff.getbbox() - draw.rectangle(((left, upper), (right, lower)), outline="red") - diff.save( - os.path.join(diff_dir, "{}_diff.png".format(simple_image_name)), - "PNG", - ) + params = _get_test_params() + results = runner.run_diags(params) - return mismatched_images + results_dir = results[0].results_dir + logger.info(f"results_dir={results_dir}") + + return results_dir -class TestAllSets: - @pytest.fixture(autouse=True) - def setup(self, get_results_dir): - self.results_dir = get_results_dir - def test_results_directory_ends_with_specific_directory(self): - assert self.results_dir.endswith("all_sets_results_test/") +class TestAllSetsImageDiffs: + @pytest.fixture(autouse=True) + def setup(self, run_diags_and_get_results_dir): + self.results_dir = run_diags_and_get_results_dir - def test_actual_images_produced_is_the_same_as_the_expected(self): + def test_num_images_is_the_same_as_the_expected(self): actual_num_images, actual_images = self._count_images_in_dir( f"{TEST_ROOT_PATH}/all_sets_results_test" ) @@ -174,15 +65,16 @@ def test_area_mean_time_series_plot_diffs(self): # Check PNG path is the same as the expected. png_path = "{}/{}.png".format(set_name, variable) - full_png_path = "{}{}".format(self.results_dir, png_path) + full_png_path = os.path.join(self.results_dir, png_path) path_exists = os.path.exists(full_png_path) assert path_exists # Check full HTML path is the same as the expected. - html_path = "{}viewer/{}/variable/{}/plot.html".format( - self.results_dir, set_name, variable_lower + filename = "viewer/{}/variable/{}/plot.html".format( + set_name, variable_lower ) + html_path = os.path.join(self.results_dir, filename) self._check_html_image(html_path, png_path, full_png_path) def test_cosp_histogram_plot_diffs(self): @@ -237,16 +129,15 @@ def test_qbo_plot_diffs(self): # Check PNG path is the same as the expected. png_path = "{}/{}/qbo_diags.png".format(set_name, case_id) - full_png_path = "{}{}".format(self.results_dir, png_path) + full_png_path = os.path.join(self.results_dir, png_path) path_exists = os.path.exists(full_png_path) assert path_exists # Check full HTML path is the same as the expected. # viewer/qbo/variable/era-interim/plot.html - html_path = "{}viewer/{}/variable/{}/plot.html".format( - self.results_dir, set_name, case_id_lower - ) + filename = "viewer/{}/variable/{}/plot.html".format(set_name, case_id_lower) + html_path = os.path.join(self.results_dir, filename) self._check_html_image(html_path, png_path, full_png_path) def test_streamflow_plot_diffs(self): @@ -353,14 +244,13 @@ def _check_plots_generic( region, ) - full_png_path = "{}{}".format(self.results_dir, png_path) + full_png_path = os.path.join(self.results_dir, png_path) path_exists = os.path.exists(full_png_path) assert path_exists # Check full HTML path is the same as the expected. - html_path = "{}viewer/{}/{}/{}-{}{}-{}/{}.html".format( - self.results_dir, + filename = "viewer/{}/{}/{}-{}{}-{}/{}.html".format( set_name, case_id_lower, variable_lower, @@ -369,6 +259,7 @@ def _check_plots_generic( ref_name_lower, season_lower, ) + html_path = os.path.join(self.results_dir, filename) self._check_html_image(html_path, png_path, full_png_path) def _check_plots_2d(self, set_name): @@ -404,15 +295,14 @@ def _check_enso_map_plots(self, case_id): png_path = "{}/{}/regression-coefficient-{}-over-{}.png".format( set_name, case_id, variable_lower, nino_region_lower ) - full_png_path = "{}{}".format(self.results_dir, png_path) + full_png_path = os.path.join(self.results_dir, png_path) path_exists = os.path.exists(full_png_path) assert path_exists # Check full HTML path is the same as the expected. - html_path = "{}viewer/{}/map/{}/plot.html".format( - self.results_dir, set_name, case_id_lower - ) + filename = "viewer/{}/map/{}/plot.html".format(set_name, case_id_lower) + html_path = os.path.join(self.results_dir, filename) self._check_html_image(html_path, png_path, full_png_path) def _check_enso_scatter_plots(self, case_id): @@ -427,15 +317,14 @@ def _check_enso_scatter_plots(self, case_id): png_path = "{}/{}/feedback-{}-{}-TS-NINO3.png".format( set_name, case_id, variable, region ) - full_png_path = "{}{}".format(self.results_dir, png_path) + full_png_path = os.path.join(self.results_dir, png_path) path_exists = os.path.exists(full_png_path) assert path_exists # Check full HTML path is the same as the expected. - html_path = "{}viewer/{}/scatter/{}/plot.html".format( - self.results_dir, set_name, case_id_lower - ) + filename = "viewer/{}/scatter/{}/plot.html".format(set_name, case_id_lower) + html_path = os.path.join(self.results_dir, filename) self._check_html_image(html_path, png_path, full_png_path) def _check_streamflow_plots(self): @@ -459,7 +348,7 @@ def _check_streamflow_plots(self): assert png_path == expected # Check path exists - full_png_path = "{}{}".format(self.results_dir, png_path) + full_png_path = os.path.join(self.results_dir, png_path) path_exists = os.path.exists(full_png_path) assert path_exists @@ -482,5 +371,5 @@ def _check_streamflow_plots(self): assert html_path == expected # Check the full HTML path is the same as the expected. - full_html_path = "{}{}".format(self.results_dir, html_path) + full_html_path = os.path.join(self.results_dir, html_path) self._check_html_image(full_html_path, png_path, full_png_path) diff --git a/tests/integration/test_dataset.py b/tests/integration/test_dataset.py deleted file mode 100644 index 312d8778c..000000000 --- a/tests/integration/test_dataset.py +++ /dev/null @@ -1,198 +0,0 @@ -import unittest - -import cdms2 - -from e3sm_diags.derivations import acme as acme_derivations -from e3sm_diags.driver.utils.dataset import Dataset -from e3sm_diags.parameter.core_parameter import CoreParameter -from tests.integration.config import TEST_DATA_PATH - - -class TestDataset(unittest.TestCase): - def setUp(self): - self.parameter = CoreParameter() - - def test_convert_units(self): - with cdms2.open(f"{TEST_DATA_PATH}/precc.nc") as precc_file: - var = precc_file("PRECC") - - new_var = acme_derivations.convert_units(var, "mm/day") - self.assertEqual(new_var.units, "mm/day") - - def test_add_user_derived_vars(self): - my_vars = { - "A_NEW_VAR": { - ("v1", "v2"): lambda v1, v2: v1 + v2, - ("v3", "v4"): lambda v3, v4: v3 - v4, - }, - "PRECT": {("MY_PRECT",): lambda my_prect: my_prect}, - } - self.parameter.derived_variables = my_vars - data = Dataset(self.parameter, test=True) - self.assertTrue("A_NEW_VAR" in data.derived_vars) - - # In the default my_vars, each entry - # ('PRECT', 'A_NEW_VAR', etc) is an OrderedDict. - # We must check that what the user inserted is - # first, so it's used first. - self.assertTrue(list(data.derived_vars["PRECT"].keys())[0] == ("MY_PRECT",)) - - def test_is_timeseries(self): - self.parameter.ref_timeseries_input = True - data = Dataset(self.parameter, ref=True) - self.assertTrue(data.is_timeseries()) - - self.parameter.test_timeseries_input = True - data = Dataset(self.parameter, test=True) - self.assertTrue(data.is_timeseries()) - - self.parameter.ref_timeseries_input = False - data = Dataset(self.parameter, ref=True) - self.assertFalse(data.is_timeseries()) - - self.parameter.test_timeseries_input = False - data = Dataset(self.parameter, test=True) - self.assertFalse(data.is_timeseries()) - - def test_is_climo(self): - self.parameter.ref_timeseries_input = True - data = Dataset(self.parameter, ref=True) - self.assertFalse(data.is_climo()) - - self.parameter.test_timeseries_input = True - data = Dataset(self.parameter, test=True) - self.assertFalse(data.is_climo()) - - self.parameter.ref_timeseries_input = False - data = Dataset(self.parameter, ref=True) - self.assertTrue(data.is_climo()) - - self.parameter.test_timeseries_input = False - data = Dataset(self.parameter, test=True) - self.assertTrue(data.is_climo()) - - def test_get_attr_from_climo(self): - # We pass in the path to a file, so the input directory - # to the tests doesn't need to be like how it is for when e3sm_diags - # is ran wit a bunch of diags. - self.parameter.reference_data_path = TEST_DATA_PATH - self.parameter.ref_file = "ta_ERA-Interim_ANN_198001_201401_climo.nc" - data = Dataset(self.parameter, ref=True) - self.assertEqual(data.get_attr_from_climo("Conventions", "ANN"), "CF-1.0") - - """ - def test_process_derived_var_passes(self): - derived_var = { - 'PRECT': { - ('pr'): lambda x: x, - ('PRECC'): lambda x: 'this is some function' - } - } - - precc_file = cdms2.open(get_abs_file_path('integration_test_data/precc.nc')) - acme_derivations.process_derived_var('PRECT', derived_var, - precc_file, self.parameter) - precc_file.close() - - def test_process_derived_var_with_wrong_dict(self): - # pr, nothing, and nothing2 are not variables in the file we open - wrong_derived_var = { - 'PRECT': { - ('pr'): lambda x: x, - ('nothing1', 'nothing2'): lambda x, y: 'this is some function' - } - } - - precc_file = cdms2.open(get_abs_file_path('integration_test_data/precc.nc')) - with self.assertRaises(RuntimeError): - acme_derivations.process_derived_var( - 'PRECT', wrong_derived_var, precc_file, self.parameter) - precc_file.close() - - def test_process_derived_var_adds_to_dict(self): - # the one that's usually in the parameters file - derived_var_dict = { - 'PRECT': {('test'): lambda x: x} - } - # use this instead of the acme.derived_variables one - default_derived_vars = { - 'PRECT': { - ('pr'): lambda x: x, - ('PRECC'): lambda x: 'this is some function' - } - } - - # add derived_var_dict to default_derived_vars - precc_file = cdms2.open(get_abs_file_path('integration_test_data/precc.nc')) - self.parameter.derived_variables = derived_var_dict - acme_derivations.process_derived_var( - 'PRECT', default_derived_vars, precc_file, self.parameter) - precc_file.close() - - if 'test' not in default_derived_vars['PRECT']: - self.fail("Failed to insert test derived variable") - - # make sure that test is the first one - if 'test' != default_derived_vars['PRECT'].keys()[0]: - self.fail( - "Failed to insert test derived variable before default derived vars") - - def test_process_derived_var_adds_duplicate_to_dict(self): - # the one that's usually in the parameters file - # the function for PRECC below is different than the one in - # default_derived_vars - derived_var_dict = { - 'PRECT': {('PRECC'): lambda x: 'PRECC'} - } - # use this instead of the acme_derivations.derived_variables one - default_derived_vars = { - 'PRECT': { - ('pr'): lambda x: x, - ('PRECC'): lambda x: 'this is some function' - } - } - - # add derived_var_dict to default_derived_vars - precc_file = cdms2.open(get_abs_file_path('integration_test_data/precc.nc')) - self.parameter.derived_variables = derived_var_dict - msg = acme_derivations.process_derived_var( - 'PRECT', default_derived_vars, precc_file, self.parameter) - precc_file.close() - if msg != 'PRECC': - self.fail("Failed to insert a duplicate test derived variable") - - def test_process_derived_var_works_with_ordereddict(self): - derived_var_dict = { - 'PRECT': OrderedDict([ - (('something'), lambda x: 'something') - ]) - } - - default_derived_vars = { - 'PRECT': OrderedDict([ - (('pr'), lambda x: x), - (('PRECC'), lambda x: 'this is some function') - ]) - } - - precc_file = cdms2.open(get_abs_file_path('integration_test_data/precc.nc')) - self.parameter.derived_variables = derived_var_dict - acme_derivations.process_derived_var( - 'PRECT', default_derived_vars, precc_file, self.parameter) - precc_file.close() - # Check that 'something' was inserted first - self.assertEqual(['something', 'pr', 'PRECC'], - default_derived_vars['PRECT'].keys()) - - def test_mask_by(self): - with cdms2.open(get_abs_file_path('integration_test_data/precc.nc')) as precc_file: - prcc = precc_file('PRECC') - with cdms2.open(get_abs_file_path('integration_test_data/precl.nc')) as precl_file: - prcl = precl_file('PRECL') - - acme_derivations.mask_by(prcc, prcl, low_limit=2.0) - """ - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/integration/utils.py b/tests/integration/utils.py index 014a7521d..be0277fa0 100644 --- a/tests/integration/utils.py +++ b/tests/integration/utils.py @@ -1,6 +1,25 @@ +import ast +import configparser +import os +import shutil import subprocess from typing import List +from PIL import Image, ImageChops, ImageDraw + +from e3sm_diags.logger import custom_logger +from e3sm_diags.parameter import SET_TO_PARAMETERS +from e3sm_diags.parameter.area_mean_time_series_parameter import ( + AreaMeanTimeSeriesParameter, +) +from e3sm_diags.parameter.core_parameter import CoreParameter +from e3sm_diags.parameter.enso_diags_parameter import EnsoDiagsParameter +from e3sm_diags.parameter.meridional_mean_2d_parameter import MeridionalMean2dParameter +from e3sm_diags.parameter.zonal_mean_2d_parameter import ZonalMean2dParameter +from tests.integration.config import TEST_ROOT_PATH + +logger = custom_logger(__name__) + def run_cmd_and_pipe_stderr(command: str) -> List[str]: """Runs the test command and pipes the stderr for further processing. @@ -44,3 +63,139 @@ def run_cmd_and_pipe_stderr(command: str) -> List[str]: print(*stderr, sep="\n") return stderr + + +def _get_test_params() -> List[CoreParameter]: + param = CoreParameter() + ts_param = AreaMeanTimeSeriesParameter() + + m2d_param = MeridionalMean2dParameter() + m2d_param.plevs = [ + 200.0, + 500.0, + ] + z2d_param = ZonalMean2dParameter() + z2d_param.plevs = [ + 200.0, + 300.0, + ] + + enso_param = EnsoDiagsParameter() + enso_param.test_name = "e3sm_v1" + + params = [param, ts_param, m2d_param, z2d_param, enso_param] + + return params + + +def _convert_cfg_to_param_objs(cfg_path: str) -> List[CoreParameter]: + """Convert diagnostic cfg entries to parameter objects. + + NOTE: ast.literal_eval is not considered "safe" on untrusted data. + The reason why it is used is because `configparser.ConfigParser` + doesn't work well with parsing Python types from strings in + `.cfg` files, resulting in things such as nested strings or string + representation of lists. Since we are only calling literal_eval on + `.cfg` files hosted in this repo, there is minimal risk here. + + Returns + ------- + List[CoreParameter] + A list of CoreParameter objects, one for each diagnotic set. + Notes + ----- + This function seems to be a duplicate of `CoreParser._get_cfg_paramters()`'. + """ + config = configparser.ConfigParser() + config.read(cfg_path) + params = [] + + for set_name in config.sections(): + param = SET_TO_PARAMETERS[set_name]() + + for option in config.options(set_name): + val = config.get(set_name, option) + val = ast.literal_eval(val) + + setattr(param, option, val) + + params.append(param) + + return params + + +def _count_images(directory: str): + """Count the number of images of type file_type in directory""" + count = 0 + + for _, __, files in os.walk(directory): + for f in files: + if f.endswith("png"): + count += 1 + + return count + + +def _compare_images( + mismatched_images: List[str], + image_name: str, + path_to_actual_png: str, + path_to_expected_png: str, +) -> List[str]: + # https://stackoverflow.com/questions/35176639/compare-images-python-pil + + actual_png = Image.open(path_to_actual_png).convert("RGB") + expected_png = Image.open(path_to_expected_png).convert("RGB") + diff = ImageChops.difference(actual_png, expected_png) + + diff_dir = f"{TEST_ROOT_PATH}image_check_failures" + if not os.path.isdir(diff_dir): + os.mkdir(diff_dir) + + bbox = diff.getbbox() + # If `diff.getbbox()` is None, then the images are in theory equal + if bbox is None: + pass + else: + # Sometimes, a few pixels will differ, but the two images appear identical. + # https://codereview.stackexchange.com/questions/55902/fastest-way-to-count-non-zero-pixels-using-python-and-pillow + nonzero_pixels = ( + diff.crop(bbox) + .point(lambda x: 255 if x else 0) + .convert("L") + .point(bool) + .getdata() + ) + num_nonzero_pixels = sum(nonzero_pixels) + logger.info("\npath_to_actual_png={}".format(path_to_actual_png)) + logger.info("path_to_expected_png={}".format(path_to_expected_png)) + logger.info("diff has {} nonzero pixels.".format(num_nonzero_pixels)) + width, height = expected_png.size + num_pixels = width * height + logger.info("total number of pixels={}".format(num_pixels)) + fraction = num_nonzero_pixels / num_pixels + logger.info("num_nonzero_pixels/num_pixels fraction={}".format(fraction)) + + # Fraction of mismatched pixels should be less than 0.02% + if fraction >= 0.0002: + mismatched_images.append(image_name) + + simple_image_name = image_name.split("/")[-1].split(".")[0] + shutil.copy( + path_to_actual_png, + os.path.join(diff_dir, "{}_actual.png".format(simple_image_name)), + ) + shutil.copy( + path_to_expected_png, + os.path.join(diff_dir, "{}_expected.png".format(simple_image_name)), + ) + # https://stackoverflow.com/questions/41405632/draw-a-rectangle-and-a-text-in-it-using-pil + draw = ImageDraw.Draw(diff) + (left, upper, right, lower) = diff.getbbox() + draw.rectangle(((left, upper), (right, lower)), outline="red") + diff.save( + os.path.join(diff_dir, "{}_diff.png".format(simple_image_name)), + "PNG", + ) + + return mismatched_images From 04ffd97ab870a6bf0a1fff5da189162de2d93d5e Mon Sep 17 00:00:00 2001 From: Tom Vo Date: Fri, 22 Dec 2023 15:56:54 -0800 Subject: [PATCH 14/46] Fix type conditional check and `UnboundLocalError` for `params_results` (#770) --- e3sm_diags/run.py | 18 ++++++++++++------ tests/integration/test_all_sets_image_diffs.py | 8 ++++++-- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/e3sm_diags/run.py b/e3sm_diags/run.py index c92f5ee83..370086df8 100644 --- a/e3sm_diags/run.py +++ b/e3sm_diags/run.py @@ -1,6 +1,6 @@ import copy from itertools import chain -from typing import List +from typing import List, Union import e3sm_diags # noqa: F401 from e3sm_diags.e3sm_diags_driver import get_default_diags_path, main @@ -47,7 +47,7 @@ def is_cfg_file_arg_set(self): def run_diags( self, parameters: List[CoreParameter], use_cfg: bool = True - ) -> List[CoreParameter]: + ) -> Union[List[CoreParameter], None]: """Run a set of diagnostics with a list of parameters. Parameters @@ -68,8 +68,8 @@ def run_diags( Returns ------- - List[CoreParameter] - A list of parameter objects with their results. + Union[List[CoreParameter], None] + A list of parameter objects with their results (if successful). Raises ------ @@ -77,6 +77,7 @@ def run_diags( If a diagnostic run using a parameter fails for any reason. """ params = self.get_run_parameters(parameters, use_cfg) + params_results = None if params is None or len(params) == 0: raise RuntimeError( @@ -89,7 +90,9 @@ def run_diags( except Exception: logger.exception("Error traceback:", exc_info=True) - move_log_to_prov_dir(params_results[0].results_dir) + # param_results might be None because the run(s) failed, so move + # the log using the `params[0].results_dir` instead. + move_log_to_prov_dir(params[0].results_dir) return params_results @@ -449,7 +452,10 @@ def _get_instance_of_param_class(self, cls, parameters): for cls_type in class_types: for p in parameters: - if isinstance(p, cls_type): + # NOTE: This conditional is used instead of + # `isinstance(p, cls_type)` because we want to check for exact + # type matching and exclude sub-class matching. + if type(p) is cls_type: return p msg = "There's weren't any class of types {} in your parameters." diff --git a/tests/integration/test_all_sets_image_diffs.py b/tests/integration/test_all_sets_image_diffs.py index 2373d2ad4..0e15be4e8 100644 --- a/tests/integration/test_all_sets_image_diffs.py +++ b/tests/integration/test_all_sets_image_diffs.py @@ -34,10 +34,14 @@ def run_diags_and_get_results_dir() -> str: params = _get_test_params() results = runner.run_diags(params) - results_dir = results[0].results_dir + # If results is None then that means some/all diagnostic set(s) failed. + # We use params[0].results_dir to check if any diagnostic sets passed. + if results is not None: + results_dir = results[0].results_dir + else: + results_dir = params[0].results_dir logger.info(f"results_dir={results_dir}") - return results_dir From d1ed07fa46b9652adc4d6858808e31bf46c741fd Mon Sep 17 00:00:00 2001 From: Tom Vo Date: Fri, 22 Dec 2023 15:59:47 -0800 Subject: [PATCH 15/46] Bump to 2.10.1rc1 (#771) --- e3sm_diags/__init__.py | 2 +- setup.py | 2 +- tbump.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/e3sm_diags/__init__.py b/e3sm_diags/__init__.py index 5ceb7ceb2..71fb676c4 100644 --- a/e3sm_diags/__init__.py +++ b/e3sm_diags/__init__.py @@ -6,7 +6,7 @@ # issue with dask when using ESMF with system compilers. import shapely -__version__ = "v2.10.0" +__version__ = "v2.10.1rc1" INSTALL_PATH = os.path.join(sys.prefix, "share/e3sm_diags/") # Disable MPI in cdms2, which is not currently supported by E3SM-unified diff --git a/setup.py b/setup.py index e80997495..6aaeed4f1 100644 --- a/setup.py +++ b/setup.py @@ -152,7 +152,7 @@ def get_all_files_in_dir(directory, pattern): "Programming Language :: Python :: 3.10", ], name="e3sm_diags", - version="2.10.0", + version="2.10.1rc1", author="Chengzhu (Jill) Zhang, Tom Vo, Ryan Forsyth, Chris Golaz and Zeshawn Shaheen", author_email="zhang40@llnl.gov", description="E3SM Diagnostics", diff --git a/tbump.toml b/tbump.toml index 824648f02..3e4cc97ae 100644 --- a/tbump.toml +++ b/tbump.toml @@ -2,7 +2,7 @@ github_url = "https://github.com/E3SM-Project/e3sm_diags" [version] -current = "2.10.0" +current = "2.10.1rc1" # Example of a semver regexp. # Make sure this matches current_version before From 59a16e1b78ebabf7d5d105159e9c0f9d71963964 Mon Sep 17 00:00:00 2001 From: Tom Vo Date: Wed, 3 Jan 2024 15:27:27 -0800 Subject: [PATCH 16/46] Port over `cdp_viewer.OutputViewer` and remove `cdp` dependency (#773) --- conda-env/ci.yml | 2 +- conda-env/dev-nompi.yml | 2 +- conda-env/dev.yml | 2 +- .../dev_guide/adding-new-diags-sets.rst | 2 +- .../dev_guide/using-cdp-output-viewer.rst | 2 +- e3sm_diags/e3sm_diags_driver.py | 3 +- e3sm_diags/viewer/aerosol_budget_viewer.py | 2 +- .../viewer/annual_cycle_zonal_mean_viewer.py | 2 +- .../viewer/area_mean_time_series_viewer.py | 2 +- e3sm_diags/viewer/arm_diags_viewer.py | 2 +- e3sm_diags/viewer/core_viewer.py | 133 ++++++++++++++++++ e3sm_diags/viewer/default_viewer.py | 2 +- e3sm_diags/viewer/enso_diags_viewer.py | 3 +- e3sm_diags/viewer/mean_2d_viewer.py | 2 +- e3sm_diags/viewer/mp_partition_viewer.py | 3 +- e3sm_diags/viewer/qbo_viewer.py | 3 +- e3sm_diags/viewer/streamflow_viewer.py | 2 +- e3sm_diags/viewer/tc_analysis_viewer.py | 2 +- 18 files changed, 151 insertions(+), 20 deletions(-) create mode 100644 e3sm_diags/viewer/core_viewer.py diff --git a/conda-env/ci.yml b/conda-env/ci.yml index 5a17600e7..46f88d448 100644 --- a/conda-env/ci.yml +++ b/conda-env/ci.yml @@ -13,7 +13,6 @@ dependencies: - beautifulsoup4 - cartopy >=0.17.0 - cartopy_offlinedata - - cdp 1.7.0 - cdms2 3.1.5 - cdutil 8.2.1 - dask @@ -23,6 +22,7 @@ dependencies: - mache >=0.15.0 - matplotlib-base - netcdf4 + - output_viewer >=1.3.0 - numpy >=1.23.0 - shapely >=2.0.0,<3.0.0 - xarray >=2023.02.0 diff --git a/conda-env/dev-nompi.yml b/conda-env/dev-nompi.yml index 5f7edfd0c..b73bc33bc 100644 --- a/conda-env/dev-nompi.yml +++ b/conda-env/dev-nompi.yml @@ -15,7 +15,6 @@ dependencies: - beautifulsoup4 - cartopy >=0.17.0 - cartopy_offlinedata - - cdp 1.7.0 - cdms2 3.1.5 - cdutil 8.2.1 - dask @@ -26,6 +25,7 @@ dependencies: - mache >=0.15.0 - matplotlib-base - netcdf4 + - output_viewer >=1.3.0 - numpy >=1.23.0 - shapely >=2.0.0,<3.0.0 - xarray >=2023.02.0 diff --git a/conda-env/dev.yml b/conda-env/dev.yml index bbdf5f46a..1d07735c1 100644 --- a/conda-env/dev.yml +++ b/conda-env/dev.yml @@ -11,7 +11,6 @@ dependencies: - beautifulsoup4 - cartopy >=0.17.0 - cartopy_offlinedata - - cdp 1.7.0 - cdms2 3.1.5 - cdutil 8.2.1 - dask @@ -21,6 +20,7 @@ dependencies: - mache >=0.15.0 - matplotlib-base - netcdf4 + - output_viewer >=1.3.0 - numpy >=1.23.0 - shapely >=2.0.0,<3.0.0 - xarray >=2023.02.0 diff --git a/docs/source/dev_guide/adding-new-diags-sets.rst b/docs/source/dev_guide/adding-new-diags-sets.rst index 39764dd09..480af91f1 100644 --- a/docs/source/dev_guide/adding-new-diags-sets.rst +++ b/docs/source/dev_guide/adding-new-diags-sets.rst @@ -616,7 +616,7 @@ create a file ``diff_diags_viewer.py`` paste in the below code. import os from .utils import add_header, h1_to_h3 from .default_viewer import create_metadata - from cdp.cdp_viewer import OutputViewer + from e3sm_diags.viewer.core_viewer import OutputViewer def create_viewer(root_dir, parameters): diff --git a/docs/source/dev_guide/using-cdp-output-viewer.rst b/docs/source/dev_guide/using-cdp-output-viewer.rst index c48418e47..2bc076161 100644 --- a/docs/source/dev_guide/using-cdp-output-viewer.rst +++ b/docs/source/dev_guide/using-cdp-output-viewer.rst @@ -99,7 +99,7 @@ The code below was used to create the figures above. .. code:: python - from cdp.cdp_viewer import OutputViewer + from e3sm_diags.viewer.core_viewer import OutputViewer viewer = OutputViewer(index_name='My Cool Results') viewer.add_page("My Results", ['Description', 'Generated File']) diff --git a/e3sm_diags/e3sm_diags_driver.py b/e3sm_diags/e3sm_diags_driver.py index 2c5336102..57ed8aed9 100644 --- a/e3sm_diags/e3sm_diags_driver.py +++ b/e3sm_diags/e3sm_diags_driver.py @@ -8,6 +8,7 @@ from typing import Dict, List, Tuple import dask +import dask.bag as db import e3sm_diags from e3sm_diags.logger import custom_logger @@ -300,7 +301,7 @@ def _run_with_dask(parameters: List[CoreParameter]) -> List[CoreParameter]: https://docs.dask.org/en/stable/generated/dask.bag.map.html https://docs.dask.org/en/stable/generated/dask.dataframe.DataFrame.compute.html """ - bag = dask.bag.from_sequence(parameters) + bag = db.from_sequence(parameters) config = {"scheduler": "processes", "multiprocessing.context": "fork"} num_workers = getattr(parameters[0], "num_workers", None) diff --git a/e3sm_diags/viewer/aerosol_budget_viewer.py b/e3sm_diags/viewer/aerosol_budget_viewer.py index e1f3f900a..271c83093 100644 --- a/e3sm_diags/viewer/aerosol_budget_viewer.py +++ b/e3sm_diags/viewer/aerosol_budget_viewer.py @@ -1,7 +1,7 @@ import os import shutil -from cdp.cdp_viewer import OutputViewer +from e3sm_diags.viewer.core_viewer import OutputViewer from .default_viewer import seasons_used from .lat_lon_viewer import _cvs_to_html diff --git a/e3sm_diags/viewer/annual_cycle_zonal_mean_viewer.py b/e3sm_diags/viewer/annual_cycle_zonal_mean_viewer.py index fcc76d899..83e7d8753 100644 --- a/e3sm_diags/viewer/annual_cycle_zonal_mean_viewer.py +++ b/e3sm_diags/viewer/annual_cycle_zonal_mean_viewer.py @@ -1,6 +1,6 @@ import os -from cdp.cdp_viewer import OutputViewer +from e3sm_diags.viewer.core_viewer import OutputViewer from .default_viewer import create_metadata from .utils import add_header, h1_to_h3 diff --git a/e3sm_diags/viewer/area_mean_time_series_viewer.py b/e3sm_diags/viewer/area_mean_time_series_viewer.py index bae423521..a9cb5d45e 100644 --- a/e3sm_diags/viewer/area_mean_time_series_viewer.py +++ b/e3sm_diags/viewer/area_mean_time_series_viewer.py @@ -1,6 +1,6 @@ import os -from cdp.cdp_viewer import OutputViewer +from e3sm_diags.viewer.core_viewer import OutputViewer from .default_viewer import create_metadata from .utils import add_header, h1_to_h3 diff --git a/e3sm_diags/viewer/arm_diags_viewer.py b/e3sm_diags/viewer/arm_diags_viewer.py index 59ea0e75c..1a1ed82c2 100644 --- a/e3sm_diags/viewer/arm_diags_viewer.py +++ b/e3sm_diags/viewer/arm_diags_viewer.py @@ -1,6 +1,6 @@ import os -from cdp.cdp_viewer import OutputViewer +from e3sm_diags.viewer.core_viewer import OutputViewer from .utils import add_header, h1_to_h3 diff --git a/e3sm_diags/viewer/core_viewer.py b/e3sm_diags/viewer/core_viewer.py new file mode 100644 index 000000000..1e29930b7 --- /dev/null +++ b/e3sm_diags/viewer/core_viewer.py @@ -0,0 +1,133 @@ +import os +import stat + +from output_viewer.build import build_page, build_viewer +from output_viewer.index import ( + OutputFile, + OutputGroup, + OutputIndex, + OutputPage, + OutputRow, +) +from output_viewer.utils import rechmod + + +class OutputViewer(object): + def __init__(self, path=".", index_name="Results"): + self.path = os.path.abspath(path) + self.index = OutputIndex(index_name) + self.cache = {} # dict of { OutputPage: { OutputGroup: [OutputRow] } } + self.page = None + self.group = None + self.row = None + + def add_page(self, page_title, *args, **kwargs): + """Add a page to the viewer's index""" + self.page = OutputPage(page_title, *args, **kwargs) + self.cache[self.page] = {} + self.index.addPage(self.page) + + def set_page(self, page_title): + """Sets the page with the title name as the current page""" + for output_page in self.cache: + if page_title == output_page.title: + self.page = output_page + return + raise RuntimeError("There is no page titled: %s" % page_title) + + def add_group(self, group_name): + """Add a group to the current page""" + if self.page is None: + raise RuntimeError("You must first insert a page with add_page()") + self.group = OutputGroup(group_name) + if self.group not in self.cache[self.page]: + self.cache[self.page][self.group] = [] # group doesn't have any rows yet + self.page.addGroup(self.group) + + def set_group(self, group_name): + """Sets the group with the title name as the current group""" + for output_group in self.cache[self.page]: + if group_name == output_group.title: + self.group = output_group + return + raise RuntimeError("There is no group titled: %s" % group_name) + + def add_row(self, row_name): + """Add a row with the title name to the current group""" + if self.group is None: + raise RuntimeError("You must first insert a group with add_group()") + self.row = OutputRow(row_name, []) + if self.row not in self.cache[self.page][self.group]: + self.cache[self.page][self.group].append(self.row) + self.page.addRow(self.row, len(self.page.groups) - 1) # type: ignore + + def set_row(self, row_name): + """Sets the row with the title name as the current row""" + for output_row in self.cache[self.page][self.group]: + if row_name == output_row.title: + self.row = output_row + return + raise RuntimeError("There is no row titled: %s" % row_name) + + def add_cols(self, cols): + """Add multiple string cols to the current row""" + self.row.columns.append(cols) # type: ignore + + def add_col(self, col, is_file=False, **kwargs): + """Add a single col to the current row. Set is_file to True if the col is a file path.""" + if is_file: + self.row.columns.append(OutputFile(col, **kwargs)) # type: ignore + else: + self.row.columns.append(col) # type: ignore + + def generate_page(self): + """ + Generate and return the location of the current HTML page. + """ + self.index.toJSON(os.path.join(self.path, "index.json")) + + default_mask = stat.S_IMODE(os.stat(self.path).st_mode) + rechmod(self.path, default_mask) + + if os.access(self.path, os.W_OK): + default_mask = stat.S_IMODE( + os.stat(self.path).st_mode + ) # mode of files to be included + url = build_page( + self.page, + os.path.join(self.path, "index.json"), + default_mask=default_mask, + ) + return url + + raise RuntimeError("Error geneating the page.") + + def generate_viewer(self, prompt_user=True): + """Generate the webpage and ask the user if they want to see it.""" + self.index.toJSON(os.path.join(self.path, "index.json")) + + default_mask = stat.S_IMODE(os.stat(self.path).st_mode) + rechmod(self.path, default_mask) + + if os.access(self.path, os.W_OK): + default_mask = stat.S_IMODE( + os.stat(self.path).st_mode + ) # mode of files to be included + build_viewer( + os.path.join(self.path, "index.json"), + diag_name="My Diagnostics", + default_mask=default_mask, + ) + + if os.path.exists(os.path.join(self.path, "index.html")): + if prompt_user: + user_prompt = "Would you like to open in a browser? y/[n]: " + should_open = input(user_prompt) + if should_open and should_open.lower()[0] == "y": + import webbrowser + + index_path = os.path.join(self.path, "index.html") + url = "file://{path}".format(path=index_path) + webbrowser.open(url) + else: + raise RuntimeError("Failed to generate the viewer.") diff --git a/e3sm_diags/viewer/default_viewer.py b/e3sm_diags/viewer/default_viewer.py index 6a01d4d09..069746ef8 100644 --- a/e3sm_diags/viewer/default_viewer.py +++ b/e3sm_diags/viewer/default_viewer.py @@ -10,10 +10,10 @@ from typing import Dict import numpy -from cdp.cdp_viewer import OutputViewer from e3sm_diags.logger import custom_logger from e3sm_diags.parser import SET_TO_PARSER +from e3sm_diags.viewer.core_viewer import OutputViewer from . import lat_lon_viewer, utils diff --git a/e3sm_diags/viewer/enso_diags_viewer.py b/e3sm_diags/viewer/enso_diags_viewer.py index b8e941dee..72db8c731 100644 --- a/e3sm_diags/viewer/enso_diags_viewer.py +++ b/e3sm_diags/viewer/enso_diags_viewer.py @@ -1,9 +1,8 @@ import os from typing import Dict, List -from cdp.cdp_viewer import OutputViewer - from e3sm_diags.logger import custom_logger +from e3sm_diags.viewer.core_viewer import OutputViewer from .default_viewer import create_metadata from .utils import add_header, h1_to_h3 diff --git a/e3sm_diags/viewer/mean_2d_viewer.py b/e3sm_diags/viewer/mean_2d_viewer.py index e9a2332be..e5e717834 100644 --- a/e3sm_diags/viewer/mean_2d_viewer.py +++ b/e3sm_diags/viewer/mean_2d_viewer.py @@ -1,6 +1,6 @@ import os -from cdp.cdp_viewer import OutputViewer +from e3sm_diags.viewer.core_viewer import OutputViewer from .default_viewer import SEASONS, create_metadata, seasons_used from .utils import add_header, h1_to_h3 diff --git a/e3sm_diags/viewer/mp_partition_viewer.py b/e3sm_diags/viewer/mp_partition_viewer.py index de67b4466..4ceeba0e1 100644 --- a/e3sm_diags/viewer/mp_partition_viewer.py +++ b/e3sm_diags/viewer/mp_partition_viewer.py @@ -1,8 +1,7 @@ import os -from cdp.cdp_viewer import OutputViewer - from e3sm_diags.logger import custom_logger +from e3sm_diags.viewer.core_viewer import OutputViewer from .default_viewer import create_metadata from .utils import add_header, h1_to_h3 diff --git a/e3sm_diags/viewer/qbo_viewer.py b/e3sm_diags/viewer/qbo_viewer.py index ee33ddbb8..7e5a9b4a8 100644 --- a/e3sm_diags/viewer/qbo_viewer.py +++ b/e3sm_diags/viewer/qbo_viewer.py @@ -1,8 +1,7 @@ import os -from cdp.cdp_viewer import OutputViewer - from e3sm_diags.logger import custom_logger +from e3sm_diags.viewer.core_viewer import OutputViewer from .default_viewer import create_metadata from .utils import add_header, h1_to_h3 diff --git a/e3sm_diags/viewer/streamflow_viewer.py b/e3sm_diags/viewer/streamflow_viewer.py index 2e9801cdf..525b50ac0 100644 --- a/e3sm_diags/viewer/streamflow_viewer.py +++ b/e3sm_diags/viewer/streamflow_viewer.py @@ -1,6 +1,6 @@ import os -from cdp.cdp_viewer import OutputViewer +from e3sm_diags.viewer.core_viewer import OutputViewer from .default_viewer import create_metadata from .utils import add_header, h1_to_h3 diff --git a/e3sm_diags/viewer/tc_analysis_viewer.py b/e3sm_diags/viewer/tc_analysis_viewer.py index 97f199de2..2e3ec7269 100644 --- a/e3sm_diags/viewer/tc_analysis_viewer.py +++ b/e3sm_diags/viewer/tc_analysis_viewer.py @@ -1,6 +1,6 @@ import os -from cdp.cdp_viewer import OutputViewer +from e3sm_diags.viewer.core_viewer import OutputViewer from .utils import add_header, h1_to_h3 From f57506569d42367acd82823859a6ea73af4c8b83 Mon Sep 17 00:00:00 2001 From: Tom Vo Date: Mon, 8 Jan 2024 14:35:33 -0800 Subject: [PATCH 17/46] Fix default diagnostics attrs not being copied to parent CoreParameter (#778) --- e3sm_diags/run.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/e3sm_diags/run.py b/e3sm_diags/run.py index 370086df8..5589795d6 100644 --- a/e3sm_diags/run.py +++ b/e3sm_diags/run.py @@ -165,16 +165,16 @@ def _get_parameters_with_api_and_cfg( """ run_params = [] - if self.is_cfg_file_arg_set: - cfg_params = self._get_custom_params_from_cfg_file() - else: - run_type = parameters[0].run_type - cfg_params = self._get_default_params_from_cfg_file(run_type) - if len(self.sets_to_run) == 0: self.sets_to_run = DEFAULT_SETS for set_name in self.sets_to_run: + if self.is_cfg_file_arg_set: + cfg_params = self._get_custom_params_from_cfg_file() + else: + run_type = parameters[0].run_type + cfg_params = self._get_default_params_from_cfg_file(run_type) + param = self._get_instance_of_param_class( SET_TO_PARAMETERS[set_name], parameters ) @@ -186,14 +186,9 @@ def _get_parameters_with_api_and_cfg( self._remove_attrs_with_default_values(param) param.sets = [set_name] - # # FIXME: Make a deep copy of cfg_params because there is some - # buggy code in this method that changes parameter attributes in - # place, which affects downstream operations. The original - # cfg_params needs to be perserved for each iteration of this - # for loop. params = self.parser.get_parameters( orig_parameters=param, - other_parameters=copy.deepcopy(cfg_params), + other_parameters=cfg_params, cmd_default_vars=False, argparse_vals_only=False, ) From a12ac88d039e637ba99bd05c34617c08b8ec1276 Mon Sep 17 00:00:00 2001 From: Tom Vo Date: Fri, 19 Jan 2024 10:01:11 -0800 Subject: [PATCH 18/46] Bump to 2.10.1 (#780) --- e3sm_diags/__init__.py | 2 +- setup.py | 2 +- tbump.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/e3sm_diags/__init__.py b/e3sm_diags/__init__.py index 71fb676c4..51beb8d1a 100644 --- a/e3sm_diags/__init__.py +++ b/e3sm_diags/__init__.py @@ -6,7 +6,7 @@ # issue with dask when using ESMF with system compilers. import shapely -__version__ = "v2.10.1rc1" +__version__ = "v2.10.1" INSTALL_PATH = os.path.join(sys.prefix, "share/e3sm_diags/") # Disable MPI in cdms2, which is not currently supported by E3SM-unified diff --git a/setup.py b/setup.py index 6aaeed4f1..a76b7de7c 100644 --- a/setup.py +++ b/setup.py @@ -152,7 +152,7 @@ def get_all_files_in_dir(directory, pattern): "Programming Language :: Python :: 3.10", ], name="e3sm_diags", - version="2.10.1rc1", + version="2.10.1", author="Chengzhu (Jill) Zhang, Tom Vo, Ryan Forsyth, Chris Golaz and Zeshawn Shaheen", author_email="zhang40@llnl.gov", description="E3SM Diagnostics", diff --git a/tbump.toml b/tbump.toml index 3e4cc97ae..3e4f9ff71 100644 --- a/tbump.toml +++ b/tbump.toml @@ -2,7 +2,7 @@ github_url = "https://github.com/E3SM-Project/e3sm_diags" [version] -current = "2.10.1rc1" +current = "2.10.1" # Example of a semver regexp. # Make sure this matches current_version before From 2cd644a911eeba63fc45902bda54aac72ba9088f Mon Sep 17 00:00:00 2001 From: Tom Vo Date: Wed, 24 Jan 2024 16:00:52 -0800 Subject: [PATCH 19/46] Fix `cosp_histogram_driver.py` not writing mean variables to netCDF (#782) --- e3sm_diags/driver/cosp_histogram_driver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e3sm_diags/driver/cosp_histogram_driver.py b/e3sm_diags/driver/cosp_histogram_driver.py index ab42bdffe..5018ad389 100755 --- a/e3sm_diags/driver/cosp_histogram_driver.py +++ b/e3sm_diags/driver/cosp_histogram_driver.py @@ -121,8 +121,8 @@ def run_diag(parameter: CoreParameter) -> CoreParameter: ) utils.general.save_ncfiles( parameter.current_set, - mv1_domain, - mv2_domain, + mv1_domain_mean, + mv2_domain_mean, diff, parameter, ) From 64ffabc1ea85c242a8e5454913c0cdf577607e88 Mon Sep 17 00:00:00 2001 From: Jill Chengzhu Zhang Date: Tue, 30 Jan 2024 12:27:58 -0800 Subject: [PATCH 20/46] update zppy cfg for perlmutter (#783) --- examples/use_zppy_to_run_e3sm_diags/README.md | 2 +- ...g_for_complete_e3sm_diags_run_on_NERSC.cfg | 22 ++++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/examples/use_zppy_to_run_e3sm_diags/README.md b/examples/use_zppy_to_run_e3sm_diags/README.md index 0e06d3e38..7439e54f2 100644 --- a/examples/use_zppy_to_run_e3sm_diags/README.md +++ b/examples/use_zppy_to_run_e3sm_diags/README.md @@ -7,6 +7,6 @@ bash zstash_extract_E3SM_data_from_NERSC_HPSS.sh 2. Generate a configuration file for zppy to run a complete set of E3SM Diags. An example is provided in ``zppy_config_for_complete_e3sm_diags_run_on_NERSC.cfg``. To run the script: -source /global/common/software/e3sm/anaconda_envs/load_latest_e3sm_unified_cori-haswell.sh +source /global/common/software/e3sm/anaconda_envs/load_latest_e3sm_unified_pm-cpu.sh zppy -c zppy_config_for_complete_e3sm_diags_run_on_NERSC.cfg diff --git a/examples/use_zppy_to_run_e3sm_diags/zppy_config_for_complete_e3sm_diags_run_on_NERSC.cfg b/examples/use_zppy_to_run_e3sm_diags/zppy_config_for_complete_e3sm_diags_run_on_NERSC.cfg index 4cf0b90dc..69b9a0b4f 100644 --- a/examples/use_zppy_to_run_e3sm_diags/zppy_config_for_complete_e3sm_diags_run_on_NERSC.cfg +++ b/examples/use_zppy_to_run_e3sm_diags/zppy_config_for_complete_e3sm_diags_run_on_NERSC.cfg @@ -1,14 +1,15 @@ [default] # Edit the default session to provide simulation data and machine specific info. case = v2.LR.historical_0101 -environment_commands = "source /global/common/software/e3sm/anaconda_envs/load_latest_e3sm_unified_cori-haswell.sh" +environment_commands = "source /global/common/software/e3sm/anaconda_envs/load_latest_e3sm_unified_pm-cpu.sh" input = "/global/cfs/cdirs/e3smpub/E3SM_simulations/v2.LR.historical_0101" input_subdir = archive/atm/hist mapping_file = "/global/homes/z/zender/data/maps/map_ne30pg2_to_cmip6_180x360_aave.20200201.nc" -output = "/global/cscratch1/sd/chengzhu/e3sm_diags_zppy_test_complete_run_output/v2.LR.historical_0101_20220909" -partition = "haswell" +output = "/global/cfs/cdirs/e3sm/chengzhu/e3sm_diags_zppy_test_complete_run_output/v2.LR.historical_0101_20240130" +constraint = "cpu" +partition = "" qos = "regular" -www = "/global/cfs/cdirs/e3sm/www/chengzhu/e3sm_diags_zppy_test_complete_run_www_20220913" +www = "/global/cfs/cdirs/e3sm/www/chengzhu/e3sm_diags_zppy_test_complete_run_www_20240130" [climo] active = True @@ -73,8 +74,17 @@ years = "1985:2014:30", [[ atm_monthly_180x360_aave ]] climo_diurnal_frequency = "diurnal_8xdaily" climo_diurnal_subsection = "atm_monthly_diurnal_8xdaily_180x360_aave" - partition = "haswell" qos = "regular" walltime = "3:00:00" - +[global_time_series] +active = True +atmosphere_only = True # Available in E3SM Unified 1.5.1 +years = "1985-2014", +ts_num_years = 30 +figstr= "v2_historical_0101" +# moc_file needed for ocean component +moc_file = "mocTimeSeries_1870-2014.nc" +experiment_name = "v2.LR.historical_0101" +ts_years = "1985-2014", +climo_years = "1985-2014", From d95153599ac26288b440979e97c59966ec66ad12 Mon Sep 17 00:00:00 2001 From: Jill Chengzhu Zhang Date: Tue, 30 Jan 2024 12:33:36 -0800 Subject: [PATCH 21/46] Update README.md --- examples/use_zppy_to_run_e3sm_diags/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/use_zppy_to_run_e3sm_diags/README.md b/examples/use_zppy_to_run_e3sm_diags/README.md index 7439e54f2..893ddaa9f 100644 --- a/examples/use_zppy_to_run_e3sm_diags/README.md +++ b/examples/use_zppy_to_run_e3sm_diags/README.md @@ -8,5 +8,6 @@ bash zstash_extract_E3SM_data_from_NERSC_HPSS.sh 2. Generate a configuration file for zppy to run a complete set of E3SM Diags. An example is provided in ``zppy_config_for_complete_e3sm_diags_run_on_NERSC.cfg``. To run the script: source /global/common/software/e3sm/anaconda_envs/load_latest_e3sm_unified_pm-cpu.sh + zppy -c zppy_config_for_complete_e3sm_diags_run_on_NERSC.cfg From 29acef227310576b3832e17fa00eb68235032110 Mon Sep 17 00:00:00 2001 From: Jill Chengzhu Zhang Date: Wed, 7 Feb 2024 16:11:28 -0800 Subject: [PATCH 22/46] Support monthly average and variable name convention for EAMxx (#712) * support monthly average in viewer * add 12 monthly mean to core sets as options * Initial modifications for EAMxx variable names * add monthly mean option for aerosol budget tables * update SCREAM variable convention * more update for supporting monthly data --------- Co-authored-by: Chris Golaz --- e3sm_diags/derivations/acme.py | 99 ++++++++- .../aerosol_budget_model_vs_model.cfg | 2 +- .../aerosol_budget_model_vs_obs.cfg | 2 +- .../cosp_histogram_model_vs_model.cfg | 6 +- .../cosp_histogram_model_vs_obs.cfg | 6 +- .../default_diags/lat_lon_model_vs_model.cfg | 142 ++++++------ .../default_diags/lat_lon_model_vs_obs.cfg | 208 +++++++++--------- .../meridional_mean_2d_model_vs_model.cfg | 8 +- .../meridional_mean_2d_model_vs_obs.cfg | 16 +- .../default_diags/polar_model_vs_model.cfg | 72 +++--- .../default_diags/polar_model_vs_obs.cfg | 100 ++++----- .../zonal_mean_2d_model_vs_model.cfg | 10 +- .../zonal_mean_2d_model_vs_obs.cfg | 32 +-- ...al_mean_2d_stratosphere_model_vs_model.cfg | 12 +- ...onal_mean_2d_stratosphere_model_vs_obs.cfg | 20 +- .../zonal_mean_xy_model_vs_model.cfg | 111 +++++----- .../zonal_mean_xy_model_vs_obs.cfg | 142 ++++++------ e3sm_diags/driver/utils/dataset.py | 20 +- e3sm_diags/viewer/default_viewer.py | 20 +- examples/run_v2_9_0_all_sets_E3SM_machines.py | 2 +- 20 files changed, 576 insertions(+), 454 deletions(-) diff --git a/e3sm_diags/derivations/acme.py b/e3sm_diags/derivations/acme.py index 52e602e64..5e58c0ba1 100644 --- a/e3sm_diags/derivations/acme.py +++ b/e3sm_diags/derivations/acme.py @@ -374,6 +374,13 @@ def restom(fsnt, flnt): return var +def restom3(swdn, swup, lwup): + """TOM(top of model) Radiative flux""" + var = swdn - swup - lwup + var.long_name = "TOM(top of model) Radiative flux" + return var + + def restoa(fsnt, flnt): """TOA(top of atmosphere) Radiative flux""" var = fsnt - flnt @@ -619,6 +626,10 @@ def cosp_histogram_standardize(cld: "FileVariable"): (("pr",), lambda pr: qflxconvert_units(rename(pr))), (("PRECC", "PRECL"), lambda precc, precl: prect(precc, precl)), (("sat_gauge_precip",), rename), + ( + ("PrecipLiqSurfMassFlux", "PrecipIceSurfMassFlux"), + lambda precl, preci: prect(precl, preci), + ), # EAMxx ] ), "PRECST": OrderedDict( @@ -654,9 +665,18 @@ def cosp_histogram_standardize(cld: "FileVariable"): ("prw",), lambda prw: convert_units(rename(prw), target_units="kg/m2"), ), + ( + ("VapWaterPath",), # EAMxx + lambda prw: convert_units(rename(prw), target_units="kg/m2"), + ), + ] + ), + "SOLIN": OrderedDict( + [ + (("rsdt",), rename), + (("SW_flux_dn_at_model_top",), rename), # EAMxx ] ), - "SOLIN": OrderedDict([(("rsdt",), rename)]), "ALBEDO": OrderedDict( [ (("ALBEDO",), rename), @@ -705,6 +725,10 @@ def cosp_histogram_standardize(cld: "FileVariable"): lambda fsntoa, fsntoac: swcf(fsntoa, fsntoac), ), (("rsut", "rsutcs"), lambda rsutcs, rsut: swcf(rsut, rsutcs)), + ( + ("SW_flux_up_at_model_top", "SW_clrsky_flux_up_at_model_top"), + lambda rsutcs, rsut: swcf(rsut, rsutcs), + ), # EAMxx ] ), "SWCFSRF": OrderedDict( @@ -913,15 +937,29 @@ def cosp_histogram_standardize(cld: "FileVariable"): ), ] ), - "FLUT": OrderedDict([(("rlut",), rename)]), + "FLUT": OrderedDict( + [ + (("rlut",), rename), + (("LW_flux_up_at_model_top",), rename), # EAMxx + ] + ), "FSUTOA": OrderedDict([(("rsut",), rename)]), "FSUTOAC": OrderedDict([(("rsutcs",), rename)]), "FLNT": OrderedDict([(("FLNT",), rename)]), - "FLUTC": OrderedDict([(("rlutcs",), rename)]), + "FLUTC": OrderedDict( + [ + (("rlutcs",), rename), + (("LW_clrsky_flux_up_at_model_top",), rename), # EAMxx + ] + ), "FSNTOA": OrderedDict( [ (("FSNTOA",), rename), (("rsdt", "rsut"), lambda rsdt, rsut: rst(rsdt, rsut)), + ( + ("SW_flux_dn_at_model_top", "SW_flux_up_at_model_top"), + lambda rsdt, rsut: rst(rsdt, rsut), + ), # EAMxx ] ), "FSNTOAC": OrderedDict( @@ -929,6 +967,10 @@ def cosp_histogram_standardize(cld: "FileVariable"): # Note: CERES_EBAF data in amwg obs sets misspells "units" as "lunits" (("FSNTOAC",), rename), (("rsdt", "rsutcs"), lambda rsdt, rsutcs: rstcs(rsdt, rsutcs)), + ( + ("SW_flux_dn_at_model_top", "SW_clrsky_flux_up_at_model_top"), + lambda rsdt, rsutcs: rstcs(rsdt, rsutcs), + ), # EAMxx ] ), "RESTOM": OrderedDict( @@ -936,6 +978,14 @@ def cosp_histogram_standardize(cld: "FileVariable"): (("RESTOA",), rename), (("toa_net_all_mon",), rename), (("FSNT", "FLNT"), lambda fsnt, flnt: restom(fsnt, flnt)), + ( + ( + "SW_flux_dn_at_model_top", + "SW_flux_up_at_model_top", + "LW_flux_up_at_model_top", + ), + lambda swdn, swup, lwup: restom3(swdn, swup, lwup), + ), # EAMxx (("rtmt",), rename), ] ), @@ -944,6 +994,14 @@ def cosp_histogram_standardize(cld: "FileVariable"): (("RESTOM",), rename), (("toa_net_all_mon",), rename), (("FSNT", "FLNT"), lambda fsnt, flnt: restoa(fsnt, flnt)), + ( + ( + "SW_flux_dn_at_model_top", + "SW_flux_up_at_model_top", + "LW_flux_up_at_model_top", + ), + lambda swdn, swup, lwup: restom3(swdn, swup, lwup), + ), # EAMxx (("rtmt",), rename), ] ), @@ -983,6 +1041,10 @@ def cosp_histogram_standardize(cld: "FileVariable"): [ (("PSL",), lambda psl: convert_units(psl, target_units="mbar")), (("psl",), lambda psl: convert_units(psl, target_units="mbar")), + ( + ("SeaLevelPressure",), + lambda psl: convert_units(psl, target_units="mbar"), + ), # EAMxx ] ), "T": OrderedDict( @@ -1011,6 +1073,7 @@ def cosp_histogram_standardize(cld: "FileVariable"): lambda t: convert_units(t, target_units="DegC"), ), (("tas",), lambda t: convert_units(t, target_units="DegC")), + (("T_2m",), lambda t: convert_units(t, target_units="DegC")), # EAMxx ] ), # Surface water flux: kg/((m^2)*s) @@ -1018,6 +1081,7 @@ def cosp_histogram_standardize(cld: "FileVariable"): [ (("evspsbl",), rename), (("QFLX",), lambda qflx: qflxconvert_units(qflx)), + (("surf_evap",), lambda qflx: qflxconvert_units(qflx)), # EAMxx ] ), # Surface latent heat flux: W/(m^2) @@ -1025,9 +1089,15 @@ def cosp_histogram_standardize(cld: "FileVariable"): [ (("hfls",), rename), (("QFLX",), lambda qflx: qflx_convert_to_lhflx_approxi(qflx)), + (("surface_upward_latent_heat_flux",), rename), # EAMxx "s^-3 kg" + ] + ), + "SHFLX": OrderedDict( + [ + (("hfss",), rename), + (("surf_sens_flux",), rename), # EAMxx ] ), - "SHFLX": OrderedDict([(("hfss",), rename)]), "TGCLDLWP_OCN": OrderedDict( [ ( @@ -1454,7 +1524,12 @@ def cosp_histogram_standardize(cld: "FileVariable"): ), # Surface temperature: Degrees C # (Temperature of the surface (land/water) itself, not the air) - "TS": OrderedDict([(("ts",), rename)]), + "TS": OrderedDict( + [ + (("ts",), rename), + (("surf_radiative_T",), rename), # EAMxx + ] + ), "PS": OrderedDict([(("ps",), rename)]), "U10": OrderedDict([(("sfcWind",), rename), (("si10",), rename)]), "QREFHT": OrderedDict( @@ -1471,8 +1546,18 @@ def cosp_histogram_standardize(cld: "FileVariable"): ] ), "PRECC": OrderedDict([(("prc",), rename)]), - "TAUX": OrderedDict([(("tauu",), lambda tauu: -tauu)]), - "TAUY": OrderedDict([(("tauv",), lambda tauv: -tauv)]), + "TAUX": OrderedDict( + [ + (("tauu",), lambda tauu: -tauu), + (("surf_mom_flux_U",), lambda tauu: -tauu), # EAMxx + ] + ), + "TAUY": OrderedDict( + [ + (("tauv",), lambda tauv: -tauv), + (("surf_mom_flux_V",), lambda tauv: -tauv), # EAMxx + ] + ), "CLDICE": OrderedDict([(("cli",), rename)]), "TGCLDIWP": OrderedDict([(("clivi",), rename)]), "CLDLIQ": OrderedDict([(("clw",), rename)]), diff --git a/e3sm_diags/driver/default_diags/aerosol_budget_model_vs_model.cfg b/e3sm_diags/driver/default_diags/aerosol_budget_model_vs_model.cfg index da879ce83..9edf9cec9 100644 --- a/e3sm_diags/driver/default_diags/aerosol_budget_model_vs_model.cfg +++ b/e3sm_diags/driver/default_diags/aerosol_budget_model_vs_model.cfg @@ -1,4 +1,4 @@ [#] sets = ["aerosol_budget"] variables = ["bc, dst, mom, ncl, pom, so4, soa"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] diff --git a/e3sm_diags/driver/default_diags/aerosol_budget_model_vs_obs.cfg b/e3sm_diags/driver/default_diags/aerosol_budget_model_vs_obs.cfg index da879ce83..9edf9cec9 100644 --- a/e3sm_diags/driver/default_diags/aerosol_budget_model_vs_obs.cfg +++ b/e3sm_diags/driver/default_diags/aerosol_budget_model_vs_obs.cfg @@ -1,4 +1,4 @@ [#] sets = ["aerosol_budget"] variables = ["bc, dst, mom, ncl, pom, so4, soa"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] diff --git a/e3sm_diags/driver/default_diags/cosp_histogram_model_vs_model.cfg b/e3sm_diags/driver/default_diags/cosp_histogram_model_vs_model.cfg index 0813fd4b7..fdf92d8e9 100644 --- a/e3sm_diags/driver/default_diags/cosp_histogram_model_vs_model.cfg +++ b/e3sm_diags/driver/default_diags/cosp_histogram_model_vs_model.cfg @@ -4,7 +4,7 @@ case_id = "model_vs_model" variables = ["COSP_HISTOGRAM_MISR"] contour_levels = [0,0.5,1.0,1.5,2.0,2.5,3.0,3.5,4.0,4.5,5.0,5.5] diff_levels = [-3.0,-2.5,-2.0,-1.5,-1.0,-0.5,0,0.5,1.0,1.5,2.0,2.5,3.0] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["cosp_histogram"] @@ -12,7 +12,7 @@ case_id = "model_vs_model" variables = ["COSP_HISTOGRAM_MODIS"] contour_levels = [0,0.5,1.0,1.5,2.0,2.5,3.0,3.5,4.0,4.5,5.0,5.5] diff_levels = [-3.0,-2.5,-2.0,-1.5,-1.0,-0.5,0,0.5,1.0,1.5,2.0,2.5,3.0] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["cosp_histogram"] @@ -20,4 +20,4 @@ case_id = "model_vs_model" variables = ["COSP_HISTOGRAM_ISCCP"] contour_levels = [0,0.5,1.0,1.5,2.0,2.5,3.0,3.5,4.0,4.5,5.0,5.5] diff_levels = [-3.0,-2.5,-2.0,-1.5,-1.0,-0.5,0,0.5,1.0,1.5,2.0,2.5,3.0] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] diff --git a/e3sm_diags/driver/default_diags/cosp_histogram_model_vs_obs.cfg b/e3sm_diags/driver/default_diags/cosp_histogram_model_vs_obs.cfg index de8386fa8..a810be9c6 100644 --- a/e3sm_diags/driver/default_diags/cosp_histogram_model_vs_obs.cfg +++ b/e3sm_diags/driver/default_diags/cosp_histogram_model_vs_obs.cfg @@ -6,7 +6,7 @@ ref_name = "MISRCOSP" reference_name = "MISR COSP" contour_levels = [0,0.5,1.0,1.5,2.0,2.5,3.0,3.5,4.0,4.5,5.0,5.5] diff_levels = [-3.0,-2.5,-2.0,-1.5,-1.0,-0.5,0,0.5,1.0,1.5,2.0,2.5,3.0] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["cosp_histogram"] @@ -16,7 +16,7 @@ ref_name = "MODISCOSP" reference_name = "MODIS COSP" contour_levels = [0,0.5,1.0,1.5,2.0,2.5,3.0,3.5,4.0,4.5,5.0,5.5] diff_levels = [-3.0,-2.5,-2.0,-1.5,-1.0,-0.5,0,0.5,1.0,1.5,2.0,2.5,3.0] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["cosp_histogram"] @@ -26,4 +26,4 @@ ref_name = "ISCCPCOSP" reference_name = "ISCCP COSP" contour_levels = [0,0.5,1.0,1.5,2.0,2.5,3.0,3.5,4.0,4.5,5.0,5.5] diff_levels = [-3.0,-2.5,-2.0,-1.5,-1.0,-0.5,0,0.5,1.0,1.5,2.0,2.5,3.0] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] diff --git a/e3sm_diags/driver/default_diags/lat_lon_model_vs_model.cfg b/e3sm_diags/driver/default_diags/lat_lon_model_vs_model.cfg index d0e883b04..9e4245c97 100644 --- a/e3sm_diags/driver/default_diags/lat_lon_model_vs_model.cfg +++ b/e3sm_diags/driver/default_diags/lat_lon_model_vs_model.cfg @@ -2,7 +2,7 @@ sets = ["lat_lon"] case_id = "model_vs_model" variables = ["PRECT"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["global"] test_colormap = "WhiteBlueGreenYellowRed.rgb" reference_colormap = "WhiteBlueGreenYellowRed.rgb" @@ -15,7 +15,7 @@ diff_levels = [-2.5, -2, -1.5, -1, -0.5, -0.25, 0.25, 0.5, 1, 1.5, 2, 2.5] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["SST"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-1, 0, 1, 3, 6, 9, 12, 15, 18, 20, 22, 24, 26, 28, 29] diff_levels = [-2.5, -2, -1.5, -1, -0.5, -0.1, 0.1, 0.5, 1, 1.5, 2.0, 2.5] regrid_method = "bilinear" @@ -25,7 +25,7 @@ regrid_method = "bilinear" sets = ["lat_lon"] case_id = "model_vs_model" variables = ["SOLIN"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450] diff_levels = [-2.5, -2, -1.5, -1, -0.5, -0.25, 0.25, 0.5, 1, 1.5, 2, 2.5] @@ -34,7 +34,7 @@ diff_levels = [-2.5, -2, -1.5, -1, -0.5, -0.25, 0.25, 0.5, 1, 1.5, 2, 2.5] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["ALBEDO"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75] diff_levels = [-0.13, -0.11, -0.09, -0.07, -0.05, -0.03, -0.01, 0.01, 0.03, 0.05, 0.07, 0.09, 0.11, 0.13] @@ -43,7 +43,7 @@ diff_levels = [-0.13, -0.11, -0.09, -0.07, -0.05, -0.03, -0.01, 0.01, 0.03, 0.05 sets = ["lat_lon"] case_id = "model_vs_model" variables = ["ALBEDOC"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75] diff_levels = [-0.13, -0.11, -0.09, -0.07, -0.05, -0.03, -0.01, 0.01, 0.03, 0.05, 0.07, 0.09, 0.11, 0.13] @@ -52,7 +52,7 @@ diff_levels = [-0.13, -0.11, -0.09, -0.07, -0.05, -0.03, -0.01, 0.01, 0.03, 0.05 sets = ["lat_lon"] case_id = "model_vs_model" variables = ["RESTOM"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-120, -100, -80, -60, -40, -20, 0, 20, 40, 60, 80] diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] @@ -61,7 +61,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["FLUT"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [120, 140, 160, 180, 200, 220, 240, 260, 280, 300] diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] @@ -70,7 +70,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["FLUTC"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [120, 140, 160, 180, 200, 220, 240, 260, 280, 300] diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] @@ -79,7 +79,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["FSNTOA"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [50, 75, 100, 125, 150, 175, 200, 225, 250, 275, 300, 325, 350] diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] @@ -88,7 +88,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["FSNTOAC"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [50, 75, 100, 125, 150, 175, 200, 225, 250, 275, 300, 325, 350, 375] diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] @@ -97,7 +97,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["SWCF"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["global"] contour_levels = [-120, -110, -100, -90, -80, -70, -60, -50, -40, -30, -20, -10, 0] diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] @@ -107,7 +107,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["LWCF"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 10, 20, 30, 40, 50, 60, 70, 80] diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] @@ -116,7 +116,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["NETCF"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-70, -60, -50, -40, -30, -20, -10, 0, 10, 20] diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] @@ -125,7 +125,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["ALBEDO_SRF"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75] diff_levels = [-0.13, -0.11, -0.09, -0.07, -0.05, -0.03, -0.01, 0.01, 0.03, 0.05, 0.07, 0.09, 0.11, 0.13] @@ -134,7 +134,7 @@ diff_levels = [-0.13, -0.11, -0.09, -0.07, -0.05, -0.03, -0.01, 0.01, 0.03, 0.05 sets = ["lat_lon"] case_id = "model_vs_model" variables = ["SWCFSRF"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-170, -150, -135, -120, -105, -90, -75, -60, -45, -30, -15, 0, 15, 30, 45] diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] @@ -143,7 +143,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["LWCFSRF"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 10, 20, 30, 40, 50, 60, 70, 80] diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] @@ -152,7 +152,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["NETCF_SRF"] -seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12","DJF", "MAM", "JJA", "SON"] contour_levels = [-135, -120, -105, -90, -75, -60, -45, -30, -15, 0, 15, 30, 45] diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] @@ -161,7 +161,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["FLDS"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450] diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] @@ -170,7 +170,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["FLDSC"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450] diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] @@ -179,7 +179,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["FLNS"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 20, 40, 60, 80, 100, 120, 140, 160] diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] @@ -188,7 +188,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["FLNSC"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 20, 40, 60, 80, 100, 120, 140, 160] diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] @@ -197,7 +197,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["FSDS"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450] diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] @@ -206,7 +206,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["FSDSC"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450] diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] @@ -215,7 +215,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["FSNS"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400] diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] @@ -224,7 +224,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["FSNSC"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400] diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] @@ -233,7 +233,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["LHFLX"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0,5, 15, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300] diff_levels = [-75, -50, -25, -10, -5, -2, 2, 5, 10, 25, 50, 75] @@ -242,7 +242,7 @@ diff_levels = [-75, -50, -25, -10, -5, -2, 2, 5, 10, 25, 50, 75] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["SHFLX"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-100, -75, -50, -25, -10, 0, 10, 25, 50, 75, 100, 125, 150] diff_levels = [-75, -50, -25, -10, -5, -2, 2, 5, 10, 25, 50, 75] @@ -251,7 +251,7 @@ diff_levels = [-75, -50, -25, -10, -5, -2, 2, 5, 10, 25, 50, 75] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["NET_FLUX_SRF"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-200, -160, -120, -80, -40, 0, 40, 80, 120, 160, 200] diff_levels = [-75, -50, -25, -10, -5, -2, 2, 5, 10, 25, 50, 75] @@ -260,7 +260,7 @@ diff_levels = [-75, -50, -25, -10, -5, -2, 2, 5, 10, 25, 50, 75] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["TMQ"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60] diff_levels = [-12, -9, -6, -4, -3, -2, -1, 1, 2, 3, 4, 6, 9, 12] @@ -269,7 +269,7 @@ diff_levels = [-12, -9, -6, -4, -3, -2, -1, 1, 2, 3, 4, 6, 9, 12] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["QREFHT"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.2, 0.5, 1, 2.5, 5, 7.5, 10, 12.5, 15, 17.5] diff_levels = [-5, -4, -3, -2, -1, -0.25, 0.25, 1, 2, 3, 4, 5] @@ -277,7 +277,7 @@ diff_levels = [-5, -4, -3, -2, -1, -0.25, 0.25, 1, 2, 3, 4, 5] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["U10"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" contour_levels = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] @@ -287,7 +287,7 @@ diff_levels = [-8, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6, 8] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["U"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [850.0] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" @@ -300,7 +300,7 @@ regrid_method = "bilinear" sets = ["lat_lon"] case_id = "model_vs_model" variables = ["U"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [200.0] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" @@ -312,7 +312,7 @@ diff_levels = [-15, -10, -8, -6, -4, -2, -1, 1, 2, 4, 6, 8, 10, 15] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["OMEGA"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [850.0] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" @@ -325,7 +325,7 @@ regrid_method = "bilinear" sets = ["lat_lon"] case_id = "model_vs_model" variables = ["OMEGA"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [500.0] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" @@ -337,7 +337,7 @@ diff_levels = [-40,-30,-20,-16,-12,-8,-4,4,8,12,16,20,30,40] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["OMEGA"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [200.0] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" @@ -348,7 +348,7 @@ diff_levels = [-20,-15,-10,-8,-6,-4,-2,2,4,6,8,10,15,20] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["Z3"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [500.0] contour_levels = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58] diff_levels = [-0.5, -0.4, -0.3, -0.2, -0.1, 0.1, 0.2, 0.3, 0.4, 0.5] @@ -358,7 +358,7 @@ diff_levels = [-0.5, -0.4, -0.3, -0.2, -0.1, 0.1, 0.2, 0.3, 0.4, 0.5] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["T"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [850.0] contour_levels = [240, 245, 250, 255, 260, 265, 270, 275, 280, 285, 290, 295] diff_levels = [-5, -4, -3, -2, -1, -0.5, -0.25, 0.25, 0.5, 1, 2, 3, 4, 5] @@ -369,7 +369,7 @@ regrid_method = "bilinear" sets = ["lat_lon"] case_id = "model_vs_model" variables = ["T"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [200.0] contour_levels = [210, 212, 214, 216, 218, 220, 222, 224, 226] diff_levels = [-5, -4, -3, -2, -1, -0.5, -0.25, 0.25, 0.5, 1, 2, 3, 4, 5] @@ -379,7 +379,7 @@ diff_levels = [-5, -4, -3, -2, -1, -0.5, -0.25, 0.25, 0.5, 1, 2, 3, 4, 5] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["PSL"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [955, 965, 975,980, 985, 990, 995, 1000, 1005, 1010, 1015, 1020, 1025, 1035] diff_levels = [ -16, -12, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 12, 16] @@ -388,7 +388,7 @@ diff_levels = [ -16, -12, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 12, 16] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["CLDTOT_TAU1.3_ISCCP"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -400,7 +400,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["CLDTOT_TAU1.3_9.4_ISCCP"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -412,7 +412,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["CLDTOT_TAU9.4_ISCCP"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -424,7 +424,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["CLDTOT_TAU1.3_MISR"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -436,7 +436,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["CLDTOT_TAU1.3_9.4_MISR"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -448,7 +448,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["CLDTOT_TAU9.4_MISR"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -460,7 +460,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["CLDLOW_TAU1.3_MISR"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -472,7 +472,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["CLDLOW_TAU1.3_9.4_MISR"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -484,7 +484,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["CLDLOW_TAU9.4_MISR"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -496,7 +496,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["CLDTOT_TAU1.3_MODIS"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -508,7 +508,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["CLDTOT_TAU1.3_9.4_MODIS"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -520,7 +520,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["CLDTOT_TAU9.4_MODIS"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -532,7 +532,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["CLDHGH_TAU1.3_MODIS"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -544,7 +544,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["CLDHGH_TAU1.3_9.4_MODIS"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -556,7 +556,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["CLDHGH_TAU9.4_MODIS"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -568,7 +568,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["CLDTOT_CAL"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -580,7 +580,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["CLDLOW_CAL"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -592,7 +592,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["CLDMED_CAL"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -604,7 +604,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["CLDHGH_CAL"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -616,7 +616,7 @@ diff_levels = [-30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30] sets = ["lat_lon"] case_id = "model_vs_model" variables = ["TGCLDLWP_OCN"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -628,7 +628,7 @@ sets = ["lat_lon"] case_id = "model_vs_model" variables = ["AODVIS"] regions = ["global"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Oranges" reference_colormap = "Oranges" diff_colormap = "BrBG_r" @@ -640,7 +640,7 @@ sets = ["lat_lon"] case_id = "model_vs_model" variables = ["TREFHT"] regions = ["land"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-5, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 5] regrid_method = "bilinear" @@ -650,7 +650,7 @@ sets = ["lat_lon"] case_id = "model_vs_model" variables = ["TREFHT"] regions = ["global"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-5, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 5] @@ -659,7 +659,7 @@ sets = ["lat_lon"] case_id = "model_vs_model" variables = ["TREFMNAV"] regions = ["global"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-12, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 12] @@ -668,7 +668,7 @@ sets = ["lat_lon"] case_id = "model_vs_model" variables = ["TREFMXAV"] regions = ["global"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-12, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 12] @@ -677,7 +677,7 @@ sets = ["lat_lon"] case_id = "model_vs_model" variables = ["TREF_range"] regions = ["global"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [2, 4, 6, 8, 10, 12, 14, 16,18] diff_levels = [ -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8] @@ -686,7 +686,7 @@ sets = ["lat_lon"] case_id = "model_vs_model" variables = ["TREFMNAV"] regions = ["land"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-12, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 12] regrid_method = "bilinear" @@ -696,7 +696,7 @@ sets = ["lat_lon"] case_id = "model_vs_model" variables = ["TREFMXAV"] regions = ["land"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-12, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 12] regrid_method = "bilinear" @@ -706,7 +706,7 @@ sets = ["lat_lon"] case_id = "model_vs_model" variables = ["TREF_range"] regions = ["land"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [2, 4, 6, 8, 10, 12, 14, 16,18] diff_levels = [ -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8] regrid_method = "bilinear" @@ -716,7 +716,7 @@ sets = ["lat_lon"] case_id = "model_vs_model" variables = ["TAUXY"] regions = ["ocean"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Purples" reference_colormap = "Purples" diff_colormap = "RdBu" @@ -728,7 +728,7 @@ regrid_method = "bilinear" sets = ["lat_lon"] case_id = "model_vs_model" variables = ["TCO"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "WhiteBlueGreenYellowRed.rgb" reference_colormap = "WhiteBlueGreenYellowRed.rgb" diff_colormap = "diverging_bwr.rgb" diff --git a/e3sm_diags/driver/default_diags/lat_lon_model_vs_obs.cfg b/e3sm_diags/driver/default_diags/lat_lon_model_vs_obs.cfg index f9d303075..6e843423a 100644 --- a/e3sm_diags/driver/default_diags/lat_lon_model_vs_obs.cfg +++ b/e3sm_diags/driver/default_diags/lat_lon_model_vs_obs.cfg @@ -4,7 +4,7 @@ case_id = "GPCP_v3.2" variables = ["PRECT"] ref_name = "GPCP_v3.2" reference_name = "GPCP_v3.2" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["global"] test_colormap = "WhiteBlueGreenYellowRed.rgb" reference_colormap = "WhiteBlueGreenYellowRed.rgb" @@ -19,7 +19,7 @@ case_id = "GPCP_v2.3" variables = ["PRECT"] ref_name = "GPCP_v2.3" reference_name = "GPCP_v2.3" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["global"] test_colormap = "WhiteBlueGreenYellowRed.rgb" reference_colormap = "WhiteBlueGreenYellowRed.rgb" @@ -35,7 +35,7 @@ variables = ["TREFHT"] regions = ["land_60S90N"] ref_name = "CRU" reference_name = "CRU Global Monthly Mean T Land" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-15, -10, -5, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 5, 10, 15] regrid_method = "bilinear" @@ -48,7 +48,7 @@ case_id = "SST_HadISST" variables = ["SST"] ref_name = "HadISST" reference_name = "HadISST" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-1, 0, 1, 3, 6, 9, 12, 15, 18, 20, 22, 24, 26, 28, 29] diff_levels = [-5, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 5] regrid_method = "bilinear" @@ -60,7 +60,7 @@ case_id = "SST_CL_HadISST" variables = ["SST"] ref_name = "HadISST_CL" reference_name = "HadISST (Climatology)" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-1, 0, 1, 3, 6, 9, 12, 15, 18, 20, 22, 24, 26, 28, 29] diff_levels = [-5, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 5] regrid_method = "bilinear" @@ -72,7 +72,7 @@ case_id = "SST_PI_HadISST" variables = ["SST"] ref_name = "HadISST_PI" reference_name = "HadISST (Pre-Indust)" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-1, 0, 1, 3, 6, 9, 12, 15, 18, 20, 22, 24, 26, 28, 29] diff_levels = [-5, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 5] regrid_method = "bilinear" @@ -84,7 +84,7 @@ case_id = "SST_PD_HadISST" variables = ["SST"] ref_name = "HadISST_PD" reference_name = "HadISST (Present Day)" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-1, 0, 1, 3, 6, 9, 12, 15, 18, 20, 22, 24, 26, 28, 29] diff_levels = [-5, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 5] regrid_method = "bilinear" @@ -96,7 +96,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["SOLIN"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450] diff_levels = [-5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5] @@ -108,7 +108,7 @@ variables = ["ALBEDO"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" regions = ["75S75N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75] diff_levels = [-0.25, -0.2, -0.15, -0.1, -0.07, -0.05, -0.02, 0.02, 0.05, 0.07, 0.1, 0.15, 0.2, 0.25] @@ -119,7 +119,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["ALBEDOC"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75] diff_levels = [-0.25, -0.2, -0.15, -0.1, -0.07, -0.05, -0.02, 0.02, 0.05, 0.07, 0.1, 0.15, 0.2, 0.25] @@ -130,7 +130,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["RESTOM"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] contour_levels = [-120, -100, -80, -60, -40, -20, 0, 20, 40, 60, 80] diff_levels = [-50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50] @@ -152,7 +152,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["FLUT"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] contour_levels = [120, 140, 160, 180, 200, 220, 240, 260, 280, 300] diff_levels = [-50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50] @@ -174,7 +174,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["FLUTC"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] contour_levels = [120, 140, 160, 180, 200, 220, 240, 260, 280, 300] diff_levels = [-35, -30, -25, -20, -15, -10, -5, -2.5, 2.5, 5, 10, 15, 20, 25, 30, 35] @@ -196,7 +196,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["FSNTOA"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] contour_levels = [50, 75, 100, 125, 150, 175, 200, 225, 250, 275, 300, 325, 350] diff_levels = [-75, -50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50, 75] @@ -218,7 +218,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["FSNTOAC"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] contour_levels = [50, 75, 100, 125, 150, 175, 200, 225, 250, 275, 300, 325, 350, 375] diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 75] @@ -240,7 +240,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["SWCF"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] regions = ["global"] contour_levels = [-120, -110, -100, -90, -80, -70, -60, -50, -40, -30, -20, -10, 0] diff_levels = [-60, -50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50, 60] @@ -264,7 +264,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["LWCF"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 10, 20, 30, 40, 50, 60, 70, 80] diff_levels = [-35, -30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30, 35] @@ -275,7 +275,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["NETCF"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] contour_levels = [-70, -60, -50, -40, -30, -20, -10, 0, 10, 20] diff_levels = [-60, -50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50, 60] @@ -297,7 +297,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["ALBEDO_SRF"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75] diff_levels = [-0.25, -0.2, -0.15, -0.1, -0.07, -0.05, -0.02, 0.02, 0.05, 0.07, 0.1, 0.15, 0.2, 0.25] @@ -308,7 +308,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["SWCFSRF"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-170, -150, -135, -120, -105, -90, -75, -60, -45, -30, -15, 0, 15, 30, 45] diff_levels = [-75, -50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50, 75] @@ -319,7 +319,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["LWCFSRF"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 10, 20, 30, 40, 50, 60, 70, 80] diff_levels = [-35, -30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30, 35] @@ -330,7 +330,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["NETCF_SRF"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12","DJF", "MAM", "JJA", "SON"] contour_levels = [-135, -120, -105, -90, -75, -60, -45, -30, -15, 0, 15, 30, 45] diff_levels = [-75, -50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50, 75] @@ -341,7 +341,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["FLDS"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450] diff_levels = [-75, -50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50, 75] @@ -352,7 +352,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["FLDSC"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450] diff_levels = [-75, -50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50, 75] @@ -363,7 +363,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["FLNS"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 20, 40, 60, 80, 100, 120, 140, 160] diff_levels = [-50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50] @@ -374,7 +374,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["FLNSC"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 20, 40, 60, 80, 100, 120, 140, 160] diff_levels = [-50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50] @@ -385,7 +385,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["FSDS"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450] diff_levels = [-75, -50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50, 75] @@ -396,7 +396,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["FSDSC"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450] diff_levels = [-75, -50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50, 75] @@ -407,7 +407,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["FSNS"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400] diff_levels = [-75, -50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50, 75] @@ -418,7 +418,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["FSNSC"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400] diff_levels = [-75, -50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50, 75] @@ -429,7 +429,7 @@ case_id = "WHOI-OAFlux" variables = ["LHFLX"] ref_name = "OAFlux" reference_name = "WHOI OAFlux" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0,5, 15, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300] diff_levels = [-150, -120, -90, -60, -30, -20, -10, -5, 5, 10, 20, 30, 60, 90, 120, 150] @@ -440,7 +440,7 @@ case_id = "WHOI-OAFlux" variables = ["SHFLX"] ref_name = "OAFlux" reference_name = "WHOI OAFlux" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-100, -75, -50, -25, -10, 0, 10, 25, 50, 75, 100, 125, 150] diff_levels = [-100, -80, -60, -40, -20, -10, -5, 5, 10, 20, 40, 60, 80, 100] @@ -451,7 +451,7 @@ case_id = "ERA5" variables = ["LHFLX"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0,5, 15, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300] diff_levels = [-150, -120, -90, -60, -30, -20, -10, -5, 5, 10, 20, 30, 60, 90, 120, 150] @@ -462,7 +462,7 @@ case_id = "ERA5" variables = ["SHFLX"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-100, -75, -50, -25, -10, 0, 10, 25, 50, 75, 100, 125, 150] diff_levels = [-100, -80, -60, -40, -20, -10, -5, 5, 10, 20, 40, 60, 80, 100] @@ -473,7 +473,7 @@ case_id = "ERA5" variables = ["PSL"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [955, 965, 975,980, 985, 990, 995, 1000, 1005, 1010, 1015, 1020, 1025, 1035] diff_levels = [ -16, -12, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 12, 16] @@ -484,7 +484,7 @@ case_id = "ERA5" variables = ["FSNS"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400] diff_levels = [-75, -50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50, 75] @@ -495,7 +495,7 @@ case_id = "ERA5" variables = ["FLNS"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 20, 40, 60, 80, 100, 120, 140, 160] diff_levels = [-50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50] @@ -506,7 +506,7 @@ case_id = "ERA5" variables = ["NET_FLUX_SRF"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-200, -160, -120, -80, -40, 0, 40, 80, 120, 160, 200] diff_levels = [-75, -50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50, 75] @@ -517,7 +517,7 @@ case_id = "ERA5" variables = ["PRECT"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "WhiteBlueGreenYellowRed.rgb" reference_colormap = "WhiteBlueGreenYellowRed.rgb" diff_colormap = "BrBG" @@ -531,7 +531,7 @@ case_id = "ERA5" variables = ["TMQ"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60] diff_levels = [-12, -9, -6, -4, -3, -2, -1, 1, 2, 3, 4, 6, 9, 12] @@ -541,7 +541,7 @@ case_id = "ERA5" variables = ["QREFHT"] ref_name = "ERA5_ext" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.2, 0.5, 1, 2.5, 5, 7.5, 10, 12.5, 15, 17.5] diff_levels = [-5, -4, -3, -2, -1, -0.25, 0.25, 1, 2, 3, 4, 5] @@ -551,7 +551,7 @@ case_id = "ERA5" variables = ["U10"] ref_name = "ERA5_ext" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] diff_levels = [-8, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6, 8] @@ -561,7 +561,7 @@ case_id = "ERA5" variables = ["U"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [850.0] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" @@ -576,7 +576,7 @@ case_id = "ERA5" variables = ["U"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] plevs = [200.0] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" @@ -604,7 +604,7 @@ case_id = "ERA5" variables = ["Z3"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [500.0] contour_levels = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58] diff_levels = [-1.8, -1.4, -1.0, -0.6, -0.2, 0.2, 0.6, 1.0, 1.4, 1.8] @@ -615,7 +615,7 @@ case_id = "ERA5" variables = ["OMEGA"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["DJF", "MAM", "JJA", "SON", "ANN"] +seasons = ["DJF", "MAM", "JJA", "SON", "ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] plevs = [200.0] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" @@ -629,7 +629,7 @@ case_id = "ERA5" variables = ["OMEGA"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["DJF", "MAM", "JJA", "SON", "ANN"] +seasons = ["DJF", "MAM", "JJA", "SON", "ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] plevs = [500.0] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" @@ -643,7 +643,7 @@ case_id = "ERA5" variables = ["OMEGA"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["DJF", "MAM", "JJA", "SON", "ANN"] +seasons = ["DJF", "MAM", "JJA", "SON", "ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] plevs = [850.0] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" @@ -658,7 +658,7 @@ case_id = "ERA5" variables = ["T"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] plevs = [850.0] contour_levels = [240, 245, 250, 255, 260, 265, 270, 275, 280, 285, 290, 295] diff_levels = [-10, -7.5, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 7.5, 10] @@ -684,7 +684,7 @@ case_id = "ERA5" variables = ["T"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] plevs = [200.0] contour_levels = [210, 212, 214, 216, 218, 220, 222, 224, 226] diff_levels = [-10, -7.5, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 7.5, 10] @@ -709,7 +709,7 @@ variables = ["TAUXY"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" regions = ["ocean"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Purples" reference_colormap = "Purples" diff_colormap = "RdBu" @@ -723,7 +723,7 @@ case_id = "MERRA2" variables = ["LHFLX"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0,5, 15, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300] diff_levels = [-150, -120, -90, -60, -30, -20, -10, -5, 5, 10, 20, 30, 60, 90, 120, 150] @@ -734,7 +734,7 @@ case_id = "MERRA2" variables = ["SHFLX"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-100, -75, -50, -25, -10, 0, 10, 25, 50, 75, 100, 125, 150] diff_levels = [-100, -80, -60, -40, -20, -10, -5, 5, 10, 20, 40, 60, 80, 100] @@ -745,7 +745,7 @@ case_id = "MERRA2" variables = ["PSL"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [955, 965, 975,980, 985, 990, 995, 1000, 1005, 1010, 1015, 1020, 1025, 1035] diff_levels = [ -16, -12, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 12, 16] @@ -756,7 +756,7 @@ case_id = "MERRA2" variables = ["FSNS"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400] diff_levels = [-75, -50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50, 75] @@ -767,7 +767,7 @@ case_id = "MERRA2" variables = ["FLNS"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 20, 40, 60, 80, 100, 120, 140, 160] diff_levels = [-50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50] @@ -778,7 +778,7 @@ case_id = "MERRA2" variables = ["NET_FLUX_SRF"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-200, -160, -120, -80, -40, 0, 40, 80, 120, 160, 200] diff_levels = [-75, -50, -40, -30, -20, -10, -5, 5, 10, 20, 30, 40, 50, 75] @@ -789,7 +789,7 @@ case_id = "MERRA2" variables = ["PRECT"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "WhiteBlueGreenYellowRed.rgb" reference_colormap = "WhiteBlueGreenYellowRed.rgb" diff_colormap = "BrBG" @@ -803,7 +803,7 @@ case_id = "MERRA2" variables = ["TMQ"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60] diff_levels = [-12, -9, -6, -4, -3, -2, -1, 1, 2, 3, 4, 6, 9, 12] @@ -814,7 +814,7 @@ case_id = "MERRA2" variables = ["U"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [850.0] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" @@ -829,7 +829,7 @@ case_id = "MERRA2" variables = ["U"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] plevs = [200.0] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" @@ -857,7 +857,7 @@ case_id = "MERRA2" variables = ["Z3"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [500.0] contour_levels = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58] diff_levels = [-1.8, -1.4, -1.0, -0.6, -0.2, 0.2, 0.6, 1.0, 1.4, 1.8] @@ -869,7 +869,7 @@ case_id = "MERRA2" variables = ["OMEGA"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["DJF", "MAM", "JJA", "SON", "ANN"] +seasons = ["DJF", "MAM", "JJA", "SON", "ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] plevs = [200.0] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" @@ -883,7 +883,7 @@ case_id = "MERRA2" variables = ["OMEGA"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["DJF", "MAM", "JJA", "SON", "ANN"] +seasons = ["DJF", "MAM", "JJA", "SON", "ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] plevs = [500.0] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" @@ -897,7 +897,7 @@ case_id = "MERRA2" variables = ["OMEGA"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["DJF", "MAM", "JJA", "SON", "ANN"] +seasons = ["DJF", "MAM", "JJA", "SON", "ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] plevs = [850.0] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" @@ -912,7 +912,7 @@ case_id = "MERRA2" variables = ["T"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] plevs = [850.0] contour_levels = [240, 245, 250, 255, 260, 265, 270, 275, 280, 285, 290, 295] diff_levels = [-10, -7.5, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 7.5, 10] @@ -938,7 +938,7 @@ case_id = "MERRA2" variables = ["T"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] plevs = [200.0] contour_levels = [210, 212, 214, 216, 218, 220, 222, 224, 226] diff_levels = [-10, -7.5, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 7.5, 10] @@ -963,7 +963,7 @@ variables = ["TAUXY"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" regions = ["ocean"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Purples" reference_colormap = "Purples" diff_colormap = "RdBu" @@ -978,7 +978,7 @@ case_id = "Cloud ISCCP" variables = ["CLDTOT_TAU1.3_ISCCP"] ref_name = "ISCCPCOSP" reference_name = "ISCCP" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -992,7 +992,7 @@ case_id = "Cloud ISCCP" variables = ["CLDTOT_TAU1.3_9.4_ISCCP"] ref_name = "ISCCPCOSP" reference_name = "ISCCP" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -1006,7 +1006,7 @@ case_id = "Cloud ISCCP" variables = ["CLDTOT_TAU9.4_ISCCP"] ref_name = "ISCCPCOSP" reference_name = "ISCCP" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -1020,7 +1020,7 @@ case_id = "Cloud MISR" variables = ["CLDTOT_TAU1.3_MISR"] ref_name = "MISRCOSP" reference_name = "MISR Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -1034,7 +1034,7 @@ case_id = "Cloud MISR" variables = ["CLDTOT_TAU1.3_9.4_MISR"] ref_name = "MISRCOSP" reference_name = "MISR Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -1048,7 +1048,7 @@ case_id = "Cloud MISR" variables = ["CLDTOT_TAU9.4_MISR"] ref_name = "MISRCOSP" reference_name = "MISR Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -1062,7 +1062,7 @@ case_id = "Cloud MISR" variables = ["CLDLOW_TAU1.3_MISR"] ref_name = "MISRCOSP" reference_name = "MISR Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -1076,7 +1076,7 @@ case_id = "Cloud MISR" variables = ["CLDLOW_TAU1.3_9.4_MISR"] ref_name = "MISRCOSP" reference_name = "MISR Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -1090,7 +1090,7 @@ case_id = "Cloud MISR" variables = ["CLDLOW_TAU9.4_MISR"] ref_name = "MISRCOSP" reference_name = "MISR Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -1104,7 +1104,7 @@ case_id = "Cloud MODIS" variables = ["CLDTOT_TAU1.3_MODIS"] ref_name = "MODISCOSP" reference_name = "MODIS Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -1118,7 +1118,7 @@ case_id = "Cloud MODIS" variables = ["CLDTOT_TAU1.3_9.4_MODIS"] ref_name = "MODISCOSP" reference_name = "MODIS Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -1132,7 +1132,7 @@ case_id = "Cloud MODIS" variables = ["CLDTOT_TAU9.4_MODIS"] ref_name = "MODISCOSP" reference_name = "MODIS Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -1146,7 +1146,7 @@ case_id = "Cloud MODIS" variables = ["CLDHGH_TAU1.3_MODIS"] ref_name = "MODISCOSP" reference_name = "MODIS Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -1160,7 +1160,7 @@ case_id = "Cloud MODIS" variables = ["CLDHGH_TAU1.3_9.4_MODIS"] ref_name = "MODISCOSP" reference_name = "MODIS Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -1174,7 +1174,7 @@ case_id = "Cloud MODIS" variables = ["CLDHGH_TAU9.4_MODIS"] ref_name = "MODISCOSP" reference_name = "MODIS Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -1188,7 +1188,7 @@ case_id = "Cloud Calipso" variables = ["CLDTOT_CAL"] ref_name = "CALIPSOCOSP" reference_name = "CALIPSO-GOCCP" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -1202,7 +1202,7 @@ case_id = "Cloud Calipso" variables = ["CLDLOW_CAL"] ref_name = "CALIPSOCOSP" reference_name = "CALIPSO-GOCCP" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -1216,7 +1216,7 @@ case_id = "Cloud Calipso" variables = ["CLDMED_CAL"] ref_name = "CALIPSOCOSP" reference_name = "CALIPSO-GOCCP" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -1230,7 +1230,7 @@ case_id = "Cloud Calipso" variables = ["CLDHGH_CAL"] ref_name = "CALIPSOCOSP" reference_name = "CALIPSO-GOCCP" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -1244,7 +1244,7 @@ case_id = "Cloud SSM/I" variables = ["TGCLDLWP_OCN"] ref_name = "SSMI" reference_name = "SSM/I (Wentz)" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "Blues" reference_colormap = "Blues" diff_colormap = "RdBu" @@ -1259,7 +1259,7 @@ variables = ["AODVIS"] ref_name = "MACv2" reference_name = "Max-Planck Aerosol climatology (MACv2)" regions = ["global"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] diff_colormap = "BrBG_r" contour_levels = [0., 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2] diff_levels = [-0.5, -0.4, -0.3, -0.2, -0.1, -0.05, -0.02, 0.02, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5] @@ -1271,7 +1271,7 @@ variables = ["AODVIS"] ref_name = "MERRA2_Aerosols" reference_name = "MERRA2 Aerosols" regions = ["global"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] diff_colormap = "BrBG_r" contour_levels = [0., 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2] diff_levels = [-0.5, -0.4, -0.3, -0.2, -0.1, -0.05, -0.02, 0.02, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5] @@ -1283,7 +1283,7 @@ variables = ["AODDUST"] ref_name = "MACv2" reference_name = "Max-Planck Aerosol climatology (MACv2)" regions = ["global"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] diff_colormap = "BrBG_r" contour_levels = [0.,0.01,0.02,0.03,0.04,0.05,0.06,0.07,0.08,0.09,1] @@ -1295,7 +1295,7 @@ variables = ["TREFHT"] regions = ["global"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-12, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 12] @@ -1306,7 +1306,7 @@ variables = ["TREFHT"] regions = ["land"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-12, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 12] regrid_method = "bilinear" @@ -1318,7 +1318,7 @@ variables = ["TREFHT"] regions = ["global"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-12, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 12] @@ -1330,7 +1330,7 @@ variables = ["TREFHT"] regions = ["land"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-12, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 12] regrid_method = "bilinear" @@ -1343,7 +1343,7 @@ regions = ["land"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" regions = ["global"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-12, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 12] regrid_method = "bilinear" @@ -1357,7 +1357,7 @@ regions = ["land"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" regions = ["global"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-12, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 12] regrid_method = "bilinear" @@ -1370,7 +1370,7 @@ regions = ["land"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" regions = ["global"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [2, 4, 6, 8, 10, 12, 14, 16,18] diff_levels = [ -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8] regrid_method = "bilinear" @@ -1383,7 +1383,7 @@ regions = ["global"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" regions = ["global"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-12, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 12] @@ -1396,7 +1396,7 @@ regions = ["global"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" regions = ["global"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-12, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 12] @@ -1408,7 +1408,7 @@ regions = ["global"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" regions = ["global"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [2, 4, 6, 8, 10, 12, 14, 16,18] diff_levels = [ -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8] @@ -1419,7 +1419,7 @@ case_id = "GPCP_OAFLux" variables = ["PminusE"] ref_name = "GPCP_OAFLux" reference_name = "PRECT(GPCP) minus QFLX(OAFLux)" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["global"] test_colormap = "RdBu" reference_colormap = "RdBu" @@ -1433,7 +1433,7 @@ case_id = "COREv2_Flux" variables = ["PminusE"] ref_name = "COREv2_Flux" reference_name = "COREv2_Flux" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["global"] test_colormap = "RdBu" reference_colormap = "RdBu" @@ -1447,7 +1447,7 @@ case_id = "OMI-MLS" variables = ["TCO"] ref_name = "OMI-MLS" reference_name = "OMI-MLS" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "WhiteBlueGreenYellowRed.rgb" reference_colormap = "WhiteBlueGreenYellowRed.rgb" diff_colormap = "diverging_bwr.rgb" diff --git a/e3sm_diags/driver/default_diags/meridional_mean_2d_model_vs_model.cfg b/e3sm_diags/driver/default_diags/meridional_mean_2d_model_vs_model.cfg index e252c562d..061ab5b12 100644 --- a/e3sm_diags/driver/default_diags/meridional_mean_2d_model_vs_model.cfg +++ b/e3sm_diags/driver/default_diags/meridional_mean_2d_model_vs_model.cfg @@ -2,7 +2,7 @@ sets = ["meridional_mean_2d"] case_id = "model_vs_model" variables = ["T"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [180,185,190,200,210,220,230,240,250,260,270,280,290,295,300] diff_levels = [-7,-6,-5,-4,-3,-2,-1,1,2,3,4,5,6,7] @@ -10,7 +10,7 @@ diff_levels = [-7,-6,-5,-4,-3,-2,-1,1,2,3,4,5,6,7] sets = ["meridional_mean_2d"] case_id = "model_vs_model" variables = ["U"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" contour_levels = [-20, -15, -10, -8, -5, -3, -1, 1, 3, 5, 8, 10, 15, 20] @@ -20,7 +20,7 @@ diff_levels = [ -5, -4, -3, -2, -1,-0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 5] sets = ["meridional_mean_2d"] case_id = "model_vs_model" variables = ["RELHUM"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [5,10,15,20,25,30,40,50,60,70,75,80,85,90,95] diff_levels = [-15,-12,-9,-6,-3,-1,1,3,6,9,12,15] @@ -28,7 +28,7 @@ diff_levels = [-15,-12,-9,-6,-3,-1,1,3,6,9,12,15] sets = ["meridional_mean_2d"] case_id = "model_vs_model" variables = ["OMEGA"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" contour_levels = [-35,-30,-25,-20,-15,-10,-5,5,10,15,20,25,30,35] diff --git a/e3sm_diags/driver/default_diags/meridional_mean_2d_model_vs_obs.cfg b/e3sm_diags/driver/default_diags/meridional_mean_2d_model_vs_obs.cfg index 3c9efb417..758aaa897 100644 --- a/e3sm_diags/driver/default_diags/meridional_mean_2d_model_vs_obs.cfg +++ b/e3sm_diags/driver/default_diags/meridional_mean_2d_model_vs_obs.cfg @@ -4,7 +4,7 @@ case_id = "MERRA2" variables = ["T"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [180,185,190,200,210,220,230,240,250,260,270,280,290,295,300] diff_levels = [-7,-6,-5,-4,-3,-2,-1,1,2,3,4,5,6,7] @@ -14,7 +14,7 @@ case_id = "MERRA2" variables = ["U"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" contour_levels = [-20, -15, -10, -8, -5, -3, -1, 1, 3, 5, 8, 10, 15, 20] @@ -26,7 +26,7 @@ case_id = "MERRA2" variables = ["RELHUM"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [5,10,15,20,25,30,40,50,60,70,75,80,85,90,95] diff_levels = [-15,-12,-9,-6,-3,-1,1,3,6,9,12,15] @@ -36,7 +36,7 @@ case_id = "MERRA2" variables = ["OMEGA"] ref_name = "MERRA2" reference_name = "MERRA2" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" contour_levels = [-35,-30,-25,-20,-15,-10,-5,5,10,15,20,25,30,35] @@ -48,7 +48,7 @@ case_id = "ERA5" variables = ["T"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [180,185,190,200,210,220,230,240,250,260,270,280,290,295,300] diff_levels = [-7,-6,-5,-4,-3,-2,-1,1,2,3,4,5,6,7] @@ -58,7 +58,7 @@ case_id = "ERA5" variables = ["U"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" contour_levels = [-20, -15, -10, -8, -5, -3, -1, 1, 3, 5, 8, 10, 15, 20] @@ -70,7 +70,7 @@ case_id = "ERA5" variables = ["OMEGA"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" contour_levels = [-35,-30,-25,-20,-15,-10,-5,5,10,15,20,25,30,35] @@ -82,6 +82,6 @@ case_id = "ERA5" variables = ["RELHUM"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [5,10,15,20,25,30,40,50,60,70,75,80,85,90,95] diff_levels = [-15,-12,-9,-6,-3,-1,1,3,6,9,12,15] diff --git a/e3sm_diags/driver/default_diags/polar_model_vs_model.cfg b/e3sm_diags/driver/default_diags/polar_model_vs_model.cfg index 72a2b1ac9..326ff0147 100644 --- a/e3sm_diags/driver/default_diags/polar_model_vs_model.cfg +++ b/e3sm_diags/driver/default_diags/polar_model_vs_model.cfg @@ -2,7 +2,7 @@ sets = ["polar"] case_id = "model_vs_model" variables = ["PRECT"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S"] test_colormap = "WhiteBlueGreenYellowRed.rgb" reference_colormap = "WhiteBlueGreenYellowRed.rgb" @@ -15,7 +15,7 @@ diff_levels = [-2, -1.5, -1, -0.75, -0.5, -0.25, 0.25, 0.5, 0.75, 1, 1.5, 2] sets = ["polar"] case_id = "model_vs_model" variables = ["PRECT"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_N"] test_colormap = "WhiteBlueGreenYellowRed.rgb" reference_colormap = "WhiteBlueGreenYellowRed.rgb" @@ -28,7 +28,7 @@ diff_levels = [-2, -1.5, -1, -0.75, -0.5, -0.25, 0.25, 0.5, 0.75, 1, 1.5, 2] sets = ["polar"] case_id = "model_vs_model" variables = ["SST"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] contour_levels = [-1, 0, 1, 3, 5, 7, 9, 11, 13, 15] diff_levels = [-5, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 5] @@ -39,7 +39,7 @@ sets = ["polar"] case_id = "model_vs_model" variables = ["SOLIN"] regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450] diff_levels = [-5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5] @@ -49,7 +49,7 @@ diff_levels = [-5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5] sets = ["polar"] case_id = "model_vs_model" variables = ["ALBEDO"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] contour_levels = [0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95] diff_levels = [-0.25, -0.2, -0.15, -0.1, -0.07, -0.05, -0.02, 0.02, 0.05, 0.07, 0.1, 0.15, 0.2, 0.25] @@ -59,7 +59,7 @@ diff_levels = [-0.25, -0.2, -0.15, -0.1, -0.07, -0.05, -0.02, 0.02, 0.05, 0.07, sets = ["polar"] case_id = "model_vs_model" variables = ["ALBEDOC"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] contour_levels = [0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95] diff_levels = [-0.25, -0.2, -0.15, -0.1, -0.07, -0.05, -0.02, 0.02, 0.05, 0.07, 0.1, 0.15, 0.2, 0.25] @@ -69,7 +69,7 @@ diff_levels = [-0.25, -0.2, -0.15, -0.1, -0.07, -0.05, -0.02, 0.02, 0.05, 0.07, sets = ["polar"] case_id = "model_vs_model" variables = ["FLUT"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] contour_levels = [145, 160, 175, 190, 205, 220, 235, 250, 265] diff_levels = [-60, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 60] @@ -79,7 +79,7 @@ diff_levels = [-60, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 6 sets = ["polar"] case_id = "model_vs_model" variables = ["FLUTC"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] contour_levels = [145, 160, 175, 190, 205, 220, 235, 250, 265] diff_levels = [-60, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 60] @@ -90,7 +90,7 @@ sets = ["polar"] case_id = "model_vs_model" variables = ["FSNTOA"] regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 25, 50, 75, 100, 125, 150, 175, 200, 225, 250, 275, 300] diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 75] @@ -99,7 +99,7 @@ diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 7 sets = ["polar"] case_id = "model_vs_model" variables = ["FSNTOAC"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] contour_levels = [0, 25, 50, 75, 100, 125, 150, 175, 200, 225, 250, 275, 300] diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 75] @@ -109,7 +109,7 @@ diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 7 sets = ["polar"] case_id = "model_vs_model" variables = ["SWCF"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] contour_levels = [-170, -150, -135, -120, -105, -90, -75, -60, -45, -30, -15, 0, 15, 30, 45] diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 75] @@ -119,7 +119,7 @@ diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 7 sets = ["polar"] case_id = "model_vs_model" variables = ["LWCF"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] contour_levels = [0, 10, 20, 30, 40, 50, 60, 70, 80] diff_levels = [-35, -30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30, 35] @@ -130,7 +130,7 @@ sets = ["polar"] case_id = "model_vs_model" variables = ["RESTOM"] regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-175, -150, -125, -100, -75, -50, -25, 0, 25, 50, 75, 100, 125] diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 75] @@ -140,7 +140,7 @@ sets = ["polar"] case_id = "model_vs_model" variables = ["ALBEDO_SRF"] regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95] diff_levels = [-0.25, -0.2, -0.15, -0.1, -0.07, -0.05, -0.02, 0.02, 0.05, 0.07, 0.1, 0.15, 0.2, 0.25] @@ -150,7 +150,7 @@ sets = ["polar"] case_id = "model_vs_model" variables = ["SWCFSRF"] regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-170, -150, -135, -120, -105, -90, -75, -60, -45, -30, -15, 0, 15, 30, 45] diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 75] @@ -160,7 +160,7 @@ sets = ["polar"] case_id = "model_vs_model" variables = ["LWCFSRF"] regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 10, 20, 30, 40, 50, 60, 70, 80] diff_levels = [-35, -30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30, 35] @@ -170,7 +170,7 @@ sets = ["polar"] case_id = "model_vs_model" variables = ["FLDS"] regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450] diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 75] @@ -181,7 +181,7 @@ sets = ["polar"] case_id = "model_vs_model" variables = ["FLDSC"] regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450] diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 75] @@ -191,7 +191,7 @@ sets = ["polar"] case_id = "model_vs_model" variables = ["FLNS"] regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 10, 20, 30, 40, 50, 60, 70, 80] diff_levels = [-50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50] @@ -201,7 +201,7 @@ sets = ["polar"] case_id = "model_vs_model" variables = ["FLNSC"] regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 20, 40, 60, 80, 100, 120, 140, 160] diff_levels = [-50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50] @@ -211,7 +211,7 @@ sets = ["polar"] case_id = "model_vs_model" variables = ["FSDS"] regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450] diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 75] @@ -221,7 +221,7 @@ sets = ["polar"] case_id = "model_vs_model" variables = ["FSDSC"] regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450] diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 75] @@ -231,7 +231,7 @@ sets = ["polar"] case_id = "model_vs_model" variables = ["FSNS"] regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400] diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 75] @@ -242,7 +242,7 @@ sets = ["polar"] case_id = "model_vs_model" variables = ["FSNSC"] regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400] diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 75] @@ -251,7 +251,7 @@ diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 7 sets = ["polar"] case_id = "model_vs_model" variables = ["LHFLX"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] contour_levels = [-15, 0, 15, 30, 45, 60, 75, 90, 105, 120] diff_levels = [-65, -55, -45, -35, -25, -15, -5, 5, 15, 25, 35, 45, 55, 65] @@ -261,7 +261,7 @@ diff_levels = [-65, -55, -45, -35, -25, -15, -5, 5, 15, 25, 35, 45, 55, 65] sets = ["polar"] case_id = "model_vs_model" variables = ["TMQ"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] contour_levels = [0, 1, 2, 4, 6, 8, 12, 14, 16, 18, 20, 22] diff_levels = [-5, -4, -3, -2, -1, 1, 2, 3, 4, 5] @@ -271,7 +271,7 @@ sets = ["polar"] case_id = "model_vs_model" variables = ["TREFHT"] regions = ["polar_N", "polar_S"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-15, -10, -5, -2, -1, -0.5, -0.2, 0, 0.2, 0.5, 1, 2, 5, 10, 15] @@ -280,7 +280,7 @@ sets = ["polar"] case_id = "model_vs_model" variables = ["TREFMNAV"] regions = ["polar_N", "polar_S"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-15, -10, -5, -2, -1, -0.5, -0.2, 0, 0.2, 0.5, 1, 2, 5, 10, 15] @@ -289,7 +289,7 @@ sets = ["polar"] case_id = "model_vs_model" variables = ["TREFMXAV"] regions = ["polar_N", "polar_S"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-15, -10, -5, -2, -1, -0.5, -0.2, 0, 0.2, 0.5, 1, 2, 5, 10, 15] @@ -297,7 +297,7 @@ diff_levels = [-15, -10, -5, -2, -1, -0.5, -0.2, 0, 0.2, 0.5, 1, 2, 5, 10, 15] sets = ["polar"] case_id = "model_vs_model" variables = ["U"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] plevs = [850.0] test_colormap = "PiYG_r" @@ -310,7 +310,7 @@ diff_levels = [-8, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6, 8] sets = ["polar"] case_id = "model_vs_model" variables = ["U"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] plevs = [200.0] test_colormap = "PiYG_r" @@ -323,7 +323,7 @@ diff_levels = [-8, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6, 8] sets = ["polar"] case_id = "model_vs_model" variables = ["Z3"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] plevs = [500.0] contour_levels = [48, 49, 50, 51, 52, 53, 54, 55, 56] @@ -334,7 +334,7 @@ diff_levels = [-1.8, -1.4, -1.0, -0.6, -0.2, 0.2, 0.6, 1.0, 1.4, 1.8] sets = ["polar"] case_id = "model_vs_model" variables = ["T"] -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] regions = ["polar_S"] plevs = [850.0] contour_levels = [240, 244, 248, 252, 256, 260, 264, 268, 272] @@ -345,7 +345,7 @@ diff_levels = [-15, -10, -7.5, -5, -2.5, -1, 1, 2.5, 5, 7.5, 10, 15] sets = ["polar"] case_id = "model_vs_model" variables = ["T"] -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] regions = ["polar_N"] plevs = [850.0] contour_levels = [256, 258, 260, 262, 264, 268, 270, 272, 274, 276] @@ -367,7 +367,7 @@ diff_levels = [-15, -10, -7.5, -5, -2.5, -1, 1, 2.5, 5, 7.5, 10, 15] sets = ["polar"] case_id = "model_vs_model" variables = ["T"] -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] regions = ["polar_S"] plevs = [200.0] contour_levels = [210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220] @@ -378,7 +378,7 @@ diff_levels = [-10, -7.5, -5, -4, -3, -2, -1, -0.5, 0.5, 1, 2, 3, 4, 5, 7.5, 10] sets = ["polar"] case_id = "model_vs_model" variables = ["T"] -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] regions = ["polar_N"] plevs = [200.0] contour_levels = [215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225] diff --git a/e3sm_diags/driver/default_diags/polar_model_vs_obs.cfg b/e3sm_diags/driver/default_diags/polar_model_vs_obs.cfg index bcec76a04..ccd654830 100644 --- a/e3sm_diags/driver/default_diags/polar_model_vs_obs.cfg +++ b/e3sm_diags/driver/default_diags/polar_model_vs_obs.cfg @@ -4,7 +4,7 @@ case_id = "GPCP_v3.2" variables = ["PRECT"] ref_name = "GPCP_v3.2" reference_name = "GPCP v2.2" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S"] test_colormap = "WhiteBlueGreenYellowRed.rgb" reference_colormap = "WhiteBlueGreenYellowRed.rgb" @@ -19,7 +19,7 @@ case_id = "GPCP_v3.2" variables = ["PRECT"] ref_name = "GPCP_v3.2" reference_name = "GPCP v2.2" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_N"] test_colormap = "WhiteBlueGreenYellowRed.rgb" reference_colormap = "WhiteBlueGreenYellowRed.rgb" @@ -34,7 +34,7 @@ case_id = "GPCP_v2.3" variables = ["PRECT"] ref_name = "GPCP_v2.3" reference_name = "GPCP v2.3" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S"] test_colormap = "WhiteBlueGreenYellowRed.rgb" reference_colormap = "WhiteBlueGreenYellowRed.rgb" @@ -49,7 +49,7 @@ case_id = "GPCP_v2.3" variables = ["PRECT"] ref_name = "GPCP_v2.3" reference_name = "GPCP v2.3" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_N"] test_colormap = "WhiteBlueGreenYellowRed.rgb" reference_colormap = "WhiteBlueGreenYellowRed.rgb" @@ -65,7 +65,7 @@ variables = ["TREFHT"] regions = ["polar_N"] ref_name = "CRU" reference_name = "CRU Global Monthly Mean T" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-15, -10, -5, -2, -1, -0.5, -0.2, 0, 0.2, 0.5, 1, 2, 5, 10, 15] @@ -76,7 +76,7 @@ case_id = "SST_HadISST" variables = ["SST"] ref_name = "HadISST" reference_name = "HadISST" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] contour_levels = [-1, 0, 1, 3, 5, 7, 9, 11, 13, 15] diff_levels = [-5, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 5] @@ -88,7 +88,7 @@ case_id = "SST_CL_HadISST" variables = ["SST"] ref_name = "HadISST_CL" reference_name = "HadISST (Climatology)" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] contour_levels = [-1, 0, 1, 3, 5, 7, 9, 11, 13, 15] diff_levels = [-5, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 5] @@ -100,7 +100,7 @@ case_id = "SST_PI_HadISST" variables = ["SST"] ref_name = "HadISST_PI" reference_name = "HadISST (Pre-Indust)" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] contour_levels = [-1, 0, 1, 3, 5, 7, 9, 11, 13, 15] diff_levels = [-5, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 5] @@ -112,7 +112,7 @@ case_id = "SST_PD_HadISST" variables = ["SST"] ref_name = "HadISST_PD" reference_name = "HadISST (Present Day)" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] contour_levels = [-1, 0, 1, 3, 5, 7, 9, 11, 13, 15] diff_levels = [-5, -4, -3, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 3, 4, 5] @@ -126,7 +126,7 @@ variables = ["ALBEDO_SRF"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95] diff_levels = [-0.25, -0.2, -0.15, -0.1, -0.07, -0.05, -0.02, 0.02, 0.05, 0.07, 0.1, 0.15, 0.2, 0.25] @@ -138,7 +138,7 @@ variables = ["SWCFSRF"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-170, -150, -135, -120, -105, -90, -75, -60, -45, -30, -15, 0, 15, 30, 45] diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 75] @@ -150,7 +150,7 @@ variables = ["LWCFSRF"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 10, 20, 30, 40, 50, 60, 70, 80] diff_levels = [-35, -30, -25, -20, -15, -10, -5, -2, 2, 5, 10, 15, 20, 25, 30, 35] @@ -162,7 +162,7 @@ variables = ["FLDS"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450] diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 75] @@ -175,7 +175,7 @@ variables = ["FLDSC"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450] diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 75] @@ -187,7 +187,7 @@ variables = ["FLNS"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 10, 20, 30, 40, 50, 60, 70, 80] diff_levels = [-50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50] @@ -199,7 +199,7 @@ variables = ["FLNSC"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 20, 40, 60, 80, 100, 120, 140, 160] diff_levels = [-50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50] @@ -211,7 +211,7 @@ variables = ["FSDS"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450] diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 75] @@ -223,7 +223,7 @@ variables = ["FSDSC"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450] diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 75] @@ -235,7 +235,7 @@ variables = ["FSNS"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400] diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 75] @@ -248,7 +248,7 @@ variables = ["FSNSC"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0, 50, 100, 150, 200, 250, 300, 350, 400] diff_levels = [-75, -50, -40, -30, -20, -10, -5, -2, 2, 5, 10, 20, 30, 40, 50, 75] @@ -259,7 +259,7 @@ case_id = "ERA5" variables = ["LHFLX"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] contour_levels = [-15, 0, 15, 30, 45, 60, 75, 90, 105, 120] diff_levels = [-65, -55, -45, -35, -25, -15, -5, 5, 15, 25, 35, 45, 55, 65] @@ -272,7 +272,7 @@ variables = ["TAUXY"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0., 0.01, 0.02,0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.1, 0.2, 0.5, 1.0] diff_levels = [-0.1, -0.08, -0.06, -0.05, -0.04, -0.03, -0.02, -0.01, 0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.08, 0.1] @@ -283,7 +283,7 @@ variables = ["TREFHT"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" regions = ["polar_N", "polar_S"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-15, -10, -5, -2, -1, -0.5, -0.2, 0, 0.2, 0.5, 1, 2, 5, 10, 15] @@ -295,7 +295,7 @@ variables = ["PSL"] ref_name = "ERA5" regions = ["polar_S", "polar_N"] reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [955, 965, 975,980, 985, 990, 995, 1000, 1005, 1010, 1015, 1020, 1025, 1035] diff_levels = [ -16, -12, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 12, 16] @@ -306,7 +306,7 @@ case_id = "ERA5" variables = ["PRECT"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S"] test_colormap = "WhiteBlueGreenYellowRed.rgb" reference_colormap = "WhiteBlueGreenYellowRed.rgb" @@ -321,7 +321,7 @@ case_id = "ERA5" variables = ["PRECT"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_N"] test_colormap = "WhiteBlueGreenYellowRed.rgb" reference_colormap = "WhiteBlueGreenYellowRed.rgb" @@ -336,7 +336,7 @@ case_id = "ERA5" variables = ["TMQ"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] contour_levels = [0, 1, 2, 4, 6, 8, 12, 14, 16, 18, 20, 22] diff_levels = [-5, -4, -3, -2, -1, 1, 2, 3, 4, 5] @@ -348,7 +348,7 @@ case_id = "ERA5" variables = ["U"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] plevs = [850.0] test_colormap = "PiYG_r" @@ -363,7 +363,7 @@ case_id = "ERA5" variables = ["U"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] plevs = [200.0] test_colormap = "PiYG_r" @@ -378,7 +378,7 @@ case_id = "ERA5" variables = ["Z3"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] plevs = [500.0] contour_levels = [48, 49, 50, 51, 52, 53, 54, 55, 56] @@ -391,7 +391,7 @@ case_id = "ERA5" variables = ["T"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] regions = ["polar_S"] plevs = [850.0] contour_levels = [240, 244, 248, 252, 256, 260, 264, 268, 272] @@ -404,7 +404,7 @@ case_id = "ERA5" variables = ["T"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] regions = ["polar_N"] plevs = [850.0] contour_levels = [256, 258, 260, 262, 264, 268, 270, 272, 274, 276] @@ -430,7 +430,7 @@ case_id = "ERA5" variables = ["T"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] regions = ["polar_S"] plevs = [200.0] contour_levels = [210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220] @@ -443,7 +443,7 @@ case_id = "ERA5" variables = ["T"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] regions = ["polar_N"] plevs = [200.0] contour_levels = [215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225] @@ -469,7 +469,7 @@ case_id = "MERRA2" variables = ["LHFLX"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] contour_levels = [-15, 0, 15, 30, 45, 60, 75, 90, 105, 120] diff_levels = [-65, -55, -45, -35, -25, -15, -5, 5, 15, 25, 35, 45, 55, 65] @@ -481,7 +481,7 @@ case_id = "MERRA2" variables = ["PRECT"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S"] test_colormap = "WhiteBlueGreenYellowRed.rgb" reference_colormap = "WhiteBlueGreenYellowRed.rgb" @@ -496,7 +496,7 @@ case_id = "MERRA2" variables = ["PRECT"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_N"] test_colormap = "WhiteBlueGreenYellowRed.rgb" reference_colormap = "WhiteBlueGreenYellowRed.rgb" @@ -511,7 +511,7 @@ case_id = "MERRA2" variables = ["TMQ"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] contour_levels = [0, 1, 2, 4, 6, 8, 12, 14, 16, 18, 20, 22] diff_levels = [-5, -4, -3, -2, -1, 1, 2, 3, 4, 5] @@ -523,7 +523,7 @@ case_id = "MERRA2" variables = ["U"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] plevs = [850.0] test_colormap = "PiYG_r" @@ -538,7 +538,7 @@ case_id = "MERRA2" variables = ["U"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] plevs = [200.0] test_colormap = "PiYG_r" @@ -553,7 +553,7 @@ case_id = "MERRA2" variables = ["Z3"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["polar_S", "polar_N"] plevs = [500.0] contour_levels = [48, 49, 50, 51, 52, 53, 54, 55, 56] @@ -566,7 +566,7 @@ case_id = "MERRA2" variables = ["T"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] regions = ["polar_S"] plevs = [850.0] contour_levels = [240, 244, 248, 252, 256, 260, 264, 268, 272] @@ -579,7 +579,7 @@ case_id = "MERRA2" variables = ["T"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] regions = ["polar_N"] plevs = [850.0] contour_levels = [256, 258, 260, 262, 264, 268, 270, 272, 274, 276] @@ -605,7 +605,7 @@ case_id = "MERRA2" variables = ["T"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] regions = ["polar_S"] plevs = [200.0] contour_levels = [210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220] @@ -618,7 +618,7 @@ case_id = "MERRA2" variables = ["T"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] regions = ["polar_N"] plevs = [200.0] contour_levels = [215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225] @@ -645,7 +645,7 @@ variables = ["TAUXY"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0., 0.01, 0.02,0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.1, 0.2, 0.5, 1.0] diff_levels = [-0.1, -0.08, -0.06, -0.05, -0.04, -0.03, -0.02, -0.01, 0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.08, 0.1] @@ -656,7 +656,7 @@ variables = ["TREFHT"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" regions = ["polar_N", "polar_S"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-15, -10, -5, -2, -1, -0.5, -0.2, 0, 0.2, 0.5, 1, 2, 5, 10, 15] @@ -667,7 +667,7 @@ variables = ["TREFMNAV"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" regions = ["polar_N", "polar_S"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-15, -10, -5, -2, -1, -0.5, -0.2, 0, 0.2, 0.5, 1, 2, 5, 10, 15] @@ -678,7 +678,7 @@ variables = ["TREFMXAV"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" regions = ["polar_N", "polar_S"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [-35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] diff_levels = [-15, -10, -5, -2, -1, -0.5, -0.2, 0, 0.2, 0.5, 1, 2, 5, 10, 15] @@ -689,7 +689,7 @@ variables = ["PSL"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" regions = ["polar_S", "polar_N"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [955, 965, 975,980, 985, 990, 995, 1000, 1005, 1010, 1015, 1020, 1025, 1035] diff_levels = [ -16, -12, -8, -4, -2, -1, -0.5, 0.5, 1, 2, 4, 8, 12, 16] diff --git a/e3sm_diags/driver/default_diags/zonal_mean_2d_model_vs_model.cfg b/e3sm_diags/driver/default_diags/zonal_mean_2d_model_vs_model.cfg index 15094086a..09e0a93c8 100644 --- a/e3sm_diags/driver/default_diags/zonal_mean_2d_model_vs_model.cfg +++ b/e3sm_diags/driver/default_diags/zonal_mean_2d_model_vs_model.cfg @@ -2,7 +2,7 @@ sets = ["zonal_mean_2d"] case_id = "model_vs_model" variables = ["T"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [180,185,190,200,210,220,230,240,250,260,270,280,290,295,300] diff_levels = [-3.0, -2.5, -2, -1.5, -1, -0.5, -0.25, 0.25, 0.5, 1, 1.5, 2, 2.5, 3.0] @@ -10,7 +10,7 @@ diff_levels = [-3.0, -2.5, -2, -1.5, -1, -0.5, -0.25, 0.25, 0.5, 1, 1.5, 2, 2.5, sets = ["zonal_mean_2d"] case_id = "model_vs_model" variables = ["U"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" contour_levels = [-40,-30,-25,-20,-15,-10,-5,-2.5,2.5,5,10,15,20,25,30,40] @@ -20,7 +20,7 @@ diff_levels = [-3.5,-3,-2.5,-2,-1.5,-1,-0.5,0.5,1,1.5,2,2.5,3,3.5] sets = ["zonal_mean_2d"] case_id = "model_vs_model" variables = ["RELHUM"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [5,10,15,20,25,30,40,50,60,70,75,80,85,90,95] diff_levels = [-20,-15,-10,-7.5,-5.0,-2.5,-1.0,1.0,2.5,5.0,7.5,10,15,20] @@ -28,7 +28,7 @@ diff_levels = [-20,-15,-10,-7.5,-5.0,-2.5,-1.0,1.0,2.5,5.0,7.5,10,15,20] sets = ["zonal_mean_2d"] case_id = "model_vs_model" variables = ["Q"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.2, 0.5, 1, 2.5, 5, 7.5, 10, 12.5, 15] diff_levels = [-1.5, -1, -0.5, -0.2, -0.1,-0.05,-0.01, 0.01,0.05, 0.1, 0.2, 0.5, 1, 1.5] @@ -36,7 +36,7 @@ diff_levels = [-1.5, -1, -0.5, -0.2, -0.1,-0.05,-0.01, 0.01,0.05, 0.1, 0.2, 0.5, sets = ["zonal_mean_2d"] case_id = "model_vs_model" variables = ["OMEGA"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" contour_levels = [-35,-30,-25,-20,-15,-10,-5,5,10,15,20,25,30,35] diff --git a/e3sm_diags/driver/default_diags/zonal_mean_2d_model_vs_obs.cfg b/e3sm_diags/driver/default_diags/zonal_mean_2d_model_vs_obs.cfg index 89dca991d..0719f7af9 100644 --- a/e3sm_diags/driver/default_diags/zonal_mean_2d_model_vs_obs.cfg +++ b/e3sm_diags/driver/default_diags/zonal_mean_2d_model_vs_obs.cfg @@ -4,7 +4,7 @@ case_id = "MERRA2" variables = ["T"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [180,185,190,200,210,220,230,240,250,260,270,280,290,295,300] diff_levels = [-7,-6,-5,-4,-3,-2,-1,1,2,3,4,5,6,7] @@ -14,7 +14,7 @@ case_id = "MERRA2" variables = ["U"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" contour_levels = [-40,-30,-25,-20,-15,-10,-5,-2.5,2.5,5,10,15,20,25,30,40] @@ -26,7 +26,7 @@ case_id = "MERRA2" variables = ["RELHUM"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [5,10,15,20,25,30,40,50,60,70,75,80,85,90,95] diff_levels = [-40,-30,-20,-15,-10,-5,-2.5,2.5,5,10,15,20,30,40] @@ -36,7 +36,7 @@ case_id = "MERRA2" variables = ["Q"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.2, 0.5, 1, 2.5, 5, 7.5, 10, 12.5, 15] diff_levels = [-1.5, -1, -0.5, -0.2, -0.1,-0.05,-0.01, 0.01,0.05, 0.1, 0.2, 0.5, 1, 1.5] @@ -46,7 +46,7 @@ case_id = "MERRA2" variables = ["H2OLNZ"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.2, 0.5, 1, 2.5, 5, 7.5, 10, 12.5, 15] diff_levels = [-1.5, -1, -0.5, -0.2, -0.1,-0.05,-0.01, 0.01,0.05, 0.1, 0.2, 0.5, 1, 1.5] @@ -56,7 +56,7 @@ case_id = "MERRA2 (relative difference)" variables = ["Q"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] diff_type = "relative" diff_title = "(Model - Obs.)/Obs. * 100" contour_levels = [0.2, 0.5, 1, 2.5, 5, 7.5, 10, 12.5, 15] @@ -68,7 +68,7 @@ case_id = "MERRA2 (relative difference)" variables = ["H2OLNZ"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] diff_type = "relative" diff_title = "(Model - Obs.)/Obs. * 100" contour_levels = [0.2, 0.5, 1, 2.5, 5, 7.5, 10, 12.5, 15] @@ -80,7 +80,7 @@ case_id = "MERRA2" variables = ["OMEGA"] ref_name = "MERRA2" reference_name = "MERRA2" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" contour_levels = [-35,-30,-25,-20,-15,-10,-5,5,10,15,20,25,30,35] @@ -93,7 +93,7 @@ case_id = "ERA5" variables = ["T"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [180,185,190,200,210,220,230,240,250,260,270,280,290,295,300] diff_levels = [-7,-6,-5,-4,-3,-2,-1,1,2,3,4,5,6,7] @@ -103,7 +103,7 @@ case_id = "ERA5" variables = ["U"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" contour_levels = [-40,-30,-25,-20,-15,-10,-5,-2.5,2.5,5,10,15,20,25,30,40] @@ -115,7 +115,7 @@ case_id = "ERA5" variables = ["OMEGA"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" contour_levels = [-35,-30,-25,-20,-15,-10,-5,5,10,15,20,25,30,35] @@ -127,7 +127,7 @@ case_id = "ERA5" variables = ["RELHUM"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [5,10,15,20,25,30,40,50,60,70,75,80,85,90,95] diff_levels = [-40,-30,-20,-15,-10,-5,-2.5,2.5,5,10,15,20,30,40] @@ -137,7 +137,7 @@ case_id = "ERA5" variables = ["Q"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.2, 0.5, 1, 2.5, 5, 7.5, 10, 12.5, 15] diff_levels = [-1.5, -1, -0.5, -0.2, -0.1, -0.05,-0.01, 0.01,0.05, 0.1, 0.2, 0.5, 1, 1.5] @@ -148,7 +148,7 @@ case_id = "ERA5" variables = ["H2OLNZ"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.2, 0.5, 1, 2.5, 5, 7.5, 10, 12.5, 15] diff_levels = [-1.5, -1, -0.5, -0.2, -0.1, -0.05,-0.01, 0.01,0.05, 0.1, 0.2, 0.5, 1, 1.5] @@ -158,7 +158,7 @@ case_id = "ERA5 (relative difference)" variables = ["Q"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] diff_type = "relative" diff_title = "(Model - Obs.)/Obs. * 100" contour_levels = [0.2, 0.5, 1, 2.5, 5, 7.5, 10, 12.5, 15] @@ -170,7 +170,7 @@ case_id = "ERA5 (relative difference)" variables = ["H2OLNZ"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] diff_type = "relative" diff_title = "(Model - Obs.)/Obs. * 100" contour_levels = [0.2, 0.5, 1, 2.5, 5, 7.5, 10, 12.5, 15] diff --git a/e3sm_diags/driver/default_diags/zonal_mean_2d_stratosphere_model_vs_model.cfg b/e3sm_diags/driver/default_diags/zonal_mean_2d_stratosphere_model_vs_model.cfg index eed978efa..9bd684ed4 100644 --- a/e3sm_diags/driver/default_diags/zonal_mean_2d_stratosphere_model_vs_model.cfg +++ b/e3sm_diags/driver/default_diags/zonal_mean_2d_stratosphere_model_vs_model.cfg @@ -2,7 +2,7 @@ sets = ["zonal_mean_2d_stratosphere"] case_id = "model_vs_model" variables = ["T"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [180,185,190,200,210,220,230,240,250,260,270,280,290,295,300] diff_levels = [-8, -6,-4,-2, -1, -0.5, 0.5, 1, 2, 4,6, 8] @@ -10,7 +10,7 @@ diff_levels = [-8, -6,-4,-2, -1, -0.5, 0.5, 1, 2, 4,6, 8] sets = ["zonal_mean_2d_stratosphere"] case_id = "model_vs_model" variables = ["U"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" contour_levels = [-40,-30,-25,-20,-15,-10,-5,-2.5,2.5,5,10,15,20,25,30,40] @@ -20,7 +20,7 @@ diff_levels = [-3.5,-3,-2.5,-2,-1.5,-1,-0.5,0.5,1,1.5,2,2.5,3,3.5] sets = ["zonal_mean_2d_stratosphere"] case_id = "model_vs_model" variables = ["RELHUM"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.5,1,2,5,10,15,20,25,30,40,50,70] diff_levels = [-15,-10,-5,-2,-1,-0.5, 0.5, 1,2,5,10,15] @@ -28,7 +28,7 @@ diff_levels = [-15,-10,-5,-2,-1,-0.5, 0.5, 1,2,5,10,15] sets = ["zonal_mean_2d_stratosphere"] case_id = "model_vs_model" variables = ["Q"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.6,0.8, 1, 1.2, 1.6, 2, 2.5, 3, 3.5, 4] diff_levels = [-3, -2,-1.5,-1,-0.5,-0.1,0.1,0.5,1,1.5,2,3] @@ -36,7 +36,7 @@ diff_levels = [-3, -2,-1.5,-1,-0.5,-0.1,0.1,0.5,1,1.5,2,3] sets = ["zonal_mean_2d_stratosphere"] case_id = "model_vs_model" variables = ["OMEGA"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" contour_levels = [-1.5,-1.0,-0.5,-0.2,-0.1,0.1,0.2,0.5,1.0,1.5] @@ -46,5 +46,5 @@ diff_levels = [-0.6,-0.3,-0.1,-0.05,0.05,0.1,0.3,0.6] sets = ["zonal_mean_2d_stratosphere"] case_id = "model_vs_model" variables = ["O3"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.0000001, 0.0000002, 0.0000005, 0.000001,0.000002,0.000003,0.000006, 0.000009, 0.000012] diff --git a/e3sm_diags/driver/default_diags/zonal_mean_2d_stratosphere_model_vs_obs.cfg b/e3sm_diags/driver/default_diags/zonal_mean_2d_stratosphere_model_vs_obs.cfg index ad60fab18..674d80911 100644 --- a/e3sm_diags/driver/default_diags/zonal_mean_2d_stratosphere_model_vs_obs.cfg +++ b/e3sm_diags/driver/default_diags/zonal_mean_2d_stratosphere_model_vs_obs.cfg @@ -4,7 +4,7 @@ case_id = "MERRA2" variables = ["T"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [180,185,190,200,210,220,230,240,250,260,270,280,290,295,300] diff_levels = [-20,-15,-10,-5,-2,-1,1,2,5,10,15,20] @@ -14,7 +14,7 @@ case_id = "MERRA2" variables = ["U"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" contour_levels = [-40,-30,-25,-20,-15,-10,-5,-2.5,2.5,5,10,15,20,25,30,40] @@ -26,7 +26,7 @@ case_id = "MERRA2" variables = ["RELHUM"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.5,1,2,5,10,15,20,25,30,40,50,70] diff_levels = [-15,-10,-5,-2,-1,-0.5, 0.5, 1,2,5,10,15] @@ -36,7 +36,7 @@ case_id = "MERRA2" variables = ["Q"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.6,0.8, 1, 1.2, 1.6, 2, 2.5, 3, 3.5, 4] diff_levels = [-3, -2,-1.5,-1,-0.5,-0.1,0.1,0.5,1,1.5,2,3] @@ -46,7 +46,7 @@ case_id = "MERRA2" variables = ["OMEGA"] ref_name = "MERRA2" reference_name = "MERRA2" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" contour_levels = [-1.5,-1.0,-0.5,-0.2,-0.1,0.1,0.2,0.5,1.0,1.5] @@ -58,7 +58,7 @@ case_id = "ERA5" variables = ["T"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [180,185,190,200,210,220,230,240,250,260,270,280,290,295,300] diff_levels = [-20,-15,-10,-5,-2,-1,1,2,5,10,15,20] @@ -68,7 +68,7 @@ case_id = "ERA5" variables = ["U"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" contour_levels = [-40,-30,-25,-20,-15,-10,-5,-2.5,2.5,5,10,15,20,25,30,40] @@ -80,7 +80,7 @@ case_id = "ERA5" variables = ["OMEGA"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] test_colormap = "PiYG_r" reference_colormap = "PiYG_r" contour_levels = [-1.5,-1.0,-0.5,-0.2,-0.1,0.1,0.2,0.5,1.0,1.5] @@ -92,7 +92,7 @@ case_id = "ERA5" variables = ["RELHUM"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.5,1,2,5,10,15,20,25,30,40,50,70] diff_levels = [-15,-10,-5,-2,-1,-0.5, 0.5, 1,2,5,10,15] @@ -102,6 +102,6 @@ case_id = "ERA5" variables = ["Q"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] contour_levels = [0.6,0.8, 1, 1.2, 1.6, 2, 2.5, 3, 3.5, 4] diff_levels = [-3, -2,-1.5,-1,-0.5,-0.1,0.1,0.5,1,1.5,2,3] diff --git a/e3sm_diags/driver/default_diags/zonal_mean_xy_model_vs_model.cfg b/e3sm_diags/driver/default_diags/zonal_mean_xy_model_vs_model.cfg index e91583974..5c3d98c28 100644 --- a/e3sm_diags/driver/default_diags/zonal_mean_xy_model_vs_model.cfg +++ b/e3sm_diags/driver/default_diags/zonal_mean_xy_model_vs_model.cfg @@ -2,178 +2,178 @@ sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["PRECT"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["global"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["SST"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["SOLIN"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["ALBEDO"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["ALBEDOC"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["RESTOM"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["FLUT"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["FLUTC"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["FSNTOA"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["FSNTOAC"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["SWCF"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["LWCF"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["NETCF"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["ALBEDO_SRF"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["SWCFSRF"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["LWCFSRF"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["FLDS"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["FLDSC"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["FLNS"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["FLNSC"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["FSDS"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["FSDSC"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["FSNS"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["FSNSC"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["LHFLX"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["TMQ"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["U"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [850.0, 200.0] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["Z3"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [500.0] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["T"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [850.0, 200.0] @@ -181,156 +181,157 @@ plevs = [850.0, 200.0] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["CLDTOT_TAU1.3_ISCCP"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["CLDTOT_TAU1.3_9.4_ISCCP"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["CLDTOT_TAU9.4_ISCCP"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["CLDTOT_TAU1.3_MISR"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["CLDTOT_TAU1.3_9.4_MISR"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["CLDTOT_TAU9.4_MISR"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["CLDLOW_TAU1.3_MISR"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["CLDLOW_TAU1.3_9.4_MISR"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["CLDLOW_TAU9.4_MISR"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["CLDTOT_TAU1.3_MODIS"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["CLDTOT_TAU1.3_9.4_MODIS"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["CLDTOT_TAU9.4_MODIS"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["CLDHGH_TAU1.3_MODIS"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["CLDHGH_TAU1.3_9.4_MODIS"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["CLDHGH_TAU9.4_MODIS"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["CLDHGH_CAL"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["CLDLOW_CAL"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["CLDMED_CAL"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["CLDTOT_CAL"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["TGCLDLWP_OCN"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["ERFtot", "ERFari", "ERFaci", "ERFres"] -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] + [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ['Mass_bc_200', 'Mass_ncl_200', 'Mass_so4_200', 'Mass_dst_200', 'Mass_pom_200', 'Mass_soa_200', 'Mass_mom_200'] -seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ['Mass_bc_srf', 'Mass_ncl_srf', 'Mass_so4_srf', 'Mass_dst_srf', 'Mass_pom_srf', 'Mass_soa_srf', 'Mass_mom_srf'] -seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ['Mass_bc_850', 'Mass_ncl_850', 'Mass_so4_850', 'Mass_dst_850', 'Mass_pom_850', 'Mass_soa_850', 'Mass_mom_850'] -seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ['Mass_bc_330', 'Mass_ncl_330', 'Mass_so4_330', 'Mass_dst_330', 'Mass_pom_330', 'Mass_soa_330', 'Mass_mom_330'] -seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "model_vs_model" variables = ["in_grid_cdnc", "in_grid_lwp", "in_cloud_cdnc", "in_cloud_lwp"] -seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] diff --git a/e3sm_diags/driver/default_diags/zonal_mean_xy_model_vs_obs.cfg b/e3sm_diags/driver/default_diags/zonal_mean_xy_model_vs_obs.cfg index bdf58e278..9f6ee8867 100644 --- a/e3sm_diags/driver/default_diags/zonal_mean_xy_model_vs_obs.cfg +++ b/e3sm_diags/driver/default_diags/zonal_mean_xy_model_vs_obs.cfg @@ -4,7 +4,7 @@ case_id = "GPCP_v3.2" variables = ["PRECT"] ref_name = "GPCP_v3.2" reference_name = "GPCP v2.2" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["global"] [#] @@ -13,7 +13,7 @@ case_id = "GPCP_v2.3" variables = ["PRECT"] ref_name = "GPCP_v2.3" reference_name = "GPCP v2.3" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] regions = ["global"] [#] @@ -22,7 +22,7 @@ case_id = "SST_HadISST" variables = ["SST"] ref_name = "HadISST" reference_name = "HadISST" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -30,7 +30,7 @@ case_id = "SST_CL_HadISST" variables = ["SST"] ref_name = "HadISST_CL" reference_name = "HadISST (Climatology)" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -38,7 +38,7 @@ case_id = "SST_PI_HadISST" variables = ["SST"] ref_name = "HadISST_PI" reference_name = "HadISST (Pre-Indust)" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -46,7 +46,7 @@ case_id = "SST_PD_HadISST" variables = ["SST"] ref_name = "HadISST_PD" reference_name = "HadISST (Present Day)" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -54,7 +54,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["SOLIN"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -62,7 +62,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["ALBEDO"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -70,7 +70,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["ALBEDOC"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -78,7 +78,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["RESTOM"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -86,7 +86,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["FLUT"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -94,7 +94,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["FLUTC"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -102,7 +102,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["FSNTOA"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -110,7 +110,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["FSNTOAC"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -118,7 +118,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["SWCF"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -126,7 +126,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["LWCF"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -134,7 +134,7 @@ case_id = "CERES-EBAF-TOA-v4.1" variables = ["NETCF"] ref_name = "ceres_ebaf_toa_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -142,7 +142,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["ALBEDO_SRF"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -150,7 +150,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["SWCFSRF"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -158,7 +158,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["LWCFSRF"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -166,7 +166,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["FLDS"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -174,7 +174,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["FLDSC"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -182,7 +182,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["FLNS"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -190,7 +190,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["FLNSC"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -198,7 +198,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["FSDS"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -206,7 +206,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["FSDSC"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -214,7 +214,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["FSNS"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -222,7 +222,7 @@ case_id = "CERES-EBAF-surface-v4.1" variables = ["FSNSC"] ref_name = "ceres_ebaf_surface_v4.1" reference_name = "CERES-EBAF v4.1" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -230,7 +230,7 @@ case_id = "ERA5" variables = ["LHFLX"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -238,7 +238,7 @@ case_id = "ERA5" variables = ["PRECT"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -246,7 +246,7 @@ case_id = "ERA5" variables = ["TMQ"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -254,7 +254,7 @@ case_id = "ERA5" variables = ["TREFHT"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -262,7 +262,7 @@ case_id = "ERA5" variables = ["TMQ"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -270,7 +270,7 @@ case_id = "ERA5" variables = ["U"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [850.0, 200.0] @@ -279,7 +279,7 @@ case_id = "ERA5" variables = ["Z3"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [500.0] [#] @@ -288,7 +288,7 @@ case_id = "ERA5" variables = ["T"] ref_name = "ERA5" reference_name = "ERA5 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [850.0, 200.0] [#] @@ -297,7 +297,7 @@ case_id = "MERRA2" variables = ["PRECT"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -305,7 +305,7 @@ case_id = "MERRA2" variables = ["TMQ"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -313,7 +313,7 @@ case_id = "MERRA2" variables = ["TREFHT"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] @@ -322,7 +322,7 @@ case_id = "MERRA2" variables = ["TREFMNAV"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] @@ -331,7 +331,7 @@ case_id = "MERRA2" variables = ["TREFMXAV"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -339,7 +339,7 @@ case_id = "MERRA2" variables = ["U"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [850, 200.0] [#] @@ -348,7 +348,7 @@ case_id = "MERRA2" variables = ["Z3"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [500.0] [#] @@ -357,7 +357,7 @@ case_id = "MERRA2" variables = ["T"] ref_name = "MERRA2" reference_name = "MERRA2 Reanalysis" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] plevs = [850.0, 200.0] [#] @@ -366,7 +366,7 @@ case_id = "Cloud ISCCP" variables = ["CLDTOT_TAU1.3_ISCCP"] ref_name = "ISCCPCOSP" reference_name = "ISCCP" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -374,7 +374,7 @@ case_id = "Cloud ISCCP" variables = ["CLDTOT_TAU1.3_9.4_ISCCP"] ref_name = "ISCCPCOSP" reference_name = "ISCCP" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -382,7 +382,7 @@ case_id = "Cloud ISCCP" variables = ["CLDTOT_TAU9.4_ISCCP"] ref_name = "ISCCPCOSP" reference_name = "ISCCP" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -390,7 +390,7 @@ case_id = "Cloud MISR" variables = ["CLDTOT_TAU1.3_MISR"] ref_name = "MISRCOSP" reference_name = "MISR Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -398,7 +398,7 @@ case_id = "Cloud MISR" variables = ["CLDTOT_TAU1.3_9.4_MISR"] ref_name = "MISRCOSP" reference_name = "MISR Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -406,7 +406,7 @@ case_id = "Cloud MISR" variables = ["CLDTOT_TAU9.4_MISR"] ref_name = "MISRCOSP" reference_name = "MISR Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -414,7 +414,7 @@ case_id = "Cloud MISR" variables = ["CLDLOW_TAU1.3_MISR"] ref_name = "MISRCOSP" reference_name = "MISR Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -422,7 +422,7 @@ case_id = "Cloud MISR" variables = ["CLDLOW_TAU1.3_9.4_MISR"] ref_name = "MISRCOSP" reference_name = "MISR Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -430,7 +430,7 @@ case_id = "Cloud MISR" variables = ["CLDLOW_TAU9.4_MISR"] ref_name = "MISRCOSP" reference_name = "MISR Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -438,7 +438,7 @@ case_id = "Cloud MODIS" variables = ["CLDTOT_TAU1.3_MODIS"] ref_name = "MODISCOSP" reference_name = "MODIS Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -446,7 +446,7 @@ case_id = "Cloud MODIS" variables = ["CLDTOT_TAU1.3_9.4_MODIS"] ref_name = "MODISCOSP" reference_name = "MODIS Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -454,7 +454,7 @@ case_id = "Cloud MODIS" variables = ["CLDTOT_TAU9.4_MODIS"] ref_name = "MODISCOSP" reference_name = "MODIS Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -462,7 +462,7 @@ case_id = "Cloud MODIS" variables = ["CLDHGH_TAU1.3_MODIS"] ref_name = "MODISCOSP" reference_name = "MODIS Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -470,7 +470,7 @@ case_id = "Cloud MODIS" variables = ["CLDHGH_TAU1.3_9.4_MODIS"] ref_name = "MODISCOSP" reference_name = "MODIS Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -478,7 +478,7 @@ case_id = "Cloud MODIS" variables = ["CLDHGH_TAU9.4_MODIS"] ref_name = "MODISCOSP" reference_name = "MODIS Simulator" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -486,7 +486,7 @@ case_id = "Cloud Calipso" variables = ["CLDHGH_CAL"] ref_name = "CALIPSOCOSP" reference_name = "CALIPSO-GOCCP" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -494,7 +494,7 @@ case_id = "Cloud Calipso" variables = ["CLDLOW_CAL"] ref_name = "CALIPSOCOSP" reference_name = "CALIPSO-GOCCP" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -502,7 +502,7 @@ case_id = "Cloud Calipso" variables = ["CLDMED_CAL"] ref_name = "CALIPSOCOSP" reference_name = "CALIPSO-GOCCP" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -510,7 +510,7 @@ case_id = "Cloud Calipso" variables = ["CLDTOT_CAL"] ref_name = "CALIPSOCOSP" reference_name = "CALIPSO-GOCCP" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -518,7 +518,7 @@ case_id = "Cloud SSM/I" variables = ["TGCLDLWP_OCN"] ref_name = "SSMI" reference_name = "SSM/I (Wentz)" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -526,7 +526,7 @@ case_id = "GPCP_OAFLux" variables = ["PminusE"] ref_name = "GPCP_OAFLux" reference_name = "PRECT(GPCP) minus QFLX(OAFLux)" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] @@ -534,36 +534,36 @@ case_id = "COREv2_Flux" variables = ["PminusE"] ref_name = "COREv2_Flux" reference_name = "COREv2_Flux" -seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "aero-no-ref-data" variables = ['Mass_bc_200', 'Mass_ncl_200', 'Mass_so4_200', 'Mass_dst_200', 'Mass_pom_200', 'Mass_soa_200', 'Mass_mom_200'] -seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "aero-no-ref-data" variables = ['Mass_bc_srf', 'Mass_ncl_srf', 'Mass_so4_srf', 'Mass_dst_srf', 'Mass_pom_srf', 'Mass_soa_srf', 'Mass_mom_srf'] -seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "aero-no-ref-data" variables = ['Mass_bc_850', 'Mass_ncl_850', 'Mass_so4_850', 'Mass_dst_850', 'Mass_pom_850', 'Mass_soa_850', 'Mass_mom_850'] -seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "aero-no-ref-data" variables = ['Mass_bc_330', 'Mass_ncl_330', 'Mass_so4_330', 'Mass_dst_330', 'Mass_pom_330', 'Mass_soa_330', 'Mass_mom_330'] -seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] [#] sets = ["zonal_mean_xy"] case_id = "aero-no-ref-data" variables = ["in_grid_cdnc", "in_grid_lwp", "in_cloud_cdnc", "in_cloud_lwp"] -seasons = ["ANN","DJF", "MAM", "JJA", "SON"] +seasons = ["ANN", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "DJF", "MAM", "JJA", "SON"] diff --git a/e3sm_diags/driver/utils/dataset.py b/e3sm_diags/driver/utils/dataset.py index ea0f25391..f1e0ae6f5 100644 --- a/e3sm_diags/driver/utils/dataset.py +++ b/e3sm_diags/driver/utils/dataset.py @@ -321,7 +321,25 @@ def _find_climo_file(self, path_name, data_name, season): return os.path.join(path_name, filename) # The below is only ran on model data, because a shorter name is passed into this software. Won't work when use month name such as '01' as season. for filename in dir_files: - if season in ["ANN", "DJF", "MAM", "JJA", "SON"]: + if season in [ + "ANN", + "DJF", + "MAM", + "JJA", + "SON", + "01", + "02", + "03", + "04", + "05", + "06", + "07", + "08", + "09", + "10", + "11", + "12", + ]: if filename.startswith(data_name) and season in filename: return os.path.join(path_name, filename) # No file found. diff --git a/e3sm_diags/viewer/default_viewer.py b/e3sm_diags/viewer/default_viewer.py index 069746ef8..f96e9581b 100644 --- a/e3sm_diags/viewer/default_viewer.py +++ b/e3sm_diags/viewer/default_viewer.py @@ -38,7 +38,25 @@ # These 'seasons' can be months as well, if a user # wants that: # SEASONS = ['01', '02', ..., '12'] -SEASONS = ["ANN", "DJF", "MAM", "JJA", "SON"] +SEASONS = [ + "ANN", + "DJF", + "MAM", + "JJA", + "SON", + "01", + "02", + "03", + "04", + "05", + "06", + "07", + "08", + "09", + "10", + "11", + "12", +] def create_viewer(root_dir, parameters): diff --git a/examples/run_v2_9_0_all_sets_E3SM_machines.py b/examples/run_v2_9_0_all_sets_E3SM_machines.py index 702cf29f0..37f43bc48 100644 --- a/examples/run_v2_9_0_all_sets_E3SM_machines.py +++ b/examples/run_v2_9_0_all_sets_E3SM_machines.py @@ -69,7 +69,7 @@ def run_all_sets(): param.results_dir = f"{machine_paths['html_path']}/v2_9_0_all_sets" param.multiprocessing = True - param.num_workers = 5 + param.num_workers = 24 # Set specific parameters for new sets enso_param = EnsoDiagsParameter() From 22fb716d350330de888a4a253be2349f13ebc47c Mon Sep 17 00:00:00 2001 From: Jill Chengzhu Zhang Date: Thu, 15 Feb 2024 14:06:42 -0800 Subject: [PATCH 23/46] Update intercomparison with cmip models workflow (#786) --- examples/e3sm_diags_for_cmip/e3sm_diags.bash | 53 +++++++++++++++++++ examples/e3sm_diags_for_cmip/generate_page.py | 32 ++++++----- examples/e3sm_diags_for_cmip/generate_xmls.py | 9 ++-- .../e3sm_diags_for_cmip/run_e3sm_diags.py | 3 +- .../templates/cmip6_template.html | 11 ++-- 5 files changed, 89 insertions(+), 19 deletions(-) create mode 100644 examples/e3sm_diags_for_cmip/e3sm_diags.bash diff --git a/examples/e3sm_diags_for_cmip/e3sm_diags.bash b/examples/e3sm_diags_for_cmip/e3sm_diags.bash new file mode 100644 index 000000000..c6a5c36d0 --- /dev/null +++ b/examples/e3sm_diags_for_cmip/e3sm_diags.bash @@ -0,0 +1,53 @@ +#!/bin/bash + +#SBATCH --job-name=e3sm_diags_{{ model }} +#SBATCH --nodes=1 +#SBATCH --output=e3sm_diags_{{ model }}.o%j +#SBATCH --exclusive +#SBATCH --time=02:00:00 + +# Load environment +#source /export/golaz1/conda/etc/profile.d/conda.sh +#conda activate e3sm_diags_env_dev +source /p/user_pub/e3sm_unified/envs/load_latest_e3sm_unified_acme1.sh + +# Make sure UVCDAT doesn't prompt us about anonymous logging +export UVCDAT_ANONYMOUS_LOG=False + +# Run E3SM Diags +time python << EOF + +import os +from e3sm_diags.parameter.core_parameter import CoreParameter +from e3sm_diags.run import runner + +param = CoreParameter() + +param.test_data_path = '{{ simulation }}' + +param.short_test_name = '{{ institution }} {{ model }} {{ experiment }} ({{ realization }})' +param.test_timeseries_input = True +param.test_start_yr = '1985' +param.test_end_yr = '2014' + +#param.reference_data_path = '/p/user_pub/e3sm/e3sm_diags_data/obs_for_e3sm_diags/climatology/' +param.reference_data_path = '/p/user_pub/e3sm/diagnostics/observations/Atm/climatology_1985-2014' + + +param.results_dir = '/var/www/acme/acme-diags/zhang40/CMIP6_20240109_1985-2014/{{ model }}/{{ experiment }}/{{ realization }}' +param.multiprocessing = True +param.num_workers = 16 +#param.output_format_subplot = ["pdf", "png"] +param.diff_title = '{{ model }} {{ experiment }} ({{ realization }}) vs Obs' + +# Use below to run all core sets of diags: +#runner.sets_to_run = ['lat_lon','zonal_mean_xy', 'zonal_mean_2d', 'polar', 'cosp_histogram', 'meridional_mean_2d'] +# Use below to run only a subset: +runner.sets_to_run = ['lat_lon']#, 'zonal_mean_xy', 'zonal_mean_2d'] +runner.run_diags([param]) + +EOF + +echo All done... + + diff --git a/examples/e3sm_diags_for_cmip/generate_page.py b/examples/e3sm_diags_for_cmip/generate_page.py index 26afc65af..ff216f5b3 100755 --- a/examples/e3sm_diags_for_cmip/generate_page.py +++ b/examples/e3sm_diags_for_cmip/generate_page.py @@ -20,8 +20,9 @@ def table_elements(search): "RESTOM global ceres_ebaf_toa_v4.1", "FSNTOA global ceres_ebaf_toa_v4.1", "FLUT global ceres_ebaf_toa_v4.1", "SWCF global ceres_ebaf_toa_v4.1", "LWCF global ceres_ebaf_toa_v4.1", - "U-850mb global ERA-Interim", "U-200mb global ERA-Interim", - "Z3-500mb global ERA-Interim" + "U-850mb global ERA5", "U-200mb global ERA5", + "T-850mb global ERA5", "T-200mb global ERA5", + "Z3-500mb global ERA5" ] metrics = ["Unit", "RMSE", "Mean_Bias", "Correlation"] seasons = ["ANN", "DJF", "MAM", "JJA", "SON"] @@ -34,9 +35,11 @@ def table_elements(search): "FLUT global ceres_ebaf_toa_v4.1":"TOA LW (vs CERES-EBAAF Ed4.1)", "SWCF global ceres_ebaf_toa_v4.1":"TOA SWCRE (vs CERES-EBAAF Ed4.1)", "LWCF global ceres_ebaf_toa_v4.1":"TOA LWCRE (vs CERES-EBAAF Ed4.1)", - "U-850mb global ERA-Interim":"u 850 hPa (vs ERA-Interim)", - "U-200mb global ERA-Interim":"u 200 hPa (vs ERA-Interim)", - "Z3-500mb global ERA-Interim":"Geo-Z 500 hPA (vs ERA-Interim)", + "U-850mb global ERA5":"u 850 hPa (vs ERA5)", + "U-200mb global ERA5":"u 200 hPa (vs ERA5)", + "T-850mb global ERA5":"t 850 hPa (vs ERA5)", + "T-200mb global ERA5":"t 200 hPa (vs ERA5)", + "Z3-500mb global ERA5":"Geo-Z 500 hPA (vs ERA5)", "Unit":"unit", "RMSE":"rmse", "Mean_Bias":"bias", @@ -51,9 +54,11 @@ def table_elements(search): "FLUT global ceres_ebaf_toa_v4.1":"lat_lon/ceres-ebaf-toa-v41/flut-global-ceres_ebaf_toa_v41", "SWCF global ceres_ebaf_toa_v4.1":"lat_lon/ceres-ebaf-toa-v41/swcf-global-ceres_ebaf_toa_v41", "LWCF global ceres_ebaf_toa_v4.1":"lat_lon/ceres-ebaf-toa-v41/lwcf-global-ceres_ebaf_toa_v41", - "U-850mb global ERA-Interim":"lat_lon/era-interim/u-850mb-global-era-interim", - "U-200mb global ERA-Interim":"lat_lon/era-interim/u-200mb-global-era-interim", - "Z3-500mb global ERA-Interim":"lat_lon/era-interim/z3-500mb-global-era-interim", + "U-850mb global ERA5":"lat_lon/era5/u-850mb-global-era5", + "U-200mb global ERA5":"lat_lon/era5/u-200mb-global-era5", + "T-850mb global ERA5":"lat_lon/era5/t-850mb-global-era5", + "T-200mb global ERA5":"lat_lon/era5/t-200mb-global-era5", + "Z3-500mb global ERA5":"lat_lon/era5/z3-500mb-global-era5", } # Loop over all simulations to gather data @@ -74,7 +79,8 @@ def table_elements(search): c['model'] = p[-3] c['institution'] = p[-4] #c['www'] = "/var/www/acme/acme-diags/zhang40/CMIP6/%s/%s/%s" \ - c['www'] = "/var/www/acme/acme-diags/e3sm_diags_for_cmip/%s/%s/%s" \ + #c['www'] = "/var/www/acme/acme-diags/e3sm_diags_for_cmip/%s/%s/%s" \ + c['www'] = "/var/www/acme/acme-diags/zhang40/CMIP6_20240109_1985-2014/%s/%s/%s" \ % (c['model'],c['experiment'],c['realization']) print(c['www']) @@ -173,14 +179,16 @@ def table_elements(search): # amip simulations #fields, header, content = table_elements('/var/www/acme/acme-diags/zhang40/CMIP6/*/amip/r1i1p1f1/') -fields, header, content = table_elements('/var/www/acme/acme-diags/e3sm_diags_for_cmip/*/amip/*/') +#fields, header, content = table_elements('/var/www/acme/acme-diags/e3sm_diags_for_cmip/*/amip/*/') +fields, header, content = table_elements('/var/www/acme/acme-diags/zhang40/CMIP6_20240109_1985-2014/*/amip/*/') c['fields'].append(fields) c['header'].append(header) c['content'].append(content) # historical simulations #fields, header, content = table_elements('/var/www/acme/acme-diags/zhang40/CMIP6/*/historical/r1i1p1f1/') -fields, header, content = table_elements('/var/www/acme/acme-diags/e3sm_diags_for_cmip/*/historical/*/') +#fields, header, content = table_elements('/var/www/acme/acme-diags/e3sm_diags_for_cmip/*/historical/*/') +fields, header, content = table_elements('/var/www/acme/acme-diags/zhang40/CMIP6_20240109_1985-2014/*/historical/*/') c['fields'].append(fields) c['header'].append(header) c['content'].append(content) @@ -191,7 +199,7 @@ def table_elements(search): template = templateEnv.get_template( 'cmip6_template.html' ) # Instantiate page -path = os.path.join('index.html') +path = os.path.join('/var/www/acme/acme-diags/zhang40/CMIP6_20240109_1985-2014','index.html') with open(path, 'w') as f: f.write(template.render( **c )) diff --git a/examples/e3sm_diags_for_cmip/generate_xmls.py b/examples/e3sm_diags_for_cmip/generate_xmls.py index 010fe4d0c..2a715361b 100644 --- a/examples/e3sm_diags_for_cmip/generate_xmls.py +++ b/examples/e3sm_diags_for_cmip/generate_xmls.py @@ -25,13 +25,15 @@ def run_command(command): dry_run = False # Output directory for xml files -destination = '/home/zhang40/e3sm_diags_for_CMIP6/CMIP6_20211206' +destination = '/home/zhang40/e3sm_diags_for_CMIP6/CMIP6_20240111' # Input search paths -paths = ('/p/css03/esgf_publish/CMIP6/CMIP', '/p/user_pub/work/CMIP6/CMIP') +paths = ('/p/user_pub/work/CMIP6/CMIP','') # for E3SM CMIP archive only +#paths = ('/p/css03/esgf_publish/CMIP6/CMIP', '/p/user_pub/work/CMIP6/CMIP') # Search patterm -patterns = ('*/*/%s/r*i1p1f1/Amon/' % (experiment),) +patterns = ('*/*/%s/r1i1p1f1/Amon/' % (experiment),) +#patterns = ('UCSB/*/%s/r*i*p*f*/Amon/' % (experiment),) # Output file to log included input netCDF files name = "%s_%s.log" % (experiment,date.today().strftime("%y%m%d")) @@ -65,6 +67,7 @@ def run_command(command): versions = sorted(versions) mostRecent = versions[-1] # Now, extract first and last date + print('most recent version', mostRecent) files = sorted(os.listdir(mostRecent)) # First time stamp first = files[0].split('_')[-1] diff --git a/examples/e3sm_diags_for_cmip/run_e3sm_diags.py b/examples/e3sm_diags_for_cmip/run_e3sm_diags.py index c1ca6a257..c5a5c41c8 100644 --- a/examples/e3sm_diags_for_cmip/run_e3sm_diags.py +++ b/examples/e3sm_diags_for_cmip/run_e3sm_diags.py @@ -8,7 +8,8 @@ import utils # Location of xml files -input = '/home/zhang40/e3sm_diags_for_CMIP6/CMIP6_20211206/CMIP/*/*/amip/*/' +input = '/home/zhang40/e3sm_diags_for_CMIP6/CMIP6_20240109_wE3SM/CMIP/*/*/amip/r1i1p1f1/' +#input = '/home/zhang40/e3sm_diags_for_CMIP6/CMIP6_20240109/CMIP/*/*/historical/r1i1p1f1/' print(input) # Initialize jinja2 template engine diff --git a/examples/e3sm_diags_for_cmip/templates/cmip6_template.html b/examples/e3sm_diags_for_cmip/templates/cmip6_template.html index 6d338669a..15f2f1526 100644 --- a/examples/e3sm_diags_for_cmip/templates/cmip6_template.html +++ b/examples/e3sm_diags_for_cmip/templates/cmip6_template.html @@ -21,12 +21,17 @@

E3SM Diags for CMIP6

-

AMIP simulations (1980-2014)

+

AMIP simulations (1985-2014)

From 765210c921fbeea63626551b4afc5b2ca2a611fc Mon Sep 17 00:00:00 2001 From: chengzhuzhang Date: Fri, 23 Feb 2024 17:34:46 -0800 Subject: [PATCH 24/46] put everything together in source --- .../run_tropical_subseasonal.py | 18 + .../tropical_subseasonal_model_vs_obs.cfg | 25 + .../driver/tropical_subseasonal_driver.py | 187 +++++ e3sm_diags/driver/utils/__init__.py | 1 + e3sm_diags/driver/utils/zwf_functions.py | 640 ++++++++++++++++++ e3sm_diags/parameter/__init__.py | 2 + e3sm_diags/parameter/core_parameter.py | 2 + .../tropical_subseasonal_parameter.py | 19 + e3sm_diags/parser/__init__.py | 2 + .../parser/tropical_subseasonal_parser.py | 44 ++ .../plot/cartopy/tropical_subseasonal_plot.py | 413 +++++++++++ e3sm_diags/viewer/main.py | 2 + .../viewer/tropical_subseasonal_viewer.py | 62 ++ setup.py | 5 + 14 files changed, 1422 insertions(+) create mode 100644 auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py create mode 100644 e3sm_diags/driver/default_diags/tropical_subseasonal_model_vs_obs.cfg create mode 100644 e3sm_diags/driver/tropical_subseasonal_driver.py create mode 100755 e3sm_diags/driver/utils/zwf_functions.py create mode 100644 e3sm_diags/parameter/tropical_subseasonal_parameter.py create mode 100644 e3sm_diags/parser/tropical_subseasonal_parser.py create mode 100755 e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py create mode 100755 e3sm_diags/viewer/tropical_subseasonal_viewer.py diff --git a/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py b/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py new file mode 100644 index 000000000..6c2e82e85 --- /dev/null +++ b/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py @@ -0,0 +1,18 @@ +import os +from e3sm_diags.parameter.tropical_subseasonal_parameter import TropicalSubseasonalParameter +from e3sm_diags.run import runner + +param = TropicalSubseasonalParameter() + +param.reference_data_path = '/Users/zhang40/Documents/e3sm_diags_data/e3sm_diags_test_data/E3SM_v2_daily' +param.test_data_path = '/Users/zhang40/Documents/e3sm_diags_data/e3sm_diags_test_data/E3SM_v2_daily' +param.test_name = 'E3SMv2' +prefix = '/Users/zhang40/Documents/repos/e3sm_diags/auxiliary_tools/tropical_subseasonal_diags/data1' +param.results_dir = os.path.join(prefix, 'tropical_variability') +param.test_start_yr = '2010' +param.test_end_yr = '2014' +param.ref_start_yr = '2010' +param.ref_end_yr = '2014' + +runner.sets_to_run = ['tropical_subseasonal'] +runner.run_diags([param]) diff --git a/e3sm_diags/driver/default_diags/tropical_subseasonal_model_vs_obs.cfg b/e3sm_diags/driver/default_diags/tropical_subseasonal_model_vs_obs.cfg new file mode 100644 index 000000000..fa377b525 --- /dev/null +++ b/e3sm_diags/driver/default_diags/tropical_subseasonal_model_vs_obs.cfg @@ -0,0 +1,25 @@ +[#] +sets = ["tropical_subseasonal"] +case_id = "wavenumber-frequency" +variables = ["PRECT"] +ref_name = "IMERG_Daily" +reference_name = "IMERG Daily" +regions = ["15S15N"] + + +#[#] +#sets = ["tropical_subseasonal"] +#case_id = "wavenumber-frequency" +#variables = ["FLUT"] +#ref_name = "NOAA-OLR_Daily" +#reference_name = "NOAA OLR Daily" +#regions = ["15S15N"] +# +# +#[#] +#sets = ["tropical_subseasonal"] +#case_id = "wavenumber-frequency" +#variables = ["U850"] +#ref_name = "ERA5_Daily" +#reference_name = "ERA5 Daily" +#regions = ["15S15N"] diff --git a/e3sm_diags/driver/tropical_subseasonal_driver.py b/e3sm_diags/driver/tropical_subseasonal_driver.py new file mode 100644 index 000000000..2305898e2 --- /dev/null +++ b/e3sm_diags/driver/tropical_subseasonal_driver.py @@ -0,0 +1,187 @@ +from __future__ import annotations + +import glob +import json +import os +from typing import TYPE_CHECKING # , Optional + +import numpy as np +import xarray as xr +from scipy.stats import binned_statistic + +import e3sm_diags +from e3sm_diags.driver import utils +from e3sm_diags.logger import custom_logger +from e3sm_diags.plot.cartopy.tropical_subseasonal_plot import plot +from e3sm_diags.viewer.tropical_subseasonal_viewer import create_viewer +from e3sm_diags.driver.utils import zwf_functions as wf + +from matplotlib.colors import ListedColormap, BoundaryNorm + +if TYPE_CHECKING: + from e3sm_diags.parameter.tropical_subseasonal_parameter import TropicalSubseasonalParameter + + +logger = custom_logger(__name__) + +# Script to compute and plot spectral powers of a subseasonal tropical field in +# zonal wavenumber-frequency space. Both the plot files and files containing the +# associated numerical data shown in the plots are created. + +# Authors: Jim Benedict and Brian Medeiros +# Modified by Jill Zhang to integrate into E3SM Diags. + +def find_nearest(array, value): + array = np.asarray(array) + idx = (np.abs(array - value)).argmin() + return idx,array[idx] + """Return index of [array] closest in value to [value] + Example: + array = [ 0.21069679 0.61290182 0.63425412 0.84635244 0.91599191 0.00213826 + 0.17104965 0.56874386 0.57319379 0.28719469] + print(find_nearest(array, value=0.5)) + # 0.568743859261 + + """ + +def wf_analysis(x, **kwargs): + """Return zonal wavenumber-frequency power spectra of x. The returned spectra are: + spec_sym: Raw (non-normalized) power spectrum of the component of x that is symmetric about the equator. + spec_asym: Raw (non-normalized) power spectrum of the component of x that is antisymmetric about the equator. + nspec_sym: Normalized (by a smoothed red-noise background spectrum) power spectrum of the component of x that is symmetric about the equator. + nspec_asym: Normalized (by a smoothed red-noise background spectrum) power spectrum of the component of x that is antisymmetric about the equator. + + The NCL version of 'wkSpaceTime' smooths the symmetric and antisymmetric components + along the frequency dimension using a 1-2-1 filter once. + + """ + # Get the "raw" spectral power + # OPTIONAL kwargs: + # segsize, noverlap, spd, latitude_bounds (tuple: (south, north)), dosymmetries, rmvLowFrq + + z2 = wf.spacetime_power(x, **kwargs) + z2avg = z2.mean(dim='component') + z2.loc[{'frequency':0}] = np.nan # get rid of spurious power at \nu = 0 (mean) + + # Following NCL's wkSpaceTime, apply one pass of a 1-2-1 filter along the frequency + # domain to the raw (non-normalized) spectra/um. + # Do not use 0 frequency when smoothing here. + # Use weights that sum to 1 to ensure that smoothing is conservative. + z2s = wf.smoothFrq121(z2,1) + + # The background is supposed to be derived from both symmetric & antisymmetric + # Inputs to the background spectrum calculation should be z2avg + background = wf.smoothBackground_wavefreq(z2avg) + # separate components + spec_sym = z2s[0,...] + spec_asy = z2s[1,...] + # normalize: Following NCL's wkSpaceTime, use lightly smoothed version of spectra/um + # as numerator + nspec_sym = spec_sym / background + nspec_asy = spec_asy / background + + spec = xr.merge([spec_sym.rename('spec_raw_sym'), spec_asy.rename('spec_raw_asy'), nspec_sym.rename('spec_norm_sym'), nspec_asy.rename('spec_norm_asy'), background.rename('spec_background')], compat='override') + spec_all = spec.drop('component') + spec_all['spec_raw_sym'].attrs = {"component": "symmetric", "type": "raw"} + spec_all['spec_raw_asy'].attrs = {"component": "antisymmetric", "type": "raw"} + spec_all['spec_norm_sym'].attrs = {"component": "symmetric", "type": "normalized"} + spec_all['spec_norm_asy'].attrs = {"component": "antisymmetric", "type": "normalized"} + spec_all['spec_background'].attrs = {"component": "", "type": "background"} + + return spec_all + + +def calculate_spectrum(path, variable): + + latBound = (-15,15) # latitude bounds for analysis + spd = 1 # SAMPLES PER DAY + nDayWin = 96 # Wheeler-Kiladis [WK] temporal window length (days) + nDaySkip = -60 # time (days) between temporal windows [segments] + # negative means there will be overlapping temporal segments + twoMonthOverlap = -1*nDaySkip + + opt = {'segsize': nDayWin, + 'noverlap': twoMonthOverlap, + 'spd': spd, + 'latitude_bounds': latBound, + 'dosymmetries': True, + 'rmvLowFrq':True} + + var = xr.open_mfdataset(glob.glob(f"{path}/{variable}_*.nc")).sel( + lat=slice(-15, 15))[variable] + + # TODO: subset time + + # Unit conversion + if var.name == "PRECT": + if var.attrs["units"] == "m/s" or var.attrs["units"] == "m s{-1}": + print("\nBEFORE unit conversion: Max/min of data: " + str(var.values.max()) + " " + str(var.values.min())) + var.values = var.values * 1000. * 86400. # convert m/s to mm/d, do not alter metadata (yet) + var.attrs["units"] = "mm/d" # adjust metadata to reflect change in units + print("\nAFTER unit conversion: Max/min of data: " + str(var.values.max()) + " " + str(var.values.min())) + if var.name == "precipAvg": + if var.attrs["units"] == "mm/hr": + print("\nBEFORE unit conversion: Max/min of data: " + str(var.values.max()) + " " + str(var.values.min())) + var.values = data.values * 24. # convert mm/hr to mm/d, do not alter metadata (yet) + var.attrs["units"] = "mm/d" # adjust metadata to reflect change in units + print("\nAFTER unit conversion: Max/min of data: " + str(var.values.max()) + " " + str(var.values.min())) + + # Wavenumber Frequency Analysis + spec_all = wf_analysis(var, **opt) + #spec_all.to_netcdf(outDataDir + "/full_spec.nc") + return spec_all + + +def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalParameter: + """Runs the wavenumber frequency analysis for tropical variability. + + :param parameter: Parameters for the run + :type parameter: CoreParameter + :raises ValueError: Invalid run type + :return: Parameters for the run + :rtype: CoreParameter + """ + run_type = parameter.run_type + #variables = parameter.variables + season = "ANN" + + test_data = utils.dataset.Dataset(parameter, test=True) + parameter.test_name_yrs = utils.general.get_name_and_yrs( + parameter, test_data, season + ) + + ref_data = utils.dataset.Dataset(parameter, ref=True) + parameter.ref_name_yrs = utils.general.get_name_and_yrs( + parameter, ref_data, season + ) + print(parameter.ref_name_yrs, parameter.test_name_yrs, parameter.test_data_path) + #print('datapath', parameter.test_data_path, parameter.ref_data_path) + + for variable in parameter.variables: + test = calculate_spectrum(parameter.test_data_path, variable) + #TODO save to netcdf + test.to_netcdf(f"{parameter.results_dir}/full_spec_test.nc") + ref = calculate_spectrum(parameter.test_data_path, variable) + ref.to_netcdf(f"{parameter.results_dir}/full_spec_ref.nc") + parameter.var_id = variable + for diff_name in ["raw_sym", "raw_asy", "norm_sym", "norm_asy", "background"]: + + # Compute percentage difference + diff = 100 * (test[f"spec_{diff_name}"]-ref[f"spec_{diff_name}"])/ref[f"spec_{diff_name}"] + diff.name = f"spec_{diff_name}" + diff.attrs.update(test[f"spec_{diff_name}"].attrs) + parameter.spec_type = diff_name + plot(parameter, test[f"spec_{diff_name}"], ref[f"spec_{diff_name}"], diff) + if "norm" in diff_name: + parameter.spec_type = f"{diff_name}_zoom" + plot(parameter, test[f"spec_{diff_name}"], ref[f"spec_{diff_name}"], diff, do_zoom = True) + + return parameter + + + + + + + + diff --git a/e3sm_diags/driver/utils/__init__.py b/e3sm_diags/driver/utils/__init__.py index 1ffc8fc22..fbe8ed56b 100644 --- a/e3sm_diags/driver/utils/__init__.py +++ b/e3sm_diags/driver/utils/__init__.py @@ -1 +1,2 @@ from . import dataset, diurnal_cycle, general +from . import zwf_functions diff --git a/e3sm_diags/driver/utils/zwf_functions.py b/e3sm_diags/driver/utils/zwf_functions.py new file mode 100755 index 000000000..1b5f6ed81 --- /dev/null +++ b/e3sm_diags/driver/utils/zwf_functions.py @@ -0,0 +1,640 @@ +import sys +import xarray as xr +import numpy as np +from scipy.signal import convolve2d, detrend +import logging +logging.basicConfig(level=logging.INFO) + + +def helper(): + """Prints all the functions that are included in this module.""" + f = [ + "decompose2SymAsym(arr)", + "rmvAnnualCycle(data, spd, fCrit)", + "smoothFrq121(data,nsmth_iter=1)", + "smoothBackground_wavefreq(data)", + "resolveWavesHayashi( varfft: xr.DataArray, nDayWin: int, spd: int ) -> xr.DataArray", + "split_hann_taper(series_length, fraction)", + "spacetime_power(data, segsize=96, noverlap=60, spd=1, latitude_bounds=None, dosymmetries=False, rmvLowFrq=False)", + "genDispersionCurves(nWaveType=6, nPlanetaryWave=50, rlat=0, Ahe=[50, 25, 12])"] + [print(fe) for fe in f] + + + +def getNearestInd1D(arr,val): + """Given a specified value, find the index of an array that most closely matches that value. + arr: numpy array or xarray DataArray + return: Integer + Example: If arr=[0.2, 0.5, -0.9, 1.3] and val=0.4, function would return 1. + Note: Uses python's 'argmin', which returns first occurrence if multiple indices "tie" + for closest to the specified value. + """ + difference_array = np.absolute(arr-val) + i_closest = int(difference_array.argmin()) + return(i_closest) + + + +def decompose2SymAsym(arr): + """Mimic NCL function to decompose into symmetric and asymmetric parts. + + arr: xarray DataArray + + return: DataArray with symmetric in SH, asymmetric in NH + + Note: + This function produces indistinguishable results from NCL version. + """ + lat_dim = arr.dims.index('lat') + # flag to follow NCL convention and put symmetric component in SH + # & asymmetric in NH + # method: use flip to reverse latitude, put in DataArray for coords, use loc/isel + # to assign to negative/positive latitudes (exact equator is left alone) + data_sym = 0.5*(arr.values + np.flip(arr.values, axis=lat_dim)) + data_asy = 0.5*(arr.values - np.flip(arr.values, axis=lat_dim)) + data_sym = xr.DataArray(data_sym, dims=arr.dims, coords=arr.coords) + data_asy = xr.DataArray(data_asy, dims=arr.dims, coords=arr.coords) + out = arr.copy() # might not be best to copy, but is safe + out.loc[{'lat':arr['lat'][arr['lat']<0]}] = data_sym.isel(lat=data_sym.lat<0) + out.loc[{'lat':arr['lat'][arr['lat']>0]}] = data_asy.isel(lat=data_asy.lat>0) + return out + + + +def rmvAnnualCycle(data, spd, fCrit): + """remove frequencies less than fCrit from data. + + data: xarray DataArray + spd: sampling frequency in samples-per-day + fCrit: frequency threshold; remove frequencies < fCrit + + return: xarray DataArray, shape of data + + Note: fft/ifft preserves the mean because z = fft(x), z[0] is the mean. + To keep the mean here, we need to keep the 0 frequency. + + Note: This function reproduces the results from the NCL version. + + Note: Two methods are available, one using fft/ifft and the other rfft/irfft. + They both produce output that is indistinguishable from NCL's result. + """ + dimz = data.sizes + ntim = dimz['time'] + time_ax = list(data.dims).index('time') + # Method 1: Uses the complex FFT, returns the negative frequencies too, but they + # should be redundant b/c they are conjugate of positive ones. + cf = np.fft.fft(data.values, axis=time_ax) + freq = np.fft.fftfreq(ntim, spd) + cf[(freq != 0)&(np.abs(freq) < fCrit), ...] = 0.0 # keeps the mean + z = np.fft.ifft(cf, n=ntim, axis=0) + # Method 2: Uses the real FFT. In this case, + # cf = np.fft.rfft(data.values, axis=time_ax) + # freq = np.linspace(1, (ntim*spd)//2, (ntim*spd)//2) / ntim + # fcrit_ndx = np.argwhere(freq < fCrit).max() + # if fcrit_ndx > 1: + # cf[1:fcrit_ndx+1, ...] = 0.0 + # z = np.fft.irfft(cf, n=ntim, axis=0) + z = xr.DataArray(z.real, dims=data.dims, coords=data.coords) + return z + + + +def smoothFrq121(data,nsmth_iter=1): + """Following what was used in the NCL routine 'wkSpaceTime', smooth input array + [nsmth_iter] times along the frequency dimension, for a sub-section of wavenumbers + (as the smoothing is cosmetic for plotting, only smooth for a subset of wavenumbers + to avoid unnecessary smoothing of non-plotted values). + + Do not use 0 frequency when smoothing. Uses weights that sum to 1 to ensure + smoothing is conservative. + """ + assert isinstance(data, xr.DataArray) + print("\nFrom smoothFrq121: Frequency smoothing " + str(nsmth_iter) + " times for subset of wavenumbers (pos frqs only)") + nfrq = len(data['frequency']) + i_frq0 = int(np.where(data['frequency']==0)[0]) # index of 'frequency' coord where it equals 0 + for smth_iter in range(nsmth_iter): + data[...,i_frq0+1] = 0.75 * data[...,i_frq0+1] + 0.25 * data[...,i_frq0+2] + data[...,-1] = 0.25 * data[...,-2] + 0.75 * data[...,-1] + for i in range(i_frq0+2, nfrq-1): + data[...,i] = 0.25 * data[...,i-1] + 0.5 * data[...,i] + 0.25 * data[...,i+1] + return(data) + + + +def smoothBackground_wavefreq(data): + """From Wheeler and Kiladis (1999, doi: ): + "[Smooth] many times with a 1-2-1 filter in frequency and wavenumber. The number of + passes of the 1-2-1 filter we have used is 10 in frequency throughout, and from 10 + to 40 in wavenumber, being 10 at low frequencies and 40 at higher frequencies + increasing in two different steps." + + Here we do the following smoothing along the wavenumber dimension (note: we only + smooth in the positive frequency domain as these will be the values plotted!): + 0 < frq < 0.1 : 5 1-2-1 smoothing passes + 0.1 <= frq < 0.2: 10 1-2-1 smoothing passes + 0.2 <= frq < 0.3: 20 1-2-1 smoothing passes + 0.3 <= frq : 40 1-2-1 smoothing passes + This follows what was used in the NCL routine 'wkSpaceTime'. + """ + assert isinstance(data, xr.DataArray) + nfrq = len(data['frequency']) + i_frq0 = int(np.where(data['frequency']==0)[0]) # index of 'frequency' coord where it equals 0 +# i_frq03 = getNearestInd1D(data['frequency'], 0.3) # index of 'frequency' coord closest to 0.3 + i_minwav4smth = int(np.where(data['wavenumber']==-27)[0]) # index of 'wavenumber' coord where it equals -27 + i_maxwav4smth = int(np.where(data['wavenumber']==27)[0]) # index of 'wavenumber' coord where it equals 27 + + # Looping over positive frequencies, smooth in wavenumber. The number of smoothing + # passes in wavenumber is dependent on the frequency, with more smoothing at high + # frequencies and less smoothing at low frequencies + print("\nSmoothing background spectrum in wavenumber (pos frq only)...") + for i in range(i_frq0+1,nfrq): # Loop over all positive frequencies + + if(data['frequency'][i] < 0.1): + nsmth_iter = 5 + print(" Wavenumber smoothing " + str(nsmth_iter) + " times for freq: " + str(float(data['frequency'][i]))) + for smth_iter in range(nsmth_iter): + for j in range(i_minwav4smth, i_maxwav4smth+1): + data[j,i] = 0.25 * data[j-1,i] + 0.5 * data[j,i] + 0.25 * data[j+1,i] + + if(data['frequency'][i] >= 0.1 and data['frequency'][i] < 0.2): + nsmth_iter = 10 + print(" Wavenumber smoothing " + str(nsmth_iter) + " times for freq: " + str(float(data['frequency'][i]))) + for smth_iter in range(10): + for j in range(i_minwav4smth, i_maxwav4smth+1): + data[j,i] = 0.25 * data[j-1,i] + 0.5 * data[j,i] + 0.25 * data[j+1,i] + + if(data['frequency'][i] >= 0.2 and data['frequency'][i] < 0.3): + nsmth_iter = 20 + print(" Wavenumber smoothing " + str(nsmth_iter) + " times for freq: " + str(float(data['frequency'][i]))) + for smth_iter in range(20): + for j in range(i_minwav4smth, i_maxwav4smth+1): + data[j,i] = 0.25 * data[j-1,i] + 0.5 * data[j,i] + 0.25 * data[j+1,i] + + if(data['frequency'][i] >= 0.3): + nsmth_iter = 40 + print(" Wavenumber smoothing " + str(nsmth_iter) + " times for freq: " + str(float(data['frequency'][i]))) + for smth_iter in range(40): + for j in range(i_minwav4smth, i_maxwav4smth+1): + data[j,i] = 0.25 * data[j-1,i] + 0.5 * data[j,i] + 0.25 * data[j+1,i] + + # For all wavenumbers, smooth in frequency: 10 passes + # Do not use 0 frequency when smoothing + # Use weights that sum to 1 to ensure smoothing is conservative + nsmth_iter = 10 + print("Frequency smoothing " + str(nsmth_iter) + " times for all wavenumbers (pos frqs only)") + for smth_iter in range(10): + data[:,i_frq0+1] = 0.75 * data[:,i_frq0+1] + 0.25 * data[:,i_frq0+2] + data[:,-1] = 0.25 * data[:,-2] + 0.75 * data[:,-1] + for i in range(i_frq0+2, nfrq-1): + data[:,i] = 0.25 * data[:,i-1] + 0.5 * data[:,i] + 0.25 * data[:,i+1] + return(data) + + + +def resolveWavesHayashi( varfft: xr.DataArray, nDayWin: int, spd: int ) -> xr.DataArray: + """This is a direct translation from the NCL routine to python/xarray. + input: + varfft : expected to have rightmost dimensions of wavenumber and frequency. + varfft : expected to be an xarray DataArray with coordinate variables. + nDayWin : integer that is the length of the segments in days. + spd : the sampling frequency in `timesteps` per day. + + returns: + a DataArray that is reordered to have correct westward & eastward propagation. + + """ + #------------------------------------------------------------- + # Special reordering to resolve the Progressive and Retrogressive waves + # Reference: Hayashi, Y. + # A Generalized Method of Resolving Disturbances into + # Progressive and Retrogressive Waves by Space and + # Fourier and TimeCross Spectral Analysis + # J. Meteor. Soc. Japan, 1971, 49: 125-128. + #------------------------------------------------------------- + + # in NCL varfft is dimensioned (2,mlon,nSampWin), but the first dim doesn't matter b/c python supports complex numbers. + # + # Create array PEE(NL+1,NT+1) which contains the (real) power spectrum. + # all the following assume indexing starting with 0 + # In this array (PEE), the negative wavenumbers will be from pn=0 to NL/2-1 (left). + # The positive wavenumbers will be for pn=NL/2+1 to NL (right). + # Negative frequencies will be from pt=0 to NT/2-1 (left). + # Positive frequencies will be from pt=NT/2+1 to NT (right). + # Information about zonal mean will be for pn=NL/2 (middle). + # Information about time mean will be for pt=NT/2 (middle). + # Information about the Nyquist Frequency is at pt=0 and pt=NT + # + + # In PEE, define the + # WESTWARD waves to be either + # positive frequency and negative wavenumber + # OR + # negative freq and positive wavenumber. + # EASTWARD waves are either positive freq and positive wavenumber + # OR negative freq and negative wavenumber. + + # Note that frequencies are returned from fftpack are ordered like so + # input_time_pos [ 0 1 2 3 4 5 6 7 ] + # ouput_fft_coef [mean 1/7 2/7 3/7 nyquist -3/7 -2/7 -1/7] + # mean,pos freq to nyq,neg freq hi to lo + # + # Rearrange the coef array to give you power array of freq and wave number east/west + # Note east/west wave number *NOT* eq to fft wavenumber see Hayashi '71 + # Hence, NCL's 'cfftf_frq_reorder' can *not* be used. + # BPM: This goes for np.fft.fftshift + # + # For ffts that return the coefficients as described above, here is the algorithm + # coeff array varfft(2,n,t) dimensioned (2,0:numlon-1,0:numtim-1) + # new space/time pee(2,pn,pt) dimensioned (2,0:numlon ,0:numtim ) + # + # NOTE: one larger in both freq/space dims + # the initial index of 2 is for the real (indx 0) and imag (indx 1) parts of the array + # + # + # if | 0 <= pn <= numlon/2-1 then | numlon/2 <= n <= 1 + # | 0 <= pt < numtim/2-1 | numtim/2 <= t <= numtim-1 + # + # if | 0 <= pn <= numlon/2-1 then | numlon/2 <= n <= 1 + # | numtime/2 <= pt <= numtim | 0 <= t <= numtim/2 + # + # if | numlon/2 <= pn <= numlon then | 0 <= n <= numlon/2 + # | 0 <= pt <= numtim/2 | numtim/2 <= t <= 0 + # + # if | numlon/2 <= pn <= numlon then | 0 <= n <= numlon/2 + # | numtim/2+1 <= pt <= numtim | numtim-1 <= t <= numtim/2 + + # local variables : dimvf, numlon, N, varspacetime, pee, wave, freq + + # bpm: if varfft is a numpy array, then we need to know which dim is longitude + # if it is an xr.DataArray, then we can just use that directly. This is + # reason enough to insist on a DataArray. + # varfft should have a last dimension of "segments" of size N; should make a convention for the name of that dimension and insist on it here. + logging.debug(f"[Hayashi] nDayWin: {nDayWin}, spd: {spd}") + dimnames = varfft.dims + dimvf = varfft.shape + mlon = len(varfft['wavenumber']) + N = dimvf[-1] + logging.info(f"[Hayashi] input dims is {dimnames}, {dimvf}") + logging.info(f"[Hayashi] input coords is {varfft.coords}") + if len(dimnames) != len(varfft.coords): + logging.error("The size of varfft.coords is incorrect.") + raise ValueError("STOP") + + nshape = list(dimvf) + nshape[-2] += 1 + nshape[-1] += 1 + logging.debug(f"[Hayashi] The nshape ends up being {nshape}") + # this is a reordering, use Ellipsis to allow arbitrary number of dimensions, + # but we insist that the wavenumber and frequency dims are rightmost. + # we will fill the new array in increasing order (arbitrary choice) + logging.debug("allocate the re-ordered array") + varspacetime = np.full(nshape, np.nan, dtype=type(varfft)) + # first two are the negative wavenumbers (westward), second two are the positive wavenumbers (eastward) + logging.debug(f"[Hayashi] Assign values into array. Notable numbers: mlon//2={mlon//2}, N//2={N//2}") + varspacetime[..., 0:mlon//2, 0:N//2 ] = varfft[..., mlon//2:0:-1, N//2:] # neg.k, pos.w + varspacetime[..., 0:mlon//2, N//2: ] = varfft[..., mlon//2:0:-1, 0:N//2+1] # pos.k, + varspacetime[..., mlon//2: , 0:N//2+1] = varfft[..., 0:mlon//2+1, N//2::-1] # assign eastward & neg.freq. + varspacetime[..., mlon//2: , N//2+1: ] = varfft[..., 0:mlon//2+1, -1:N//2-1:-1] # assign eastward & pos.freq. + print(varspacetime.shape) + # Create the real power spectrum pee = sqrt(real^2+imag^2)^2 + logging.debug("calculate power") + pee = (np.abs(varspacetime))**2 # JJB: abs(a+bi) = sqrt(a**2 + b**2), which is what is done in NCL's resolveWavesHayashi + logging.debug("put into DataArray") + # add meta data for use upon return + wave = np.arange(-mlon // 2, (mlon // 2 )+ 1, 1, dtype=int) + freq = np.linspace(-1*nDayWin*spd/2, nDayWin*spd/2, (nDayWin*spd)+1) / nDayWin + + print(f"freq size is {freq.shape}.") + odims = list(dimnames) + odims[-2] = "wavenumber" + odims[-1] = "frequency" + ocoords = {} + for c in varfft.coords: + logging.debug(f"[hayashi] working on coordinate {c}") + if (c != "wavenumber") and (c != "frequency"): + ocoords[c] = varfft[c] + elif c == "wavenumber": + ocoords['wavenumber'] = wave + elif c == "frequency": + ocoords['frequency'] = freq + pee = xr.DataArray(pee, dims=odims, coords=ocoords) + return pee + + + +def split_hann_taper(series_length, fraction): + """Implements `split cosine bell` taper of length series_length where only fraction of points are tapered (combined on both ends). + + This returns a function that tapers to zero on the ends. To taper to the mean of a series X: + XTAPER = (X - X.mean())*series_taper + X.mean() + """ + npts = int(np.rint(fraction * series_length)) # total size of taper + taper = np.hanning(npts) + series_taper = np.ones(series_length) + series_taper[0:npts//2+1] = taper[0:npts//2+1] + series_taper[-npts//2+1:] = taper[npts//2+1:] + return series_taper + + + +def spacetime_power(data, segsize=96, noverlap=60, spd=1, latitude_bounds=None, dosymmetries=False, rmvLowFrq=False): + """Perform space-time spectral decomposition and return power spectrum following Wheeler-Kiladis approach. + + data: an xarray DataArray to be analyzed; needs to have (time, lat, lon) dimensions. + segsize: integer denoting the size of time samples that will be decomposed (typically about 96) + noverlap: integer denoting the number of days of overlap from one segment to the next (typically about segsize-60 => 2-month overlap) + spd: sampling rate, in "samples per day" (e.g. daily=1, 6-houry=4) + + latitude_bounds: a tuple of (southern_extent, northern_extent) to reduce data size. + + dosymmetries: if True, follow NCL convention of putting symmetric component in SH, antisymmetric in NH + If True, the function returns a DataArray with a `component` dimension. + + rmvLowFrq: if True, remove frequencies < 1/segsize from data. + + Method + ------ + 1. Subsample in latitude if latitude_bounds is specified. + 2. Detrend the data (but keeps the mean value, as in NCL) + 3. High-pass filter if rmvLowFrq is True + 4. Construct symmetric/antisymmetric array if dosymmetries is True. + 5. Construct overlapping window view of data. + 6. Detrend the segments (strange enough, removing mean). + 7. Apply taper in time dimension of windows (aka segments). + 8. Fourier transform + 9. Apply Hayashi reordering to get propagation direction & convert to power. + 10. return DataArray with power. + + Notes + ----- + Upon returning power, this should be comparable to "raw" spectra. + Next step would be be to smooth with `smooth_wavefreq`, + and divide raw spectra by smooth background to obtain "significant" spectral power. + + """ + + segsize_steps = spd*segsize # Segment length, in time step units + noverlap_steps = spd*noverlap # Segment overlap, in time step units + stride_steps = segsize_steps - noverlap_steps # Stride for overlapping windows, in time step units + + if latitude_bounds is not None: + assert isinstance(latitude_bounds, tuple) + data = data.sel(lat=slice(*latitude_bounds)) # CAUTION: is this a mutable argument? + logging.info(f"Data reduced by latitude bounds. Size is {data.sizes}") + slat = latitude_bounds[0] + nlat = latitude_bounds[1] + else: + slat = data['lat'].min().item() + nlat = data['lat'].max().item() + + # Remove the *long-term* temporal linear trend + # Using scipy.signal.detrend will remove the mean as well, but we will add the time + # mean back into the detrended data to be consistent with the approach used in the + # NCL version (https://www.ncl.ucar.edu/Document/Functions/Diagnostics/wkSpaceTime.shtml): + xmean = data.mean(dim='time') + xdetr = detrend(data.values, axis=0, type='linear') + xdetr = xr.DataArray(xdetr, dims=data.dims, coords=data.coords) + xdetr += xmean # put the mean back in + # --> Tested and confirmed that this approach gives same answer as NCL + + # filter low-frequencies + if rmvLowFrq: + data = rmvAnnualCycle(xdetr, spd, 1/segsize_steps) + # --> Tested and confirmed that this function gives same answer as NCL + + # NOTE: at this point "data" has been modified to have its long-term linear trend and + # low frequencies (lower than 1./segsize_steps) removed + + dimsizes = data.sizes # dict + lon_size = dimsizes['lon'] + lat_size = dimsizes['lat'] + lat_dim = data.dims.index('lat') + if dosymmetries: + data = decompose2SymAsym(data) + # testing: pass -- Gets the same result as NCL. + + # Windowing with the xarray "rolling" operation, and then limit overlap with `construct` to produce a new dataArray. + # JJB: Windowing segment bounds have been validated with test prints. + x_roll = data.rolling(time=segsize_steps, min_periods=segsize_steps) # WK99 use 96-day window + x_win = x_roll.construct("segments", stride=stride_steps).dropna("time") # WK99 say "2-month" overlap; dims: (nSegments,nlat,nlon,segment_length_steps) + logging.debug(f"[spacetime_power] x_win shape is {x_win.shape}") + + # For each segment, remove the temporal mean and linear trend: + if np.logical_not(np.any(np.isnan(x_win))): + logging.info("No missing, so use simplest segment detrend.") + x_win_detr = detrend(x_win.values, axis=-1, type='linear') #<-- missing data makes this not work; axis=-1 for segment window dimension + x_win = xr.DataArray(x_win_detr, dims=x_win.dims, coords=x_win.coords) + else: + logging.warning("EXTREME WARNING -- This method to detrend with missing values present does not quite work, probably need to do interpolation instead.") + logging.warning("There are missing data in x_win, so have to try to detrend around them.") + x_win_cp = x_win.values.copy() + logging.info(f"[spacetime_power] x_win_cp windowed data has shape {x_win_cp.shape} \n \t It is a numpy array, copied from x_win which has dims: {x_win.sizes} \n \t ** about to detrend this in the rightmost dimension.") + x_win_cp[np.logical_not(np.isnan(x_win_cp))] = detrend(x_win_cp[np.logical_not(np.isnan(x_win_cp))]) + x_win = xr.DataArray(x_win_cp, dims=x_win.dims, coords=x_win.coords) + + + # 3. Taper in time to reduce spectral "leakage" and make the segment periodic for FFT analysis + # taper = np.hanning(segsize) # WK seem to use some kind of stretched out hanning window; unclear if it matters + taper = split_hann_taper(segsize_steps, 0.1) # try to replicate NCL's approach of tapering 10% of segment window + x_wintap = x_win*taper # would do XTAPER = (X - X.mean())*series_taper + X.mean() + # But since we have removed the mean, taper going to 0 is equivalent to taper going to the mean. + + + # Do the transform using 2D FFT + # JJB: As written below, this does not give the same answer as the 2-step approach, + # not sure why but didn't look into it more. Will use 2-step approach to be + # safe (also mimics NCL version better) + #z = np.fft.fft2(x_wintap, axes=(2,3)) / (lon_size * segsize) + + # Or do the transform with 2 steps + # Note: x_wintap is shape (nSegments, nlat, nlon, segment_length_steps) + z = np.fft.fft(x_wintap, axis=2) / lon_size # note that np.fft.fft() produces same answers as NCL cfftf; axis=2 to do fft along dim=lon + z = np.fft.fft(z, axis=3) / segsize_steps # axis=3 to do fft along dim=segment_length_steps + + z = xr.DataArray(z, dims=("time","lat","wavenumber","frequency"), + coords={"time":x_wintap["time"], + "lat":x_wintap["lat"], + "wavenumber":np.fft.fftfreq(lon_size, 1/lon_size), + "frequency":np.fft.fftfreq(segsize_steps, 1/spd)}) + # + # The FFT is returned following ``standard order`` which has negative frequencies in second half of array. + # + # IMPORTANT: + # If this were typical 2D FFT, we would do the following to get the frequencies and reorder: + # z_k = np.fft.fftfreq(x_wintap.shape[-2], 1/lon_size) + # z_v = np.fft.fftfreq(x_wintap.shape[-1], 1) # Assumes 1/(1-day) timestep + # reshape to get the frequencies centered + # z_centered = np.fft.fftshift(z, axes=(2,3)) + # z_k_c = np.fft.fftshift(z_k) + # z_v_c = np.fft.fftshift(z_v) + # and convert to DataArray as this: + # d1 = list(x_win.dims) + # d1[-2] = "wavenumber" + # d1[-1] = "frequency" + # c1 = {} + # for d in d1: + # if d in x_win.coords: + # c1[d] = x_win[d] + # elif d == "wavenumber": + # c1[d] = z_k_c + # elif d == "frequency": + # c1[d] = z_v_c + # z_centered = xr.DataArray(z_centered, dims=d1, coords=c1) + # BUT THAT IS INCORRECT TO GET THE PROPAGATION DIRECTION OF ZONAL WAVES + # (in testing, it seems to end up opposite in wavenumber) + # Apply reordering per Hayashi to get correct wave propagation convention + # this function is customized to expect z to be a DataArray + z_pee = resolveWavesHayashi(z, segsize_steps//spd, spd) + # z_pee is spectral power already. + # z_pee is a DataArray w/ coordinate vars for wavenumber & frequency + + # average over all available segments and sum over latitude + # OUTPUT DEPENDS ON SYMMETRIES + if dosymmetries: + # multipy by 2 b/c we only used one hemisphere + # JJB: This summing of powers over latitude appears to be done a little bit earlier + # here when compared to NCL's version, but this should not matter. + # JJB: I think the factor of 2 comes in here because the symmetric and antisymmetric + # data were packaged into either the Norther or Southern Hemis, so the 'sum' + # across latitude would need to be multiplied by 2. For example, if the sym/asym data + # were -not- packaged into single hemispheres, no multiplicative factor would + # be needed. + # JJB: Also, 'squeeze' removed degenerate dimensions 'time' and 'lat' + z_symmetric = 2.0 * z_pee.isel(lat=z_pee.lat<=0).mean(dim='time').sum(dim='lat').squeeze() + z_symmetric.name = "power" + z_antisymmetric = 2.0 * z_pee.isel(lat=z_pee.lat>0).mean(dim='time').sum(dim='lat').squeeze() + z_antisymmetric.name = "power" + z_final = xr.concat([z_symmetric, z_antisymmetric], "component") + z_final = z_final.assign_coords({"component":["symmetric","antisymmetric"]}) + print("\nMetadata for z_final is:") + print(z_final.dims) + print(z_final.coords) + print(z_final.attrs) + else: + lat = z_pee['lat'] + lat_inds = np.argwhere(((lat <= nlat)&(lat >= slat)).values).squeeze() + z_final = z_pee.isel(lat=lat_inds).mean(dim='time').sum(dim='lat').squeeze() + return z_final + + + +def genDispersionCurves(nWaveType=6, nPlanetaryWave=50, rlat=0, Ahe=[50, 25, 12]): + """ + Function to derive the shallow water dispersion curves. Closely follows NCL version. + + input: + nWaveType : integer, number of wave types to do + nPlanetaryWave: integer + rlat: latitude in radians (just one latitude, usually 0.0) + Ahe: [50.,25.,12.] equivalent depths + ==> defines parameter: nEquivDepth ; integer, number of equivalent depths to do == len(Ahe) + + returns: tuple of size 2 + Afreq: Frequency, shape is (nWaveType, nEquivDepth, nPlanetaryWave) + Apzwn: Zonal savenumber, shape is (nWaveType, nEquivDepth, nPlanetaryWave) + + notes: + The outputs contain both symmetric and antisymmetric waves. In the case of + nWaveType == 6: + 0,1,2 are (ASYMMETRIC) "MRG", "IG", "EIG" (mixed rossby gravity, inertial gravity, equatorial inertial gravity) + 3,4,5 are (SYMMETRIC) "Kelvin", "ER", "IG" (Kelvin, equatorial rossby, inertial gravity) + """ + nEquivDepth = len(Ahe) # this was an input originally, but I don't know why. + pi = np.pi + radius = 6.37122e06 # [m] average radius of earth + g = 9.80665 # [m/s] gravity at 45 deg lat used by the WMO + omega = 7.292e-05 # [1/s] earth's angular vel + # U = 0.0 # NOT USED, so Commented + # Un = 0.0 # since Un = U*T/L # NOT USED, so Commented + ll = 2.*pi*radius*np.cos(np.abs(rlat)) + Beta = 2.*omega*np.cos(np.abs(rlat))/radius + fillval = 1e20 + + # NOTE: original code used a variable called del, + # I just replace that with `dell` because `del` is a python keyword. + + # Initialize the output arrays + Afreq = np.empty((nWaveType, nEquivDepth, nPlanetaryWave)) + Apzwn = np.empty((nWaveType, nEquivDepth, nPlanetaryWave)) + + for ww in range(1, nWaveType+1): + for ed, he in enumerate(Ahe): + # this loops through the specified equivalent depths + # ed provides index to fill in output array, while + # he is the current equivalent depth + # T = 1./np.sqrt(Beta)*(g*he)**(0.25) This is close to pre-factor of the dispersion relation, but is not used. + c = np.sqrt(g * he) # phase speed + L = np.sqrt(c/Beta) # was: (g*he)**(0.25)/np.sqrt(Beta), this is Rossby radius of deformation + + for wn in range(1, nPlanetaryWave+1): + s = -20.*(wn-1)*2./(nPlanetaryWave-1) + 20. + k = 2.0 * pi * s / ll + kn = k * L + + # Anti-symmetric curves + if (ww == 1): # MRG wave + if (k < 0): + dell = np.sqrt(1.0 + (4.0 * Beta)/(k**2 * c)) + deif = k * c * (0.5 - 0.5 * dell) + + if (k == 0): + deif = np.sqrt(c * Beta) + + if (k > 0): + deif = fillval + + + if (ww == 2): # n=0 IG wave + if (k < 0): + deif = fillval + + if (k == 0): + deif = np.sqrt( c * Beta) + + if (k > 0): + dell = np.sqrt(1.+(4.0*Beta)/(k**2 * c)) + deif = k * c * (0.5 + 0.5 * dell) + + + if (ww == 3): # n=2 IG wave + n=2. + dell = (Beta*c) + deif = np.sqrt((2.*n+1.)*dell + (g*he) * k**2) + # do some corrections to the above calculated frequency....... + for i in range(1,5+1): + deif = np.sqrt((2.*n+1.)*dell + (g*he) * k**2 + g*he*Beta*k/deif) + + + # symmetric curves + if (ww == 4): # n=1 ER wave + n=1. + if (k < 0.0): + dell = (Beta/c)*(2.*n+1.) + deif = -Beta*k/(k**2 + dell) + else: + deif = fillval + + if (ww == 5): # Kelvin wave + deif = k*c + + if (ww == 6): # n=1 IG wave + n=1. + dell = (Beta*c) + deif = np.sqrt((2. * n+1.) * dell + (g*he)*k**2) + # do some corrections to the above calculated frequency + for i in range(1,5+1): + deif = np.sqrt((2.*n+1.)*dell + (g*he)*k**2 + g*he*Beta*k/deif) + + eif = deif # + k*U since U=0.0 + P = 2.*pi/(eif*24.*60.*60.) # => PERIOD + # dps = deif/k # Does not seem to be used. + # R = L #<-- this seemed unnecessary, I just changed R to L in Rdeg + # Rdeg = (180.*L)/(pi*6.37e6) # And it doesn't get used. + + Apzwn[ww-1,ed,wn-1] = s + if (deif != fillval): + # P = 2.*pi/(eif*24.*60.*60.) # not sure why we would re-calculate now + Afreq[ww-1,ed,wn-1] = 1./P + else: + Afreq[ww-1,ed,wn-1] = fillval + return Afreq, Apzwn \ No newline at end of file diff --git a/e3sm_diags/parameter/__init__.py b/e3sm_diags/parameter/__init__.py index a09a9f393..d37d0bccd 100644 --- a/e3sm_diags/parameter/__init__.py +++ b/e3sm_diags/parameter/__init__.py @@ -11,6 +11,7 @@ from .qbo_parameter import QboParameter from .streamflow_parameter import StreamflowParameter from .tc_analysis_parameter import TCAnalysisParameter +from .tropical_subseasonal_parameter import TropicalSubseasonalParameter from .zonal_mean_2d_parameter import ZonalMean2dParameter from .zonal_mean_2d_stratosphere_parameter import ZonalMean2dStratosphereParameter @@ -35,4 +36,5 @@ "aerosol_aeronet": CoreParameter, "aerosol_budget": CoreParameter, "mp_partition": MPpartitionParameter, + "tropical_subseasonal": TropicalSubseasonalParameter, } diff --git a/e3sm_diags/parameter/core_parameter.py b/e3sm_diags/parameter/core_parameter.py index 5382c74c1..160e54da9 100644 --- a/e3sm_diags/parameter/core_parameter.py +++ b/e3sm_diags/parameter/core_parameter.py @@ -31,6 +31,8 @@ "lat_lon_river", "aerosol_aeronet", "aerosol_budget", +# "mp_partition", +# "tropical_subseasonal", ] diff --git a/e3sm_diags/parameter/tropical_subseasonal_parameter.py b/e3sm_diags/parameter/tropical_subseasonal_parameter.py new file mode 100644 index 000000000..8477900fe --- /dev/null +++ b/e3sm_diags/parameter/tropical_subseasonal_parameter.py @@ -0,0 +1,19 @@ +from typing import Optional + +from .time_series_parameter import TimeSeriesParameter + + +class TropicalSubseasonalParameter(TimeSeriesParameter): + def __init__(self): + super(TropicalSubseasonalParameter, self).__init__() + # Override existing attributes + # ============================= + self.print_statements = False + self.ref_timeseries_input = True + self.test_timeseries_input = True + self.granulate.remove("seasons") + + # Custom attributes + # ----------------- + self.ref_yrs: Optional[str] = None + self.test_yrs: Optional[str] = None diff --git a/e3sm_diags/parser/__init__.py b/e3sm_diags/parser/__init__.py index c762f0b6e..0db03970b 100644 --- a/e3sm_diags/parser/__init__.py +++ b/e3sm_diags/parser/__init__.py @@ -8,6 +8,7 @@ from e3sm_diags.parser.qbo_parser import QboParser from e3sm_diags.parser.streamflow_parser import StreamflowParser from e3sm_diags.parser.tc_analysis_parser import TCAnalysisParser +from e3sm_diags.parser.tropical_subseasonal_parser import TropicalSubseasonalParser from e3sm_diags.parser.zonal_mean_2d_parser import ZonalMean2dParser from e3sm_diags.parser.zonal_mean_2d_stratosphere_parser import ( ZonalMean2dStratosphereParser, @@ -34,4 +35,5 @@ "aerosol_aeronet": CoreParser, "aerosol_budget": CoreParser, "mp_partition": MPpartitionParser, + "tropical_subseasonal_parser": TropicalSubseasonalParser, } diff --git a/e3sm_diags/parser/tropical_subseasonal_parser.py b/e3sm_diags/parser/tropical_subseasonal_parser.py new file mode 100644 index 000000000..903ff5e99 --- /dev/null +++ b/e3sm_diags/parser/tropical_subseasonal_parser.py @@ -0,0 +1,44 @@ +from e3sm_diags.parameter.tropical_subseasonal_parameter import TropicalSubseasonalParameter +from e3sm_diags.parser.core_parser import CoreParser + + +class TropicalSubseasonalParser(CoreParser): + def __init__(self, *args, **kwargs): + super().__init__( + parameter_cls=TropicalSubseasonalParameter, *args, **kwargs + ) # type:ignore + + def add_arguments(self): + super().add_arguments() + + self.parser.add_argument( + "--ref_timeseries_input", + dest="ref_timeseries_input", + help="The input reference data are timeseries files.", + action="store_const", + const=True, + required=False, + ) + + self.parser.add_argument( + "--test_timeseries_input", + dest="test_timeseries_input", + help="The input test data are timeseries files.", + action="store_const", + const=True, + required=False, + ) + + self.parser.add_argument( + "--start_yr", + dest="start_yr", + help="Start year for the timeseries files.", + required=False, + ) + + self.parser.add_argument( + "--end_yr", + dest="end_yr", + help="End year for the timeseries files.", + required=False, + ) diff --git a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py new file mode 100755 index 000000000..406550b79 --- /dev/null +++ b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py @@ -0,0 +1,413 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import matplotlib +import xarray as xr + +from e3sm_diags.logger import custom_logger +from e3sm_diags.parameter.core_parameter import CoreParameter +import matplotlib.pyplot as plt +from e3sm_diags.driver.utils.general import get_output_dir +from matplotlib.colors import ListedColormap, BoundaryNorm +import numpy as np +import os +#from e3sm_diags.plot.utils import _add_colormap, _save_plot +from zwf import zwf_functions as wf + + +if TYPE_CHECKING: + from e3sm_diags.driver.lat_lon_driver import MetricsDict + + +matplotlib.use("Agg") +import matplotlib.pyplot as plt # isort:skip # noqa: E402 + +logger = custom_logger(__name__) + +# Plot title and side title configurations. +PLOT_TITLE = {"fontsize": 11.5} +PLOT_SIDE_TITLE = {"fontsize": 9.5} + +# Position and sizes of subplot axes in page coordinates (0 to 1) +PANEL = [ + (0.17, 0.70, 0.50, 0.25), + (0.17, 0.37, 0.50, 0.25), + (0.17, 0.04, 0.50, 0.25), +] + +# Border padding relative to subplot axes for saving individual panels +# (left, bottom, right, top) in page coordinates +BORDER_PADDING = (-0.06, -0.03, 0.13, 0.03) + +CONTOUR_LEVS_SPEC_RAW = (-1.4,-1.2,-1,-0.9,-0.8,-0.7,-0.6,-0.5,-0.4,-0.3,-0.2,-0.1,0,0.1,0.2) + +CMAP_SPEC_RAW = ["white", + "paleturquoise","lightblue","skyblue", + "lightgreen","limegreen","green","darkgreen", + "yellow","orange","orangered", + "red","maroon","magenta","orchid","pink", + "lavenderblush"] + +CONTOUR_LEVS_SPEC_NORM = (0.9,1.0,1.1,1.2,1.3,1.4,1.5,1.7,1.9,2.1,2.4,2.7,3.0) + +CMAP_SPEC_NORM = ["white", + "gainsboro","lightgray","silver", + "paleturquoise","skyblue", + "lightgreen","mediumseagreen","seagreen", + "yellow","orange", + "red","maroon","pink"] + +CONTOUR_LEVS_SPEC_RAW_DIFF = (-80.,-60.,-40.,-20.,-10.,-5.,5.,10.,20.,40.,60.,80.) +CONTOUR_LEVS_SPEC_NORM_DIFF = (-60.,-30.,-20.,-15.,-10.,-5.,5.,10.,15.,20.,30.,60.) + + +def find_nearest(array, value): + array = np.asarray(array) + idx = (np.abs(array - value)).argmin() + return idx,array[idx] + """Return index of [array] closest in value to [value] + Example: + array = [ 0.21069679 0.61290182 0.63425412 0.84635244 0.91599191 0.00213826 + 0.17104965 0.56874386 0.57319379 0.28719469] + print(find_nearest(array, value=0.5)) + # 0.568743859261 + + """ + +def create_colormap_clevs(cmapSpec, clevs): + cmapSpecUse = ListedColormap(cmapSpec[1:-1]) # recall: range is NOT inclusive for final index + cmapSpecUse.set_under(cmapSpec[0]) + cmapSpecUse.set_over(cmapSpec[-1]) + normSpecUse = BoundaryNorm(clevs, cmapSpecUse.N) + + return cmapSpecUse, normSpecUse + + + +def _save_plot(fig: plt.figure, parameter: CoreParameter): + """Save the plot using the figure object and parameter configs. + + This function creates the output filename to save the plot. It also + saves each individual subplot if the reference name is an empty string (""). + + Parameters + ---------- + fig : plt.figure + The plot figure. + parameter : CoreParameter + The CoreParameter with file configurations. + """ + for f in parameter.output_format: + f = f.lower().split(".")[-1] + fnm = os.path.join( + get_output_dir(parameter.current_set, parameter), + parameter.output_file + "." + f, + ) + #fnm = f'{parameter.var_id}_{parameter.spec_type}_15N-15N.png' + plt.savefig(fnm) + logger.info(f"Plot saved in: {fnm}") + + # Save individual subplots + if parameter.ref_name == "": + panels = [PANEL[0]] + else: + panels = PANEL + + for f in parameter.output_format_subplot: + fnm = os.path.join( + get_output_dir(parameter.current_set, parameter), + parameter.output_file, + ) + page = fig.get_size_inches() + + for idx, panel in enumerate(panels): + # Extent of subplot + subpage = np.array(panel).reshape(2, 2) + subpage[1, :] = subpage[0, :] + subpage[1, :] + subpage = subpage + np.array(BORDER_PADDING).reshape(2, 2) + subpage = list(((subpage) * page).flatten()) # type: ignore + extent = Bbox.from_extents(*subpage) + + # Save subplot + fname = fnm + ".%i." % idx + f + plt.savefig(fname, bbox_inches=extent) + + orig_fnm = os.path.join( + get_output_dir(parameter.current_set, parameter), + parameter.output_file, + ) + fname = orig_fnm + ".%i." % idx + f + logger.info(f"Sub-plot saved in: {fname}") + + + +def _wave_frequency_plot( + subplot_num: int, + var: xr.DataArray, + fig: plt.figure, + parameter: CoreParameter, + title: Tuple[str | None, str, str], + do_zoom: Boolean = False, +): + """Create wave frequency plot. + + Parameters + ---------- + subplot_num : int + The subplot number. + var : xr.DataArray + The variable to plot. + fig : plt.figure + The figure object to add the subplot to. + parameter : CoreParameter + The CoreParameter object containing plot configurations. + title : Tuple[str | None, str, str] + A tuple of strings to form the title of the colormap, in the format + ( years, title, units). + do_zoom: Boolean + """ + #TODO link var_id + varName = parameter.var_id + #varName = 'PRECT' + PlotDesc = {} + PlotDesc['spec_raw_sym'] = {"long_name_desc": f"{varName}: log-base10 of lightly smoothed spectral power of component symmetric about equator", "ref_fig_num": "Figure 1"} # Figure number from Wheeler and Kiladis (1999) + PlotDesc['spec_raw_asy'] = {"long_name_desc": f"{varName}: log-base10 of lightly smoothed spectral power of component antisymmetric about equator", "ref_fig_num": "Figure 1"} + PlotDesc['spec_norm_sym'] = {"long_name_desc": f"{varName}: lightly smoothed spectral power of component symmetric about equator, normalized by heavily smoothed background spectrum", "ref_fig_num": "Figure 3"} + PlotDesc['spec_norm_asy'] = {"long_name_desc": f"{varName}: lightly smoothed spectral power of component antisymmetric about equator, normalized by heavily smoothed background spectrum", "ref_fig_num": "Figure 3"} + PlotDesc['spec_background'] = {"long_name_desc": f"{varName}: heavily smoothed version of the mean of spectral powers associated with the components symmetric and antisymmetric about equator", "ref_fig_num": "Figure 2"} + + text_offset = 0.005 + fb = [0, .8] # frequency bounds for plot + wnb = [-15, 15] # zonal wavenumber bounds for plot + + if(max(var['frequency'].values) == 0.5): + fb = [0, .5] + + if(do_zoom): + fb = [0, .18] + wnb = [-7, 7] + + # get data for dispersion curves: + equivDepths=[50, 25, 12] + swfreq,swwn = wf.genDispersionCurves(Ahe=equivDepths) + # swfreq.shape # -->(6, 3, 50) + + if 'spec_norm' in var.name: # with dispersion curves to plot + # For n=1 ER waves, allow dispersion curves to touch 0 -- this is for plot aesthetics only + for i in range(0,3): # loop 0-->2 for the assumed 3 shallow water dispersion curves for ER waves + indMinPosFrqER = np.where(swwn[3,i,:] >= 0., swwn[3,i,:], 1e20).argmin() # index of swwn for least positive wn + swwn[3,i,indMinPosFrqER],swfreq[3,i,indMinPosFrqER] = 0.,0. # this sets ER's frequencies to 0. at wavenumber 0. + + swf = np.where(swfreq == 1e20, np.nan, swfreq) + swk = np.where(swwn == 1e20, np.nan, swwn) + + + # Final data refinement: transpose and trim, set 0 freq to NaN, take log10 for raw, refine metadata + z = var.transpose().sel(frequency=slice(*fb), wavenumber=slice(*wnb)) + z.loc[{'frequency':0}] = np.nan + + if 'spec_raw' in var.name: + + east_power = z.sel(frequency=slice((1./96.),(1./24.)), wavenumber=slice(1,3)).sum() + west_power = z.sel(frequency=slice((1./96.),(1./24.)), wavenumber=slice(-3,-1)).sum() + ew_ratio = east_power / west_power + print("\neast_power: %12.5f" % east_power) + print("west_power: %12.5f" % west_power) + print("ew_ratio: %12.5f\n" % ew_ratio) + + z = np.log10(z) + + if 'spec_background' in var.name: + z = np.log10(z) + + z.attrs["long_name"] = PlotDesc[var.name]["long_name_desc"] + z.attrs["method"] = f"Follows {PlotDesc[var.name]['ref_fig_num']} methods of Wheeler and Kiladis (1999; https://doi.org/10.1175/1520-0469(1999)056<0374:CCEWAO>2.0.CO;2)" + + if 'spec_raw' in var.name: + + z.attrs["ew_ratio_method"] = "Sum of raw (not log10) symmetric spectral power for ZWNs +/- 1-3, periods 24-96 days" + z.attrs["east_power"] = east_power.values + z.attrs["west_power"] = west_power.values + z.attrs["ew_ratio"] = ew_ratio.values + +# # TODO Save plotted data z to file as xArray data array +# dataDesc = f"spectral_power_{srcID}_{vari}_{spec_type}_{component}_200101_201412" +# z.to_netcdf(outDataDir + "/"+ dataDesc + ".nc") + + #fig, ax = plt.subplots() + ax = fig.add_axes(PANEL[subplot_num]) + + kmesh0, vmesh0 = np.meshgrid(z['wavenumber'], z['frequency']) + #img = ax.contourf(kmesh0, vmesh0, z, levels=np.linspace(0.2, 3.0, 16), cmap='Spectral_r', extend='both') + + # for test and ref: + if subplot_num < 2: + if 'spec_norm' in var.name: + contour_level_spec = CONTOUR_LEVS_SPEC_NORM + cmapSpecUse, normSpecUse = create_colormap_clevs(CMAP_SPEC_NORM, CONTOUR_LEVS_SPEC_NORM) + else: + contour_level_spec = CONTOUR_LEVS_SPEC_RAW + cmapSpecUse, normSpecUse = create_colormap_clevs(CMAP_SPEC_RAW, CONTOUR_LEVS_SPEC_RAW) + img = ax.contourf(kmesh0, vmesh0, z, levels=contour_level_spec, cmap=cmapSpecUse, norm=normSpecUse, extend='both') + img2 = ax.contour(kmesh0, vmesh0, z, levels=contour_level_spec, linewidths=1., linestyles='solid', colors='gray', alpha=0.7) + + # for diff ratio + if subplot_num == 2: + # TODO refine color bar + if 'spec_norm' in var.name: + contour_level_spec = CONTOUR_LEVS_SPEC_NORM_DIFF + else: + contour_level_spec = CONTOUR_LEVS_SPEC_RAW_DIFF + cmapSpecUse = 'seismic' + + img = ax.contourf(kmesh0, vmesh0, z, levels=contour_level_spec, cmap=cmapSpecUse, extend='both') + img2 = ax.contour(kmesh0, vmesh0, z, levels=contour_level_spec, linewidths=1., linestyles='solid', colors='gray', alpha=0.7) + + ax.axvline(0, linestyle='dashed', color='dimgray', linewidth=1.0, alpha=0.60) + if( (1./30.) < fb[1] ): + ax.axhline((1./30.), linestyle='dashed', color='dimgray', alpha=0.80) + ax.text(wnb[0]+1,(1./30.)+text_offset,'30 days',color='dimgray', alpha=0.80) + if( (1./6.) < fb[1] ): + ax.axhline((1./6.), linestyle='dashed', color='dimgray', alpha=0.80) + ax.text(wnb[0]+1,(1./6.)+text_offset,'6 days',color='dimgray', alpha=0.80) + if( (1./3.) < fb[1] ): + ax.axhline((1./3.), linestyle='dashed', color='dimgray', alpha=0.80) + ax.text(wnb[0]+1,(1./3.)+text_offset,'3 days',color='dimgray', alpha=0.80) + for ii in range(3,6): + ax.plot(swk[ii, 0,:], swf[ii,0,:], color='black', linewidth=1.5, alpha=0.80) + ax.plot(swk[ii, 1,:], swf[ii,1,:], color='black', linewidth=1.5, alpha=0.80) + ax.plot(swk[ii, 2,:], swf[ii,2,:], color='black', linewidth=1.5, alpha=0.80) + ax.set_xlim(wnb) + ax.set_ylim(fb) + #ax.set_title(varName + ": Log ${\sum_{15^{\circ}S}^{15^{\circ}N} Power_{SYM}}$") # Version w/ LaTeX + if 'spec_raw' in var.name: + ax.set_title(f"{varName}: Log{{Sum(Power) from 15°S-15°N}}\n") # Version w/o LaTeX + elif 'spec_norm' in var.name: + ax.set_title(f"{varName}: {{Sum(Power) from 15°S-15°N}}/Background\n") # Version w/o LaTeX + else: + ax.set_title(f"{varName}: Log{{Smoothed Background Power}}\n") + + ax.set_title('model', loc='left') + print('*****',var) + ax.set_title(f"{var.component}", loc='right') + + if 'spec_norm' in var.name: + # Shallow water dispersion curve line labels: See https://matplotlib.org/stable/tutorials/text/text_intro.html + # n=1 ER dispersion curve labels + text_opt = {'fontsize': 9,'verticalalignment': 'center','horizontalalignment': 'center','clip_on': True,'bbox': {'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}} + iwave, ih = 3, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -11.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 3, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -9.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 3, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + ax.text(-7.,0.10,'n=1 ER',text_opt) + + # Kelvin dispersion curve labels + iwave, ih = 4, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 4, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 10.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 4, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 14.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + ax.text(6.,0.13,'Kelvin',text_opt) + + # IG dispersion curve labels + iwave, ih = 5, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 5, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 5, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + ax.text(-10.,0.48,'n=1 WIG',text_opt) + ax.text(5.,0.48,'n=1 EIG',text_opt) + + # MJO label + ax.text(6.,0.0333,'MJO',text_opt) + + + plt.ylabel("Frequency (CPD)") + plt.xlabel("Zonal wavenumber") + fig.text( + PANEL[subplot_num][0] - 0.03 , + PANEL[subplot_num][1] - 0.03, "Westward", fontsize=11) + fig.text( + PANEL[subplot_num][0] + 0.35 , + PANEL[subplot_num][1] -0.03 , "Eastward", fontsize=11) + fig.colorbar(img) +# # Save fig +# fig.savefig(outDataDir + "/"+ dataDesc + "_plot.png", bbox_inches='tight', dpi=300) +# +# print("Plot file created: %s\n" % outDataDir + "/"+ dataDesc + "_plot.png") + + + + +def plot( + parameter: CoreParameter, + da_test: xr.DataArray, + da_ref: xr.DataArray | None, + da_diff: xr.DataArray | None, + do_zoom: Boolean = False, +): + """Plot the variable's metrics generated for the lat_lon set. + + Parameters + ---------- + parameter : CoreParameter + The CoreParameter object containing plot configurations. + da_test : xr.DataArray + The test data. + da_ref : xr.DataArray | None + The optional reference data. + ds_diff : xr.DataArray | None + The difference between ``ds_test`` and ``ds_ref``. + """ + #fig = plt.figure(figsize=parameter.figsize, dpi=parameter.dpi) + fig = plt.figure(figsize=[8.5, 12.0], dpi=300) + #fig.suptitle(parameter.main_title, x=0.5, y=0.96, fontsize=18) + + + _wave_frequency_plot( + 0, + da_test, + fig, + parameter, + title=(parameter.test_name_yrs, parameter.test_title), # type: ignore + do_zoom=do_zoom, + ) + + + _wave_frequency_plot( + 1, + da_ref, + fig, + parameter, + title=(parameter.ref_name_yrs, parameter.reference_title), # type: ignore + do_zoom=do_zoom, + ) + + + _wave_frequency_plot( + 2, + da_diff, + fig, + parameter, + title=(None, parameter.diff_title), # type: ignore + do_zoom=do_zoom, + ) + +#TODO: save plot:NameError: name 'get_output_dir' is not defined + _save_plot(fig, parameter) + + plt.close() diff --git a/e3sm_diags/viewer/main.py b/e3sm_diags/viewer/main.py index 4227b3e1e..2410a877b 100644 --- a/e3sm_diags/viewer/main.py +++ b/e3sm_diags/viewer/main.py @@ -18,6 +18,7 @@ qbo_viewer, streamflow_viewer, tc_analysis_viewer, + tropical_subseasonal_viewer, utils, ) @@ -46,6 +47,7 @@ "aerosol_aeronet": default_viewer.create_viewer, "aerosol_budget": aerosol_budget_viewer.create_viewer, "mp_partition": mp_partition_viewer.create_viewer, + "tropical_subseasonal": tropical_subseasonal_viewer.create_viewer, } diff --git a/e3sm_diags/viewer/tropical_subseasonal_viewer.py b/e3sm_diags/viewer/tropical_subseasonal_viewer.py new file mode 100755 index 000000000..a38e03b11 --- /dev/null +++ b/e3sm_diags/viewer/tropical_subseasonal_viewer.py @@ -0,0 +1,62 @@ +import os +from typing import Dict, List + +from cdp.cdp_viewer import OutputViewer + +from e3sm_diags.logger import custom_logger + + +logger = custom_logger(__name__) + + +def create_viewer(root_dir, parameters): + """ + Given a set of parameters for the enso_diags set, + create a single webpage. + + Return the title and url for this page. + """ + viewer = OutputViewer(path=root_dir) + + # The name that's displayed on the viewer. + display_name = "Tropical Subseasonal Variability Diagnostics" + set_name = "tropical_subseasonal" + # The title of the colums on the webpage. + # Appears in the second and third columns of the bolded rows. + cols = ["Description", "Plot"] + viewer.add_page(display_name, short_name=set_name, columns=cols) + for param in parameters: + for plot_type in ["Wavenumber Frequency", "Lag correlation"]: + # Appears in the first column of the bolded rows. + viewer.add_group(plot_type.capitalize()) + + for var in param.variables: + # Appears in the first column of the non-bolded rows. + # This should use param.case_id to match the output_dir determined by + # get_output_dir in e3sm_diags/plot/cartopy/enso_diags_plot.py. + # Otherwise, the plot image and the plot HTML file will have URLs + # differing in the final directory name. + for spec_type in ["norm_sym", "norm_sym_zoom", "norm_asy", "norm_asy_zoom", "raw_sym", "raw_asy", "background"]: + viewer.add_row(f'{var} {spec_type} ref_name') + # Adding the description for this var to the current row. + # This was obtained and stored in the driver for this plotset. + # Appears in the second column of the non-bolded rows. + #viewer.add_col(param.viewer_descr[var]) + viewer.add_col(f'Long description for var') + # Link to an html version of the plot png file. + # Appears in the third column of the non-bolded rows. + ext = param.output_format[0] + relative_path = os.path.join("..", set_name, param.case_id, param.output_file) + image_relative_path = "{}.{}".format(relative_path, ext) + #image_relative_path = f'{var}_{spec_type}_15N-15N.png' + viewer.add_col( + image_relative_path, + is_file=True, + title="Plot", + #other_files=formatted_files, + #meta=create_metadata(param), + ) + + url = viewer.generate_page() + + return display_name, url diff --git a/setup.py b/setup.py index a76b7de7c..8d556c0cf 100644 --- a/setup.py +++ b/setup.py @@ -85,6 +85,10 @@ def get_all_files_in_dir(directory, pattern): mp_partition_files = get_all_files_in_dir( "e3sm_diags/driver/default_diags", "mp_partition*cfg" ) +tropical_subseasonal_files = get_all_files_in_dir( + "e3sm_diags/driver/default_diags", "tropical_subseasonal*cfg" +) + rgb_files = get_all_files_in_dir("e3sm_diags/plot/colormaps", "*.rgb") control_runs_files = get_all_files_in_dir("e3sm_diags/driver/control_runs", "*") @@ -131,6 +135,7 @@ def get_all_files_in_dir(directory, pattern): (os.path.join(INSTALL_PATH, "aerosol_aeronet"), aerosol_aeronet_files), (os.path.join(INSTALL_PATH, "aerosol_budget"), aerosol_budget_files), (os.path.join(INSTALL_PATH, "mp_partition"), mp_partition_files), + (os.path.join(INSTALL_PATH, "tropical_subseasonal"), tropical_subseasonal_files), (os.path.join(INSTALL_PATH, "colormaps"), rgb_files), (os.path.join(INSTALL_PATH, "control_runs"), control_runs_files), ( From 9768b790d579636b49bdd5de3612ff51167fc87d Mon Sep 17 00:00:00 2001 From: chengzhuzhang Date: Fri, 23 Feb 2024 17:36:49 -0800 Subject: [PATCH 25/46] fix formatting --- .../driver/tropical_subseasonal_driver.py | 186 +++-- e3sm_diags/driver/utils/__init__.py | 3 +- e3sm_diags/driver/utils/zwf_functions.py | 662 ++++++++++-------- e3sm_diags/parameter/core_parameter.py | 4 +- .../parser/tropical_subseasonal_parser.py | 4 +- .../plot/cartopy/tropical_subseasonal_plot.py | 488 +++++++++---- .../viewer/tropical_subseasonal_viewer.py | 27 +- 7 files changed, 859 insertions(+), 515 deletions(-) diff --git a/e3sm_diags/driver/tropical_subseasonal_driver.py b/e3sm_diags/driver/tropical_subseasonal_driver.py index 2305898e2..b62ea8ede 100644 --- a/e3sm_diags/driver/tropical_subseasonal_driver.py +++ b/e3sm_diags/driver/tropical_subseasonal_driver.py @@ -7,34 +7,36 @@ import numpy as np import xarray as xr +from matplotlib.colors import BoundaryNorm, ListedColormap from scipy.stats import binned_statistic import e3sm_diags from e3sm_diags.driver import utils +from e3sm_diags.driver.utils import zwf_functions as wf from e3sm_diags.logger import custom_logger from e3sm_diags.plot.cartopy.tropical_subseasonal_plot import plot from e3sm_diags.viewer.tropical_subseasonal_viewer import create_viewer -from e3sm_diags.driver.utils import zwf_functions as wf - -from matplotlib.colors import ListedColormap, BoundaryNorm if TYPE_CHECKING: - from e3sm_diags.parameter.tropical_subseasonal_parameter import TropicalSubseasonalParameter + from e3sm_diags.parameter.tropical_subseasonal_parameter import ( + TropicalSubseasonalParameter, + ) logger = custom_logger(__name__) -# Script to compute and plot spectral powers of a subseasonal tropical field in +# Script to compute and plot spectral powers of a subseasonal tropical field in # zonal wavenumber-frequency space. Both the plot files and files containing the # associated numerical data shown in the plots are created. # Authors: Jim Benedict and Brian Medeiros # Modified by Jill Zhang to integrate into E3SM Diags. + def find_nearest(array, value): array = np.asarray(array) idx = (np.abs(array - value)).argmin() - return idx,array[idx] + return idx, array[idx] """Return index of [array] closest in value to [value] Example: array = [ 0.21069679 0.61290182 0.63425412 0.84635244 0.91599191 0.00213826 @@ -44,91 +46,130 @@ def find_nearest(array, value): """ + def wf_analysis(x, **kwargs): """Return zonal wavenumber-frequency power spectra of x. The returned spectra are: spec_sym: Raw (non-normalized) power spectrum of the component of x that is symmetric about the equator. spec_asym: Raw (non-normalized) power spectrum of the component of x that is antisymmetric about the equator. nspec_sym: Normalized (by a smoothed red-noise background spectrum) power spectrum of the component of x that is symmetric about the equator. nspec_asym: Normalized (by a smoothed red-noise background spectrum) power spectrum of the component of x that is antisymmetric about the equator. - + The NCL version of 'wkSpaceTime' smooths the symmetric and antisymmetric components - along the frequency dimension using a 1-2-1 filter once. - + along the frequency dimension using a 1-2-1 filter once. + """ # Get the "raw" spectral power - # OPTIONAL kwargs: + # OPTIONAL kwargs: # segsize, noverlap, spd, latitude_bounds (tuple: (south, north)), dosymmetries, rmvLowFrq z2 = wf.spacetime_power(x, **kwargs) - z2avg = z2.mean(dim='component') - z2.loc[{'frequency':0}] = np.nan # get rid of spurious power at \nu = 0 (mean) + z2avg = z2.mean(dim="component") + z2.loc[{"frequency": 0}] = np.nan # get rid of spurious power at \nu = 0 (mean) # Following NCL's wkSpaceTime, apply one pass of a 1-2-1 filter along the frequency # domain to the raw (non-normalized) spectra/um. # Do not use 0 frequency when smoothing here. # Use weights that sum to 1 to ensure that smoothing is conservative. - z2s = wf.smoothFrq121(z2,1) + z2s = wf.smoothFrq121(z2, 1) # The background is supposed to be derived from both symmetric & antisymmetric # Inputs to the background spectrum calculation should be z2avg background = wf.smoothBackground_wavefreq(z2avg) # separate components - spec_sym = z2s[0,...] - spec_asy = z2s[1,...] + spec_sym = z2s[0, ...] + spec_asy = z2s[1, ...] # normalize: Following NCL's wkSpaceTime, use lightly smoothed version of spectra/um # as numerator nspec_sym = spec_sym / background nspec_asy = spec_asy / background - spec = xr.merge([spec_sym.rename('spec_raw_sym'), spec_asy.rename('spec_raw_asy'), nspec_sym.rename('spec_norm_sym'), nspec_asy.rename('spec_norm_asy'), background.rename('spec_background')], compat='override') - spec_all = spec.drop('component') - spec_all['spec_raw_sym'].attrs = {"component": "symmetric", "type": "raw"} - spec_all['spec_raw_asy'].attrs = {"component": "antisymmetric", "type": "raw"} - spec_all['spec_norm_sym'].attrs = {"component": "symmetric", "type": "normalized"} - spec_all['spec_norm_asy'].attrs = {"component": "antisymmetric", "type": "normalized"} - spec_all['spec_background'].attrs = {"component": "", "type": "background"} + spec = xr.merge( + [ + spec_sym.rename("spec_raw_sym"), + spec_asy.rename("spec_raw_asy"), + nspec_sym.rename("spec_norm_sym"), + nspec_asy.rename("spec_norm_asy"), + background.rename("spec_background"), + ], + compat="override", + ) + spec_all = spec.drop("component") + spec_all["spec_raw_sym"].attrs = {"component": "symmetric", "type": "raw"} + spec_all["spec_raw_asy"].attrs = {"component": "antisymmetric", "type": "raw"} + spec_all["spec_norm_sym"].attrs = {"component": "symmetric", "type": "normalized"} + spec_all["spec_norm_asy"].attrs = { + "component": "antisymmetric", + "type": "normalized", + } + spec_all["spec_background"].attrs = {"component": "", "type": "background"} return spec_all def calculate_spectrum(path, variable): - - latBound = (-15,15) # latitude bounds for analysis - spd = 1 # SAMPLES PER DAY - nDayWin = 96 # Wheeler-Kiladis [WK] temporal window length (days) + latBound = (-15, 15) # latitude bounds for analysis + spd = 1 # SAMPLES PER DAY + nDayWin = 96 # Wheeler-Kiladis [WK] temporal window length (days) nDaySkip = -60 # time (days) between temporal windows [segments] - # negative means there will be overlapping temporal segments - twoMonthOverlap = -1*nDaySkip - - opt = {'segsize': nDayWin, - 'noverlap': twoMonthOverlap, - 'spd': spd, - 'latitude_bounds': latBound, - 'dosymmetries': True, - 'rmvLowFrq':True} + # negative means there will be overlapping temporal segments + twoMonthOverlap = -1 * nDaySkip + + opt = { + "segsize": nDayWin, + "noverlap": twoMonthOverlap, + "spd": spd, + "latitude_bounds": latBound, + "dosymmetries": True, + "rmvLowFrq": True, + } var = xr.open_mfdataset(glob.glob(f"{path}/{variable}_*.nc")).sel( - lat=slice(-15, 15))[variable] + lat=slice(-15, 15) + )[variable] # TODO: subset time # Unit conversion if var.name == "PRECT": if var.attrs["units"] == "m/s" or var.attrs["units"] == "m s{-1}": - print("\nBEFORE unit conversion: Max/min of data: " + str(var.values.max()) + " " + str(var.values.min())) - var.values = var.values * 1000. * 86400. # convert m/s to mm/d, do not alter metadata (yet) - var.attrs["units"] = "mm/d" # adjust metadata to reflect change in units - print("\nAFTER unit conversion: Max/min of data: " + str(var.values.max()) + " " + str(var.values.min())) + print( + "\nBEFORE unit conversion: Max/min of data: " + + str(var.values.max()) + + " " + + str(var.values.min()) + ) + var.values = ( + var.values * 1000.0 * 86400.0 + ) # convert m/s to mm/d, do not alter metadata (yet) + var.attrs["units"] = "mm/d" # adjust metadata to reflect change in units + print( + "\nAFTER unit conversion: Max/min of data: " + + str(var.values.max()) + + " " + + str(var.values.min()) + ) if var.name == "precipAvg": if var.attrs["units"] == "mm/hr": - print("\nBEFORE unit conversion: Max/min of data: " + str(var.values.max()) + " " + str(var.values.min())) - var.values = data.values * 24. # convert mm/hr to mm/d, do not alter metadata (yet) - var.attrs["units"] = "mm/d" # adjust metadata to reflect change in units - print("\nAFTER unit conversion: Max/min of data: " + str(var.values.max()) + " " + str(var.values.min())) + print( + "\nBEFORE unit conversion: Max/min of data: " + + str(var.values.max()) + + " " + + str(var.values.min()) + ) + var.values = ( + data.values * 24.0 + ) # convert mm/hr to mm/d, do not alter metadata (yet) + var.attrs["units"] = "mm/d" # adjust metadata to reflect change in units + print( + "\nAFTER unit conversion: Max/min of data: " + + str(var.values.max()) + + " " + + str(var.values.min()) + ) # Wavenumber Frequency Analysis spec_all = wf_analysis(var, **opt) - #spec_all.to_netcdf(outDataDir + "/full_spec.nc") + # spec_all.to_netcdf(outDataDir + "/full_spec.nc") return spec_all @@ -142,46 +183,45 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara :rtype: CoreParameter """ run_type = parameter.run_type - #variables = parameter.variables + # variables = parameter.variables season = "ANN" test_data = utils.dataset.Dataset(parameter, test=True) parameter.test_name_yrs = utils.general.get_name_and_yrs( parameter, test_data, season ) - + ref_data = utils.dataset.Dataset(parameter, ref=True) - parameter.ref_name_yrs = utils.general.get_name_and_yrs( - parameter, ref_data, season - ) + parameter.ref_name_yrs = utils.general.get_name_and_yrs(parameter, ref_data, season) print(parameter.ref_name_yrs, parameter.test_name_yrs, parameter.test_data_path) - #print('datapath', parameter.test_data_path, parameter.ref_data_path) - + # print('datapath', parameter.test_data_path, parameter.ref_data_path) + for variable in parameter.variables: test = calculate_spectrum(parameter.test_data_path, variable) - #TODO save to netcdf + # TODO save to netcdf test.to_netcdf(f"{parameter.results_dir}/full_spec_test.nc") ref = calculate_spectrum(parameter.test_data_path, variable) ref.to_netcdf(f"{parameter.results_dir}/full_spec_ref.nc") parameter.var_id = variable for diff_name in ["raw_sym", "raw_asy", "norm_sym", "norm_asy", "background"]: - - # Compute percentage difference - diff = 100 * (test[f"spec_{diff_name}"]-ref[f"spec_{diff_name}"])/ref[f"spec_{diff_name}"] - diff.name = f"spec_{diff_name}" - diff.attrs.update(test[f"spec_{diff_name}"].attrs) - parameter.spec_type = diff_name - plot(parameter, test[f"spec_{diff_name}"], ref[f"spec_{diff_name}"], diff) - if "norm" in diff_name: - parameter.spec_type = f"{diff_name}_zoom" - plot(parameter, test[f"spec_{diff_name}"], ref[f"spec_{diff_name}"], diff, do_zoom = True) - - return parameter - - - - - - - - + # Compute percentage difference + diff = ( + 100 + * (test[f"spec_{diff_name}"] - ref[f"spec_{diff_name}"]) + / ref[f"spec_{diff_name}"] + ) + diff.name = f"spec_{diff_name}" + diff.attrs.update(test[f"spec_{diff_name}"].attrs) + parameter.spec_type = diff_name + plot(parameter, test[f"spec_{diff_name}"], ref[f"spec_{diff_name}"], diff) + if "norm" in diff_name: + parameter.spec_type = f"{diff_name}_zoom" + plot( + parameter, + test[f"spec_{diff_name}"], + ref[f"spec_{diff_name}"], + diff, + do_zoom=True, + ) + + return parameter diff --git a/e3sm_diags/driver/utils/__init__.py b/e3sm_diags/driver/utils/__init__.py index fbe8ed56b..f96321a52 100644 --- a/e3sm_diags/driver/utils/__init__.py +++ b/e3sm_diags/driver/utils/__init__.py @@ -1,2 +1 @@ -from . import dataset, diurnal_cycle, general -from . import zwf_functions +from . import dataset, diurnal_cycle, general, zwf_functions diff --git a/e3sm_diags/driver/utils/zwf_functions.py b/e3sm_diags/driver/utils/zwf_functions.py index 1b5f6ed81..cef13690b 100755 --- a/e3sm_diags/driver/utils/zwf_functions.py +++ b/e3sm_diags/driver/utils/zwf_functions.py @@ -1,27 +1,29 @@ +import logging import sys -import xarray as xr + import numpy as np +import xarray as xr from scipy.signal import convolve2d, detrend -import logging + logging.basicConfig(level=logging.INFO) def helper(): """Prints all the functions that are included in this module.""" f = [ - "decompose2SymAsym(arr)", - "rmvAnnualCycle(data, spd, fCrit)", - "smoothFrq121(data,nsmth_iter=1)", - "smoothBackground_wavefreq(data)", - "resolveWavesHayashi( varfft: xr.DataArray, nDayWin: int, spd: int ) -> xr.DataArray", - "split_hann_taper(series_length, fraction)", - "spacetime_power(data, segsize=96, noverlap=60, spd=1, latitude_bounds=None, dosymmetries=False, rmvLowFrq=False)", - "genDispersionCurves(nWaveType=6, nPlanetaryWave=50, rlat=0, Ahe=[50, 25, 12])"] + "decompose2SymAsym(arr)", + "rmvAnnualCycle(data, spd, fCrit)", + "smoothFrq121(data,nsmth_iter=1)", + "smoothBackground_wavefreq(data)", + "resolveWavesHayashi( varfft: xr.DataArray, nDayWin: int, spd: int ) -> xr.DataArray", + "split_hann_taper(series_length, fraction)", + "spacetime_power(data, segsize=96, noverlap=60, spd=1, latitude_bounds=None, dosymmetries=False, rmvLowFrq=False)", + "genDispersionCurves(nWaveType=6, nPlanetaryWave=50, rlat=0, Ahe=[50, 25, 12])", + ] [print(fe) for fe in f] - - - -def getNearestInd1D(arr,val): + + +def getNearestInd1D(arr, val): """Given a specified value, find the index of an array that most closely matches that value. arr: numpy array or xarray DataArray return: Integer @@ -29,15 +31,14 @@ def getNearestInd1D(arr,val): Note: Uses python's 'argmin', which returns first occurrence if multiple indices "tie" for closest to the specified value. """ - difference_array = np.absolute(arr-val) + difference_array = np.absolute(arr - val) i_closest = int(difference_array.argmin()) - return(i_closest) - + return i_closest -def decompose2SymAsym(arr): +def decompose2SymAsym(arr): """Mimic NCL function to decompose into symmetric and asymmetric parts. - + arr: xarray DataArray return: DataArray with symmetric in SH, asymmetric in NH @@ -45,49 +46,48 @@ def decompose2SymAsym(arr): Note: This function produces indistinguishable results from NCL version. """ - lat_dim = arr.dims.index('lat') - # flag to follow NCL convention and put symmetric component in SH + lat_dim = arr.dims.index("lat") + # flag to follow NCL convention and put symmetric component in SH # & asymmetric in NH # method: use flip to reverse latitude, put in DataArray for coords, use loc/isel # to assign to negative/positive latitudes (exact equator is left alone) - data_sym = 0.5*(arr.values + np.flip(arr.values, axis=lat_dim)) - data_asy = 0.5*(arr.values - np.flip(arr.values, axis=lat_dim)) + data_sym = 0.5 * (arr.values + np.flip(arr.values, axis=lat_dim)) + data_asy = 0.5 * (arr.values - np.flip(arr.values, axis=lat_dim)) data_sym = xr.DataArray(data_sym, dims=arr.dims, coords=arr.coords) data_asy = xr.DataArray(data_asy, dims=arr.dims, coords=arr.coords) - out = arr.copy() # might not be best to copy, but is safe - out.loc[{'lat':arr['lat'][arr['lat']<0]}] = data_sym.isel(lat=data_sym.lat<0) - out.loc[{'lat':arr['lat'][arr['lat']>0]}] = data_asy.isel(lat=data_asy.lat>0) + out = arr.copy() # might not be best to copy, but is safe + out.loc[{"lat": arr["lat"][arr["lat"] < 0]}] = data_sym.isel(lat=data_sym.lat < 0) + out.loc[{"lat": arr["lat"][arr["lat"] > 0]}] = data_asy.isel(lat=data_asy.lat > 0) return out - def rmvAnnualCycle(data, spd, fCrit): """remove frequencies less than fCrit from data. - + data: xarray DataArray spd: sampling frequency in samples-per-day fCrit: frequency threshold; remove frequencies < fCrit - + return: xarray DataArray, shape of data - + Note: fft/ifft preserves the mean because z = fft(x), z[0] is the mean. To keep the mean here, we need to keep the 0 frequency. - + Note: This function reproduces the results from the NCL version. Note: Two methods are available, one using fft/ifft and the other rfft/irfft. They both produce output that is indistinguishable from NCL's result. """ dimz = data.sizes - ntim = dimz['time'] - time_ax = list(data.dims).index('time') + ntim = dimz["time"] + time_ax = list(data.dims).index("time") # Method 1: Uses the complex FFT, returns the negative frequencies too, but they # should be redundant b/c they are conjugate of positive ones. cf = np.fft.fft(data.values, axis=time_ax) freq = np.fft.fftfreq(ntim, spd) - cf[(freq != 0)&(np.abs(freq) < fCrit), ...] = 0.0 # keeps the mean + cf[(freq != 0) & (np.abs(freq) < fCrit), ...] = 0.0 # keeps the mean z = np.fft.ifft(cf, n=ntim, axis=0) - # Method 2: Uses the real FFT. In this case, + # Method 2: Uses the real FFT. In this case, # cf = np.fft.rfft(data.values, axis=time_ax) # freq = np.linspace(1, (ntim*spd)//2, (ntim*spd)//2) / ntim # fcrit_ndx = np.argwhere(freq < fCrit).max() @@ -98,100 +98,146 @@ def rmvAnnualCycle(data, spd, fCrit): return z - -def smoothFrq121(data,nsmth_iter=1): +def smoothFrq121(data, nsmth_iter=1): """Following what was used in the NCL routine 'wkSpaceTime', smooth input array - [nsmth_iter] times along the frequency dimension, for a sub-section of wavenumbers - (as the smoothing is cosmetic for plotting, only smooth for a subset of wavenumbers - to avoid unnecessary smoothing of non-plotted values). - - Do not use 0 frequency when smoothing. Uses weights that sum to 1 to ensure - smoothing is conservative. + [nsmth_iter] times along the frequency dimension, for a sub-section of wavenumbers + (as the smoothing is cosmetic for plotting, only smooth for a subset of wavenumbers + to avoid unnecessary smoothing of non-plotted values). + + Do not use 0 frequency when smoothing. Uses weights that sum to 1 to ensure + smoothing is conservative. """ assert isinstance(data, xr.DataArray) - print("\nFrom smoothFrq121: Frequency smoothing " + str(nsmth_iter) + " times for subset of wavenumbers (pos frqs only)") - nfrq = len(data['frequency']) - i_frq0 = int(np.where(data['frequency']==0)[0]) # index of 'frequency' coord where it equals 0 + print( + "\nFrom smoothFrq121: Frequency smoothing " + + str(nsmth_iter) + + " times for subset of wavenumbers (pos frqs only)" + ) + nfrq = len(data["frequency"]) + i_frq0 = int( + np.where(data["frequency"] == 0)[0] + ) # index of 'frequency' coord where it equals 0 for smth_iter in range(nsmth_iter): - data[...,i_frq0+1] = 0.75 * data[...,i_frq0+1] + 0.25 * data[...,i_frq0+2] - data[...,-1] = 0.25 * data[...,-2] + 0.75 * data[...,-1] - for i in range(i_frq0+2, nfrq-1): - data[...,i] = 0.25 * data[...,i-1] + 0.5 * data[...,i] + 0.25 * data[...,i+1] - return(data) - + data[..., i_frq0 + 1] = ( + 0.75 * data[..., i_frq0 + 1] + 0.25 * data[..., i_frq0 + 2] + ) + data[..., -1] = 0.25 * data[..., -2] + 0.75 * data[..., -1] + for i in range(i_frq0 + 2, nfrq - 1): + data[..., i] = ( + 0.25 * data[..., i - 1] + 0.5 * data[..., i] + 0.25 * data[..., i + 1] + ) + return data def smoothBackground_wavefreq(data): """From Wheeler and Kiladis (1999, doi: ): - "[Smooth] many times with a 1-2-1 filter in frequency and wavenumber. The number of - passes of the 1-2-1 filter we have used is 10 in frequency throughout, and from 10 - to 40 in wavenumber, being 10 at low frequencies and 40 at higher frequencies - increasing in two different steps." - - Here we do the following smoothing along the wavenumber dimension (note: we only - smooth in the positive frequency domain as these will be the values plotted!): - 0 < frq < 0.1 : 5 1-2-1 smoothing passes - 0.1 <= frq < 0.2: 10 1-2-1 smoothing passes - 0.2 <= frq < 0.3: 20 1-2-1 smoothing passes - 0.3 <= frq : 40 1-2-1 smoothing passes - This follows what was used in the NCL routine 'wkSpaceTime'. + "[Smooth] many times with a 1-2-1 filter in frequency and wavenumber. The number of + passes of the 1-2-1 filter we have used is 10 in frequency throughout, and from 10 + to 40 in wavenumber, being 10 at low frequencies and 40 at higher frequencies + increasing in two different steps." + + Here we do the following smoothing along the wavenumber dimension (note: we only + smooth in the positive frequency domain as these will be the values plotted!): + 0 < frq < 0.1 : 5 1-2-1 smoothing passes + 0.1 <= frq < 0.2: 10 1-2-1 smoothing passes + 0.2 <= frq < 0.3: 20 1-2-1 smoothing passes + 0.3 <= frq : 40 1-2-1 smoothing passes + This follows what was used in the NCL routine 'wkSpaceTime'. """ assert isinstance(data, xr.DataArray) - nfrq = len(data['frequency']) - i_frq0 = int(np.where(data['frequency']==0)[0]) # index of 'frequency' coord where it equals 0 -# i_frq03 = getNearestInd1D(data['frequency'], 0.3) # index of 'frequency' coord closest to 0.3 - i_minwav4smth = int(np.where(data['wavenumber']==-27)[0]) # index of 'wavenumber' coord where it equals -27 - i_maxwav4smth = int(np.where(data['wavenumber']==27)[0]) # index of 'wavenumber' coord where it equals 27 - - # Looping over positive frequencies, smooth in wavenumber. The number of smoothing + nfrq = len(data["frequency"]) + i_frq0 = int( + np.where(data["frequency"] == 0)[0] + ) # index of 'frequency' coord where it equals 0 + # i_frq03 = getNearestInd1D(data['frequency'], 0.3) # index of 'frequency' coord closest to 0.3 + i_minwav4smth = int( + np.where(data["wavenumber"] == -27)[0] + ) # index of 'wavenumber' coord where it equals -27 + i_maxwav4smth = int( + np.where(data["wavenumber"] == 27)[0] + ) # index of 'wavenumber' coord where it equals 27 + + # Looping over positive frequencies, smooth in wavenumber. The number of smoothing # passes in wavenumber is dependent on the frequency, with more smoothing at high # frequencies and less smoothing at low frequencies print("\nSmoothing background spectrum in wavenumber (pos frq only)...") - for i in range(i_frq0+1,nfrq): # Loop over all positive frequencies - - if(data['frequency'][i] < 0.1): - nsmth_iter = 5 - print(" Wavenumber smoothing " + str(nsmth_iter) + " times for freq: " + str(float(data['frequency'][i]))) - for smth_iter in range(nsmth_iter): - for j in range(i_minwav4smth, i_maxwav4smth+1): - data[j,i] = 0.25 * data[j-1,i] + 0.5 * data[j,i] + 0.25 * data[j+1,i] - - if(data['frequency'][i] >= 0.1 and data['frequency'][i] < 0.2): - nsmth_iter = 10 - print(" Wavenumber smoothing " + str(nsmth_iter) + " times for freq: " + str(float(data['frequency'][i]))) - for smth_iter in range(10): - for j in range(i_minwav4smth, i_maxwav4smth+1): - data[j,i] = 0.25 * data[j-1,i] + 0.5 * data[j,i] + 0.25 * data[j+1,i] - - if(data['frequency'][i] >= 0.2 and data['frequency'][i] < 0.3): - nsmth_iter = 20 - print(" Wavenumber smoothing " + str(nsmth_iter) + " times for freq: " + str(float(data['frequency'][i]))) - for smth_iter in range(20): - for j in range(i_minwav4smth, i_maxwav4smth+1): - data[j,i] = 0.25 * data[j-1,i] + 0.5 * data[j,i] + 0.25 * data[j+1,i] - - if(data['frequency'][i] >= 0.3): - nsmth_iter = 40 - print(" Wavenumber smoothing " + str(nsmth_iter) + " times for freq: " + str(float(data['frequency'][i]))) - for smth_iter in range(40): - for j in range(i_minwav4smth, i_maxwav4smth+1): - data[j,i] = 0.25 * data[j-1,i] + 0.5 * data[j,i] + 0.25 * data[j+1,i] + for i in range(i_frq0 + 1, nfrq): # Loop over all positive frequencies + if data["frequency"][i] < 0.1: + nsmth_iter = 5 + print( + " Wavenumber smoothing " + + str(nsmth_iter) + + " times for freq: " + + str(float(data["frequency"][i])) + ) + for smth_iter in range(nsmth_iter): + for j in range(i_minwav4smth, i_maxwav4smth + 1): + data[j, i] = ( + 0.25 * data[j - 1, i] + 0.5 * data[j, i] + 0.25 * data[j + 1, i] + ) + + if data["frequency"][i] >= 0.1 and data["frequency"][i] < 0.2: + nsmth_iter = 10 + print( + " Wavenumber smoothing " + + str(nsmth_iter) + + " times for freq: " + + str(float(data["frequency"][i])) + ) + for smth_iter in range(10): + for j in range(i_minwav4smth, i_maxwav4smth + 1): + data[j, i] = ( + 0.25 * data[j - 1, i] + 0.5 * data[j, i] + 0.25 * data[j + 1, i] + ) + + if data["frequency"][i] >= 0.2 and data["frequency"][i] < 0.3: + nsmth_iter = 20 + print( + " Wavenumber smoothing " + + str(nsmth_iter) + + " times for freq: " + + str(float(data["frequency"][i])) + ) + for smth_iter in range(20): + for j in range(i_minwav4smth, i_maxwav4smth + 1): + data[j, i] = ( + 0.25 * data[j - 1, i] + 0.5 * data[j, i] + 0.25 * data[j + 1, i] + ) + + if data["frequency"][i] >= 0.3: + nsmth_iter = 40 + print( + " Wavenumber smoothing " + + str(nsmth_iter) + + " times for freq: " + + str(float(data["frequency"][i])) + ) + for smth_iter in range(40): + for j in range(i_minwav4smth, i_maxwav4smth + 1): + data[j, i] = ( + 0.25 * data[j - 1, i] + 0.5 * data[j, i] + 0.25 * data[j + 1, i] + ) # For all wavenumbers, smooth in frequency: 10 passes # Do not use 0 frequency when smoothing # Use weights that sum to 1 to ensure smoothing is conservative nsmth_iter = 10 - print("Frequency smoothing " + str(nsmth_iter) + " times for all wavenumbers (pos frqs only)") + print( + "Frequency smoothing " + + str(nsmth_iter) + + " times for all wavenumbers (pos frqs only)" + ) for smth_iter in range(10): - data[:,i_frq0+1] = 0.75 * data[:,i_frq0+1] + 0.25 * data[:,i_frq0+2] - data[:,-1] = 0.25 * data[:,-2] + 0.75 * data[:,-1] - for i in range(i_frq0+2, nfrq-1): - data[:,i] = 0.25 * data[:,i-1] + 0.5 * data[:,i] + 0.25 * data[:,i+1] - return(data) + data[:, i_frq0 + 1] = 0.75 * data[:, i_frq0 + 1] + 0.25 * data[:, i_frq0 + 2] + data[:, -1] = 0.25 * data[:, -2] + 0.75 * data[:, -1] + for i in range(i_frq0 + 2, nfrq - 1): + data[:, i] = ( + 0.25 * data[:, i - 1] + 0.5 * data[:, i] + 0.25 * data[:, i + 1] + ) + return data - -def resolveWavesHayashi( varfft: xr.DataArray, nDayWin: int, spd: int ) -> xr.DataArray: +def resolveWavesHayashi(varfft: xr.DataArray, nDayWin: int, spd: int) -> xr.DataArray: """This is a direct translation from the NCL routine to python/xarray. input: varfft : expected to have rightmost dimensions of wavenumber and frequency. @@ -201,16 +247,16 @@ def resolveWavesHayashi( varfft: xr.DataArray, nDayWin: int, spd: int ) -> xr.Da returns: a DataArray that is reordered to have correct westward & eastward propagation. - + """ - #------------------------------------------------------------- - # Special reordering to resolve the Progressive and Retrogressive waves - # Reference: Hayashi, Y. - # A Generalized Method of Resolving Disturbances into - # Progressive and Retrogressive Waves by Space and + # ------------------------------------------------------------- + # Special reordering to resolve the Progressive and Retrogressive waves + # Reference: Hayashi, Y. + # A Generalized Method of Resolving Disturbances into + # Progressive and Retrogressive Waves by Space and # Fourier and TimeCross Spectral Analysis # J. Meteor. Soc. Japan, 1971, 49: 125-128. - #------------------------------------------------------------- + # ------------------------------------------------------------- # in NCL varfft is dimensioned (2,mlon,nSampWin), but the first dim doesn't matter b/c python supports complex numbers. # @@ -225,27 +271,27 @@ def resolveWavesHayashi( varfft: xr.DataArray, nDayWin: int, spd: int ) -> xr.Da # Information about the Nyquist Frequency is at pt=0 and pt=NT # - # In PEE, define the - # WESTWARD waves to be either - # positive frequency and negative wavenumber - # OR + # In PEE, define the + # WESTWARD waves to be either + # positive frequency and negative wavenumber + # OR # negative freq and positive wavenumber. - # EASTWARD waves are either positive freq and positive wavenumber + # EASTWARD waves are either positive freq and positive wavenumber # OR negative freq and negative wavenumber. # Note that frequencies are returned from fftpack are ordered like so # input_time_pos [ 0 1 2 3 4 5 6 7 ] - # ouput_fft_coef [mean 1/7 2/7 3/7 nyquist -3/7 -2/7 -1/7] + # ouput_fft_coef [mean 1/7 2/7 3/7 nyquist -3/7 -2/7 -1/7] # mean,pos freq to nyq,neg freq hi to lo # # Rearrange the coef array to give you power array of freq and wave number east/west - # Note east/west wave number *NOT* eq to fft wavenumber see Hayashi '71 - # Hence, NCL's 'cfftf_frq_reorder' can *not* be used. + # Note east/west wave number *NOT* eq to fft wavenumber see Hayashi '71 + # Hence, NCL's 'cfftf_frq_reorder' can *not* be used. # BPM: This goes for np.fft.fftshift # # For ffts that return the coefficients as described above, here is the algorithm # coeff array varfft(2,n,t) dimensioned (2,0:numlon-1,0:numtim-1) - # new space/time pee(2,pn,pt) dimensioned (2,0:numlon ,0:numtim ) + # new space/time pee(2,pn,pt) dimensioned (2,0:numlon ,0:numtim ) # # NOTE: one larger in both freq/space dims # the initial index of 2 is for the real (indx 0) and imag (indx 1) parts of the array @@ -266,14 +312,14 @@ def resolveWavesHayashi( varfft: xr.DataArray, nDayWin: int, spd: int ) -> xr.Da # local variables : dimvf, numlon, N, varspacetime, pee, wave, freq # bpm: if varfft is a numpy array, then we need to know which dim is longitude - # if it is an xr.DataArray, then we can just use that directly. This is + # if it is an xr.DataArray, then we can just use that directly. This is # reason enough to insist on a DataArray. # varfft should have a last dimension of "segments" of size N; should make a convention for the name of that dimension and insist on it here. logging.debug(f"[Hayashi] nDayWin: {nDayWin}, spd: {spd}") dimnames = varfft.dims - dimvf = varfft.shape - mlon = len(varfft['wavenumber']) - N = dimvf[-1] + dimvf = varfft.shape + mlon = len(varfft["wavenumber"]) + N = dimvf[-1] logging.info(f"[Hayashi] input dims is {dimnames}, {dimvf}") logging.info(f"[Hayashi] input coords is {varfft.coords}") if len(dimnames) != len(varfft.coords): @@ -290,19 +336,34 @@ def resolveWavesHayashi( varfft: xr.DataArray, nDayWin: int, spd: int ) -> xr.Da logging.debug("allocate the re-ordered array") varspacetime = np.full(nshape, np.nan, dtype=type(varfft)) # first two are the negative wavenumbers (westward), second two are the positive wavenumbers (eastward) - logging.debug(f"[Hayashi] Assign values into array. Notable numbers: mlon//2={mlon//2}, N//2={N//2}") - varspacetime[..., 0:mlon//2, 0:N//2 ] = varfft[..., mlon//2:0:-1, N//2:] # neg.k, pos.w - varspacetime[..., 0:mlon//2, N//2: ] = varfft[..., mlon//2:0:-1, 0:N//2+1] # pos.k, - varspacetime[..., mlon//2: , 0:N//2+1] = varfft[..., 0:mlon//2+1, N//2::-1] # assign eastward & neg.freq. - varspacetime[..., mlon//2: , N//2+1: ] = varfft[..., 0:mlon//2+1, -1:N//2-1:-1] # assign eastward & pos.freq. + logging.debug( + f"[Hayashi] Assign values into array. Notable numbers: mlon//2={mlon//2}, N//2={N//2}" + ) + varspacetime[..., 0 : mlon // 2, 0 : N // 2] = varfft[ + ..., mlon // 2 : 0 : -1, N // 2 : + ] # neg.k, pos.w + varspacetime[..., 0 : mlon // 2, N // 2 :] = varfft[ + ..., mlon // 2 : 0 : -1, 0 : N // 2 + 1 + ] # pos.k, + varspacetime[..., mlon // 2 :, 0 : N // 2 + 1] = varfft[ + ..., 0 : mlon // 2 + 1, N // 2 :: -1 + ] # assign eastward & neg.freq. + varspacetime[..., mlon // 2 :, N // 2 + 1 :] = varfft[ + ..., 0 : mlon // 2 + 1, -1 : N // 2 - 1 : -1 + ] # assign eastward & pos.freq. print(varspacetime.shape) # Create the real power spectrum pee = sqrt(real^2+imag^2)^2 logging.debug("calculate power") - pee = (np.abs(varspacetime))**2 # JJB: abs(a+bi) = sqrt(a**2 + b**2), which is what is done in NCL's resolveWavesHayashi + pee = ( + np.abs(varspacetime) + ) ** 2 # JJB: abs(a+bi) = sqrt(a**2 + b**2), which is what is done in NCL's resolveWavesHayashi logging.debug("put into DataArray") # add meta data for use upon return - wave = np.arange(-mlon // 2, (mlon // 2 )+ 1, 1, dtype=int) - freq = np.linspace(-1*nDayWin*spd/2, nDayWin*spd/2, (nDayWin*spd)+1) / nDayWin + wave = np.arange(-mlon // 2, (mlon // 2) + 1, 1, dtype=int) + freq = ( + np.linspace(-1 * nDayWin * spd / 2, nDayWin * spd / 2, (nDayWin * spd) + 1) + / nDayWin + ) print(f"freq size is {freq.shape}.") odims = list(dimnames) @@ -314,44 +375,50 @@ def resolveWavesHayashi( varfft: xr.DataArray, nDayWin: int, spd: int ) -> xr.Da if (c != "wavenumber") and (c != "frequency"): ocoords[c] = varfft[c] elif c == "wavenumber": - ocoords['wavenumber'] = wave + ocoords["wavenumber"] = wave elif c == "frequency": - ocoords['frequency'] = freq + ocoords["frequency"] = freq pee = xr.DataArray(pee, dims=odims, coords=ocoords) - return pee - + return pee def split_hann_taper(series_length, fraction): """Implements `split cosine bell` taper of length series_length where only fraction of points are tapered (combined on both ends). - + This returns a function that tapers to zero on the ends. To taper to the mean of a series X: XTAPER = (X - X.mean())*series_taper + X.mean() """ npts = int(np.rint(fraction * series_length)) # total size of taper taper = np.hanning(npts) series_taper = np.ones(series_length) - series_taper[0:npts//2+1] = taper[0:npts//2+1] - series_taper[-npts//2+1:] = taper[npts//2+1:] + series_taper[0 : npts // 2 + 1] = taper[0 : npts // 2 + 1] + series_taper[-npts // 2 + 1 :] = taper[npts // 2 + 1 :] return series_taper - -def spacetime_power(data, segsize=96, noverlap=60, spd=1, latitude_bounds=None, dosymmetries=False, rmvLowFrq=False): +def spacetime_power( + data, + segsize=96, + noverlap=60, + spd=1, + latitude_bounds=None, + dosymmetries=False, + rmvLowFrq=False, +): """Perform space-time spectral decomposition and return power spectrum following Wheeler-Kiladis approach. - + data: an xarray DataArray to be analyzed; needs to have (time, lat, lon) dimensions. segsize: integer denoting the size of time samples that will be decomposed (typically about 96) noverlap: integer denoting the number of days of overlap from one segment to the next (typically about segsize-60 => 2-month overlap) spd: sampling rate, in "samples per day" (e.g. daily=1, 6-houry=4) - + latitude_bounds: a tuple of (southern_extent, northern_extent) to reduce data size. - + dosymmetries: if True, follow NCL convention of putting symmetric component in SH, antisymmetric in NH If True, the function returns a DataArray with a `component` dimension. - + rmvLowFrq: if True, remove frequencies < 1/segsize from data. - + Method ------ 1. Subsample in latitude if latitude_bounds is specified. @@ -364,102 +431,129 @@ def spacetime_power(data, segsize=96, noverlap=60, spd=1, latitude_bounds=None, 8. Fourier transform 9. Apply Hayashi reordering to get propagation direction & convert to power. 10. return DataArray with power. - + Notes ----- - Upon returning power, this should be comparable to "raw" spectra. - Next step would be be to smooth with `smooth_wavefreq`, + Upon returning power, this should be comparable to "raw" spectra. + Next step would be be to smooth with `smooth_wavefreq`, and divide raw spectra by smooth background to obtain "significant" spectral power. - + """ - segsize_steps = spd*segsize # Segment length, in time step units - noverlap_steps = spd*noverlap # Segment overlap, in time step units - stride_steps = segsize_steps - noverlap_steps # Stride for overlapping windows, in time step units + segsize_steps = spd * segsize # Segment length, in time step units + noverlap_steps = spd * noverlap # Segment overlap, in time step units + stride_steps = ( + segsize_steps - noverlap_steps + ) # Stride for overlapping windows, in time step units if latitude_bounds is not None: assert isinstance(latitude_bounds, tuple) - data = data.sel(lat=slice(*latitude_bounds)) # CAUTION: is this a mutable argument? + data = data.sel( + lat=slice(*latitude_bounds) + ) # CAUTION: is this a mutable argument? logging.info(f"Data reduced by latitude bounds. Size is {data.sizes}") slat = latitude_bounds[0] nlat = latitude_bounds[1] else: - slat = data['lat'].min().item() - nlat = data['lat'].max().item() + slat = data["lat"].min().item() + nlat = data["lat"].max().item() # Remove the *long-term* temporal linear trend # Using scipy.signal.detrend will remove the mean as well, but we will add the time # mean back into the detrended data to be consistent with the approach used in the # NCL version (https://www.ncl.ucar.edu/Document/Functions/Diagnostics/wkSpaceTime.shtml): - xmean = data.mean(dim='time') - xdetr = detrend(data.values, axis=0, type='linear') + xmean = data.mean(dim="time") + xdetr = detrend(data.values, axis=0, type="linear") xdetr = xr.DataArray(xdetr, dims=data.dims, coords=data.coords) - xdetr += xmean # put the mean back in + xdetr += xmean # put the mean back in # --> Tested and confirmed that this approach gives same answer as NCL - # filter low-frequencies + # filter low-frequencies if rmvLowFrq: - data = rmvAnnualCycle(xdetr, spd, 1/segsize_steps) + data = rmvAnnualCycle(xdetr, spd, 1 / segsize_steps) # --> Tested and confirmed that this function gives same answer as NCL - # NOTE: at this point "data" has been modified to have its long-term linear trend and - # low frequencies (lower than 1./segsize_steps) removed + # NOTE: at this point "data" has been modified to have its long-term linear trend and + # low frequencies (lower than 1./segsize_steps) removed dimsizes = data.sizes # dict - lon_size = dimsizes['lon'] - lat_size = dimsizes['lat'] - lat_dim = data.dims.index('lat') + lon_size = dimsizes["lon"] + lat_size = dimsizes["lat"] + lat_dim = data.dims.index("lat") if dosymmetries: data = decompose2SymAsym(data) # testing: pass -- Gets the same result as NCL. - + # Windowing with the xarray "rolling" operation, and then limit overlap with `construct` to produce a new dataArray. # JJB: Windowing segment bounds have been validated with test prints. - x_roll = data.rolling(time=segsize_steps, min_periods=segsize_steps) # WK99 use 96-day window - x_win = x_roll.construct("segments", stride=stride_steps).dropna("time") # WK99 say "2-month" overlap; dims: (nSegments,nlat,nlon,segment_length_steps) + x_roll = data.rolling( + time=segsize_steps, min_periods=segsize_steps + ) # WK99 use 96-day window + x_win = x_roll.construct("segments", stride=stride_steps).dropna( + "time" + ) # WK99 say "2-month" overlap; dims: (nSegments,nlat,nlon,segment_length_steps) logging.debug(f"[spacetime_power] x_win shape is {x_win.shape}") - + # For each segment, remove the temporal mean and linear trend: - if np.logical_not(np.any(np.isnan(x_win))): + if np.logical_not(np.any(np.isnan(x_win))): logging.info("No missing, so use simplest segment detrend.") - x_win_detr = detrend(x_win.values, axis=-1, type='linear') #<-- missing data makes this not work; axis=-1 for segment window dimension + x_win_detr = detrend( + x_win.values, axis=-1, type="linear" + ) # <-- missing data makes this not work; axis=-1 for segment window dimension x_win = xr.DataArray(x_win_detr, dims=x_win.dims, coords=x_win.coords) else: - logging.warning("EXTREME WARNING -- This method to detrend with missing values present does not quite work, probably need to do interpolation instead.") - logging.warning("There are missing data in x_win, so have to try to detrend around them.") + logging.warning( + "EXTREME WARNING -- This method to detrend with missing values present does not quite work, probably need to do interpolation instead." + ) + logging.warning( + "There are missing data in x_win, so have to try to detrend around them." + ) x_win_cp = x_win.values.copy() - logging.info(f"[spacetime_power] x_win_cp windowed data has shape {x_win_cp.shape} \n \t It is a numpy array, copied from x_win which has dims: {x_win.sizes} \n \t ** about to detrend this in the rightmost dimension.") - x_win_cp[np.logical_not(np.isnan(x_win_cp))] = detrend(x_win_cp[np.logical_not(np.isnan(x_win_cp))]) + logging.info( + f"[spacetime_power] x_win_cp windowed data has shape {x_win_cp.shape} \n \t It is a numpy array, copied from x_win which has dims: {x_win.sizes} \n \t ** about to detrend this in the rightmost dimension." + ) + x_win_cp[np.logical_not(np.isnan(x_win_cp))] = detrend( + x_win_cp[np.logical_not(np.isnan(x_win_cp))] + ) x_win = xr.DataArray(x_win_cp, dims=x_win.dims, coords=x_win.coords) - # 3. Taper in time to reduce spectral "leakage" and make the segment periodic for FFT analysis # taper = np.hanning(segsize) # WK seem to use some kind of stretched out hanning window; unclear if it matters - taper = split_hann_taper(segsize_steps, 0.1) # try to replicate NCL's approach of tapering 10% of segment window - x_wintap = x_win*taper # would do XTAPER = (X - X.mean())*series_taper + X.mean() - # But since we have removed the mean, taper going to 0 is equivalent to taper going to the mean. - - + taper = split_hann_taper( + segsize_steps, 0.1 + ) # try to replicate NCL's approach of tapering 10% of segment window + x_wintap = x_win * taper # would do XTAPER = (X - X.mean())*series_taper + X.mean() + # But since we have removed the mean, taper going to 0 is equivalent to taper going to the mean. + # Do the transform using 2D FFT # JJB: As written below, this does not give the same answer as the 2-step approach, # not sure why but didn't look into it more. Will use 2-step approach to be # safe (also mimics NCL version better) - #z = np.fft.fft2(x_wintap, axes=(2,3)) / (lon_size * segsize) + # z = np.fft.fft2(x_wintap, axes=(2,3)) / (lon_size * segsize) # Or do the transform with 2 steps # Note: x_wintap is shape (nSegments, nlat, nlon, segment_length_steps) - z = np.fft.fft(x_wintap, axis=2) / lon_size # note that np.fft.fft() produces same answers as NCL cfftf; axis=2 to do fft along dim=lon - z = np.fft.fft(z, axis=3) / segsize_steps # axis=3 to do fft along dim=segment_length_steps - - z = xr.DataArray(z, dims=("time","lat","wavenumber","frequency"), - coords={"time":x_wintap["time"], - "lat":x_wintap["lat"], - "wavenumber":np.fft.fftfreq(lon_size, 1/lon_size), - "frequency":np.fft.fftfreq(segsize_steps, 1/spd)}) + z = ( + np.fft.fft(x_wintap, axis=2) / lon_size + ) # note that np.fft.fft() produces same answers as NCL cfftf; axis=2 to do fft along dim=lon + z = ( + np.fft.fft(z, axis=3) / segsize_steps + ) # axis=3 to do fft along dim=segment_length_steps + + z = xr.DataArray( + z, + dims=("time", "lat", "wavenumber", "frequency"), + coords={ + "time": x_wintap["time"], + "lat": x_wintap["lat"], + "wavenumber": np.fft.fftfreq(lon_size, 1 / lon_size), + "frequency": np.fft.fftfreq(segsize_steps, 1 / spd), + }, + ) # - # The FFT is returned following ``standard order`` which has negative frequencies in second half of array. + # The FFT is returned following ``standard order`` which has negative frequencies in second half of array. # - # IMPORTANT: + # IMPORTANT: # If this were typical 2D FFT, we would do the following to get the frequencies and reorder: # z_k = np.fft.fftfreq(x_wintap.shape[-2], 1/lon_size) # z_v = np.fft.fftfreq(x_wintap.shape[-1], 1) # Assumes 1/(1-day) timestep @@ -484,8 +578,8 @@ def spacetime_power(data, segsize=96, noverlap=60, spd=1, latitude_bounds=None, # (in testing, it seems to end up opposite in wavenumber) # Apply reordering per Hayashi to get correct wave propagation convention # this function is customized to expect z to be a DataArray - z_pee = resolveWavesHayashi(z, segsize_steps//spd, spd) - # z_pee is spectral power already. + z_pee = resolveWavesHayashi(z, segsize_steps // spd, spd) + # z_pee is spectral power already. # z_pee is a DataArray w/ coordinate vars for wavenumber & frequency # average over all available segments and sum over latitude @@ -493,31 +587,36 @@ def spacetime_power(data, segsize=96, noverlap=60, spd=1, latitude_bounds=None, if dosymmetries: # multipy by 2 b/c we only used one hemisphere # JJB: This summing of powers over latitude appears to be done a little bit earlier - # here when compared to NCL's version, but this should not matter. + # here when compared to NCL's version, but this should not matter. # JJB: I think the factor of 2 comes in here because the symmetric and antisymmetric # data were packaged into either the Norther or Southern Hemis, so the 'sum' # across latitude would need to be multiplied by 2. For example, if the sym/asym data # were -not- packaged into single hemispheres, no multiplicative factor would # be needed. # JJB: Also, 'squeeze' removed degenerate dimensions 'time' and 'lat' - z_symmetric = 2.0 * z_pee.isel(lat=z_pee.lat<=0).mean(dim='time').sum(dim='lat').squeeze() + z_symmetric = ( + 2.0 + * z_pee.isel(lat=z_pee.lat <= 0).mean(dim="time").sum(dim="lat").squeeze() + ) z_symmetric.name = "power" - z_antisymmetric = 2.0 * z_pee.isel(lat=z_pee.lat>0).mean(dim='time').sum(dim='lat').squeeze() + z_antisymmetric = ( + 2.0 + * z_pee.isel(lat=z_pee.lat > 0).mean(dim="time").sum(dim="lat").squeeze() + ) z_antisymmetric.name = "power" z_final = xr.concat([z_symmetric, z_antisymmetric], "component") - z_final = z_final.assign_coords({"component":["symmetric","antisymmetric"]}) + z_final = z_final.assign_coords({"component": ["symmetric", "antisymmetric"]}) print("\nMetadata for z_final is:") print(z_final.dims) print(z_final.coords) print(z_final.attrs) else: - lat = z_pee['lat'] - lat_inds = np.argwhere(((lat <= nlat)&(lat >= slat)).values).squeeze() - z_final = z_pee.isel(lat=lat_inds).mean(dim='time').sum(dim='lat').squeeze() + lat = z_pee["lat"] + lat_inds = np.argwhere(((lat <= nlat) & (lat >= slat)).values).squeeze() + z_final = z_pee.isel(lat=lat_inds).mean(dim="time").sum(dim="lat").squeeze() return z_final - def genDispersionCurves(nWaveType=6, nPlanetaryWave=50, rlat=0, Ahe=[50, 25, 12]): """ Function to derive the shallow water dispersion curves. Closely follows NCL version. @@ -532,24 +631,24 @@ def genDispersionCurves(nWaveType=6, nPlanetaryWave=50, rlat=0, Ahe=[50, 25, 12] returns: tuple of size 2 Afreq: Frequency, shape is (nWaveType, nEquivDepth, nPlanetaryWave) Apzwn: Zonal savenumber, shape is (nWaveType, nEquivDepth, nPlanetaryWave) - + notes: - The outputs contain both symmetric and antisymmetric waves. In the case of + The outputs contain both symmetric and antisymmetric waves. In the case of nWaveType == 6: 0,1,2 are (ASYMMETRIC) "MRG", "IG", "EIG" (mixed rossby gravity, inertial gravity, equatorial inertial gravity) 3,4,5 are (SYMMETRIC) "Kelvin", "ER", "IG" (Kelvin, equatorial rossby, inertial gravity) """ - nEquivDepth = len(Ahe) # this was an input originally, but I don't know why. - pi = np.pi - radius = 6.37122e06 # [m] average radius of earth - g = 9.80665 # [m/s] gravity at 45 deg lat used by the WMO - omega = 7.292e-05 # [1/s] earth's angular vel + nEquivDepth = len(Ahe) # this was an input originally, but I don't know why. + pi = np.pi + radius = 6.37122e06 # [m] average radius of earth + g = 9.80665 # [m/s] gravity at 45 deg lat used by the WMO + omega = 7.292e-05 # [1/s] earth's angular vel # U = 0.0 # NOT USED, so Commented # Un = 0.0 # since Un = U*T/L # NOT USED, so Commented - ll = 2.*pi*radius*np.cos(np.abs(rlat)) - Beta = 2.*omega*np.cos(np.abs(rlat))/radius + ll = 2.0 * pi * radius * np.cos(np.abs(rlat)) + Beta = 2.0 * omega * np.cos(np.abs(rlat)) / radius fillval = 1e20 - + # NOTE: original code used a variable called del, # I just replace that with `dell` because `del` is a python keyword. @@ -557,84 +656,91 @@ def genDispersionCurves(nWaveType=6, nPlanetaryWave=50, rlat=0, Ahe=[50, 25, 12] Afreq = np.empty((nWaveType, nEquivDepth, nPlanetaryWave)) Apzwn = np.empty((nWaveType, nEquivDepth, nPlanetaryWave)) - for ww in range(1, nWaveType+1): + for ww in range(1, nWaveType + 1): for ed, he in enumerate(Ahe): # this loops through the specified equivalent depths # ed provides index to fill in output array, while # he is the current equivalent depth # T = 1./np.sqrt(Beta)*(g*he)**(0.25) This is close to pre-factor of the dispersion relation, but is not used. - c = np.sqrt(g * he) # phase speed - L = np.sqrt(c/Beta) # was: (g*he)**(0.25)/np.sqrt(Beta), this is Rossby radius of deformation - - for wn in range(1, nPlanetaryWave+1): - s = -20.*(wn-1)*2./(nPlanetaryWave-1) + 20. - k = 2.0 * pi * s / ll - kn = k * L - - # Anti-symmetric curves - if (ww == 1): # MRG wave - if (k < 0): - dell = np.sqrt(1.0 + (4.0 * Beta)/(k**2 * c)) + c = np.sqrt(g * he) # phase speed + L = np.sqrt( + c / Beta + ) # was: (g*he)**(0.25)/np.sqrt(Beta), this is Rossby radius of deformation + + for wn in range(1, nPlanetaryWave + 1): + s = -20.0 * (wn - 1) * 2.0 / (nPlanetaryWave - 1) + 20.0 + k = 2.0 * pi * s / ll + kn = k * L + + # Anti-symmetric curves + if ww == 1: # MRG wave + if k < 0: + dell = np.sqrt(1.0 + (4.0 * Beta) / (k**2 * c)) deif = k * c * (0.5 - 0.5 * dell) - - if (k == 0): + + if k == 0: deif = np.sqrt(c * Beta) - - if (k > 0): + + if k > 0: deif = fillval - - - if (ww == 2): # n=0 IG wave - if (k < 0): + + if ww == 2: # n=0 IG wave + if k < 0: deif = fillval - - if (k == 0): - deif = np.sqrt( c * Beta) - - if (k > 0): - dell = np.sqrt(1.+(4.0*Beta)/(k**2 * c)) + + if k == 0: + deif = np.sqrt(c * Beta) + + if k > 0: + dell = np.sqrt(1.0 + (4.0 * Beta) / (k**2 * c)) deif = k * c * (0.5 + 0.5 * dell) - - - if (ww == 3): # n=2 IG wave - n=2. - dell = (Beta*c) - deif = np.sqrt((2.*n+1.)*dell + (g*he) * k**2) + + if ww == 3: # n=2 IG wave + n = 2.0 + dell = Beta * c + deif = np.sqrt((2.0 * n + 1.0) * dell + (g * he) * k**2) # do some corrections to the above calculated frequency....... - for i in range(1,5+1): - deif = np.sqrt((2.*n+1.)*dell + (g*he) * k**2 + g*he*Beta*k/deif) - - + for i in range(1, 5 + 1): + deif = np.sqrt( + (2.0 * n + 1.0) * dell + + (g * he) * k**2 + + g * he * Beta * k / deif + ) + # symmetric curves - if (ww == 4): # n=1 ER wave - n=1. - if (k < 0.0): - dell = (Beta/c)*(2.*n+1.) - deif = -Beta*k/(k**2 + dell) + if ww == 4: # n=1 ER wave + n = 1.0 + if k < 0.0: + dell = (Beta / c) * (2.0 * n + 1.0) + deif = -Beta * k / (k**2 + dell) else: deif = fillval - - if (ww == 5): # Kelvin wave - deif = k*c - - if (ww == 6): # n=1 IG wave - n=1. - dell = (Beta*c) - deif = np.sqrt((2. * n+1.) * dell + (g*he)*k**2) + + if ww == 5: # Kelvin wave + deif = k * c + + if ww == 6: # n=1 IG wave + n = 1.0 + dell = Beta * c + deif = np.sqrt((2.0 * n + 1.0) * dell + (g * he) * k**2) # do some corrections to the above calculated frequency - for i in range(1,5+1): - deif = np.sqrt((2.*n+1.)*dell + (g*he)*k**2 + g*he*Beta*k/deif) - - eif = deif # + k*U since U=0.0 - P = 2.*pi/(eif*24.*60.*60.) # => PERIOD + for i in range(1, 5 + 1): + deif = np.sqrt( + (2.0 * n + 1.0) * dell + + (g * he) * k**2 + + g * he * Beta * k / deif + ) + + eif = deif # + k*U since U=0.0 + P = 2.0 * pi / (eif * 24.0 * 60.0 * 60.0) # => PERIOD # dps = deif/k # Does not seem to be used. # R = L #<-- this seemed unnecessary, I just changed R to L in Rdeg # Rdeg = (180.*L)/(pi*6.37e6) # And it doesn't get used. - - Apzwn[ww-1,ed,wn-1] = s - if (deif != fillval): + + Apzwn[ww - 1, ed, wn - 1] = s + if deif != fillval: # P = 2.*pi/(eif*24.*60.*60.) # not sure why we would re-calculate now - Afreq[ww-1,ed,wn-1] = 1./P + Afreq[ww - 1, ed, wn - 1] = 1.0 / P else: - Afreq[ww-1,ed,wn-1] = fillval - return Afreq, Apzwn \ No newline at end of file + Afreq[ww - 1, ed, wn - 1] = fillval + return Afreq, Apzwn diff --git a/e3sm_diags/parameter/core_parameter.py b/e3sm_diags/parameter/core_parameter.py index 160e54da9..c7b75189f 100644 --- a/e3sm_diags/parameter/core_parameter.py +++ b/e3sm_diags/parameter/core_parameter.py @@ -31,8 +31,8 @@ "lat_lon_river", "aerosol_aeronet", "aerosol_budget", -# "mp_partition", -# "tropical_subseasonal", + # "mp_partition", + # "tropical_subseasonal", ] diff --git a/e3sm_diags/parser/tropical_subseasonal_parser.py b/e3sm_diags/parser/tropical_subseasonal_parser.py index 903ff5e99..7e95b6fae 100644 --- a/e3sm_diags/parser/tropical_subseasonal_parser.py +++ b/e3sm_diags/parser/tropical_subseasonal_parser.py @@ -1,4 +1,6 @@ -from e3sm_diags.parameter.tropical_subseasonal_parameter import TropicalSubseasonalParameter +from e3sm_diags.parameter.tropical_subseasonal_parameter import ( + TropicalSubseasonalParameter, +) from e3sm_diags.parser.core_parser import CoreParser diff --git a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py index 406550b79..59fc9db38 100755 --- a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py +++ b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py @@ -1,20 +1,19 @@ from __future__ import annotations +import os from typing import TYPE_CHECKING import matplotlib -import xarray as xr - -from e3sm_diags.logger import custom_logger -from e3sm_diags.parameter.core_parameter import CoreParameter import matplotlib.pyplot as plt -from e3sm_diags.driver.utils.general import get_output_dir -from matplotlib.colors import ListedColormap, BoundaryNorm import numpy as np -import os -#from e3sm_diags.plot.utils import _add_colormap, _save_plot +import xarray as xr +from matplotlib.colors import BoundaryNorm, ListedColormap + from zwf import zwf_functions as wf +from e3sm_diags.driver.utils.general import get_output_dir +from e3sm_diags.logger import custom_logger +from e3sm_diags.parameter.core_parameter import CoreParameter if TYPE_CHECKING: from e3sm_diags.driver.lat_lon_driver import MetricsDict @@ -28,44 +27,123 @@ # Plot title and side title configurations. PLOT_TITLE = {"fontsize": 11.5} PLOT_SIDE_TITLE = {"fontsize": 9.5} - + # Position and sizes of subplot axes in page coordinates (0 to 1) PANEL = [ (0.17, 0.70, 0.50, 0.25), (0.17, 0.37, 0.50, 0.25), (0.17, 0.04, 0.50, 0.25), -] - +] + # Border padding relative to subplot axes for saving individual panels # (left, bottom, right, top) in page coordinates BORDER_PADDING = (-0.06, -0.03, 0.13, 0.03) -CONTOUR_LEVS_SPEC_RAW = (-1.4,-1.2,-1,-0.9,-0.8,-0.7,-0.6,-0.5,-0.4,-0.3,-0.2,-0.1,0,0.1,0.2) - -CMAP_SPEC_RAW = ["white", - "paleturquoise","lightblue","skyblue", - "lightgreen","limegreen","green","darkgreen", - "yellow","orange","orangered", - "red","maroon","magenta","orchid","pink", - "lavenderblush"] - -CONTOUR_LEVS_SPEC_NORM = (0.9,1.0,1.1,1.2,1.3,1.4,1.5,1.7,1.9,2.1,2.4,2.7,3.0) - -CMAP_SPEC_NORM = ["white", - "gainsboro","lightgray","silver", - "paleturquoise","skyblue", - "lightgreen","mediumseagreen","seagreen", - "yellow","orange", - "red","maroon","pink"] - -CONTOUR_LEVS_SPEC_RAW_DIFF = (-80.,-60.,-40.,-20.,-10.,-5.,5.,10.,20.,40.,60.,80.) -CONTOUR_LEVS_SPEC_NORM_DIFF = (-60.,-30.,-20.,-15.,-10.,-5.,5.,10.,15.,20.,30.,60.) +CONTOUR_LEVS_SPEC_RAW = ( + -1.4, + -1.2, + -1, + -0.9, + -0.8, + -0.7, + -0.6, + -0.5, + -0.4, + -0.3, + -0.2, + -0.1, + 0, + 0.1, + 0.2, +) + +CMAP_SPEC_RAW = [ + "white", + "paleturquoise", + "lightblue", + "skyblue", + "lightgreen", + "limegreen", + "green", + "darkgreen", + "yellow", + "orange", + "orangered", + "red", + "maroon", + "magenta", + "orchid", + "pink", + "lavenderblush", +] + +CONTOUR_LEVS_SPEC_NORM = ( + 0.9, + 1.0, + 1.1, + 1.2, + 1.3, + 1.4, + 1.5, + 1.7, + 1.9, + 2.1, + 2.4, + 2.7, + 3.0, +) + +CMAP_SPEC_NORM = [ + "white", + "gainsboro", + "lightgray", + "silver", + "paleturquoise", + "skyblue", + "lightgreen", + "mediumseagreen", + "seagreen", + "yellow", + "orange", + "red", + "maroon", + "pink", +] + +CONTOUR_LEVS_SPEC_RAW_DIFF = ( + -80.0, + -60.0, + -40.0, + -20.0, + -10.0, + -5.0, + 5.0, + 10.0, + 20.0, + 40.0, + 60.0, + 80.0, +) +CONTOUR_LEVS_SPEC_NORM_DIFF = ( + -60.0, + -30.0, + -20.0, + -15.0, + -10.0, + -5.0, + 5.0, + 10.0, + 15.0, + 20.0, + 30.0, + 60.0, +) def find_nearest(array, value): array = np.asarray(array) idx = (np.abs(array - value)).argmin() - return idx,array[idx] + return idx, array[idx] """Return index of [array] closest in value to [value] Example: array = [ 0.21069679 0.61290182 0.63425412 0.84635244 0.91599191 0.00213826 @@ -75,8 +153,11 @@ def find_nearest(array, value): """ + def create_colormap_clevs(cmapSpec, clevs): - cmapSpecUse = ListedColormap(cmapSpec[1:-1]) # recall: range is NOT inclusive for final index + cmapSpecUse = ListedColormap( + cmapSpec[1:-1] + ) # recall: range is NOT inclusive for final index cmapSpecUse.set_under(cmapSpec[0]) cmapSpecUse.set_over(cmapSpec[-1]) normSpecUse = BoundaryNorm(clevs, cmapSpecUse.N) @@ -84,7 +165,6 @@ def create_colormap_clevs(cmapSpec, clevs): return cmapSpecUse, normSpecUse - def _save_plot(fig: plt.figure, parameter: CoreParameter): """Save the plot using the figure object and parameter configs. @@ -104,7 +184,7 @@ def _save_plot(fig: plt.figure, parameter: CoreParameter): get_output_dir(parameter.current_set, parameter), parameter.output_file + "." + f, ) - #fnm = f'{parameter.var_id}_{parameter.spec_type}_15N-15N.png' + # fnm = f'{parameter.var_id}_{parameter.spec_type}_15N-15N.png' plt.savefig(fnm) logger.info(f"Plot saved in: {fnm}") @@ -141,7 +221,6 @@ def _save_plot(fig: plt.figure, parameter: CoreParameter): logger.info(f"Sub-plot saved in: {fname}") - def _wave_frequency_plot( subplot_num: int, var: xr.DataArray, @@ -167,192 +246,304 @@ def _wave_frequency_plot( ( years, title, units). do_zoom: Boolean """ - #TODO link var_id + # TODO link var_id varName = parameter.var_id - #varName = 'PRECT' + # varName = 'PRECT' PlotDesc = {} - PlotDesc['spec_raw_sym'] = {"long_name_desc": f"{varName}: log-base10 of lightly smoothed spectral power of component symmetric about equator", "ref_fig_num": "Figure 1"} # Figure number from Wheeler and Kiladis (1999) - PlotDesc['spec_raw_asy'] = {"long_name_desc": f"{varName}: log-base10 of lightly smoothed spectral power of component antisymmetric about equator", "ref_fig_num": "Figure 1"} - PlotDesc['spec_norm_sym'] = {"long_name_desc": f"{varName}: lightly smoothed spectral power of component symmetric about equator, normalized by heavily smoothed background spectrum", "ref_fig_num": "Figure 3"} - PlotDesc['spec_norm_asy'] = {"long_name_desc": f"{varName}: lightly smoothed spectral power of component antisymmetric about equator, normalized by heavily smoothed background spectrum", "ref_fig_num": "Figure 3"} - PlotDesc['spec_background'] = {"long_name_desc": f"{varName}: heavily smoothed version of the mean of spectral powers associated with the components symmetric and antisymmetric about equator", "ref_fig_num": "Figure 2"} + PlotDesc["spec_raw_sym"] = { + "long_name_desc": f"{varName}: log-base10 of lightly smoothed spectral power of component symmetric about equator", + "ref_fig_num": "Figure 1", + } # Figure number from Wheeler and Kiladis (1999) + PlotDesc["spec_raw_asy"] = { + "long_name_desc": f"{varName}: log-base10 of lightly smoothed spectral power of component antisymmetric about equator", + "ref_fig_num": "Figure 1", + } + PlotDesc["spec_norm_sym"] = { + "long_name_desc": f"{varName}: lightly smoothed spectral power of component symmetric about equator, normalized by heavily smoothed background spectrum", + "ref_fig_num": "Figure 3", + } + PlotDesc["spec_norm_asy"] = { + "long_name_desc": f"{varName}: lightly smoothed spectral power of component antisymmetric about equator, normalized by heavily smoothed background spectrum", + "ref_fig_num": "Figure 3", + } + PlotDesc["spec_background"] = { + "long_name_desc": f"{varName}: heavily smoothed version of the mean of spectral powers associated with the components symmetric and antisymmetric about equator", + "ref_fig_num": "Figure 2", + } text_offset = 0.005 - fb = [0, .8] # frequency bounds for plot + fb = [0, 0.8] # frequency bounds for plot wnb = [-15, 15] # zonal wavenumber bounds for plot - if(max(var['frequency'].values) == 0.5): - fb = [0, .5] + if max(var["frequency"].values) == 0.5: + fb = [0, 0.5] - if(do_zoom): - fb = [0, .18] + if do_zoom: + fb = [0, 0.18] wnb = [-7, 7] # get data for dispersion curves: - equivDepths=[50, 25, 12] - swfreq,swwn = wf.genDispersionCurves(Ahe=equivDepths) + equivDepths = [50, 25, 12] + swfreq, swwn = wf.genDispersionCurves(Ahe=equivDepths) # swfreq.shape # -->(6, 3, 50) - if 'spec_norm' in var.name: # with dispersion curves to plot + if "spec_norm" in var.name: # with dispersion curves to plot # For n=1 ER waves, allow dispersion curves to touch 0 -- this is for plot aesthetics only - for i in range(0,3): # loop 0-->2 for the assumed 3 shallow water dispersion curves for ER waves - indMinPosFrqER = np.where(swwn[3,i,:] >= 0., swwn[3,i,:], 1e20).argmin() # index of swwn for least positive wn - swwn[3,i,indMinPosFrqER],swfreq[3,i,indMinPosFrqER] = 0.,0. # this sets ER's frequencies to 0. at wavenumber 0. + for i in range( + 0, 3 + ): # loop 0-->2 for the assumed 3 shallow water dispersion curves for ER waves + indMinPosFrqER = np.where( + swwn[3, i, :] >= 0.0, swwn[3, i, :], 1e20 + ).argmin() # index of swwn for least positive wn + swwn[3, i, indMinPosFrqER], swfreq[3, i, indMinPosFrqER] = ( + 0.0, + 0.0, + ) # this sets ER's frequencies to 0. at wavenumber 0. swf = np.where(swfreq == 1e20, np.nan, swfreq) swk = np.where(swwn == 1e20, np.nan, swwn) - # Final data refinement: transpose and trim, set 0 freq to NaN, take log10 for raw, refine metadata - z = var.transpose().sel(frequency=slice(*fb), wavenumber=slice(*wnb)) - z.loc[{'frequency':0}] = np.nan - - if 'spec_raw' in var.name: - - east_power = z.sel(frequency=slice((1./96.),(1./24.)), wavenumber=slice(1,3)).sum() - west_power = z.sel(frequency=slice((1./96.),(1./24.)), wavenumber=slice(-3,-1)).sum() - ew_ratio = east_power / west_power + z = var.transpose().sel(frequency=slice(*fb), wavenumber=slice(*wnb)) + z.loc[{"frequency": 0}] = np.nan + + if "spec_raw" in var.name: + east_power = z.sel( + frequency=slice((1.0 / 96.0), (1.0 / 24.0)), wavenumber=slice(1, 3) + ).sum() + west_power = z.sel( + frequency=slice((1.0 / 96.0), (1.0 / 24.0)), wavenumber=slice(-3, -1) + ).sum() + ew_ratio = east_power / west_power print("\neast_power: %12.5f" % east_power) print("west_power: %12.5f" % west_power) print("ew_ratio: %12.5f\n" % ew_ratio) z = np.log10(z) - if 'spec_background' in var.name: - z = np.log10(z) + if "spec_background" in var.name: + z = np.log10(z) z.attrs["long_name"] = PlotDesc[var.name]["long_name_desc"] - z.attrs["method"] = f"Follows {PlotDesc[var.name]['ref_fig_num']} methods of Wheeler and Kiladis (1999; https://doi.org/10.1175/1520-0469(1999)056<0374:CCEWAO>2.0.CO;2)" - - if 'spec_raw' in var.name: - - z.attrs["ew_ratio_method"] = "Sum of raw (not log10) symmetric spectral power for ZWNs +/- 1-3, periods 24-96 days" + z.attrs[ + "method" + ] = f"Follows {PlotDesc[var.name]['ref_fig_num']} methods of Wheeler and Kiladis (1999; https://doi.org/10.1175/1520-0469(1999)056<0374:CCEWAO>2.0.CO;2)" + + if "spec_raw" in var.name: + z.attrs[ + "ew_ratio_method" + ] = "Sum of raw (not log10) symmetric spectral power for ZWNs +/- 1-3, periods 24-96 days" z.attrs["east_power"] = east_power.values z.attrs["west_power"] = west_power.values - z.attrs["ew_ratio"] = ew_ratio.values + z.attrs["ew_ratio"] = ew_ratio.values -# # TODO Save plotted data z to file as xArray data array -# dataDesc = f"spectral_power_{srcID}_{vari}_{spec_type}_{component}_200101_201412" -# z.to_netcdf(outDataDir + "/"+ dataDesc + ".nc") + # # TODO Save plotted data z to file as xArray data array + # dataDesc = f"spectral_power_{srcID}_{vari}_{spec_type}_{component}_200101_201412" + # z.to_netcdf(outDataDir + "/"+ dataDesc + ".nc") - #fig, ax = plt.subplots() + # fig, ax = plt.subplots() ax = fig.add_axes(PANEL[subplot_num]) - - kmesh0, vmesh0 = np.meshgrid(z['wavenumber'], z['frequency']) - #img = ax.contourf(kmesh0, vmesh0, z, levels=np.linspace(0.2, 3.0, 16), cmap='Spectral_r', extend='both') + + kmesh0, vmesh0 = np.meshgrid(z["wavenumber"], z["frequency"]) + # img = ax.contourf(kmesh0, vmesh0, z, levels=np.linspace(0.2, 3.0, 16), cmap='Spectral_r', extend='both') # for test and ref: if subplot_num < 2: - if 'spec_norm' in var.name: + if "spec_norm" in var.name: contour_level_spec = CONTOUR_LEVS_SPEC_NORM - cmapSpecUse, normSpecUse = create_colormap_clevs(CMAP_SPEC_NORM, CONTOUR_LEVS_SPEC_NORM) + cmapSpecUse, normSpecUse = create_colormap_clevs( + CMAP_SPEC_NORM, CONTOUR_LEVS_SPEC_NORM + ) else: contour_level_spec = CONTOUR_LEVS_SPEC_RAW - cmapSpecUse, normSpecUse = create_colormap_clevs(CMAP_SPEC_RAW, CONTOUR_LEVS_SPEC_RAW) - img = ax.contourf(kmesh0, vmesh0, z, levels=contour_level_spec, cmap=cmapSpecUse, norm=normSpecUse, extend='both') - img2 = ax.contour(kmesh0, vmesh0, z, levels=contour_level_spec, linewidths=1., linestyles='solid', colors='gray', alpha=0.7) - + cmapSpecUse, normSpecUse = create_colormap_clevs( + CMAP_SPEC_RAW, CONTOUR_LEVS_SPEC_RAW + ) + img = ax.contourf( + kmesh0, + vmesh0, + z, + levels=contour_level_spec, + cmap=cmapSpecUse, + norm=normSpecUse, + extend="both", + ) + img2 = ax.contour( + kmesh0, + vmesh0, + z, + levels=contour_level_spec, + linewidths=1.0, + linestyles="solid", + colors="gray", + alpha=0.7, + ) + # for diff ratio if subplot_num == 2: # TODO refine color bar - if 'spec_norm' in var.name: + if "spec_norm" in var.name: contour_level_spec = CONTOUR_LEVS_SPEC_NORM_DIFF else: contour_level_spec = CONTOUR_LEVS_SPEC_RAW_DIFF - cmapSpecUse = 'seismic' - - img = ax.contourf(kmesh0, vmesh0, z, levels=contour_level_spec, cmap=cmapSpecUse, extend='both') - img2 = ax.contour(kmesh0, vmesh0, z, levels=contour_level_spec, linewidths=1., linestyles='solid', colors='gray', alpha=0.7) - - ax.axvline(0, linestyle='dashed', color='dimgray', linewidth=1.0, alpha=0.60) - if( (1./30.) < fb[1] ): - ax.axhline((1./30.), linestyle='dashed', color='dimgray', alpha=0.80) - ax.text(wnb[0]+1,(1./30.)+text_offset,'30 days',color='dimgray', alpha=0.80) - if( (1./6.) < fb[1] ): - ax.axhline((1./6.), linestyle='dashed', color='dimgray', alpha=0.80) - ax.text(wnb[0]+1,(1./6.)+text_offset,'6 days',color='dimgray', alpha=0.80) - if( (1./3.) < fb[1] ): - ax.axhline((1./3.), linestyle='dashed', color='dimgray', alpha=0.80) - ax.text(wnb[0]+1,(1./3.)+text_offset,'3 days',color='dimgray', alpha=0.80) - for ii in range(3,6): - ax.plot(swk[ii, 0,:], swf[ii,0,:], color='black', linewidth=1.5, alpha=0.80) - ax.plot(swk[ii, 1,:], swf[ii,1,:], color='black', linewidth=1.5, alpha=0.80) - ax.plot(swk[ii, 2,:], swf[ii,2,:], color='black', linewidth=1.5, alpha=0.80) + cmapSpecUse = "seismic" + + img = ax.contourf( + kmesh0, + vmesh0, + z, + levels=contour_level_spec, + cmap=cmapSpecUse, + extend="both", + ) + img2 = ax.contour( + kmesh0, + vmesh0, + z, + levels=contour_level_spec, + linewidths=1.0, + linestyles="solid", + colors="gray", + alpha=0.7, + ) + + ax.axvline(0, linestyle="dashed", color="dimgray", linewidth=1.0, alpha=0.60) + if (1.0 / 30.0) < fb[1]: + ax.axhline((1.0 / 30.0), linestyle="dashed", color="dimgray", alpha=0.80) + ax.text( + wnb[0] + 1, + (1.0 / 30.0) + text_offset, + "30 days", + color="dimgray", + alpha=0.80, + ) + if (1.0 / 6.0) < fb[1]: + ax.axhline((1.0 / 6.0), linestyle="dashed", color="dimgray", alpha=0.80) + ax.text( + wnb[0] + 1, (1.0 / 6.0) + text_offset, "6 days", color="dimgray", alpha=0.80 + ) + if (1.0 / 3.0) < fb[1]: + ax.axhline((1.0 / 3.0), linestyle="dashed", color="dimgray", alpha=0.80) + ax.text( + wnb[0] + 1, (1.0 / 3.0) + text_offset, "3 days", color="dimgray", alpha=0.80 + ) + for ii in range(3, 6): + ax.plot(swk[ii, 0, :], swf[ii, 0, :], color="black", linewidth=1.5, alpha=0.80) + ax.plot(swk[ii, 1, :], swf[ii, 1, :], color="black", linewidth=1.5, alpha=0.80) + ax.plot(swk[ii, 2, :], swf[ii, 2, :], color="black", linewidth=1.5, alpha=0.80) ax.set_xlim(wnb) ax.set_ylim(fb) - #ax.set_title(varName + ": Log ${\sum_{15^{\circ}S}^{15^{\circ}N} Power_{SYM}}$") # Version w/ LaTeX - if 'spec_raw' in var.name: - ax.set_title(f"{varName}: Log{{Sum(Power) from 15°S-15°N}}\n") # Version w/o LaTeX - elif 'spec_norm' in var.name: - ax.set_title(f"{varName}: {{Sum(Power) from 15°S-15°N}}/Background\n") # Version w/o LaTeX + # ax.set_title(varName + ": Log ${\sum_{15^{\circ}S}^{15^{\circ}N} Power_{SYM}}$") # Version w/ LaTeX + if "spec_raw" in var.name: + ax.set_title( + f"{varName}: Log{{Sum(Power) from 15°S-15°N}}\n" + ) # Version w/o LaTeX + elif "spec_norm" in var.name: + ax.set_title( + f"{varName}: {{Sum(Power) from 15°S-15°N}}/Background\n" + ) # Version w/o LaTeX else: ax.set_title(f"{varName}: Log{{Smoothed Background Power}}\n") - ax.set_title('model', loc='left') - print('*****',var) - ax.set_title(f"{var.component}", loc='right') + ax.set_title("model", loc="left") + print("*****", var) + ax.set_title(f"{var.component}", loc="right") - if 'spec_norm' in var.name: + if "spec_norm" in var.name: # Shallow water dispersion curve line labels: See https://matplotlib.org/stable/tutorials/text/text_intro.html # n=1 ER dispersion curve labels - text_opt = {'fontsize': 9,'verticalalignment': 'center','horizontalalignment': 'center','clip_on': True,'bbox': {'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}} + text_opt = { + "fontsize": 9, + "verticalalignment": "center", + "horizontalalignment": "center", + "clip_on": True, + "bbox": { + "facecolor": "white", + "edgecolor": "none", + "alpha": 0.7, + "pad": 0.0, + }, + } iwave, ih = 3, 0 - idxClose,valClose = find_nearest(swk[iwave,ih,:], -11.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + idxClose, valClose = find_nearest( + swk[iwave, ih, :], -11.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) iwave, ih = 3, 1 - idxClose,valClose = find_nearest(swk[iwave,ih,:], -9.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + idxClose, valClose = find_nearest( + swk[iwave, ih, :], -9.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) iwave, ih = 3, 2 - idxClose,valClose = find_nearest(swk[iwave,ih,:], -8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) - ax.text(-7.,0.10,'n=1 ER',text_opt) + idxClose, valClose = find_nearest( + swk[iwave, ih, :], -8.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + ax.text(-7.0, 0.10, "n=1 ER", text_opt) # Kelvin dispersion curve labels iwave, ih = 4, 0 - idxClose,valClose = find_nearest(swk[iwave,ih,:], 8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + idxClose, valClose = find_nearest( + swk[iwave, ih, :], 8.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) iwave, ih = 4, 1 - idxClose,valClose = find_nearest(swk[iwave,ih,:], 10.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + idxClose, valClose = find_nearest( + swk[iwave, ih, :], 10.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) iwave, ih = 4, 2 - idxClose,valClose = find_nearest(swk[iwave,ih,:], 14.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) - ax.text(6.,0.13,'Kelvin',text_opt) + idxClose, valClose = find_nearest( + swk[iwave, ih, :], 14.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + ax.text(6.0, 0.13, "Kelvin", text_opt) # IG dispersion curve labels iwave, ih = 5, 0 - idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + idxClose, valClose = find_nearest( + swk[iwave, ih, :], 0.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) iwave, ih = 5, 1 - idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + idxClose, valClose = find_nearest( + swk[iwave, ih, :], 0.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) iwave, ih = 5, 2 - idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) - ax.text(-10.,0.48,'n=1 WIG',text_opt) - ax.text(5.,0.48,'n=1 EIG',text_opt) + idxClose, valClose = find_nearest( + swk[iwave, ih, :], 0.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + ax.text(-10.0, 0.48, "n=1 WIG", text_opt) + ax.text(5.0, 0.48, "n=1 EIG", text_opt) # MJO label - ax.text(6.,0.0333,'MJO',text_opt) - + ax.text(6.0, 0.0333, "MJO", text_opt) plt.ylabel("Frequency (CPD)") plt.xlabel("Zonal wavenumber") fig.text( - PANEL[subplot_num][0] - 0.03 , - PANEL[subplot_num][1] - 0.03, "Westward", fontsize=11) + PANEL[subplot_num][0] - 0.03, + PANEL[subplot_num][1] - 0.03, + "Westward", + fontsize=11, + ) fig.text( - PANEL[subplot_num][0] + 0.35 , - PANEL[subplot_num][1] -0.03 , "Eastward", fontsize=11) + PANEL[subplot_num][0] + 0.35, + PANEL[subplot_num][1] - 0.03, + "Eastward", + fontsize=11, + ) fig.colorbar(img) + + # # Save fig # fig.savefig(outDataDir + "/"+ dataDesc + "_plot.png", bbox_inches='tight', dpi=300) # # print("Plot file created: %s\n" % outDataDir + "/"+ dataDesc + "_plot.png") - - def plot( parameter: CoreParameter, da_test: xr.DataArray, @@ -373,10 +564,9 @@ def plot( ds_diff : xr.DataArray | None The difference between ``ds_test`` and ``ds_ref``. """ - #fig = plt.figure(figsize=parameter.figsize, dpi=parameter.dpi) + # fig = plt.figure(figsize=parameter.figsize, dpi=parameter.dpi) fig = plt.figure(figsize=[8.5, 12.0], dpi=300) - #fig.suptitle(parameter.main_title, x=0.5, y=0.96, fontsize=18) - + # fig.suptitle(parameter.main_title, x=0.5, y=0.96, fontsize=18) _wave_frequency_plot( 0, @@ -387,7 +577,6 @@ def plot( do_zoom=do_zoom, ) - _wave_frequency_plot( 1, da_ref, @@ -397,7 +586,6 @@ def plot( do_zoom=do_zoom, ) - _wave_frequency_plot( 2, da_diff, @@ -407,7 +595,7 @@ def plot( do_zoom=do_zoom, ) -#TODO: save plot:NameError: name 'get_output_dir' is not defined + # TODO: save plot:NameError: name 'get_output_dir' is not defined _save_plot(fig, parameter) plt.close() diff --git a/e3sm_diags/viewer/tropical_subseasonal_viewer.py b/e3sm_diags/viewer/tropical_subseasonal_viewer.py index a38e03b11..d45771d14 100755 --- a/e3sm_diags/viewer/tropical_subseasonal_viewer.py +++ b/e3sm_diags/viewer/tropical_subseasonal_viewer.py @@ -5,7 +5,6 @@ from e3sm_diags.logger import custom_logger - logger = custom_logger(__name__) @@ -36,25 +35,35 @@ def create_viewer(root_dir, parameters): # get_output_dir in e3sm_diags/plot/cartopy/enso_diags_plot.py. # Otherwise, the plot image and the plot HTML file will have URLs # differing in the final directory name. - for spec_type in ["norm_sym", "norm_sym_zoom", "norm_asy", "norm_asy_zoom", "raw_sym", "raw_asy", "background"]: - viewer.add_row(f'{var} {spec_type} ref_name') + for spec_type in [ + "norm_sym", + "norm_sym_zoom", + "norm_asy", + "norm_asy_zoom", + "raw_sym", + "raw_asy", + "background", + ]: + viewer.add_row(f"{var} {spec_type} ref_name") # Adding the description for this var to the current row. # This was obtained and stored in the driver for this plotset. # Appears in the second column of the non-bolded rows. - #viewer.add_col(param.viewer_descr[var]) - viewer.add_col(f'Long description for var') + # viewer.add_col(param.viewer_descr[var]) + viewer.add_col(f"Long description for var") # Link to an html version of the plot png file. # Appears in the third column of the non-bolded rows. ext = param.output_format[0] - relative_path = os.path.join("..", set_name, param.case_id, param.output_file) + relative_path = os.path.join( + "..", set_name, param.case_id, param.output_file + ) image_relative_path = "{}.{}".format(relative_path, ext) - #image_relative_path = f'{var}_{spec_type}_15N-15N.png' + # image_relative_path = f'{var}_{spec_type}_15N-15N.png' viewer.add_col( image_relative_path, is_file=True, title="Plot", - #other_files=formatted_files, - #meta=create_metadata(param), + # other_files=formatted_files, + # meta=create_metadata(param), ) url = viewer.generate_page() From 13c5be84d1f45f1dce0c6b3f9cd255597312d53d Mon Sep 17 00:00:00 2001 From: chengzhuzhang Date: Tue, 27 Feb 2024 14:54:20 -0800 Subject: [PATCH 26/46] fix viewer --- .../driver/tropical_subseasonal_driver.py | 24 +++++++++---------- .../plot/cartopy/tropical_subseasonal_plot.py | 8 +------ .../viewer/tropical_subseasonal_viewer.py | 10 ++++---- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/e3sm_diags/driver/tropical_subseasonal_driver.py b/e3sm_diags/driver/tropical_subseasonal_driver.py index b62ea8ede..3c65507e9 100644 --- a/e3sm_diags/driver/tropical_subseasonal_driver.py +++ b/e3sm_diags/driver/tropical_subseasonal_driver.py @@ -1,21 +1,15 @@ from __future__ import annotations import glob -import json -import os from typing import TYPE_CHECKING # , Optional import numpy as np import xarray as xr -from matplotlib.colors import BoundaryNorm, ListedColormap -from scipy.stats import binned_statistic -import e3sm_diags from e3sm_diags.driver import utils from e3sm_diags.driver.utils import zwf_functions as wf from e3sm_diags.logger import custom_logger from e3sm_diags.plot.cartopy.tropical_subseasonal_plot import plot -from e3sm_diags.viewer.tropical_subseasonal_viewer import create_viewer if TYPE_CHECKING: from e3sm_diags.parameter.tropical_subseasonal_parameter import ( @@ -157,7 +151,7 @@ def calculate_spectrum(path, variable): + str(var.values.min()) ) var.values = ( - data.values * 24.0 + var.values * 24.0 ) # convert mm/hr to mm/d, do not alter metadata (yet) var.attrs["units"] = "mm/d" # adjust metadata to reflect change in units print( @@ -182,7 +176,7 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara :return: Parameters for the run :rtype: CoreParameter """ - run_type = parameter.run_type + # run_type = parameter.run_type # variables = parameter.variables season = "ANN" @@ -197,11 +191,13 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara # print('datapath', parameter.test_data_path, parameter.ref_data_path) for variable in parameter.variables: - test = calculate_spectrum(parameter.test_data_path, variable) + # test = calculate_spectrum(parameter.test_data_path, variable) + # test.to_netcdf(f"{parameter.results_dir}/full_spec_test.nc") + test = xr.open_dataset(f"{parameter.results_dir}/full_spec_test.nc").load() + # ref = calculate_spectrum(parameter.test_data_path, variable) + # ref.to_netcdf(f"{parameter.results_dir}/full_spec_ref.nc") + ref = xr.open_dataset(f"{parameter.results_dir}/full_spec_ref.nc").load() # TODO save to netcdf - test.to_netcdf(f"{parameter.results_dir}/full_spec_test.nc") - ref = calculate_spectrum(parameter.test_data_path, variable) - ref.to_netcdf(f"{parameter.results_dir}/full_spec_ref.nc") parameter.var_id = variable for diff_name in ["raw_sym", "raw_asy", "norm_sym", "norm_asy", "background"]: # Compute percentage difference @@ -213,9 +209,13 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara diff.name = f"spec_{diff_name}" diff.attrs.update(test[f"spec_{diff_name}"].attrs) parameter.spec_type = diff_name + parameter.output_file = f"{parameter.var_id}_{parameter.spec_type}_15N-15S" plot(parameter, test[f"spec_{diff_name}"], ref[f"spec_{diff_name}"], diff) if "norm" in diff_name: parameter.spec_type = f"{diff_name}_zoom" + parameter.output_file = ( + f"{parameter.var_id}_{parameter.spec_type}_15N-15S" + ) plot( parameter, test[f"spec_{diff_name}"], diff --git a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py index 59fc9db38..0ee3c8fc9 100755 --- a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py +++ b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py @@ -4,21 +4,15 @@ from typing import TYPE_CHECKING import matplotlib -import matplotlib.pyplot as plt import numpy as np import xarray as xr from matplotlib.colors import BoundaryNorm, ListedColormap - from zwf import zwf_functions as wf from e3sm_diags.driver.utils.general import get_output_dir from e3sm_diags.logger import custom_logger from e3sm_diags.parameter.core_parameter import CoreParameter -if TYPE_CHECKING: - from e3sm_diags.driver.lat_lon_driver import MetricsDict - - matplotlib.use("Agg") import matplotlib.pyplot as plt # isort:skip # noqa: E402 @@ -184,7 +178,7 @@ def _save_plot(fig: plt.figure, parameter: CoreParameter): get_output_dir(parameter.current_set, parameter), parameter.output_file + "." + f, ) - # fnm = f'{parameter.var_id}_{parameter.spec_type}_15N-15N.png' + # fnm = f'{parameter.var_id}_{parameter.spec_type}_15N-15S.png' plt.savefig(fnm) logger.info(f"Plot saved in: {fnm}") diff --git a/e3sm_diags/viewer/tropical_subseasonal_viewer.py b/e3sm_diags/viewer/tropical_subseasonal_viewer.py index d45771d14..d893f66bd 100755 --- a/e3sm_diags/viewer/tropical_subseasonal_viewer.py +++ b/e3sm_diags/viewer/tropical_subseasonal_viewer.py @@ -1,5 +1,4 @@ import os -from typing import Dict, List from cdp.cdp_viewer import OutputViewer @@ -44,17 +43,20 @@ def create_viewer(root_dir, parameters): "raw_asy", "background", ]: - viewer.add_row(f"{var} {spec_type} ref_name") + viewer.add_row(f"{var} {spec_type} {param.ref_name}") # Adding the description for this var to the current row. # This was obtained and stored in the driver for this plotset. # Appears in the second column of the non-bolded rows. # viewer.add_col(param.viewer_descr[var]) - viewer.add_col(f"Long description for var") + descrip = f"{spec_type} power spectral for {var} 15N-15S" + if "zoom" in spec_type: + descrip = f"{descrip} zoom in for MJO" + viewer.add_col(descrip) # Link to an html version of the plot png file. # Appears in the third column of the non-bolded rows. ext = param.output_format[0] relative_path = os.path.join( - "..", set_name, param.case_id, param.output_file + "..", set_name, param.case_id, f"{var}_{spec_type}_15N-15S" ) image_relative_path = "{}.{}".format(relative_path, ext) # image_relative_path = f'{var}_{spec_type}_15N-15N.png' From 29e9caafd093bf334406a88a9071e73f3756fb02 Mon Sep 17 00:00:00 2001 From: chengzhuzhang Date: Tue, 27 Feb 2024 16:22:58 -0800 Subject: [PATCH 27/46] support model vs model --- .../run_tropical_subseasonal.py | 4 ++- .../tropical_subseasonal_model_vs_model.cfg | 19 ++++++++++++++ .../tropical_subseasonal_model_vs_obs.cfg | 6 ++--- .../driver/tropical_subseasonal_driver.py | 26 ++++++++++++------- .../plot/cartopy/tropical_subseasonal_plot.py | 11 ++++---- 5 files changed, 47 insertions(+), 19 deletions(-) create mode 100644 e3sm_diags/driver/default_diags/tropical_subseasonal_model_vs_model.cfg diff --git a/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py b/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py index 6c2e82e85..1bd1a0367 100644 --- a/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py +++ b/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py @@ -9,9 +9,11 @@ param.test_name = 'E3SMv2' prefix = '/Users/zhang40/Documents/repos/e3sm_diags/auxiliary_tools/tropical_subseasonal_diags/data1' param.results_dir = os.path.join(prefix, 'tropical_variability') +param.run_type = "model_vs_model" +param.ref_name = 'E3SMv2' param.test_start_yr = '2010' param.test_end_yr = '2014' -param.ref_start_yr = '2010' +param.ref_start_yr = '2012' param.ref_end_yr = '2014' runner.sets_to_run = ['tropical_subseasonal'] diff --git a/e3sm_diags/driver/default_diags/tropical_subseasonal_model_vs_model.cfg b/e3sm_diags/driver/default_diags/tropical_subseasonal_model_vs_model.cfg new file mode 100644 index 000000000..b92656baa --- /dev/null +++ b/e3sm_diags/driver/default_diags/tropical_subseasonal_model_vs_model.cfg @@ -0,0 +1,19 @@ +[#] +sets = ["tropical_subseasonal"] +case_id = "wavenumber-frequency" +variables = ["PRECT"] +regions = ["15S15N"] + + +#[#] +#sets = ["tropical_subseasonal"] +#case_id = "wavenumber-frequency" +#variables = ["FLUT"] +#regions = ["15S15N"] +# +# +#[#] +#sets = ["tropical_subseasonal"] +#case_id = "wavenumber-frequency" +#variables = ["U850"] +#regions = ["15S15N"] diff --git a/e3sm_diags/driver/default_diags/tropical_subseasonal_model_vs_obs.cfg b/e3sm_diags/driver/default_diags/tropical_subseasonal_model_vs_obs.cfg index fa377b525..ae9d35eca 100644 --- a/e3sm_diags/driver/default_diags/tropical_subseasonal_model_vs_obs.cfg +++ b/e3sm_diags/driver/default_diags/tropical_subseasonal_model_vs_obs.cfg @@ -3,7 +3,7 @@ sets = ["tropical_subseasonal"] case_id = "wavenumber-frequency" variables = ["PRECT"] ref_name = "IMERG_Daily" -reference_name = "IMERG Daily" +reference_name = "IMERG Daily (2001-2021)" regions = ["15S15N"] @@ -12,7 +12,7 @@ regions = ["15S15N"] #case_id = "wavenumber-frequency" #variables = ["FLUT"] #ref_name = "NOAA-OLR_Daily" -#reference_name = "NOAA OLR Daily" +#reference_name = "NOAA OLR Daily (1979-2021)" #regions = ["15S15N"] # # @@ -21,5 +21,5 @@ regions = ["15S15N"] #case_id = "wavenumber-frequency" #variables = ["U850"] #ref_name = "ERA5_Daily" -#reference_name = "ERA5 Daily" +#reference_name = "ERA5 Daily (1980-2022)" #regions = ["15S15N"] diff --git a/e3sm_diags/driver/tropical_subseasonal_driver.py b/e3sm_diags/driver/tropical_subseasonal_driver.py index 3c65507e9..af98343c1 100644 --- a/e3sm_diags/driver/tropical_subseasonal_driver.py +++ b/e3sm_diags/driver/tropical_subseasonal_driver.py @@ -100,7 +100,7 @@ def wf_analysis(x, **kwargs): return spec_all -def calculate_spectrum(path, variable): +def calculate_spectrum(path, variable, start_year, end_year): latBound = (-15, 15) # latitude bounds for analysis spd = 1 # SAMPLES PER DAY nDayWin = 96 # Wheeler-Kiladis [WK] temporal window length (days) @@ -116,9 +116,9 @@ def calculate_spectrum(path, variable): "dosymmetries": True, "rmvLowFrq": True, } - + var = xr.open_mfdataset(glob.glob(f"{path}/{variable}_*.nc")).sel( - lat=slice(-15, 15) + lat=slice(-15, 15), time=slice(f'{start_year}-01-01', f'{end_year}-12-31') )[variable] # TODO: subset time @@ -176,7 +176,7 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara :return: Parameters for the run :rtype: CoreParameter """ - # run_type = parameter.run_type + run_type = parameter.run_type # variables = parameter.variables season = "ANN" @@ -187,16 +187,23 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara ref_data = utils.dataset.Dataset(parameter, ref=True) parameter.ref_name_yrs = utils.general.get_name_and_yrs(parameter, ref_data, season) - print(parameter.ref_name_yrs, parameter.test_name_yrs, parameter.test_data_path) - # print('datapath', parameter.test_data_path, parameter.ref_data_path) for variable in parameter.variables: - # test = calculate_spectrum(parameter.test_data_path, variable) + test = calculate_spectrum(parameter.test_data_path, variable, parameter.test_start_yr, parameter.test_end_yr) # test.to_netcdf(f"{parameter.results_dir}/full_spec_test.nc") - test = xr.open_dataset(f"{parameter.results_dir}/full_spec_test.nc").load() + if run_type == "model_vs_model": + ref = calculate_spectrum(parameter.reference_data_path, variable, parameter.ref_start_yr, parameter.ref_end_yr) + elif run_type == "model_vs_obs": + if parameter.ref_start_yr == "": + parameter.ref_name_yrs = parameter.reference_name + # read precalculated data. + else: + ref_data_path = f"{parameter.ref_data_path}/time_series/{parameter.ref_name}" + ref = calculate_spectrum(ref_data_path, variable, parameter.ref_start_yr, parameter.ref_end_yr) # ref = calculate_spectrum(parameter.test_data_path, variable) + #test = xr.open_dataset(f"{parameter.results_dir}/full_spec_test.nc").load() # ref.to_netcdf(f"{parameter.results_dir}/full_spec_ref.nc") - ref = xr.open_dataset(f"{parameter.results_dir}/full_spec_ref.nc").load() + #ref = xr.open_dataset(f"{parameter.results_dir}/full_spec_ref.nc").load() # TODO save to netcdf parameter.var_id = variable for diff_name in ["raw_sym", "raw_asy", "norm_sym", "norm_asy", "background"]: @@ -210,6 +217,7 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara diff.attrs.update(test[f"spec_{diff_name}"].attrs) parameter.spec_type = diff_name parameter.output_file = f"{parameter.var_id}_{parameter.spec_type}_15N-15S" + parameter.diff_title = "percent difference" plot(parameter, test[f"spec_{diff_name}"], ref[f"spec_{diff_name}"], diff) if "norm" in diff_name: parameter.spec_type = f"{diff_name}_zoom" diff --git a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py index 0ee3c8fc9..e3d5c5834 100755 --- a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py +++ b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py @@ -220,7 +220,7 @@ def _wave_frequency_plot( var: xr.DataArray, fig: plt.figure, parameter: CoreParameter, - title: Tuple[str | None, str, str], + title: str, do_zoom: Boolean = False, ): """Create wave frequency plot. @@ -439,8 +439,7 @@ def _wave_frequency_plot( else: ax.set_title(f"{varName}: Log{{Smoothed Background Power}}\n") - ax.set_title("model", loc="left") - print("*****", var) + ax.set_title(title, loc="left") ax.set_title(f"{var.component}", loc="right") if "spec_norm" in var.name: @@ -567,7 +566,7 @@ def plot( da_test, fig, parameter, - title=(parameter.test_name_yrs, parameter.test_title), # type: ignore + title=parameter.test_name_yrs, # type: ignore do_zoom=do_zoom, ) @@ -576,7 +575,7 @@ def plot( da_ref, fig, parameter, - title=(parameter.ref_name_yrs, parameter.reference_title), # type: ignore + title=parameter.ref_name_yrs, # type: ignore do_zoom=do_zoom, ) @@ -585,7 +584,7 @@ def plot( da_diff, fig, parameter, - title=(None, parameter.diff_title), # type: ignore + title= parameter.diff_title, # type: ignore do_zoom=do_zoom, ) From 2f6cec2a379b51fe8f7f89eb62a3f5e34a99ca87 Mon Sep 17 00:00:00 2001 From: chengzhuzhang Date: Tue, 27 Feb 2024 16:30:16 -0800 Subject: [PATCH 28/46] fix format --- .../driver/tropical_subseasonal_driver.py | 35 ++++++++++++++----- .../plot/cartopy/tropical_subseasonal_plot.py | 2 +- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/e3sm_diags/driver/tropical_subseasonal_driver.py b/e3sm_diags/driver/tropical_subseasonal_driver.py index af98343c1..97395f4dc 100644 --- a/e3sm_diags/driver/tropical_subseasonal_driver.py +++ b/e3sm_diags/driver/tropical_subseasonal_driver.py @@ -116,9 +116,9 @@ def calculate_spectrum(path, variable, start_year, end_year): "dosymmetries": True, "rmvLowFrq": True, } - + var = xr.open_mfdataset(glob.glob(f"{path}/{variable}_*.nc")).sel( - lat=slice(-15, 15), time=slice(f'{start_year}-01-01', f'{end_year}-12-31') + lat=slice(-15, 15), time=slice(f"{start_year}-01-01", f"{end_year}-12-31") )[variable] # TODO: subset time @@ -189,21 +189,38 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara parameter.ref_name_yrs = utils.general.get_name_and_yrs(parameter, ref_data, season) for variable in parameter.variables: - test = calculate_spectrum(parameter.test_data_path, variable, parameter.test_start_yr, parameter.test_end_yr) + test = calculate_spectrum( + parameter.test_data_path, + variable, + parameter.test_start_yr, + parameter.test_end_yr, + ) # test.to_netcdf(f"{parameter.results_dir}/full_spec_test.nc") if run_type == "model_vs_model": - ref = calculate_spectrum(parameter.reference_data_path, variable, parameter.ref_start_yr, parameter.ref_end_yr) + ref = calculate_spectrum( + parameter.reference_data_path, + variable, + parameter.ref_start_yr, + parameter.ref_end_yr, + ) elif run_type == "model_vs_obs": if parameter.ref_start_yr == "": - parameter.ref_name_yrs = parameter.reference_name + parameter.ref_name_yrs = parameter.reference_name # read precalculated data. else: - ref_data_path = f"{parameter.ref_data_path}/time_series/{parameter.ref_name}" - ref = calculate_spectrum(ref_data_path, variable, parameter.ref_start_yr, parameter.ref_end_yr) + ref_data_path = ( + f"{parameter.ref_data_path}/time_series/{parameter.ref_name}" + ) + ref = calculate_spectrum( + ref_data_path, + variable, + parameter.ref_start_yr, + parameter.ref_end_yr, + ) # ref = calculate_spectrum(parameter.test_data_path, variable) - #test = xr.open_dataset(f"{parameter.results_dir}/full_spec_test.nc").load() + # test = xr.open_dataset(f"{parameter.results_dir}/full_spec_test.nc").load() # ref.to_netcdf(f"{parameter.results_dir}/full_spec_ref.nc") - #ref = xr.open_dataset(f"{parameter.results_dir}/full_spec_ref.nc").load() + # ref = xr.open_dataset(f"{parameter.results_dir}/full_spec_ref.nc").load() # TODO save to netcdf parameter.var_id = variable for diff_name in ["raw_sym", "raw_asy", "norm_sym", "norm_asy", "background"]: diff --git a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py index e3d5c5834..56304f061 100755 --- a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py +++ b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py @@ -584,7 +584,7 @@ def plot( da_diff, fig, parameter, - title= parameter.diff_title, # type: ignore + title=parameter.diff_title, # type: ignore do_zoom=do_zoom, ) From 8b851e2aad6ec5c9b29216e8f391bd0911785969 Mon Sep 17 00:00:00 2001 From: ChengzhuZhang Date: Wed, 6 Mar 2024 17:05:00 -0800 Subject: [PATCH 29/46] Fix for OLR --- .../run_tropical_subseasonal.py | 19 ++++++---- .../tropical_diags.cfg | 28 ++++++++++++++ .../tropical_subseasonal_model_vs_obs.cfg | 37 ++++++++++--------- .../driver/tropical_subseasonal_driver.py | 30 ++++----------- e3sm_diags/driver/utils/zwf_functions.py | 2 +- .../plot/cartopy/tropical_subseasonal_plot.py | 37 +++++++++++++++---- .../viewer/tropical_subseasonal_viewer.py | 3 +- 7 files changed, 100 insertions(+), 56 deletions(-) create mode 100644 auxiliary_tools/tropical_subseasonal_diags/tropical_diags.cfg diff --git a/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py b/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py index 1bd1a0367..148099a76 100644 --- a/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py +++ b/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py @@ -4,17 +4,20 @@ param = TropicalSubseasonalParameter() -param.reference_data_path = '/Users/zhang40/Documents/e3sm_diags_data/e3sm_diags_test_data/E3SM_v2_daily' -param.test_data_path = '/Users/zhang40/Documents/e3sm_diags_data/e3sm_diags_test_data/E3SM_v2_daily' +param.reference_data_path = '/global/cfs/cdirs/e3sm/e3sm_diags/obs_for_e3sm_diags/time-series' +#param.reference_data_path = '/global/cfs/cdirs/e3sm/forsyth/E3SMv2/v2.LR.historical_0201/post/atm/180x360_aave/ts/daily/5yr' +param.test_data_path = '/global/cfs/cdirs/e3sm/forsyth/E3SMv2/v2.LR.historical_0201/post/atm/180x360_aave/ts/daily/5yr' +param.test_data_path = '/global/cfs/cdirs/e3sm/chengzhu/e3sm_diags_zppy_test_complete_run_output/v2.LR.historical_0101_20240130/post/atm/180x360_aave/ts/daily/5yr' param.test_name = 'E3SMv2' prefix = '/Users/zhang40/Documents/repos/e3sm_diags/auxiliary_tools/tropical_subseasonal_diags/data1' -param.results_dir = os.path.join(prefix, 'tropical_variability') -param.run_type = "model_vs_model" -param.ref_name = 'E3SMv2' +prefix = '/global/cfs/cdirs/e3sm/www/chengzhu/tests/tropical_diags' +param.results_dir = os.path.join(prefix, 'tropical_variability_model_obs') +#param.run_type = "model_vs_model" +#param.ref_name = 'E3SMv2' param.test_start_yr = '2010' -param.test_end_yr = '2014' -param.ref_start_yr = '2012' -param.ref_end_yr = '2014' +param.test_end_yr = '2010' +param.ref_start_yr = '2010' +param.ref_end_yr = '2010' runner.sets_to_run = ['tropical_subseasonal'] runner.run_diags([param]) diff --git a/auxiliary_tools/tropical_subseasonal_diags/tropical_diags.cfg b/auxiliary_tools/tropical_subseasonal_diags/tropical_diags.cfg new file mode 100644 index 000000000..d7eee5277 --- /dev/null +++ b/auxiliary_tools/tropical_subseasonal_diags/tropical_diags.cfg @@ -0,0 +1,28 @@ +[#] +sets = ["tropical_subseasonal"] +case_id = "wavenumber-frequency" +variables = ["PRECT"] +ref_name = "IMERG_Daily" +reference_name = "IMERG Daily" +# (2001-2021)" +regions = ["15S15N"] +# + +#[#] +#sets = ["tropical_subseasonal"] +#case_id = "wavenumber-frequency" +#variables = ["FLUT"] +#ref_name = "NOAA-OLR_Daily" +#reference_name = "NOAA OLR Daily" +## (1979-2021)" +#regions = ["15S15N"] +## +## +#[#] +#sets = ["tropical_subseasonal"] +#case_id = "wavenumber-frequency" +#variables = ["U850"] +#ref_name = "ERA5_Daily" +#reference_name = "ERA5 Daily" +## (1980-2022)" +#regions = ["15S15N"] diff --git a/e3sm_diags/driver/default_diags/tropical_subseasonal_model_vs_obs.cfg b/e3sm_diags/driver/default_diags/tropical_subseasonal_model_vs_obs.cfg index ae9d35eca..d81bdd23e 100644 --- a/e3sm_diags/driver/default_diags/tropical_subseasonal_model_vs_obs.cfg +++ b/e3sm_diags/driver/default_diags/tropical_subseasonal_model_vs_obs.cfg @@ -3,23 +3,26 @@ sets = ["tropical_subseasonal"] case_id = "wavenumber-frequency" variables = ["PRECT"] ref_name = "IMERG_Daily" -reference_name = "IMERG Daily (2001-2021)" +reference_name = "IMERG Daily" +# (2001-2021)" regions = ["15S15N"] -#[#] -#sets = ["tropical_subseasonal"] -#case_id = "wavenumber-frequency" -#variables = ["FLUT"] -#ref_name = "NOAA-OLR_Daily" -#reference_name = "NOAA OLR Daily (1979-2021)" -#regions = ["15S15N"] -# -# -#[#] -#sets = ["tropical_subseasonal"] -#case_id = "wavenumber-frequency" -#variables = ["U850"] -#ref_name = "ERA5_Daily" -#reference_name = "ERA5 Daily (1980-2022)" -#regions = ["15S15N"] +[#] +sets = ["tropical_subseasonal"] +case_id = "wavenumber-frequency" +variables = ["FLUT"] +ref_name = "NOAA-OLR_Daily" +reference_name = "NOAA OLR Daily" +# (1986-2021)" +regions = ["15S15N"] + + +[#] +sets = ["tropical_subseasonal"] +case_id = "wavenumber-frequency" +variables = ["U850"] +ref_name = "ERA5_Daily" +reference_name = "ERA5 Daily" +# (1980-2022)" +regions = ["15S15N"] diff --git a/e3sm_diags/driver/tropical_subseasonal_driver.py b/e3sm_diags/driver/tropical_subseasonal_driver.py index 97395f4dc..f889fefc0 100644 --- a/e3sm_diags/driver/tropical_subseasonal_driver.py +++ b/e3sm_diags/driver/tropical_subseasonal_driver.py @@ -142,24 +142,6 @@ def calculate_spectrum(path, variable, start_year, end_year): + " " + str(var.values.min()) ) - if var.name == "precipAvg": - if var.attrs["units"] == "mm/hr": - print( - "\nBEFORE unit conversion: Max/min of data: " - + str(var.values.max()) - + " " - + str(var.values.min()) - ) - var.values = ( - var.values * 24.0 - ) # convert mm/hr to mm/d, do not alter metadata (yet) - var.attrs["units"] = "mm/d" # adjust metadata to reflect change in units - print( - "\nAFTER unit conversion: Max/min of data: " - + str(var.values.max()) - + " " - + str(var.values.min()) - ) # Wavenumber Frequency Analysis spec_all = wf_analysis(var, **opt) @@ -195,7 +177,7 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara parameter.test_start_yr, parameter.test_end_yr, ) - # test.to_netcdf(f"{parameter.results_dir}/full_spec_test.nc") + test.to_netcdf(f"{parameter.results_dir}/full_spec_test.nc") if run_type == "model_vs_model": ref = calculate_spectrum( parameter.reference_data_path, @@ -208,15 +190,17 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara parameter.ref_name_yrs = parameter.reference_name # read precalculated data. else: - ref_data_path = ( - f"{parameter.ref_data_path}/time_series/{parameter.ref_name}" - ) + ref_data_path = f"{parameter.reference_data_path}/{parameter.ref_name}" + # parameter.ref_name_yrs = f"{parameter.ref_name}({parameter.test_start_yr}-{parameter.test_start_yr})" ref = calculate_spectrum( ref_data_path, variable, parameter.ref_start_yr, parameter.ref_end_yr, ) + ref.to_netcdf( + f"{parameter.results_dir}/full_spec_ref_{parameter.ref_name}.nc" + ) # ref = calculate_spectrum(parameter.test_data_path, variable) # test = xr.open_dataset(f"{parameter.results_dir}/full_spec_test.nc").load() # ref.to_netcdf(f"{parameter.results_dir}/full_spec_ref.nc") @@ -230,6 +214,8 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara * (test[f"spec_{diff_name}"] - ref[f"spec_{diff_name}"]) / ref[f"spec_{diff_name}"] ) + print("diff_name****888") + print(diff) diff.name = f"spec_{diff_name}" diff.attrs.update(test[f"spec_{diff_name}"].attrs) parameter.spec_type = diff_name diff --git a/e3sm_diags/driver/utils/zwf_functions.py b/e3sm_diags/driver/utils/zwf_functions.py index cef13690b..1c86a3c4e 100755 --- a/e3sm_diags/driver/utils/zwf_functions.py +++ b/e3sm_diags/driver/utils/zwf_functions.py @@ -462,7 +462,7 @@ def spacetime_power( # Using scipy.signal.detrend will remove the mean as well, but we will add the time # mean back into the detrended data to be consistent with the approach used in the # NCL version (https://www.ncl.ucar.edu/Document/Functions/Diagnostics/wkSpaceTime.shtml): - xmean = data.mean(dim="time") + xmean = data.mean(dim="time").load() xdetr = detrend(data.values, axis=0, type="linear") xdetr = xr.DataArray(xdetr, dims=data.dims, coords=data.coords) xdetr += xmean # put the mean back in diff --git a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py index 56304f061..24a5d779b 100755 --- a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py +++ b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py @@ -51,6 +51,23 @@ 0.2, ) +CONTOUR_LEVS_SPEC_RAW_FLUT = ( + -0.4, + -0.2, + 0.0, + 0.2, + 0.4, + 0.6, + 0.8, + 1.0, + 1.2, + 1.4, + 1.6, + 1.8, + 2.2, + 2.4, +) + CMAP_SPEC_RAW = [ "white", "paleturquoise", @@ -240,9 +257,7 @@ def _wave_frequency_plot( ( years, title, units). do_zoom: Boolean """ - # TODO link var_id varName = parameter.var_id - # varName = 'PRECT' PlotDesc = {} PlotDesc["spec_raw_sym"] = { "long_name_desc": f"{varName}: log-base10 of lightly smoothed spectral power of component symmetric about equator", @@ -331,9 +346,14 @@ def _wave_frequency_plot( z.attrs["west_power"] = west_power.values z.attrs["ew_ratio"] = ew_ratio.values - # # TODO Save plotted data z to file as xArray data array - # dataDesc = f"spectral_power_{srcID}_{vari}_{spec_type}_{component}_200101_201412" - # z.to_netcdf(outDataDir + "/"+ dataDesc + ".nc") + # # TODO Save plotted data z to file as xArray data array + # dataDesc = f"spectral_power_{srcID}_{vari}_{spec_type}_{component}_200101_201412" + # z.to_netcdf(outDataDir + "/"+ dataDesc + ".nc") + fnm = os.path.join( + get_output_dir(parameter.current_set, parameter), + parameter.output_file + ".nc", + ) + z.to_netcdf(fnm) # fig, ax = plt.subplots() ax = fig.add_axes(PANEL[subplot_num]) @@ -349,9 +369,12 @@ def _wave_frequency_plot( CMAP_SPEC_NORM, CONTOUR_LEVS_SPEC_NORM ) else: - contour_level_spec = CONTOUR_LEVS_SPEC_RAW + if varName == "FLUT": + contour_level_spec = CONTOUR_LEVS_SPEC_RAW_FLUT + else: + contour_level_spec = CONTOUR_LEVS_SPEC_RAW cmapSpecUse, normSpecUse = create_colormap_clevs( - CMAP_SPEC_RAW, CONTOUR_LEVS_SPEC_RAW + CMAP_SPEC_RAW, contour_level_spec ) img = ax.contourf( kmesh0, diff --git a/e3sm_diags/viewer/tropical_subseasonal_viewer.py b/e3sm_diags/viewer/tropical_subseasonal_viewer.py index d893f66bd..b3b0889b6 100755 --- a/e3sm_diags/viewer/tropical_subseasonal_viewer.py +++ b/e3sm_diags/viewer/tropical_subseasonal_viewer.py @@ -24,7 +24,8 @@ def create_viewer(root_dir, parameters): cols = ["Description", "Plot"] viewer.add_page(display_name, short_name=set_name, columns=cols) for param in parameters: - for plot_type in ["Wavenumber Frequency", "Lag correlation"]: + # for plot_type in ["Wavenumber Frequency", "Lag correlation"]: + for plot_type in ["Wavenumber Frequency"]: # Appears in the first column of the bolded rows. viewer.add_group(plot_type.capitalize()) From 2bdbd08def839e39fb252e21060dc0f000647120 Mon Sep 17 00:00:00 2001 From: ChengzhuZhang Date: Thu, 7 Mar 2024 20:19:17 -0800 Subject: [PATCH 30/46] fix diff ratio --- .../run_tropical_subseasonal.py | 3 +- .../tropical_diags.cfg | 20 ++-- .../driver/tropical_subseasonal_driver.py | 9 +- e3sm_diags/parser/__init__.py | 2 +- .../plot/cartopy/tropical_subseasonal_plot.py | 12 +- .../viewer/tropical_subseasonal_viewer.py | 104 +++++++++++------- 6 files changed, 82 insertions(+), 68 deletions(-) diff --git a/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py b/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py index 148099a76..5d4aa552f 100644 --- a/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py +++ b/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py @@ -10,7 +10,7 @@ param.test_data_path = '/global/cfs/cdirs/e3sm/chengzhu/e3sm_diags_zppy_test_complete_run_output/v2.LR.historical_0101_20240130/post/atm/180x360_aave/ts/daily/5yr' param.test_name = 'E3SMv2' prefix = '/Users/zhang40/Documents/repos/e3sm_diags/auxiliary_tools/tropical_subseasonal_diags/data1' -prefix = '/global/cfs/cdirs/e3sm/www/chengzhu/tests/tropical_diags' +prefix = '/global/cfs/cdirs/e3sm/www/chengzhu/tests/tropical_diags_try2' param.results_dir = os.path.join(prefix, 'tropical_variability_model_obs') #param.run_type = "model_vs_model" #param.ref_name = 'E3SMv2' @@ -18,6 +18,7 @@ param.test_end_yr = '2010' param.ref_start_yr = '2010' param.ref_end_yr = '2010' +param.save_netcdf = True runner.sets_to_run = ['tropical_subseasonal'] runner.run_diags([param]) diff --git a/auxiliary_tools/tropical_subseasonal_diags/tropical_diags.cfg b/auxiliary_tools/tropical_subseasonal_diags/tropical_diags.cfg index d7eee5277..46fb6d650 100644 --- a/auxiliary_tools/tropical_subseasonal_diags/tropical_diags.cfg +++ b/auxiliary_tools/tropical_subseasonal_diags/tropical_diags.cfg @@ -8,16 +8,16 @@ reference_name = "IMERG Daily" regions = ["15S15N"] # -#[#] -#sets = ["tropical_subseasonal"] -#case_id = "wavenumber-frequency" -#variables = ["FLUT"] -#ref_name = "NOAA-OLR_Daily" -#reference_name = "NOAA OLR Daily" -## (1979-2021)" -#regions = ["15S15N"] -## -## +[#] +sets = ["tropical_subseasonal"] +case_id = "wavenumber-frequency" +variables = ["FLUT"] +ref_name = "NOAA-OLR_Daily" +reference_name = "NOAA OLR Daily" +# (1979-2021)" +regions = ["15S15N"] +# +# #[#] #sets = ["tropical_subseasonal"] #case_id = "wavenumber-frequency" diff --git a/e3sm_diags/driver/tropical_subseasonal_driver.py b/e3sm_diags/driver/tropical_subseasonal_driver.py index f889fefc0..ca144ade7 100644 --- a/e3sm_diags/driver/tropical_subseasonal_driver.py +++ b/e3sm_diags/driver/tropical_subseasonal_driver.py @@ -201,21 +201,16 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara ref.to_netcdf( f"{parameter.results_dir}/full_spec_ref_{parameter.ref_name}.nc" ) - # ref = calculate_spectrum(parameter.test_data_path, variable) # test = xr.open_dataset(f"{parameter.results_dir}/full_spec_test.nc").load() - # ref.to_netcdf(f"{parameter.results_dir}/full_spec_ref.nc") - # ref = xr.open_dataset(f"{parameter.results_dir}/full_spec_ref.nc").load() - # TODO save to netcdf + # ref = xr.open_dataset(f"{parameter.results_dir}/full_spec_ref_{parameter.ref_name}.nc").load() + parameter.var_id = variable for diff_name in ["raw_sym", "raw_asy", "norm_sym", "norm_asy", "background"]: - # Compute percentage difference diff = ( 100 * (test[f"spec_{diff_name}"] - ref[f"spec_{diff_name}"]) / ref[f"spec_{diff_name}"] ) - print("diff_name****888") - print(diff) diff.name = f"spec_{diff_name}" diff.attrs.update(test[f"spec_{diff_name}"].attrs) parameter.spec_type = diff_name diff --git a/e3sm_diags/parser/__init__.py b/e3sm_diags/parser/__init__.py index 0db03970b..781531d36 100644 --- a/e3sm_diags/parser/__init__.py +++ b/e3sm_diags/parser/__init__.py @@ -35,5 +35,5 @@ "aerosol_aeronet": CoreParser, "aerosol_budget": CoreParser, "mp_partition": MPpartitionParser, - "tropical_subseasonal_parser": TropicalSubseasonalParser, + "tropical_subseasonal": TropicalSubseasonalParser, } diff --git a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py index 24a5d779b..a2ae12c0b 100755 --- a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py +++ b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py @@ -316,7 +316,7 @@ def _wave_frequency_plot( z = var.transpose().sel(frequency=slice(*fb), wavenumber=slice(*wnb)) z.loc[{"frequency": 0}] = np.nan - if "spec_raw" in var.name: + if "spec_raw" in var.name and subplot_num < 2: east_power = z.sel( frequency=slice((1.0 / 96.0), (1.0 / 24.0)), wavenumber=slice(1, 3) ).sum() @@ -330,7 +330,7 @@ def _wave_frequency_plot( z = np.log10(z) - if "spec_background" in var.name: + if "spec_background" in var.name and subplot_num < 2: z = np.log10(z) z.attrs["long_name"] = PlotDesc[var.name]["long_name_desc"] @@ -338,7 +338,7 @@ def _wave_frequency_plot( "method" ] = f"Follows {PlotDesc[var.name]['ref_fig_num']} methods of Wheeler and Kiladis (1999; https://doi.org/10.1175/1520-0469(1999)056<0374:CCEWAO>2.0.CO;2)" - if "spec_raw" in var.name: + if "spec_raw" in var.name and subplot_num < 2: z.attrs[ "ew_ratio_method" ] = "Sum of raw (not log10) symmetric spectral power for ZWNs +/- 1-3, periods 24-96 days" @@ -346,12 +346,10 @@ def _wave_frequency_plot( z.attrs["west_power"] = west_power.values z.attrs["ew_ratio"] = ew_ratio.values - # # TODO Save plotted data z to file as xArray data array - # dataDesc = f"spectral_power_{srcID}_{vari}_{spec_type}_{component}_200101_201412" - # z.to_netcdf(outDataDir + "/"+ dataDesc + ".nc") + if parameter.save_netcdf: fnm = os.path.join( get_output_dir(parameter.current_set, parameter), - parameter.output_file + ".nc", + parameter.output_file + f"_{subplot_num}.nc", ) z.to_netcdf(fnm) diff --git a/e3sm_diags/viewer/tropical_subseasonal_viewer.py b/e3sm_diags/viewer/tropical_subseasonal_viewer.py index b3b0889b6..bde8632a9 100755 --- a/e3sm_diags/viewer/tropical_subseasonal_viewer.py +++ b/e3sm_diags/viewer/tropical_subseasonal_viewer.py @@ -4,6 +4,9 @@ from e3sm_diags.logger import custom_logger +from .default_viewer import create_metadata +from .utils import add_header, h1_to_h3 + logger = custom_logger(__name__) @@ -23,52 +26,69 @@ def create_viewer(root_dir, parameters): # Appears in the second and third columns of the bolded rows. cols = ["Description", "Plot"] viewer.add_page(display_name, short_name=set_name, columns=cols) + count = 0 for param in parameters: # for plot_type in ["Wavenumber Frequency", "Lag correlation"]: - for plot_type in ["Wavenumber Frequency"]: - # Appears in the first column of the bolded rows. - viewer.add_group(plot_type.capitalize()) + count = count + 1 + print("count, count", count) + # for plot_type in ["Wavenumber Frequency"]: + # Appears in the first column of the bolded rows. + plot_type = param.case_id + viewer.add_group(plot_type.capitalize()) + print(param.variables) + + for var in param.variables: + print("var,var", var) + # Appears in the first column of the non-bolded rows. + # This should use param.case_id to match the output_dir determined by + # get_output_dir in e3sm_diags/plot/cartopy/enso_diags_plot.py. + # Otherwise, the plot image and the plot HTML file will have URLs + # differing in the final directory name. + formatted_files = [] + for spec_type in [ + "norm_sym", + "norm_sym_zoom", + "norm_asy", + "norm_asy_zoom", + "raw_sym", + "raw_asy", + "background", + ]: + viewer.add_row(f"{var} {spec_type} {param.ref_name}") + # Adding the description for this var to the current row. + # This was obtained and stored in the driver for this plotset. + # Appears in the second column of the non-bolded rows. + # viewer.add_col(param.viewer_descr[var]) + descrip = f"{spec_type} power spectral for {var} 15N-15S" + if "zoom" in spec_type: + descrip = f"{descrip} zoom in for MJO" + viewer.add_col(descrip) + # Link to an html version of the plot png file. + # Appears in the third column of the non-bolded rows. + ext = param.output_format[0] + relative_path = os.path.join( + "..", set_name, param.case_id, f"{var}_{spec_type}_15N-15S" + ) + if param.save_netcdf: + nc_files = [] + for ifile in range(3): + nc_file_path = "{}_{}.nc".format(relative_path, ifile) + nc_files.append(nc_file_path) + formatted_files = [{"url": f, "title": f} for f in nc_files] + image_relative_path = "{}.{}".format(relative_path, ext) - for var in param.variables: - # Appears in the first column of the non-bolded rows. - # This should use param.case_id to match the output_dir determined by - # get_output_dir in e3sm_diags/plot/cartopy/enso_diags_plot.py. - # Otherwise, the plot image and the plot HTML file will have URLs - # differing in the final directory name. - for spec_type in [ - "norm_sym", - "norm_sym_zoom", - "norm_asy", - "norm_asy_zoom", - "raw_sym", - "raw_asy", - "background", - ]: - viewer.add_row(f"{var} {spec_type} {param.ref_name}") - # Adding the description for this var to the current row. - # This was obtained and stored in the driver for this plotset. - # Appears in the second column of the non-bolded rows. - # viewer.add_col(param.viewer_descr[var]) - descrip = f"{spec_type} power spectral for {var} 15N-15S" - if "zoom" in spec_type: - descrip = f"{descrip} zoom in for MJO" - viewer.add_col(descrip) - # Link to an html version of the plot png file. - # Appears in the third column of the non-bolded rows. - ext = param.output_format[0] - relative_path = os.path.join( - "..", set_name, param.case_id, f"{var}_{spec_type}_15N-15S" - ) - image_relative_path = "{}.{}".format(relative_path, ext) - # image_relative_path = f'{var}_{spec_type}_15N-15N.png' - viewer.add_col( - image_relative_path, - is_file=True, - title="Plot", - # other_files=formatted_files, - # meta=create_metadata(param), - ) + # image_relative_path = f'{var}_{spec_type}_15N-15N.png' + print("image_relative_path", image_relative_path) + viewer.add_col( + image_relative_path, + is_file=True, + title="Plot", + other_files=formatted_files, + meta=create_metadata(param), + ) url = viewer.generate_page() + add_header(root_dir, os.path.join(root_dir, url), parameters) + h1_to_h3(os.path.join(root_dir, url)) return display_name, url From c693e8a01b9a5174bf0f80ec7a4e4cc114a30900 Mon Sep 17 00:00:00 2001 From: ChengzhuZhang Date: Fri, 8 Mar 2024 09:10:57 -0800 Subject: [PATCH 31/46] fix viewer --- e3sm_diags/driver/tropical_subseasonal_driver.py | 5 +++-- e3sm_diags/viewer/tropical_subseasonal_viewer.py | 6 +----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/e3sm_diags/driver/tropical_subseasonal_driver.py b/e3sm_diags/driver/tropical_subseasonal_driver.py index ca144ade7..6cd445ed0 100644 --- a/e3sm_diags/driver/tropical_subseasonal_driver.py +++ b/e3sm_diags/driver/tropical_subseasonal_driver.py @@ -186,6 +186,7 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara parameter.ref_end_yr, ) elif run_type == "model_vs_obs": + # TODO use pre-calculated spectral power if parameter.ref_start_yr == "": parameter.ref_name_yrs = parameter.reference_name # read precalculated data. @@ -201,8 +202,8 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara ref.to_netcdf( f"{parameter.results_dir}/full_spec_ref_{parameter.ref_name}.nc" ) - # test = xr.open_dataset(f"{parameter.results_dir}/full_spec_test.nc").load() - # ref = xr.open_dataset(f"{parameter.results_dir}/full_spec_ref_{parameter.ref_name}.nc").load() + # test = xr.open_dataset(f"{parameter.results_dir}/full_spec_test.nc").load() + # ref = xr.open_dataset(f"{parameter.results_dir}/full_spec_ref_{parameter.ref_name}.nc").load() parameter.var_id = variable for diff_name in ["raw_sym", "raw_asy", "norm_sym", "norm_asy", "background"]: diff --git a/e3sm_diags/viewer/tropical_subseasonal_viewer.py b/e3sm_diags/viewer/tropical_subseasonal_viewer.py index bde8632a9..a7fb12356 100755 --- a/e3sm_diags/viewer/tropical_subseasonal_viewer.py +++ b/e3sm_diags/viewer/tropical_subseasonal_viewer.py @@ -26,18 +26,14 @@ def create_viewer(root_dir, parameters): # Appears in the second and third columns of the bolded rows. cols = ["Description", "Plot"] viewer.add_page(display_name, short_name=set_name, columns=cols) - count = 0 for param in parameters: # for plot_type in ["Wavenumber Frequency", "Lag correlation"]: - count = count + 1 - print("count, count", count) # for plot_type in ["Wavenumber Frequency"]: # Appears in the first column of the bolded rows. plot_type = param.case_id - viewer.add_group(plot_type.capitalize()) - print(param.variables) for var in param.variables: + viewer.add_group(f"{plot_type.capitalize()} - {var}") print("var,var", var) # Appears in the first column of the non-bolded rows. # This should use param.case_id to match the output_dir determined by From 166494159207b2c16faa8f0238946c963bc08a7b Mon Sep 17 00:00:00 2001 From: ChengzhuZhang Date: Mon, 11 Mar 2024 17:08:38 -0700 Subject: [PATCH 32/46] fix pre-commit problems; clean up --- .pre-commit-config.yaml | 2 +- .../run_tropical_subseasonal.py | 4 +-- .../driver/tropical_subseasonal_driver.py | 34 +++++++++---------- e3sm_diags/driver/utils/zwf_functions.py | 7 ++-- .../tropical_subseasonal_parameter.py | 1 + .../plot/cartopy/tropical_subseasonal_plot.py | 26 +++++++------- .../viewer/tropical_subseasonal_viewer.py | 1 - 7 files changed, 37 insertions(+), 38 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 05513b719..b01df7202 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -exclude: "docs|node_modules|migrations|.git|.tox|examples|analysis_data_preprocess|auxiliary_tools|conda/meta.yaml" +exclude: "docs|node_modules|migrations|.git|.tox|examples|analysis_data_preprocess|auxiliary_tools|conda/meta.yaml|e3sm_diags/driver/utils/zwf_functions.py" default_stages: [commit] fail_fast: true diff --git a/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py b/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py index 5d4aa552f..003be071d 100644 --- a/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py +++ b/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py @@ -14,9 +14,9 @@ param.results_dir = os.path.join(prefix, 'tropical_variability_model_obs') #param.run_type = "model_vs_model" #param.ref_name = 'E3SMv2' -param.test_start_yr = '2010' +param.test_start_yr = '2001' param.test_end_yr = '2010' -param.ref_start_yr = '2010' +param.ref_start_yr = '2001' param.ref_end_yr = '2010' param.save_netcdf = True diff --git a/e3sm_diags/driver/tropical_subseasonal_driver.py b/e3sm_diags/driver/tropical_subseasonal_driver.py index 6cd445ed0..39d76d342 100644 --- a/e3sm_diags/driver/tropical_subseasonal_driver.py +++ b/e3sm_diags/driver/tropical_subseasonal_driver.py @@ -121,8 +121,6 @@ def calculate_spectrum(path, variable, start_year, end_year): lat=slice(-15, 15), time=slice(f"{start_year}-01-01", f"{end_year}-12-31") )[variable] - # TODO: subset time - # Unit conversion if var.name == "PRECT": if var.attrs["units"] == "m/s" or var.attrs["units"] == "m s{-1}": @@ -177,7 +175,7 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara parameter.test_start_yr, parameter.test_end_yr, ) - test.to_netcdf(f"{parameter.results_dir}/full_spec_test.nc") + # test.to_netcdf(f"{parameter.results_dir}/full_spec_test.nc") if run_type == "model_vs_model": ref = calculate_spectrum( parameter.reference_data_path, @@ -187,21 +185,21 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara ) elif run_type == "model_vs_obs": # TODO use pre-calculated spectral power - if parameter.ref_start_yr == "": - parameter.ref_name_yrs = parameter.reference_name - # read precalculated data. - else: - ref_data_path = f"{parameter.reference_data_path}/{parameter.ref_name}" - # parameter.ref_name_yrs = f"{parameter.ref_name}({parameter.test_start_yr}-{parameter.test_start_yr})" - ref = calculate_spectrum( - ref_data_path, - variable, - parameter.ref_start_yr, - parameter.ref_end_yr, - ) - ref.to_netcdf( - f"{parameter.results_dir}/full_spec_ref_{parameter.ref_name}.nc" - ) + # if parameter.ref_start_yr == "": + # parameter.ref_name_yrs = parameter.reference_name + # # read precalculated data. + # else: + ref_data_path = f"{parameter.reference_data_path}/{parameter.ref_name}" + # parameter.ref_name_yrs = f"{parameter.ref_name}({parameter.test_start_yr}-{parameter.test_start_yr})" + ref = calculate_spectrum( + ref_data_path, + variable, + parameter.ref_start_yr, + parameter.ref_end_yr, + ) + # ref.to_netcdf( + # f"{parameter.results_dir}/full_spec_ref_{parameter.ref_name}.nc" + # ) # test = xr.open_dataset(f"{parameter.results_dir}/full_spec_test.nc").load() # ref = xr.open_dataset(f"{parameter.results_dir}/full_spec_ref_{parameter.ref_name}.nc").load() diff --git a/e3sm_diags/driver/utils/zwf_functions.py b/e3sm_diags/driver/utils/zwf_functions.py index 1c86a3c4e..9c4626f8d 100755 --- a/e3sm_diags/driver/utils/zwf_functions.py +++ b/e3sm_diags/driver/utils/zwf_functions.py @@ -1,9 +1,8 @@ import logging -import sys import numpy as np import xarray as xr -from scipy.signal import convolve2d, detrend +from scipy.signal import detrend logging.basicConfig(level=logging.INFO) @@ -617,7 +616,9 @@ def spacetime_power( return z_final -def genDispersionCurves(nWaveType=6, nPlanetaryWave=50, rlat=0, Ahe=[50, 25, 12]): +def genDispersionCurves( + nWaveType=6, nPlanetaryWave=50, rlat=0, Ahe=[50, 25, 12] +): # noqa: C901 """ Function to derive the shallow water dispersion curves. Closely follows NCL version. diff --git a/e3sm_diags/parameter/tropical_subseasonal_parameter.py b/e3sm_diags/parameter/tropical_subseasonal_parameter.py index 8477900fe..571b228d5 100644 --- a/e3sm_diags/parameter/tropical_subseasonal_parameter.py +++ b/e3sm_diags/parameter/tropical_subseasonal_parameter.py @@ -12,6 +12,7 @@ def __init__(self): self.ref_timeseries_input = True self.test_timeseries_input = True self.granulate.remove("seasons") + self.spec_type = "" # Custom attributes # ----------------- diff --git a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py index a2ae12c0b..446bc4fb2 100755 --- a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py +++ b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py @@ -1,7 +1,7 @@ from __future__ import annotations import os -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING # noqa: F401 import matplotlib import numpy as np @@ -218,7 +218,7 @@ def _save_plot(fig: plt.figure, parameter: CoreParameter): subpage[1, :] = subpage[0, :] + subpage[1, :] subpage = subpage + np.array(BORDER_PADDING).reshape(2, 2) subpage = list(((subpage) * page).flatten()) # type: ignore - extent = Bbox.from_extents(*subpage) + extent = matplotlib.transforms.Bbox.from_extents(*subpage) # Save subplot fname = fnm + ".%i." % idx + f @@ -232,13 +232,13 @@ def _save_plot(fig: plt.figure, parameter: CoreParameter): logger.info(f"Sub-plot saved in: {fname}") -def _wave_frequency_plot( +def _wave_frequency_plot( # noqa: C901 subplot_num: int, var: xr.DataArray, fig: plt.figure, parameter: CoreParameter, title: str, - do_zoom: Boolean = False, + do_zoom: bool = False, ): """Create wave frequency plot. @@ -368,9 +368,9 @@ def _wave_frequency_plot( ) else: if varName == "FLUT": - contour_level_spec = CONTOUR_LEVS_SPEC_RAW_FLUT + contour_level_spec = CONTOUR_LEVS_SPEC_RAW_FLUT # type: ignore else: - contour_level_spec = CONTOUR_LEVS_SPEC_RAW + contour_level_spec = CONTOUR_LEVS_SPEC_RAW # type: ignore cmapSpecUse, normSpecUse = create_colormap_clevs( CMAP_SPEC_RAW, contour_level_spec ) @@ -398,9 +398,9 @@ def _wave_frequency_plot( if subplot_num == 2: # TODO refine color bar if "spec_norm" in var.name: - contour_level_spec = CONTOUR_LEVS_SPEC_NORM_DIFF + contour_level_spec = CONTOUR_LEVS_SPEC_NORM_DIFF # type: ignore else: - contour_level_spec = CONTOUR_LEVS_SPEC_RAW_DIFF + contour_level_spec = CONTOUR_LEVS_SPEC_RAW_DIFF # type: ignore cmapSpecUse = "seismic" img = ax.contourf( @@ -411,7 +411,7 @@ def _wave_frequency_plot( cmap=cmapSpecUse, extend="both", ) - img2 = ax.contour( + img2 = ax.contour( # noqa: F841 kmesh0, vmesh0, z, @@ -563,7 +563,7 @@ def plot( da_test: xr.DataArray, da_ref: xr.DataArray | None, da_diff: xr.DataArray | None, - do_zoom: Boolean = False, + do_zoom: bool = False, ): """Plot the variable's metrics generated for the lat_lon set. @@ -587,7 +587,7 @@ def plot( da_test, fig, parameter, - title=parameter.test_name_yrs, # type: ignore + title=parameter.test_name_yrs, do_zoom=do_zoom, ) @@ -596,7 +596,7 @@ def plot( da_ref, fig, parameter, - title=parameter.ref_name_yrs, # type: ignore + title=parameter.ref_name_yrs, do_zoom=do_zoom, ) @@ -605,7 +605,7 @@ def plot( da_diff, fig, parameter, - title=parameter.diff_title, # type: ignore + title=parameter.diff_title, do_zoom=do_zoom, ) diff --git a/e3sm_diags/viewer/tropical_subseasonal_viewer.py b/e3sm_diags/viewer/tropical_subseasonal_viewer.py index a7fb12356..dd93675a7 100755 --- a/e3sm_diags/viewer/tropical_subseasonal_viewer.py +++ b/e3sm_diags/viewer/tropical_subseasonal_viewer.py @@ -74,7 +74,6 @@ def create_viewer(root_dir, parameters): image_relative_path = "{}.{}".format(relative_path, ext) # image_relative_path = f'{var}_{spec_type}_15N-15N.png' - print("image_relative_path", image_relative_path) viewer.add_col( image_relative_path, is_file=True, From ca0ded4a136f4797f2154a340d1985b698fb760a Mon Sep 17 00:00:00 2001 From: Jill Chengzhu Zhang Date: Tue, 12 Mar 2024 13:50:29 -0700 Subject: [PATCH 33/46] Apply Tom's suggestions from code review Co-authored-by: Tom Vo --- .../driver/tropical_subseasonal_driver.py | 30 ++++++++++--------- e3sm_diags/parameter/core_parameter.py | 1 - .../plot/cartopy/tropical_subseasonal_plot.py | 14 --------- .../viewer/tropical_subseasonal_viewer.py | 8 ++--- 4 files changed, 19 insertions(+), 34 deletions(-) diff --git a/e3sm_diags/driver/tropical_subseasonal_driver.py b/e3sm_diags/driver/tropical_subseasonal_driver.py index 39d76d342..b01b543b8 100644 --- a/e3sm_diags/driver/tropical_subseasonal_driver.py +++ b/e3sm_diags/driver/tropical_subseasonal_driver.py @@ -1,3 +1,11 @@ +""" +Script to compute and plot spectral powers of a subseasonal tropical field in +zonal wavenumber-frequency space. Both the plot files and files containing the +associated numerical data shown in the plots are created. + +Authors: Jim Benedict and Brian Medeiros +Modified by Jill Zhang to integrate into E3SM Diags. +""" from __future__ import annotations import glob @@ -19,13 +27,6 @@ logger = custom_logger(__name__) -# Script to compute and plot spectral powers of a subseasonal tropical field in -# zonal wavenumber-frequency space. Both the plot files and files containing the -# associated numerical data shown in the plots are created. - -# Authors: Jim Benedict and Brian Medeiros -# Modified by Jill Zhang to integrate into E3SM Diags. - def find_nearest(array, value): array = np.asarray(array) @@ -101,10 +102,14 @@ def wf_analysis(x, **kwargs): def calculate_spectrum(path, variable, start_year, end_year): - latBound = (-15, 15) # latitude bounds for analysis - spd = 1 # SAMPLES PER DAY - nDayWin = 96 # Wheeler-Kiladis [WK] temporal window length (days) - nDaySkip = -60 # time (days) between temporal windows [segments] + # latitude bounds for analysis + latBound = (-15, 15) + # SAMPLES PER DAY + spd = 1 + # Wheeler-Kiladis [WK] temporal window length (days) + nDayWin = 96 + # time (days) between temporal windows [segments] + nDaySkip = -60 # negative means there will be overlapping temporal segments twoMonthOverlap = -1 * nDaySkip @@ -143,7 +148,6 @@ def calculate_spectrum(path, variable, start_year, end_year): # Wavenumber Frequency Analysis spec_all = wf_analysis(var, **opt) - # spec_all.to_netcdf(outDataDir + "/full_spec.nc") return spec_all @@ -157,7 +161,6 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara :rtype: CoreParameter """ run_type = parameter.run_type - # variables = parameter.variables season = "ANN" test_data = utils.dataset.Dataset(parameter, test=True) @@ -175,7 +178,6 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara parameter.test_start_yr, parameter.test_end_yr, ) - # test.to_netcdf(f"{parameter.results_dir}/full_spec_test.nc") if run_type == "model_vs_model": ref = calculate_spectrum( parameter.reference_data_path, diff --git a/e3sm_diags/parameter/core_parameter.py b/e3sm_diags/parameter/core_parameter.py index bdfcf1833..c7b75189f 100644 --- a/e3sm_diags/parameter/core_parameter.py +++ b/e3sm_diags/parameter/core_parameter.py @@ -33,7 +33,6 @@ "aerosol_budget", # "mp_partition", # "tropical_subseasonal", - ] diff --git a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py index 446bc4fb2..3eee52577 100755 --- a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py +++ b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py @@ -195,7 +195,6 @@ def _save_plot(fig: plt.figure, parameter: CoreParameter): get_output_dir(parameter.current_set, parameter), parameter.output_file + "." + f, ) - # fnm = f'{parameter.var_id}_{parameter.spec_type}_15N-15S.png' plt.savefig(fnm) logger.info(f"Plot saved in: {fnm}") @@ -353,11 +352,9 @@ def _wave_frequency_plot( # noqa: C901 ) z.to_netcdf(fnm) - # fig, ax = plt.subplots() ax = fig.add_axes(PANEL[subplot_num]) kmesh0, vmesh0 = np.meshgrid(z["wavenumber"], z["frequency"]) - # img = ax.contourf(kmesh0, vmesh0, z, levels=np.linspace(0.2, 3.0, 16), cmap='Spectral_r', extend='both') # for test and ref: if subplot_num < 2: @@ -448,7 +445,6 @@ def _wave_frequency_plot( # noqa: C901 ax.plot(swk[ii, 2, :], swf[ii, 2, :], color="black", linewidth=1.5, alpha=0.80) ax.set_xlim(wnb) ax.set_ylim(fb) - # ax.set_title(varName + ": Log ${\sum_{15^{\circ}S}^{15^{\circ}N} Power_{SYM}}$") # Version w/ LaTeX if "spec_raw" in var.name: ax.set_title( f"{varName}: Log{{Sum(Power) from 15°S-15°N}}\n" @@ -551,13 +547,6 @@ def _wave_frequency_plot( # noqa: C901 ) fig.colorbar(img) - -# # Save fig -# fig.savefig(outDataDir + "/"+ dataDesc + "_plot.png", bbox_inches='tight', dpi=300) -# -# print("Plot file created: %s\n" % outDataDir + "/"+ dataDesc + "_plot.png") - - def plot( parameter: CoreParameter, da_test: xr.DataArray, @@ -578,9 +567,7 @@ def plot( ds_diff : xr.DataArray | None The difference between ``ds_test`` and ``ds_ref``. """ - # fig = plt.figure(figsize=parameter.figsize, dpi=parameter.dpi) fig = plt.figure(figsize=[8.5, 12.0], dpi=300) - # fig.suptitle(parameter.main_title, x=0.5, y=0.96, fontsize=18) _wave_frequency_plot( 0, @@ -609,7 +596,6 @@ def plot( do_zoom=do_zoom, ) - # TODO: save plot:NameError: name 'get_output_dir' is not defined _save_plot(fig, parameter) plt.close() diff --git a/e3sm_diags/viewer/tropical_subseasonal_viewer.py b/e3sm_diags/viewer/tropical_subseasonal_viewer.py index dd93675a7..aa66db3ea 100755 --- a/e3sm_diags/viewer/tropical_subseasonal_viewer.py +++ b/e3sm_diags/viewer/tropical_subseasonal_viewer.py @@ -1,11 +1,9 @@ import os -from cdp.cdp_viewer import OutputViewer - from e3sm_diags.logger import custom_logger - -from .default_viewer import create_metadata -from .utils import add_header, h1_to_h3 +from e3sm_diags.viewer.core_viewer import OutputViewer +from e3sm_diags.viewer.default_viewer import create_metadata +from e3sm_diags.viewer.utils import add_header, h1_to_h3 logger = custom_logger(__name__) From 0422f9855d69b2796f8490f7da3040a7be6eb5ce Mon Sep 17 00:00:00 2001 From: ChengzhuZhang Date: Wed, 13 Mar 2024 17:07:58 -0700 Subject: [PATCH 34/46] correct print out start end year for subset data --- .../run_tropical_subseasonal.py | 13 ++++--- .../tropical_subseasonal_model_vs_model.cfg | 24 ++++++------- .../driver/tropical_subseasonal_driver.py | 34 +++++++++++-------- 3 files changed, 37 insertions(+), 34 deletions(-) diff --git a/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py b/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py index 003be071d..97b58ffe5 100644 --- a/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py +++ b/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py @@ -5,19 +5,18 @@ param = TropicalSubseasonalParameter() param.reference_data_path = '/global/cfs/cdirs/e3sm/e3sm_diags/obs_for_e3sm_diags/time-series' -#param.reference_data_path = '/global/cfs/cdirs/e3sm/forsyth/E3SMv2/v2.LR.historical_0201/post/atm/180x360_aave/ts/daily/5yr' -param.test_data_path = '/global/cfs/cdirs/e3sm/forsyth/E3SMv2/v2.LR.historical_0201/post/atm/180x360_aave/ts/daily/5yr' -param.test_data_path = '/global/cfs/cdirs/e3sm/chengzhu/e3sm_diags_zppy_test_complete_run_output/v2.LR.historical_0101_20240130/post/atm/180x360_aave/ts/daily/5yr' +#param.reference_data_path = '/global/cfs/cdirs/e3sm/chengzhu/e3sm_diags_zppy_test_complete_run_output/v2.LR.historical_0101_20240130/post/atm/180x360_aave/ts/daily/15yr' +param.test_data_path = '/global/cfs/cdirs/e3sm/chengzhu/e3sm_diags_zppy_test_complete_run_output/v2.LR.historical_0101_20240130/post/atm/180x360_aave/ts/daily/15yr' param.test_name = 'E3SMv2' prefix = '/Users/zhang40/Documents/repos/e3sm_diags/auxiliary_tools/tropical_subseasonal_diags/data1' -prefix = '/global/cfs/cdirs/e3sm/www/chengzhu/tests/tropical_diags_try2' +prefix = '/global/cfs/cdirs/e3sm/www/chengzhu/tests/tropical_diags_subsetting' param.results_dir = os.path.join(prefix, 'tropical_variability_model_obs') #param.run_type = "model_vs_model" #param.ref_name = 'E3SMv2' -param.test_start_yr = '2001' -param.test_end_yr = '2010' +param.test_start_yr = '1999' +param.test_end_yr = '2000' param.ref_start_yr = '2001' -param.ref_end_yr = '2010' +param.ref_end_yr = '2002' param.save_netcdf = True runner.sets_to_run = ['tropical_subseasonal'] diff --git a/e3sm_diags/driver/default_diags/tropical_subseasonal_model_vs_model.cfg b/e3sm_diags/driver/default_diags/tropical_subseasonal_model_vs_model.cfg index b92656baa..6b24d55ea 100644 --- a/e3sm_diags/driver/default_diags/tropical_subseasonal_model_vs_model.cfg +++ b/e3sm_diags/driver/default_diags/tropical_subseasonal_model_vs_model.cfg @@ -5,15 +5,15 @@ variables = ["PRECT"] regions = ["15S15N"] -#[#] -#sets = ["tropical_subseasonal"] -#case_id = "wavenumber-frequency" -#variables = ["FLUT"] -#regions = ["15S15N"] -# -# -#[#] -#sets = ["tropical_subseasonal"] -#case_id = "wavenumber-frequency" -#variables = ["U850"] -#regions = ["15S15N"] +[#] +sets = ["tropical_subseasonal"] +case_id = "wavenumber-frequency" +variables = ["FLUT"] +regions = ["15S15N"] + + +[#] +sets = ["tropical_subseasonal"] +case_id = "wavenumber-frequency" +variables = ["U850"] +regions = ["15S15N"] diff --git a/e3sm_diags/driver/tropical_subseasonal_driver.py b/e3sm_diags/driver/tropical_subseasonal_driver.py index b01b543b8..1db67e3f6 100644 --- a/e3sm_diags/driver/tropical_subseasonal_driver.py +++ b/e3sm_diags/driver/tropical_subseasonal_driver.py @@ -36,7 +36,7 @@ def find_nearest(array, value): Example: array = [ 0.21069679 0.61290182 0.63425412 0.84635244 0.91599191 0.00213826 0.17104965 0.56874386 0.57319379 0.28719469] - print(find_nearest(array, value=0.5)) + logger.info(find_nearest(array, value=0.5)) # 0.568743859261 """ @@ -125,11 +125,13 @@ def calculate_spectrum(path, variable, start_year, end_year): var = xr.open_mfdataset(glob.glob(f"{path}/{variable}_*.nc")).sel( lat=slice(-15, 15), time=slice(f"{start_year}-01-01", f"{end_year}-12-31") )[variable] + actual_start = var.time.dt.year.values[0] + actual_end = var.time.dt.year.values[-1] # Unit conversion if var.name == "PRECT": if var.attrs["units"] == "m/s" or var.attrs["units"] == "m s{-1}": - print( + logger.info( "\nBEFORE unit conversion: Max/min of data: " + str(var.values.max()) + " " @@ -139,7 +141,7 @@ def calculate_spectrum(path, variable, start_year, end_year): var.values * 1000.0 * 86400.0 ) # convert m/s to mm/d, do not alter metadata (yet) var.attrs["units"] = "mm/d" # adjust metadata to reflect change in units - print( + logger.info( "\nAFTER unit conversion: Max/min of data: " + str(var.values.max()) + " " @@ -148,7 +150,7 @@ def calculate_spectrum(path, variable, start_year, end_year): # Wavenumber Frequency Analysis spec_all = wf_analysis(var, **opt) - return spec_all + return spec_all, str(actual_start), str(actual_end) def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalParameter: @@ -164,22 +166,23 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara season = "ANN" test_data = utils.dataset.Dataset(parameter, test=True) - parameter.test_name_yrs = utils.general.get_name_and_yrs( - parameter, test_data, season - ) ref_data = utils.dataset.Dataset(parameter, ref=True) - parameter.ref_name_yrs = utils.general.get_name_and_yrs(parameter, ref_data, season) for variable in parameter.variables: - test = calculate_spectrum( + test, test_start, test_end = calculate_spectrum( parameter.test_data_path, variable, parameter.test_start_yr, parameter.test_end_yr, ) + parameter.test_start_yr = test_start + parameter.test_end_yr = test_end + parameter.test_name_yrs = utils.general.get_name_and_yrs( + parameter, test_data, season + ) if run_type == "model_vs_model": - ref = calculate_spectrum( + ref, ref_start, ref_end = calculate_spectrum( parameter.reference_data_path, variable, parameter.ref_start_yr, @@ -192,16 +195,17 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara # # read precalculated data. # else: ref_data_path = f"{parameter.reference_data_path}/{parameter.ref_name}" - # parameter.ref_name_yrs = f"{parameter.ref_name}({parameter.test_start_yr}-{parameter.test_start_yr})" - ref = calculate_spectrum( + ref, ref_start, ref_end = calculate_spectrum( ref_data_path, variable, parameter.ref_start_yr, parameter.ref_end_yr, ) - # ref.to_netcdf( - # f"{parameter.results_dir}/full_spec_ref_{parameter.ref_name}.nc" - # ) + parameter.ref_start_yr = ref_start + parameter.ref_end_yr = ref_end + parameter.ref_name_yrs = utils.general.get_name_and_yrs( + parameter, ref_data, season + ) # test = xr.open_dataset(f"{parameter.results_dir}/full_spec_test.nc").load() # ref = xr.open_dataset(f"{parameter.results_dir}/full_spec_ref_{parameter.ref_name}.nc").load() From f0f72530ee6b1c5a8caf247f403a255c7bcb3d18 Mon Sep 17 00:00:00 2001 From: chengzhuzhang Date: Wed, 20 Mar 2024 14:36:37 -0700 Subject: [PATCH 35/46] fix dispersion curves for antisymmetric --- .../run_tropical_subseasonal.py | 11 +- .../plot/cartopy/tropical_subseasonal_plot.py | 195 +++++++++++------- 2 files changed, 127 insertions(+), 79 deletions(-) diff --git a/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py b/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py index 97b58ffe5..327fceeff 100644 --- a/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py +++ b/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py @@ -10,14 +10,17 @@ param.test_name = 'E3SMv2' prefix = '/Users/zhang40/Documents/repos/e3sm_diags/auxiliary_tools/tropical_subseasonal_diags/data1' prefix = '/global/cfs/cdirs/e3sm/www/chengzhu/tests/tropical_diags_subsetting' + param.results_dir = os.path.join(prefix, 'tropical_variability_model_obs') -#param.run_type = "model_vs_model" -#param.ref_name = 'E3SMv2' -param.test_start_yr = '1999' +param.run_type = "model_vs_model" +param.ref_name = 'E3SMv2' +param.test_start_yr = '2000' param.test_end_yr = '2000' param.ref_start_yr = '2001' -param.ref_end_yr = '2002' +param.ref_end_yr = '2001' param.save_netcdf = True runner.sets_to_run = ['tropical_subseasonal'] runner.run_diags([param]) + + diff --git a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py index 3eee52577..73f22aa36 100755 --- a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py +++ b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py @@ -292,21 +292,28 @@ def _wave_frequency_plot( # noqa: C901 # get data for dispersion curves: equivDepths = [50, 25, 12] + # notes: + # The outputs from genDispersionCurves contain both symmetric and antisymmetric waves. In the case of + # nWaveType == 6: + # 0,1,2 are (ASYMMETRIC) "MRG", "IG", "EIG" (mixed rossby gravity, inertial gravity, equatorial inertial gravity) + # 3,4,5 are (SYMMETRIC) "Kelvin", "ER", "IG" (Kelvin, equatorial rossby, inertial gravity) swfreq, swwn = wf.genDispersionCurves(Ahe=equivDepths) # swfreq.shape # -->(6, 3, 50) - if "spec_norm" in var.name: # with dispersion curves to plot - # For n=1 ER waves, allow dispersion curves to touch 0 -- this is for plot aesthetics only - for i in range( - 0, 3 - ): # loop 0-->2 for the assumed 3 shallow water dispersion curves for ER waves - indMinPosFrqER = np.where( - swwn[3, i, :] >= 0.0, swwn[3, i, :], 1e20 - ).argmin() # index of swwn for least positive wn - swwn[3, i, indMinPosFrqER], swfreq[3, i, indMinPosFrqER] = ( - 0.0, - 0.0, - ) # this sets ER's frequencies to 0. at wavenumber 0. + # For n=1 ER waves, allow dispersion curves to touch 0 -- this is for plot aesthetics only + for i in range( + 0, 3 + ): # loop 0-->2 for the assumed 3 shallow water dispersion curves for ER waves + indMinPosFrqER = np.where( + swwn[3, i, :] >= 0.0, swwn[3, i, :], 1e20 + ).argmin() # index of swwn for least positive wn + swwn[3, i, indMinPosFrqER], swfreq[3, i, indMinPosFrqER] = ( + 0.0, + 0.0, + ) # this sets ER's frequencies to 0. at wavenumber 0. + + #if "spec_norm_sys" in var.name: # with dispersion curves to plot + # if "spec_norm_asy" in var.name: swf = np.where(swfreq == 1e20, np.nan, swfreq) swk = np.where(swwn == 1e20, np.nan, swwn) @@ -439,10 +446,8 @@ def _wave_frequency_plot( # noqa: C901 ax.text( wnb[0] + 1, (1.0 / 3.0) + text_offset, "3 days", color="dimgray", alpha=0.80 ) - for ii in range(3, 6): - ax.plot(swk[ii, 0, :], swf[ii, 0, :], color="black", linewidth=1.5, alpha=0.80) - ax.plot(swk[ii, 1, :], swf[ii, 1, :], color="black", linewidth=1.5, alpha=0.80) - ax.plot(swk[ii, 2, :], swf[ii, 2, :], color="black", linewidth=1.5, alpha=0.80) + + ax.set_xlim(wnb) ax.set_ylim(fb) if "spec_raw" in var.name: @@ -459,9 +464,8 @@ def _wave_frequency_plot( # noqa: C901 ax.set_title(title, loc="left") ax.set_title(f"{var.component}", loc="right") - if "spec_norm" in var.name: - # Shallow water dispersion curve line labels: See https://matplotlib.org/stable/tutorials/text/text_intro.html - # n=1 ER dispersion curve labels + # set up lines and lables for dispersion curves + if "background" not in var.name: text_opt = { "fontsize": 9, "verticalalignment": "center", @@ -474,62 +478,103 @@ def _wave_frequency_plot( # noqa: C901 "pad": 0.0, }, } - iwave, ih = 3, 0 - idxClose, valClose = find_nearest( - swk[iwave, ih, :], -11.0 - ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) - iwave, ih = 3, 1 - idxClose, valClose = find_nearest( - swk[iwave, ih, :], -9.0 - ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) - iwave, ih = 3, 2 - idxClose, valClose = find_nearest( - swk[iwave, ih, :], -8.0 - ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) - ax.text(-7.0, 0.10, "n=1 ER", text_opt) - - # Kelvin dispersion curve labels - iwave, ih = 4, 0 - idxClose, valClose = find_nearest( - swk[iwave, ih, :], 8.0 - ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) - iwave, ih = 4, 1 - idxClose, valClose = find_nearest( - swk[iwave, ih, :], 10.0 - ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) - iwave, ih = 4, 2 - idxClose, valClose = find_nearest( - swk[iwave, ih, :], 14.0 - ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) - ax.text(6.0, 0.13, "Kelvin", text_opt) - - # IG dispersion curve labels - iwave, ih = 5, 0 - idxClose, valClose = find_nearest( - swk[iwave, ih, :], 0.0 - ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) - iwave, ih = 5, 1 - idxClose, valClose = find_nearest( - swk[iwave, ih, :], 0.0 - ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) - iwave, ih = 5, 2 - idxClose, valClose = find_nearest( - swk[iwave, ih, :], 0.0 - ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) - ax.text(-10.0, 0.48, "n=1 WIG", text_opt) - ax.text(5.0, 0.48, "n=1 EIG", text_opt) - - # MJO label - ax.text(6.0, 0.0333, "MJO", text_opt) + if "sym" in var.name: + wave_types = [3,4,5] + if "norm" in var.name: + # Shallow water dispersion curve line labels: See https://matplotlib.org/stable/tutorials/text/text_intro.html + # n=1 ER dispersion curve labels + iwave, ih = 3, 0 + idxClose, valClose = find_nearest( + swk[iwave, ih, :], -11.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + iwave, ih = 3, 1 + idxClose, valClose = find_nearest( + swk[iwave, ih, :], -9.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + iwave, ih = 3, 2 + idxClose, valClose = find_nearest( + swk[iwave, ih, :], -8.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + ax.text(-7.0, 0.10, "n=1 ER", text_opt) + + # Kelvin dispersion curve labels + iwave, ih = 4, 0 + idxClose, valClose = find_nearest( + swk[iwave, ih, :], 8.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + iwave, ih = 4, 1 + idxClose, valClose = find_nearest( + swk[iwave, ih, :], 10.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + iwave, ih = 4, 2 + idxClose, valClose = find_nearest( + swk[iwave, ih, :], 14.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + ax.text(6.0, 0.13, "Kelvin", text_opt) + + # IG dispersion curve labels + iwave, ih = 5, 0 + idxClose, valClose = find_nearest( + swk[iwave, ih, :], 0.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + iwave, ih = 5, 1 + idxClose, valClose = find_nearest( + swk[iwave, ih, :], 0.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + iwave, ih = 5, 2 + idxClose, valClose = find_nearest( + swk[iwave, ih, :], 0.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + ax.text(-10.0, 0.48, "n=1 WIG", text_opt) + ax.text(5.0, 0.48, "n=1 EIG", text_opt) + + # MJO label + ax.text(6.0, 0.0333, "MJO", text_opt) + else: + wave_types = [0,1,2] + if "norm" in var.name: + # n=0 EIG dispersion curve labels + iwave, ih = 1, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 5.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 1, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 1, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + ax.text(9.,0.48,'n=0 EIG',text_opt) + + # n=2 IG dispersion curve labels + iwave, ih = 2, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -2.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 2, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -2.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + iwave, ih = 2, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -2.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + ax.text(-10.,0.65,'n=2 WIG',text_opt) + ax.text(8.,0.65,'n=2 EIG',text_opt) + + # MJO label + ax.text(3.,0.0333,'MJO',text_opt) + + # plotting dispersion curves + for ii in wave_types: + ax.plot(swk[ii, 0, :], swf[ii, 0, :], color="black", linewidth=1.5, alpha=0.80) + ax.plot(swk[ii, 1, :], swf[ii, 1, :], color="black", linewidth=1.5, alpha=0.80) + ax.plot(swk[ii, 2, :], swf[ii, 2, :], color="black", linewidth=1.5, alpha=0.80) plt.ylabel("Frequency (CPD)") plt.xlabel("Zonal wavenumber") From 31dc8cdf843c03198dba155b66209abd9375c6b5 Mon Sep 17 00:00:00 2001 From: chengzhuzhang Date: Wed, 20 Mar 2024 14:37:43 -0700 Subject: [PATCH 36/46] fix formatting --- e3sm_diags/__init__.py | 1 - .../plot/cartopy/tropical_subseasonal_plot.py | 122 ++++++++++++------ 2 files changed, 85 insertions(+), 38 deletions(-) diff --git a/e3sm_diags/__init__.py b/e3sm_diags/__init__.py index a1087c5a4..74b7ab718 100644 --- a/e3sm_diags/__init__.py +++ b/e3sm_diags/__init__.py @@ -6,7 +6,6 @@ # issue with dask when using ESMF with system compilers. import shapely - __version__ = "v2.11.0rc1" INSTALL_PATH = os.path.join(sys.prefix, "share/e3sm_diags/") diff --git a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py index 73f22aa36..91abe4109 100755 --- a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py +++ b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py @@ -293,7 +293,7 @@ def _wave_frequency_plot( # noqa: C901 # get data for dispersion curves: equivDepths = [50, 25, 12] # notes: - # The outputs from genDispersionCurves contain both symmetric and antisymmetric waves. In the case of + # The outputs from genDispersionCurves contain both symmetric and antisymmetric waves. In the case of # nWaveType == 6: # 0,1,2 are (ASYMMETRIC) "MRG", "IG", "EIG" (mixed rossby gravity, inertial gravity, equatorial inertial gravity) # 3,4,5 are (SYMMETRIC) "Kelvin", "ER", "IG" (Kelvin, equatorial rossby, inertial gravity) @@ -312,7 +312,7 @@ def _wave_frequency_plot( # noqa: C901 0.0, ) # this sets ER's frequencies to 0. at wavenumber 0. - #if "spec_norm_sys" in var.name: # with dispersion curves to plot + # if "spec_norm_sys" in var.name: # with dispersion curves to plot # if "spec_norm_asy" in var.name: swf = np.where(swfreq == 1e20, np.nan, swfreq) @@ -447,7 +447,6 @@ def _wave_frequency_plot( # noqa: C901 wnb[0] + 1, (1.0 / 3.0) + text_offset, "3 days", color="dimgray", alpha=0.80 ) - ax.set_xlim(wnb) ax.set_ylim(fb) if "spec_raw" in var.name: @@ -479,7 +478,7 @@ def _wave_frequency_plot( # noqa: C901 }, } if "sym" in var.name: - wave_types = [3,4,5] + wave_types = [3, 4, 5] if "norm" in var.name: # Shallow water dispersion curve line labels: See https://matplotlib.org/stable/tutorials/text/text_intro.html # n=1 ER dispersion curve labels @@ -487,17 +486,23 @@ def _wave_frequency_plot( # noqa: C901 idxClose, valClose = find_nearest( swk[iwave, ih, :], -11.0 ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + ax.text( + valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt + ) iwave, ih = 3, 1 idxClose, valClose = find_nearest( swk[iwave, ih, :], -9.0 ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + ax.text( + valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt + ) iwave, ih = 3, 2 idxClose, valClose = find_nearest( swk[iwave, ih, :], -8.0 ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + ax.text( + valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt + ) ax.text(-7.0, 0.10, "n=1 ER", text_opt) # Kelvin dispersion curve labels @@ -505,17 +510,23 @@ def _wave_frequency_plot( # noqa: C901 idxClose, valClose = find_nearest( swk[iwave, ih, :], 8.0 ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + ax.text( + valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt + ) iwave, ih = 4, 1 idxClose, valClose = find_nearest( swk[iwave, ih, :], 10.0 ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + ax.text( + valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt + ) iwave, ih = 4, 2 idxClose, valClose = find_nearest( swk[iwave, ih, :], 14.0 ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + ax.text( + valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt + ) ax.text(6.0, 0.13, "Kelvin", text_opt) # IG dispersion curve labels @@ -523,58 +534,94 @@ def _wave_frequency_plot( # noqa: C901 idxClose, valClose = find_nearest( swk[iwave, ih, :], 0.0 ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + ax.text( + valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt + ) iwave, ih = 5, 1 idxClose, valClose = find_nearest( swk[iwave, ih, :], 0.0 ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + ax.text( + valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt + ) iwave, ih = 5, 2 idxClose, valClose = find_nearest( swk[iwave, ih, :], 0.0 ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt) + ax.text( + valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt + ) ax.text(-10.0, 0.48, "n=1 WIG", text_opt) ax.text(5.0, 0.48, "n=1 EIG", text_opt) # MJO label ax.text(6.0, 0.0333, "MJO", text_opt) else: - wave_types = [0,1,2] + wave_types = [0, 1, 2] if "norm" in var.name: # n=0 EIG dispersion curve labels iwave, ih = 1, 0 - idxClose,valClose = find_nearest(swk[iwave,ih,:], 5.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + idxClose, valClose = find_nearest( + swk[iwave, ih, :], 5.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text( + valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt + ) iwave, ih = 1, 1 - idxClose,valClose = find_nearest(swk[iwave,ih,:], 8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + idxClose, valClose = find_nearest( + swk[iwave, ih, :], 8.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text( + valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt + ) iwave, ih = 1, 2 - idxClose,valClose = find_nearest(swk[iwave,ih,:], 8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) - ax.text(9.,0.48,'n=0 EIG',text_opt) - + idxClose, valClose = find_nearest( + swk[iwave, ih, :], 8.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text( + valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt + ) + ax.text(9.0, 0.48, "n=0 EIG", text_opt) + # n=2 IG dispersion curve labels iwave, ih = 2, 0 - idxClose,valClose = find_nearest(swk[iwave,ih,:], -2.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + idxClose, valClose = find_nearest( + swk[iwave, ih, :], -2.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text( + valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt + ) iwave, ih = 2, 1 - idxClose,valClose = find_nearest(swk[iwave,ih,:], -2.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) + idxClose, valClose = find_nearest( + swk[iwave, ih, :], -2.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text( + valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt + ) iwave, ih = 2, 2 - idxClose,valClose = find_nearest(swk[iwave,ih,:], -2.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] - ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',text_opt) - ax.text(-10.,0.65,'n=2 WIG',text_opt) - ax.text(8.,0.65,'n=2 EIG',text_opt) - + idxClose, valClose = find_nearest( + swk[iwave, ih, :], -2.0 + ) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text( + valClose, swf[iwave, ih, idxClose], f"{equivDepths[ih]}", text_opt + ) + ax.text(-10.0, 0.65, "n=2 WIG", text_opt) + ax.text(8.0, 0.65, "n=2 EIG", text_opt) + # MJO label - ax.text(3.,0.0333,'MJO',text_opt) - - # plotting dispersion curves + ax.text(3.0, 0.0333, "MJO", text_opt) + + # plotting dispersion curves for ii in wave_types: - ax.plot(swk[ii, 0, :], swf[ii, 0, :], color="black", linewidth=1.5, alpha=0.80) - ax.plot(swk[ii, 1, :], swf[ii, 1, :], color="black", linewidth=1.5, alpha=0.80) - ax.plot(swk[ii, 2, :], swf[ii, 2, :], color="black", linewidth=1.5, alpha=0.80) + ax.plot( + swk[ii, 0, :], swf[ii, 0, :], color="black", linewidth=1.5, alpha=0.80 + ) + ax.plot( + swk[ii, 1, :], swf[ii, 1, :], color="black", linewidth=1.5, alpha=0.80 + ) + ax.plot( + swk[ii, 2, :], swf[ii, 2, :], color="black", linewidth=1.5, alpha=0.80 + ) plt.ylabel("Frequency (CPD)") plt.xlabel("Zonal wavenumber") @@ -592,6 +639,7 @@ def _wave_frequency_plot( # noqa: C901 ) fig.colorbar(img) + def plot( parameter: CoreParameter, da_test: xr.DataArray, From 0cc310c832529478fe45bfa74ea04077530c5423 Mon Sep 17 00:00:00 2001 From: chengzhuzhang Date: Wed, 20 Mar 2024 15:39:20 -0700 Subject: [PATCH 37/46] fix importing zwf_functions --- e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py index 91abe4109..84617ab24 100755 --- a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py +++ b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py @@ -7,8 +7,8 @@ import numpy as np import xarray as xr from matplotlib.colors import BoundaryNorm, ListedColormap -from zwf import zwf_functions as wf +from e3sm_diags.driver.utils import zwf_functions as wf from e3sm_diags.driver.utils.general import get_output_dir from e3sm_diags.logger import custom_logger from e3sm_diags.parameter.core_parameter import CoreParameter From 913293f0fbe3c3dfade2d04a1cc0ccbc2d0541fa Mon Sep 17 00:00:00 2001 From: chengzhuzhang Date: Thu, 21 Mar 2024 12:10:30 -0700 Subject: [PATCH 38/46] address code review; clean up --- .../driver/tropical_subseasonal_driver.py | 274 ++++++++++-------- 1 file changed, 148 insertions(+), 126 deletions(-) diff --git a/e3sm_diags/driver/tropical_subseasonal_driver.py b/e3sm_diags/driver/tropical_subseasonal_driver.py index 1db67e3f6..f2c6e1bc1 100644 --- a/e3sm_diags/driver/tropical_subseasonal_driver.py +++ b/e3sm_diags/driver/tropical_subseasonal_driver.py @@ -28,131 +28,6 @@ logger = custom_logger(__name__) -def find_nearest(array, value): - array = np.asarray(array) - idx = (np.abs(array - value)).argmin() - return idx, array[idx] - """Return index of [array] closest in value to [value] - Example: - array = [ 0.21069679 0.61290182 0.63425412 0.84635244 0.91599191 0.00213826 - 0.17104965 0.56874386 0.57319379 0.28719469] - logger.info(find_nearest(array, value=0.5)) - # 0.568743859261 - - """ - - -def wf_analysis(x, **kwargs): - """Return zonal wavenumber-frequency power spectra of x. The returned spectra are: - spec_sym: Raw (non-normalized) power spectrum of the component of x that is symmetric about the equator. - spec_asym: Raw (non-normalized) power spectrum of the component of x that is antisymmetric about the equator. - nspec_sym: Normalized (by a smoothed red-noise background spectrum) power spectrum of the component of x that is symmetric about the equator. - nspec_asym: Normalized (by a smoothed red-noise background spectrum) power spectrum of the component of x that is antisymmetric about the equator. - - The NCL version of 'wkSpaceTime' smooths the symmetric and antisymmetric components - along the frequency dimension using a 1-2-1 filter once. - - """ - # Get the "raw" spectral power - # OPTIONAL kwargs: - # segsize, noverlap, spd, latitude_bounds (tuple: (south, north)), dosymmetries, rmvLowFrq - - z2 = wf.spacetime_power(x, **kwargs) - z2avg = z2.mean(dim="component") - z2.loc[{"frequency": 0}] = np.nan # get rid of spurious power at \nu = 0 (mean) - - # Following NCL's wkSpaceTime, apply one pass of a 1-2-1 filter along the frequency - # domain to the raw (non-normalized) spectra/um. - # Do not use 0 frequency when smoothing here. - # Use weights that sum to 1 to ensure that smoothing is conservative. - z2s = wf.smoothFrq121(z2, 1) - - # The background is supposed to be derived from both symmetric & antisymmetric - # Inputs to the background spectrum calculation should be z2avg - background = wf.smoothBackground_wavefreq(z2avg) - # separate components - spec_sym = z2s[0, ...] - spec_asy = z2s[1, ...] - # normalize: Following NCL's wkSpaceTime, use lightly smoothed version of spectra/um - # as numerator - nspec_sym = spec_sym / background - nspec_asy = spec_asy / background - - spec = xr.merge( - [ - spec_sym.rename("spec_raw_sym"), - spec_asy.rename("spec_raw_asy"), - nspec_sym.rename("spec_norm_sym"), - nspec_asy.rename("spec_norm_asy"), - background.rename("spec_background"), - ], - compat="override", - ) - spec_all = spec.drop("component") - spec_all["spec_raw_sym"].attrs = {"component": "symmetric", "type": "raw"} - spec_all["spec_raw_asy"].attrs = {"component": "antisymmetric", "type": "raw"} - spec_all["spec_norm_sym"].attrs = {"component": "symmetric", "type": "normalized"} - spec_all["spec_norm_asy"].attrs = { - "component": "antisymmetric", - "type": "normalized", - } - spec_all["spec_background"].attrs = {"component": "", "type": "background"} - - return spec_all - - -def calculate_spectrum(path, variable, start_year, end_year): - # latitude bounds for analysis - latBound = (-15, 15) - # SAMPLES PER DAY - spd = 1 - # Wheeler-Kiladis [WK] temporal window length (days) - nDayWin = 96 - # time (days) between temporal windows [segments] - nDaySkip = -60 - # negative means there will be overlapping temporal segments - twoMonthOverlap = -1 * nDaySkip - - opt = { - "segsize": nDayWin, - "noverlap": twoMonthOverlap, - "spd": spd, - "latitude_bounds": latBound, - "dosymmetries": True, - "rmvLowFrq": True, - } - - var = xr.open_mfdataset(glob.glob(f"{path}/{variable}_*.nc")).sel( - lat=slice(-15, 15), time=slice(f"{start_year}-01-01", f"{end_year}-12-31") - )[variable] - actual_start = var.time.dt.year.values[0] - actual_end = var.time.dt.year.values[-1] - - # Unit conversion - if var.name == "PRECT": - if var.attrs["units"] == "m/s" or var.attrs["units"] == "m s{-1}": - logger.info( - "\nBEFORE unit conversion: Max/min of data: " - + str(var.values.max()) - + " " - + str(var.values.min()) - ) - var.values = ( - var.values * 1000.0 * 86400.0 - ) # convert m/s to mm/d, do not alter metadata (yet) - var.attrs["units"] = "mm/d" # adjust metadata to reflect change in units - logger.info( - "\nAFTER unit conversion: Max/min of data: " - + str(var.values.max()) - + " " - + str(var.values.min()) - ) - - # Wavenumber Frequency Analysis - spec_all = wf_analysis(var, **opt) - return spec_all, str(actual_start), str(actual_end) - - def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalParameter: """Runs the wavenumber frequency analysis for tropical variability. @@ -168,7 +43,6 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara test_data = utils.dataset.Dataset(parameter, test=True) ref_data = utils.dataset.Dataset(parameter, ref=True) - for variable in parameter.variables: test, test_start, test_end = calculate_spectrum( parameter.test_data_path, @@ -236,3 +110,151 @@ def run_diag(parameter: TropicalSubseasonalParameter) -> TropicalSubseasonalPara ) return parameter + + +def calculate_spectrum(path, variable, start_year, end_year): + # latitude bounds for analysis + latBound = (-15, 15) + # SAMPLES PER DAY + spd = 1 + # Wheeler-Kiladis [WK] temporal window length (days) + nDayWin = 96 + # time (days) between temporal windows [segments] + nDaySkip = -60 + # negative means there will be overlapping temporal segments + twoMonthOverlap = -1 * nDaySkip + + opt = { + "segsize": nDayWin, + "noverlap": twoMonthOverlap, + "spd": spd, + "latitude_bounds": latBound, + "dosymmetries": True, + "rmvLowFrq": True, + } + # TODO the time subsetting and variable derivation should be replaced during cdat revamp + try: + var = xr.open_mfdataset(glob.glob(f"{path}/{variable}_*.nc")).sel( + lat=slice(-15, 15), time=slice(f"{start_year}-01-01", f"{end_year}-12-31") + )[variable] + actual_start = var.time.dt.year.values[0] + actual_end = var.time.dt.year.values[-1] + except OSError: + logger.info( + f"No files to open for {variable} within {start_year} and {end_year} from {path}." + ) + raise + # Unit conversion + if var.name == "PRECT": + if var.attrs["units"] == "m/s" or var.attrs["units"] == "m s{-1}": + logger.info( + "\nBEFORE unit conversion: Max/min of data: " + + str(var.values.max()) + + " " + + str(var.values.min()) + ) + var.values = ( + var.values * 1000.0 * 86400.0 + ) # convert m/s to mm/d, do not alter metadata (yet) + var.attrs["units"] = "mm/d" # adjust metadata to reflect change in units + logger.info( + "\nAFTER unit conversion: Max/min of data: " + + str(var.values.max()) + + " " + + str(var.values.min()) + ) + + # Wavenumber Frequency Analysis + spec_all = wf_analysis(var, **opt) + return spec_all, str(actual_start), str(actual_end) + + +def wf_analysis(x, **kwargs): + """Calculate zonal wavenumber-frequency power spectra of x. + + Input: + ---------- + x : xr.DataArray + The input variable with dimension (ntime, nlat, nlon) + **kwargs : dict + A dictionary providing options for spectrum calculation + Example: + # Latitude bounds + latBound = (-15, 15) + # SAMPLES PER DAY + spd = 1 + # Wheeler-Kiladis [WK] temporal window length (days) + nDayWin = 96 + # time (days) between temporal windows [segments] + nDaySkip = -60 + # negative means there will be overlapping temporal segments + twoMonthOverlap = -1 * nDaySkip + + opt = { + "segsize": nDayWin, + "noverlap": twoMonthOverlap, + "spd": spd, + "latitude_bounds": latBound, + "dosymmetries": True, + "rmvLowFrq": True, + } + + Returns + ------- + spectra: xr.DataArray + The returned spectra are: + spec_raw_sym: Raw (non-normalized) power spectrum of the component of x that is symmetric about the equator. + spec_raw_asy: Raw (non-normalized) power spectrum of the component of x that is antisymmetric about the equator. + spec_norm_sym: Normalized (by a smoothed red-noise background spectrum) power spectrum of the component of x that is symmetric about the equator. + spec_norm_asy: Normalized (by a smoothed red-noise background spectrum) power spectrum of the component of x that is antisymmetric about the equator. + + The NCL version of 'wkSpaceTime' smooths the symmetric and antisymmetric components + along the frequency dimension using a 1-2-1 filter once. + + """ + # Get the "raw" spectral power + # OPTIONAL kwargs: + # segsize, noverlap, spd, latitude_bounds (tuple: (south, north)), dosymmetries, rmvLowFrq + + z2 = wf.spacetime_power(x, **kwargs) + z2avg = z2.mean(dim="component") + z2.loc[{"frequency": 0}] = np.nan # get rid of spurious power at \nu = 0 (mean) + + # Following NCL's wkSpaceTime, apply one pass of a 1-2-1 filter along the frequency + # domain to the raw (non-normalized) spectra/um. + # Do not use 0 frequency when smoothing here. + # Use weights that sum to 1 to ensure that smoothing is conservative. + z2s = wf.smoothFrq121(z2, 1) + + # The background is supposed to be derived from both symmetric & antisymmetric + # Inputs to the background spectrum calculation should be z2avg + background = wf.smoothBackground_wavefreq(z2avg) + # separate components + spec_sym = z2s[0, ...] + spec_asy = z2s[1, ...] + # normalize: Following NCL's wkSpaceTime, use lightly smoothed version of spectra/um + # as numerator + nspec_sym = spec_sym / background + nspec_asy = spec_asy / background + + spec = xr.merge( + [ + spec_sym.rename("spec_raw_sym"), + spec_asy.rename("spec_raw_asy"), + nspec_sym.rename("spec_norm_sym"), + nspec_asy.rename("spec_norm_asy"), + background.rename("spec_background"), + ], + compat="override", + ) + spec_all = spec.drop("component") + spec_all["spec_raw_sym"].attrs = {"component": "symmetric", "type": "raw"} + spec_all["spec_raw_asy"].attrs = {"component": "antisymmetric", "type": "raw"} + spec_all["spec_norm_sym"].attrs = {"component": "symmetric", "type": "normalized"} + spec_all["spec_norm_asy"].attrs = { + "component": "antisymmetric", + "type": "normalized", + } + spec_all["spec_background"].attrs = {"component": "", "type": "background"} + + return spec_all From 19219651eb0b5e4955041c2148e269448940b3a0 Mon Sep 17 00:00:00 2001 From: chengzhuzhang Date: Thu, 21 Mar 2024 12:19:34 -0700 Subject: [PATCH 39/46] address code review; clean up --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3130b6ce0..6862ed03c 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ E3SM Diags is modeled after the National Center for Atmospheric Research (NCAR) ### New Feature added during v2 development | Feature name
(set name) | Brief Introduction | Developers Contributors* | Released version | |--- | ---------------- | --- | --- | +| Wavenumber frequency analysis (tropical_subseasonal) | The wavenumber frequency analysis (Wheeler-Kiladis Diagram) for Tropical subseasonal analysis | Jim Benedict, Brian Medeiros, Jill Zhang | 2.11.0 | | T5050 diagnostic /Mixed phase partition (mp_partition) | Temperature at which cloud top is 50% ice, 50% liquid, following McCoy et al. (2015). | Yuying Zhang, Jill Zhang, Jiwen Fan, Yunpeng Shan | 2.9.0 | | ARM diagnostics v3 (arm_diags) | Enhanced ARM diagnostics with new aerosol-cloud-interaction and aerosol activation metrics | Xiaojian Zheng, Jill Zhang, Cheng Tao, Shaocheng Xie | 2.9.0 | | Aerosol budget diagnostics (aerosol_budget) | Global mean burdens, source/sink budgets and lifetimes for aerosol species. | Jill Zhang, Susannah Burrows, Naser Mahfouz, Tom Vo, Kai Zhang, Taufiq Hassan Mozumder, Xue Zheng, Ziming Ke, Yan Feng, Manish Shrivastava, Hailong Wang, Mingxuan Wu | 2.8.0 | From d1f4b94d9194a79f8d4ff21a42932ebcac3c501e Mon Sep 17 00:00:00 2001 From: chengzhuzhang Date: Thu, 21 Mar 2024 12:20:17 -0700 Subject: [PATCH 40/46] Revert "address code review; clean up" This reverts commit 19219651eb0b5e4955041c2148e269448940b3a0. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 6862ed03c..3130b6ce0 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,6 @@ E3SM Diags is modeled after the National Center for Atmospheric Research (NCAR) ### New Feature added during v2 development | Feature name
(set name) | Brief Introduction | Developers Contributors* | Released version | |--- | ---------------- | --- | --- | -| Wavenumber frequency analysis (tropical_subseasonal) | The wavenumber frequency analysis (Wheeler-Kiladis Diagram) for Tropical subseasonal analysis | Jim Benedict, Brian Medeiros, Jill Zhang | 2.11.0 | | T5050 diagnostic /Mixed phase partition (mp_partition) | Temperature at which cloud top is 50% ice, 50% liquid, following McCoy et al. (2015). | Yuying Zhang, Jill Zhang, Jiwen Fan, Yunpeng Shan | 2.9.0 | | ARM diagnostics v3 (arm_diags) | Enhanced ARM diagnostics with new aerosol-cloud-interaction and aerosol activation metrics | Xiaojian Zheng, Jill Zhang, Cheng Tao, Shaocheng Xie | 2.9.0 | | Aerosol budget diagnostics (aerosol_budget) | Global mean burdens, source/sink budgets and lifetimes for aerosol species. | Jill Zhang, Susannah Burrows, Naser Mahfouz, Tom Vo, Kai Zhang, Taufiq Hassan Mozumder, Xue Zheng, Ziming Ke, Yan Feng, Manish Shrivastava, Hailong Wang, Mingxuan Wu | 2.8.0 | From a8bcf9e9c5e9740d3569fe9032d57b19dccae33a Mon Sep 17 00:00:00 2001 From: chengzhuzhang Date: Thu, 21 Mar 2024 12:33:11 -0700 Subject: [PATCH 41/46] update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3130b6ce0..528820ac1 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,8 @@ E3SM Diags is modeled after the National Center for Atmospheric Research (NCAR) ### New Feature added during v2 development | Feature name
(set name) | Brief Introduction | Developers Contributors* | Released version | |--- | ---------------- | --- | --- | +| Wavenumber frequency analysis (tropical_subseasonal) | The wavenumber frequency analysis (Wheeler-Kiladis Diagram) for Tropical subseasonal analysis | Jim Benedict, Brian Medeiros, Jill Zhang, Tom Vo | 2.11.0 + | T5050 diagnostic /Mixed phase partition (mp_partition) | Temperature at which cloud top is 50% ice, 50% liquid, following McCoy et al. (2015). | Yuying Zhang, Jill Zhang, Jiwen Fan, Yunpeng Shan | 2.9.0 | | ARM diagnostics v3 (arm_diags) | Enhanced ARM diagnostics with new aerosol-cloud-interaction and aerosol activation metrics | Xiaojian Zheng, Jill Zhang, Cheng Tao, Shaocheng Xie | 2.9.0 | | Aerosol budget diagnostics (aerosol_budget) | Global mean burdens, source/sink budgets and lifetimes for aerosol species. | Jill Zhang, Susannah Burrows, Naser Mahfouz, Tom Vo, Kai Zhang, Taufiq Hassan Mozumder, Xue Zheng, Ziming Ke, Yan Feng, Manish Shrivastava, Hailong Wang, Mingxuan Wu | 2.8.0 | From 78d4fb85d3b7574eb5518d4397c912dc69e3e804 Mon Sep 17 00:00:00 2001 From: chengzhuzhang Date: Tue, 26 Mar 2024 18:25:00 -0500 Subject: [PATCH 42/46] update mp_partition with similar time subsetting --- e3sm_diags/driver/mp_partition_driver.py | 71 +++++++++++++++--------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/e3sm_diags/driver/mp_partition_driver.py b/e3sm_diags/driver/mp_partition_driver.py index 66d154f9b..d1e3da56c 100644 --- a/e3sm_diags/driver/mp_partition_driver.py +++ b/e3sm_diags/driver/mp_partition_driver.py @@ -89,19 +89,28 @@ def run_diag(parameter: MPpartitionParameter) -> MPpartitionParameter: # cliq = test_data.get_timeseries_variable("CLDLIQ")(cdutil.region.domain(latitude=(-70.0, -30, "ccb"))) test_data_path = parameter.test_data_path - # xr.open_mfdataset() can accept an explicit list of files. - landfrac = xr.open_mfdataset(glob.glob(f"{test_data_path}/LANDFRAC_*")).sel( - lat=slice(-70, -30) - )["LANDFRAC"] - temp = xr.open_mfdataset(glob.glob(f"{test_data_path}/T_*.nc")).sel( - lat=slice(-70, -30) - )["T"] - cice = xr.open_mfdataset(glob.glob(f"{test_data_path}/CLDICE_*.nc")).sel( - lat=slice(-70, -30) - )["CLDICE"] - cliq = xr.open_mfdataset(glob.glob(f"{test_data_path}/CLDLIQ_*.nc")).sel( - lat=slice(-70, -30) - )["CLDLIQ"] + start_year = parameter.test_start_yr + end_year = parameter.test_end_yr + # TODO the time subsetting and variable derivation should be replaced during cdat revamp + try: + # xr.open_mfdataset() can accept an explicit list of files. + landfrac = xr.open_mfdataset(glob.glob(f"{test_data_path}/LANDFRAC_*")).sel( + lat=slice(-70, -30), time=slice(f"{start_year}-01-01", f"{end_year}-12-31") + )["LANDFRAC"] + temp = xr.open_mfdataset(glob.glob(f"{test_data_path}/T_*.nc")).sel( + lat=slice(-70, -30), time=slice(f"{start_year}-01-01", f"{end_year}-12-31") + )["T"] + cice = xr.open_mfdataset(glob.glob(f"{test_data_path}/CLDICE_*.nc")).sel( + lat=slice(-70, -30), time=slice(f"{start_year}-01-01", f"{end_year}-12-31") + )["CLDICE"] + cliq = xr.open_mfdataset(glob.glob(f"{test_data_path}/CLDLIQ_*.nc")).sel( + lat=slice(-70, -30), time=slice(f"{start_year}-01-01", f"{end_year}-12-31") + )["CLDLIQ"] + except OSError: + logger.info( + f"No files to open for variables within {start_year} and {end_year} from {test_data_path}." + ) + raise parameter.test_name_yrs = utils.general.get_name_and_yrs( parameter, test_data, season @@ -115,19 +124,31 @@ def run_diag(parameter: MPpartitionParameter) -> MPpartitionParameter: if run_type == "model-vs-model": ref_data = utils.dataset.Dataset(parameter, ref=True) ref_data_path = parameter.reference_data_path + start_year = parameter.ref_start_yr + end_year = parameter.ref_end_yr # xr.open_mfdataset() can accept an explicit list of files. - landfrac = xr.open_mfdataset(glob.glob(f"{ref_data_path}/LANDFRAC_*")).sel( - lat=slice(-70, -30) - )["LANDFRAC"] - temp = xr.open_mfdataset(glob.glob(f"{ref_data_path}/T_*.nc")).sel( - lat=slice(-70, -30) - )["T"] - cice = xr.open_mfdataset(glob.glob(f"{ref_data_path}/CLDICE_*.nc")).sel( - lat=slice(-70, -30) - )["CLDICE"] - cliq = xr.open_mfdataset(glob.glob(f"{ref_data_path}/CLDLIQ_*.nc")).sel( - lat=slice(-70, -30) - )["CLDLIQ"] + try: + landfrac = xr.open_mfdataset(glob.glob(f"{ref_data_path}/LANDFRAC_*")).sel( + lat=slice(-70, -30), + time=slice(f"{start_year}-01-01", f"{end_year}-12-31"), + )["LANDFRAC"] + temp = xr.open_mfdataset(glob.glob(f"{ref_data_path}/T_*.nc")).sel( + lat=slice(-70, -30), + time=slice(f"{start_year}-01-01", f"{end_year}-12-31"), + )["T"] + cice = xr.open_mfdataset(glob.glob(f"{ref_data_path}/CLDICE_*.nc")).sel( + lat=slice(-70, -30), + time=slice(f"{start_year}-01-01", f"{end_year}-12-31"), + )["CLDICE"] + cliq = xr.open_mfdataset(glob.glob(f"{ref_data_path}/CLDLIQ_*.nc")).sel( + lat=slice(-70, -30), + time=slice(f"{start_year}-01-01", f"{end_year}-12-31"), + )["CLDLIQ"] + except OSError: + logger.info( + f"No files to open for variables within {start_year} and {end_year} from {ref_data_path}." + ) + raise # landfrac = ref_data.get_timeseries_variable("LANDFRAC")( # cdutil.region.domain(latitude=(-70.0, -30, "ccb")) From e334b15f0a627f62bdff2857e893214652f2b410 Mon Sep 17 00:00:00 2001 From: Jim Benedict Date: Fri, 29 Mar 2024 14:32:49 -0700 Subject: [PATCH 43/46] updated original zwf_plot.py to allow dispersion curvers to cross zero and add missing curves to most plots --- .../zwf/zwf_plot.py | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/auxiliary_tools/tropical_subseasonal_diags/zwf/zwf_plot.py b/auxiliary_tools/tropical_subseasonal_diags/zwf/zwf_plot.py index 5357b8ba0..2eec5512b 100755 --- a/auxiliary_tools/tropical_subseasonal_diags/zwf/zwf_plot.py +++ b/auxiliary_tools/tropical_subseasonal_diags/zwf/zwf_plot.py @@ -93,6 +93,10 @@ def plot_raw_symmetric_spectrum(s, ofil=None, dataDesc=None, clevs=None, cmapSpe # get data for dispersion curves: swfreq,swwn = wf.genDispersionCurves(Ahe=equivDepths) # swfreq.shape # -->(6, 3, 50) + # For n=1 ER waves, allow dispersion curves to touch 0 -- this is for plot aesthetics only + for i in range(0,3): # loop 0-->2 for the assumed 3 shallow water dispersion curves for ER waves + indMinPosFrqER = np.where(swwn[3,i,:] >= 0., swwn[3,i,:], 1e20).argmin() # index of swwn for least positive wn + swwn[3,i,indMinPosFrqER],swfreq[3,i,indMinPosFrqER] = 0.,0. # this sets ER's frequencies to 0. at wavenumber 0. swf = np.where(swfreq == 1e20, np.nan, swfreq) swk = np.where(swwn == 1e20, np.nan, swwn) @@ -145,6 +149,49 @@ def plot_raw_symmetric_spectrum(s, ofil=None, dataDesc=None, clevs=None, cmapSpe ax.set_title(f"{varName}: Log{{Sum(Power) from 15°S-15°N}}\n") # Version w/o LaTeX ax.set_title(sourceID, loc='left') ax.set_title("Symmetric", loc='right') + + if(not do_zoom): # For now, only add equivalent depth and shallow water curve labels -NOT- to zoomed-in plots + # Shallow water dispersion curve line labels: See https://matplotlib.org/stable/tutorials/text/text_intro.html + # n=1 ER dispersion curve labels + iwave, ih = 3, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -11.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + iwave, ih = 3, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -9.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + iwave, ih = 3, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + ax.text(-7.,0.10,'n=1 ER',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + + # Kelvin dispersion curve labels + iwave, ih = 4, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + iwave, ih = 4, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 10.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + iwave, ih = 4, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 14.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + ax.text(6.,0.13,'Kelvin',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + + # IG dispersion curve labels + iwave, ih = 5, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + iwave, ih = 5, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + iwave, ih = 5, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 0.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + ax.text(-10.,0.48,'n=1 WIG',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + ax.text(5.,0.48,'n=1 EIG',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + + # MJO label + ax.text(6.,0.0333,'MJO',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + plt.ylabel("Frequency (CPD)") plt.xlabel("Zonal wavenumber") plt.gcf().text(0.12, 0.03, "Westward", fontsize=11) @@ -302,6 +349,10 @@ def plot_raw_asymmetric_spectrum(s, ofil=None, dataDesc=None, clevs=None, cmapSp # get data for dispersion curves: swfreq,swwn = wf.genDispersionCurves(Ahe=equivDepths) # swfreq.shape # -->(6, 3, 50) + # "Connect" MRG and n=0 IG dispersion curves across wavenumber 0 -- this is for plot aesthetics only + for i in range(0,3): # loop 0-->2 for the assumed 3 shallow water dispersion curves for MRG and n=0 IG curves + indPosWNclosest0 = np.where(swwn[0,i,:] >= 0., swwn[0,i,:], 1e20).argmin() # index of swwn for positive wn closest to 0 + swfreq[0,i,indPosWNclosest0] = swfreq[1,i,indPosWNclosest0] # this sets MRG's frequencies at least positive wn to n=0 IG's frequencies at least positive wn swf = np.where(swfreq == 1e20, np.nan, swfreq) swk = np.where(swwn == 1e20, np.nan, swwn) @@ -341,6 +392,49 @@ def plot_raw_asymmetric_spectrum(s, ofil=None, dataDesc=None, clevs=None, cmapSp ax.set_title(f"{varName}: Log{{Sum(Power) from 15°S-15°N}}\n") # Version w/o LaTeX ax.set_title(sourceID, loc='left') ax.set_title("Antisymmetric", loc='right') + + if(not do_zoom): + # Shallow water dispersion curve line labels: See https://matplotlib.org/stable/tutorials/text/text_intro.html + # MRG dispersion curve labels -- SKIP LABELING EQUIVALENT DEPTHS FOR MRG WAVES AND ONLY LABEL FOR N=2 EIG WAVES, WHICH ARE POSTIVE-WAVENUMBER EXTENSIONS OF THE MRG CURVES +# iwave, ih = 0, 0 +# idxClose,valClose = find_nearest(swk[iwave,ih,:], 4.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] +# ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) +# iwave, ih = 0, 1 +# idxClose,valClose = find_nearest(swk[iwave,ih,:], 6.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] +# ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) +# iwave, ih = 0, 2 +# idxClose,valClose = find_nearest(swk[iwave,ih,:], 8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] +# ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + ax.text(-6.,0.18,'MRG',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + + # n=0 EIG dispersion curve labels + iwave, ih = 1, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 5.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + iwave, ih = 1, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + iwave, ih = 1, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], 8.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + ax.text(9.,0.48,'n=0 EIG',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + + # n=2 IG dispersion curve labels + iwave, ih = 2, 0 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -2.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + iwave, ih = 2, 1 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -2.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + iwave, ih = 2, 2 + idxClose,valClose = find_nearest(swk[iwave,ih,:], -2.) # Locate index of wavenumber closest to input value [and the actual (float) wavenumber value] + ax.text(valClose,swf[iwave,ih,idxClose],f'{equivDepths[ih]}',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + ax.text(-10.,0.65,'n=2 WIG',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + ax.text(8.,0.65,'n=2 EIG',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + + # MJO label + ax.text(3.,0.0333,'MJO',fontsize=9,verticalalignment='center',horizontalalignment='center',clip_on=True,bbox={'facecolor': 'white', 'edgecolor':'none', 'alpha': 0.7, 'pad': 0.0}) + plt.ylabel("Frequency (CPD)") plt.xlabel("Zonal wavenumber") plt.gcf().text(0.12, 0.03, "Westward", fontsize=11) @@ -372,6 +466,10 @@ def plot_normalized_asymmetric_spectrum(s, ofil=None, dataDesc=None, clevs=None, # get data for dispersion curves: swfreq,swwn = wf.genDispersionCurves(Ahe=equivDepths) # swfreq.shape # -->(6, 3, 50) + # "Connect" MRG and n=0 IG dispersion curves across wavenumber 0 -- this is for plot aesthetics only + for i in range(0,3): # loop 0-->2 for the assumed 3 shallow water dispersion curves for MRG and n=0 IG curves + indPosWNclosest0 = np.where(swwn[0,i,:] >= 0., swwn[0,i,:], 1e20).argmin() # index of swwn for positive wn closest to 0 + swfreq[0,i,indPosWNclosest0] = swfreq[1,i,indPosWNclosest0] # this sets MRG's frequencies at least positive wn to n=0 IG's frequencies at least positive wn swf = np.where(swfreq == 1e20, np.nan, swfreq) swk = np.where(swwn == 1e20, np.nan, swwn) @@ -490,6 +588,10 @@ def plot_background_spectrum(s, ofil=None, dataDesc=None, clevs=None, cmapSpec=' for i in range(0,3): # loop 0-->2 for the assumed 3 shallow water dispersion curves for ER waves indMinPosFrqER = np.where(swwn[3,i,:] >= 0., swwn[3,i,:], 1e20).argmin() # index of swwn for least positive wn swwn[3,i,indMinPosFrqER],swfreq[3,i,indMinPosFrqER] = 0.,0. # this sets ER's frequencies to 0. at wavenumber 0. + # "Connect" MRG and n=0 IG dispersion curves across wavenumber 0 -- this is for plot aesthetics only + for i in range(0,3): # loop 0-->2 for the assumed 3 shallow water dispersion curves for MRG and n=0 IG curves + indPosWNclosest0 = np.where(swwn[0,i,:] >= 0., swwn[0,i,:], 1e20).argmin() # index of swwn for positive wn closest to 0 + swfreq[0,i,indPosWNclosest0] = swfreq[1,i,indPosWNclosest0] # this sets MRG's frequencies at least positive wn to n=0 IG's frequencies at least positive wn swf = np.where(swfreq == 1e20, np.nan, swfreq) swk = np.where(swwn == 1e20, np.nan, swwn) From 0db4fa0977de1cea1da42521ec0014da3a9c0407 Mon Sep 17 00:00:00 2001 From: chengzhuzhang Date: Tue, 9 Apr 2024 13:29:12 -0500 Subject: [PATCH 44/46] update dispersion lines labels --- .../plot/cartopy/tropical_subseasonal_plot.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py index 84617ab24..801cfaa17 100755 --- a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py +++ b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py @@ -312,8 +312,16 @@ def _wave_frequency_plot( # noqa: C901 0.0, ) # this sets ER's frequencies to 0. at wavenumber 0. - # if "spec_norm_sys" in var.name: # with dispersion curves to plot - # if "spec_norm_asy" in var.name: + # "Connect" MRG and n=0 IG dispersion curves across wavenumber 0 -- this is for plot aesthetics only + for i in range( + 0, 3 + ): # loop 0-->2 for the assumed 3 shallow water dispersion curves for MRG and n=0 IG curves + indPosWNclosest0 = np.where( + swwn[0, i, :] >= 0.0, swwn[0, i, :], 1e20 + ).argmin() # index of swwn for positive wn closest to 0 + swfreq[0, i, indPosWNclosest0] = swfreq[ + 1, i, indPosWNclosest0 + ] # this sets MRG's frequencies at least positive wn to n=0 IG's frequencies at least positive wn swf = np.where(swfreq == 1e20, np.nan, swfreq) swk = np.where(swwn == 1e20, np.nan, swwn) @@ -479,7 +487,7 @@ def _wave_frequency_plot( # noqa: C901 } if "sym" in var.name: wave_types = [3, 4, 5] - if "norm" in var.name: + if "norm" in var.name and not do_zoom: # Shallow water dispersion curve line labels: See https://matplotlib.org/stable/tutorials/text/text_intro.html # n=1 ER dispersion curve labels iwave, ih = 3, 0 @@ -558,7 +566,9 @@ def _wave_frequency_plot( # noqa: C901 ax.text(6.0, 0.0333, "MJO", text_opt) else: wave_types = [0, 1, 2] - if "norm" in var.name: + + if "norm" in var.name and not do_zoom: + ax.text(-6.0, 0.18, "MRG", text_opt) # n=0 EIG dispersion curve labels iwave, ih = 1, 0 idxClose, valClose = find_nearest( From d87c554111f4e726c6fe041bafda536ad9e804c9 Mon Sep 17 00:00:00 2001 From: chengzhuzhang Date: Tue, 9 Apr 2024 14:42:08 -0500 Subject: [PATCH 45/46] update contour map colorbars --- .../run_tropical_subseasonal.py | 2 +- .../plot/cartopy/tropical_subseasonal_plot.py | 71 ++++++++++++++++++- 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py b/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py index 327fceeff..ef258a4e3 100644 --- a/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py +++ b/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py @@ -11,7 +11,7 @@ prefix = '/Users/zhang40/Documents/repos/e3sm_diags/auxiliary_tools/tropical_subseasonal_diags/data1' prefix = '/global/cfs/cdirs/e3sm/www/chengzhu/tests/tropical_diags_subsetting' -param.results_dir = os.path.join(prefix, 'tropical_variability_model_obs') +param.results_dir = os.path.join(prefix, 'tropical_variability_model_obs_refine') param.run_type = "model_vs_model" param.ref_name = 'E3SMv2' param.test_start_yr = '2000' diff --git a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py index 801cfaa17..70247397f 100755 --- a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py +++ b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py @@ -50,6 +50,23 @@ 0.1, 0.2, ) +CONTOUR_LEVS_SPEC_RAW_PRECT = ( + -1.4, + -1.2, + -1, + -0.9, + -0.8, + -0.7, + -0.6, + -0.5, + -0.4, + -0.3, + -0.2, + -0.1, + 0, + 0.2, + 0.4, +) CONTOUR_LEVS_SPEC_RAW_FLUT = ( -0.4, @@ -67,6 +84,23 @@ 2.2, 2.4, ) +CONTOUR_LEVS_SPEC_RAW_U850 = ( + -1.6, + -1.4, + -1.2, + -1.0, + -0.8, + -0.6, + -0.5, + -0.4, + -0.3, + -0.2, + -0.1, + 0.0, + 0.2, + 0.4, + 0.6, +) CMAP_SPEC_RAW = [ "white", @@ -103,6 +137,24 @@ 2.7, 3.0, ) +CONTOUR_LEVS_SPEC_NORM_PRECT = CONTOUR_LEVS_SPEC_NORM +CONTOUR_LEVS_SPEC_NORM_FLUT = CONTOUR_LEVS_SPEC_NORM + +CONTOUR_LEVS_SPEC_NORM_U850 = ( + 0.9, + 1.0, + 1.1, + 1.2, + 1.3, + 1.4, + 1.5, + 1.7, + 1.9, + 2.3, + 2.7, + 3.3, + 3.9, +) CMAP_SPEC_NORM = [ "white", @@ -374,13 +426,24 @@ def _wave_frequency_plot( # noqa: C901 # for test and ref: if subplot_num < 2: if "spec_norm" in var.name: - contour_level_spec = CONTOUR_LEVS_SPEC_NORM + if varName == "FLUT": + contour_level_spec = CONTOUR_LEVS_SPEC_NORM_FLUT + elif varName == "PRECT": + contour_level_spec = CONTOUR_LEVS_SPEC_NORM_PRECT + elif varName == "U850": + contour_level_spec = CONTOUR_LEVS_SPEC_NORM_U850 + else: + contour_level_spec = CONTOUR_LEVS_SPEC_NORM cmapSpecUse, normSpecUse = create_colormap_clevs( - CMAP_SPEC_NORM, CONTOUR_LEVS_SPEC_NORM + CMAP_SPEC_NORM, contour_level_spec ) else: if varName == "FLUT": contour_level_spec = CONTOUR_LEVS_SPEC_RAW_FLUT # type: ignore + elif varName == "PRECT": + contour_level_spec = CONTOUR_LEVS_SPEC_RAW_PRECT # type: ignore + elif varName == "U850": + contour_level_spec = CONTOUR_LEVS_SPEC_RAW_U850 # type: ignore else: contour_level_spec = CONTOUR_LEVS_SPEC_RAW # type: ignore cmapSpecUse, normSpecUse = create_colormap_clevs( @@ -395,6 +458,8 @@ def _wave_frequency_plot( # noqa: C901 norm=normSpecUse, extend="both", ) + cbar = fig.colorbar(img) + cbar.set_ticks(contour_level_spec[1:-1]) img2 = ax.contour( kmesh0, vmesh0, @@ -423,6 +488,8 @@ def _wave_frequency_plot( # noqa: C901 cmap=cmapSpecUse, extend="both", ) + cbar = fig.colorbar(img) + cbar.set_ticks(contour_level_spec[1:-1]) img2 = ax.contour( # noqa: F841 kmesh0, vmesh0, From 9fff78dd28221ef51d0301646996521ebfd73c7d Mon Sep 17 00:00:00 2001 From: ChengzhuZhang Date: Tue, 9 Apr 2024 17:09:31 -0700 Subject: [PATCH 46/46] fix colarbar try2 --- .../run_tropical_subseasonal.py | 4 ++-- e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py b/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py index ef258a4e3..45b6d6f61 100644 --- a/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py +++ b/auxiliary_tools/tropical_subseasonal_diags/run_tropical_subseasonal.py @@ -12,8 +12,8 @@ prefix = '/global/cfs/cdirs/e3sm/www/chengzhu/tests/tropical_diags_subsetting' param.results_dir = os.path.join(prefix, 'tropical_variability_model_obs_refine') -param.run_type = "model_vs_model" -param.ref_name = 'E3SMv2' +#param.run_type = "model_vs_model" +#param.ref_name = 'E3SMv2' param.test_start_yr = '2000' param.test_end_yr = '2000' param.ref_start_yr = '2001' diff --git a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py index 70247397f..6f4a35b5a 100755 --- a/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py +++ b/e3sm_diags/plot/cartopy/tropical_subseasonal_plot.py @@ -458,8 +458,11 @@ def _wave_frequency_plot( # noqa: C901 norm=normSpecUse, extend="both", ) + # Manually include all levels on colorbar cbar = fig.colorbar(img) - cbar.set_ticks(contour_level_spec[1:-1]) + cbar.set_ticks(contour_level_spec) + cbar.ax.set_yticklabels(contour_level_spec) + img2 = ax.contour( kmesh0, vmesh0, @@ -473,7 +476,6 @@ def _wave_frequency_plot( # noqa: C901 # for diff ratio if subplot_num == 2: - # TODO refine color bar if "spec_norm" in var.name: contour_level_spec = CONTOUR_LEVS_SPEC_NORM_DIFF # type: ignore else: @@ -489,7 +491,9 @@ def _wave_frequency_plot( # noqa: C901 extend="both", ) cbar = fig.colorbar(img) - cbar.set_ticks(contour_level_spec[1:-1]) + cbar.set_ticks(contour_level_spec) + cbar.ax.set_yticklabels(contour_level_spec) + img2 = ax.contour( # noqa: F841 kmesh0, vmesh0, @@ -714,7 +718,6 @@ def _wave_frequency_plot( # noqa: C901 "Eastward", fontsize=11, ) - fig.colorbar(img) def plot(