Source code for ufs_da_diagnostics.spectra.spectra_analysis_tiles

#!/usr/bin/env python3
"""
ANA–INC Spectral Diagnostics Driver
===================================

This script performs spectral diagnostics comparing **analysis increments**
from two experiments (CTRL vs EXP). It is the backend for the CLI entry point:

    ufsda-spectra-ana-inc --yaml spectra_ana_inc.yaml

The workflow:

1. Read YAML configuration
2. Load increment fields for CTRL and EXP
3. Regrid cubed-sphere tiles → global lat/lon
4. Compute isotropic spectra for selected variables and levels
5. Generate comparison plots:
   - CTRL spectrum
   - EXP spectrum
   - CTRL vs EXP comparison
   - Spectral ratio (EXP / CTRL)
   - Optional NICAS length-scale overlay

This driver uses:
- ``SpectraCore`` for computation
- ``SpectraPlotter`` for visualization
"""

import argparse
import yaml
import os

from .spectra_core import SpectraCore
from ..plots.spectra_plots import SpectraPlotter


[docs] def main(): """Run ANA–INC spectral diagnostics from a YAML configuration file. This function implements the full workflow for comparing analysis increments between two experiments (CTRL vs EXP). It is invoked by the CLI entry point ``ufsda-spectra-ana-inc``. YAML Configuration ------------------ Expected structure: .. code-block:: yaml experiments: - name: ctrl prefix: "/path/to/ctrl/atminc.tile" - name: exp prefix: "/path/to/exp/atminc.tile" grid: prefix: "/path/to/C96_grid.tile" vars: [u_inc, v_inc, T_inc] levels: [126, 75, 50] output_dir: "./spectra_ana_inc" nicas_length_scale: 120000 # optional Parameters ---------- None Returns ------- None All output is written to PNG files in the configured output directory. Notes ----- - This driver always loads **127 vertical levels** internally, even if only a subset is plotted. - ``SpectraCore`` performs all computation; this script only orchestrates workflow and plotting. - The NICAS length scale (if provided) is passed through to the plotter for optional overlay. """ parser = argparse.ArgumentParser(description="FV3-JEDI Spectral Diagnostics (Tiles → Lat/Lon)") parser.add_argument("--config", "--yaml", dest="config", required=True, help="YAML configuration file") args = parser.parse_args() with open(args.config, "r") as f: cfg = yaml.safe_load(f) vars_list = cfg["vars"] levels = cfg["levels"] exps = cfg["experiments"] outdir = cfg.get("output_dir", "./plot-outputs") nicas_L = cfg.get("nicas_length_scale", None) if "grid" in cfg and "prefix" in cfg["grid"]: grid_prefix = cfg["grid"]["prefix"] else: grid_prefix = cfg["grid_prefix"] os.makedirs(outdir, exist_ok=True) ctrl = exps[0] exp = exps[1] ctrl_name = ctrl["name"] ctrl_prefix = ctrl["prefix"] exp_name = exp["name"] exp_prefix = exp["prefix"] print("\n==============================================") print(" FV3-JEDI Spectral Diagnostics (Tiles → Lat/Lon)") print("==============================================") print(f" CTRL: {ctrl_name}{ctrl_prefix}") print(f" EXP: {exp_name}{exp_prefix}") print(f" Vars: {vars_list}") print(f" Levels: {levels}") print(f" Grid prefix: {grid_prefix}") print(f" Output dir: {outdir}") print("==============================================\n") spec_plotter = SpectraPlotter() for var in vars_list: print(f"\n=== Processing variable: {var} ===") core = SpectraCore(varname=var, grid_prefix=grid_prefix) print("Loading global fields for all 127 levels...") core.load_fields(ctrl_prefix, exp_prefix, nlevels=127) for lev in levels: print(f" → Level {lev}") outname = ( f"{outdir}/" f"{var}_{ctrl_name}_vs_{exp_name}_spectra_L{lev}.png" ) spec_plotter.plot_spectra( core, lev, ctrl_name, exp_name, fname=outname, nicas_length_scale=nicas_L ) print("\nAll spectral diagnostics complete.\n")
if __name__ == "__main__": main()