diff --git a/utils/soca/fig_gallery/README.md b/utils/soca/fig_gallery/README.md new file mode 100644 index 000000000..d021a7d81 --- /dev/null +++ b/utils/soca/fig_gallery/README.md @@ -0,0 +1,38 @@ +## How to generate the EVA and State space figures + +#### Create a scratch place to run `run_vrfy.py`. This script will generate a bunch of sbatch scripts and logs. +``` +mkdir /somewhere/scratch +cd /somewhere/scratch +ln -s /path/to/run_vrfy.py . # to be sorted out properly in the future +cp /path/to/vrfy_config.yaml . +module use ... +module load EVA/.... +``` +--- +#### Edit `vrfy_config.yaml` +It's actually read as a jinja template to render `pslot` if necessary. Anything that is a templated variable in `vrfy_jobcard.sh.j2` can be added to the yaml below. +```yaml +pslot: "nomlb" +start_pdy: '20210701' +end_pdy: '20210701' +cycs: ["00", "06", "12", "18"] +run: "gdas" +homegdas: "/work2/noaa/da/gvernier/runs/mlb/GDASApp" +base_exp_path: "/work2/noaa/da/gvernier/runs/mlb/{{ pslot }}/COMROOT/{{ pslot }}" +plot_ensemble_b: "OFF" +plot_parametric_b: "OFF" +plot_background: "OFF" +plot_increment: "ON" +plot_analysis: "OFF" +eva_plots: "ON" +qos: "batch" +hpc: "hercules" +eva_module: "EVA/orion" +``` + +--- +#### Run the application +```python run_vrfy.py vrfy_config.yaml``` +This will generate and submit the job cards for all the **cycles** defined by `cycs`, from `start_pdy` to `end_pdy`. + diff --git a/utils/soca/fig_gallery/run_marine_analysis_vrfy_manual.job b/utils/soca/fig_gallery/run_marine_analysis_vrfy_manual.job deleted file mode 100644 index c9b9145cb..000000000 --- a/utils/soca/fig_gallery/run_marine_analysis_vrfy_manual.job +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/bash -#SBATCH --job-name=marine_vrfy # Assign a name to the job (customize as needed) -#SBATCH --account=da-cpu -#SBATCH --qos=debug -#SBATCH -A da-cpu -#SBATCH --output=run_marine_vrfy_analysis.out -#SBATCH --nodes=1 # Request 1 node -#SBATCH --ntasks=40 # Request 40 total tasks (processors across nodes) -#SBATCH --partition=hera # Specify the partition (cluster) named "hera" -#SBATCH --cpus-per-task=1 # Set 1 CPU per task (equivalent to ppn=40 and tpp=1) -#SBATCH --mem=24GB # Request 24GB of memory -#SBATCH --time=00:30:00 # Set the walltime limit to 30 minutes - -# Define Your HOMEgfs -export HOMEgfs="/scratch1/NCEPDEV/da/Mindo.Choi/wk_01152025/global-workflow/" - -# Define Base_Direcrory (to the achieves for v2) -base_dir="/scratch1/NCEPDEV/global/John.Steffen/hpss_arch/marine_test_112024_v2" - -# Define Target Date and cycle -tdate="20210701" -cyc="00" - -# Load EVA module -module purge -module use ${HOMEgfs}sorc/gdas.cd/modulefiles -module load EVA/hera - -# Set PYTHONPATH using HOMEgfs -export PYTHONPATH="${HOMEgfs}sorc/gdas.cd/ush/:\ -${HOMEgfs}sorc/gdas.cd/ush/eva/:\ -${HOMEgfs}sorc/gdas.cd/ush/soca/:\ -$PYTHONPATH" - -# Set flags to control plotConfig in the Python script -export RUN_ENSENBLE_ANALYSIS=OFF # Check if ensemble run is ON -export RUN_BACKGROUND_ERROR_ANALYSIS=ON -export RUN_BACKGROUND_ANALYSIS=ON -export RUN_INCREMENT_ANALYSIS=ON -export RUN_EVA_ANALYSIS=ON - -# Define and export the environment variables -# Calculate bcyc and gcyc -bcyc=$(printf "%02d" $(( (10#$cyc - 3 + 24) % 24 ))) -gcyc=$(printf "%02d" $(( (10#$cyc - 6 + 24) % 24 ))) - -# Determine PDY_PREV if cyc is "00" -if [ "$cyc" == "00" ]; then - PDY_PREV=$(date -d "$tdate -1 day" +%Y%m%d) -else - PDY_PREV="$tdate" -fi - -# Export the necessary environment variables -export cyc="$cyc" -export bcyc="$bcyc" -export gcyc="$gcyc" -export PDY="$tdate" -export PDY_PREV="$PDY_PREV" -export RUN="gdas" -export PSLOT="gdas_test" - -# Define and export environment variables with paths -export COM_OCEAN_ANALYSIS="${base_dir}/${tdate}${cyc}/${RUN}.${tdate}/${cyc}/analysis/ocean" -export COM_ICE_HISTORY_PREV="${base_dir}/${PDY_PREV}${gcyc}/${RUN}.${PDY_PREV}/${gcyc}/model/ice/history" -export COM_OCEAN_HISTORY_PREV="${base_dir}/${PDY_PREV}${gcyc}/${RUN}.${PDY_PREV}/${gcyc}/model/ocean/history" - -# Excute Marine Verify Analysis -python3 ${HOMEgfs}sorc/gdas.cd/utils/soca/fig_gallery/exgdas_global_marine_analysis_vrfy_manual.py diff --git a/utils/soca/fig_gallery/run_vrfy.py b/utils/soca/fig_gallery/run_vrfy.py new file mode 100644 index 000000000..bd99cdb22 --- /dev/null +++ b/utils/soca/fig_gallery/run_vrfy.py @@ -0,0 +1,71 @@ +from jinja2 import Template +import subprocess +from datetime import datetime, timedelta +import yaml +import sys +import copy +import os + +def iterate_pdy_range(start_pdy, end_pdy): + """Generate a range of dates in YYYYMMDD format.""" + start_date = datetime.strptime(start_pdy, "%Y%m%d") + end_date = datetime.strptime(end_pdy, "%Y%m%d") + current_date = start_date + + while current_date <= end_date: + yield current_date.strftime("%Y%m%d") + current_date += timedelta(days=1) + + +def generate_jobcard(template_path, output_path, context): + # Read the Jinja2 template file + with open(template_path, 'r') as file: + template_content = file.read() + + # Create a Jinja2 template object + template = Template(template_content) + + # Render the template with custom values + rendered_script = template.render(**context) + + # Write the rendered script to the output file + with open(output_path, 'w') as file: + file.write(rendered_script) + + print(f"Bash script generated at: {output_path}") + +# Example usage +if __name__ == "__main__": + + # Get the YAML configuration file name from the input argument + if len(sys.argv) != 2: + print("Usage: python run_vrfy.py ") + sys.exit(1) + + config_file = sys.argv[1] + + # Read the YAML template from the file + with open(config_file, "r") as file: + yaml_template = file.read() + + # Load the template YAML as a dictionary + template_dict = yaml.safe_load(yaml_template) + + # Render the template with Jinja2 + template = Template(yaml_template) + config = yaml.safe_load(template.render(pslot=template_dict["pslot"])) + + # Iterate over the date range + for pdy in iterate_pdy_range(config['start_pdy'], config['end_pdy']): + context = copy.deepcopy(config) + for cyc in config["cycs"]: + # Update the cycle's date + context.update({"pdy": pdy, "cyc": cyc}) + + # Prepare the job card + template_jobcard = os.path.join(context['homegdas'], 'utils', 'soca', 'fig_gallery', 'vrfy_jobcard.sh.j2') # Assumes a Jinja2 template file in the moegdas directory + jobcard = f"vrfy_jobcard.{context['pslot']}.{context['pdy']}.{context['cyc']}.sh" + generate_jobcard(template_jobcard, jobcard, context) + + # Submit the plotting job + subprocess.run(f"sbatch {jobcard}", shell=True) diff --git a/utils/soca/fig_gallery/vrfy_config.yaml b/utils/soca/fig_gallery/vrfy_config.yaml new file mode 100644 index 000000000..0df76cf88 --- /dev/null +++ b/utils/soca/fig_gallery/vrfy_config.yaml @@ -0,0 +1,16 @@ +pslot: "nomlb" +start_pdy: '20210701' +end_pdy: '20210701' +cycs: ["00", "06", "12", "18"] +run: "gdas" +homegdas: "/work2/noaa/da/gvernier/runs/mlb/GDASApp" +base_exp_path: "/work2/noaa/da/gvernier/runs/mlb/{{ pslot }}/COMROOT/{{ pslot }}" +plot_ensemble_b: "OFF" +plot_parametric_b: "OFF" +plot_background: "OFF" +plot_increment: "ON" +plot_analysis: "OFF" +eva_plots: "ON" +qos: "batch" +hpc: "hercules" +eva_module: "EVA/orion" diff --git a/utils/soca/fig_gallery/vrfy_jobcard.sh.j2 b/utils/soca/fig_gallery/vrfy_jobcard.sh.j2 new file mode 100644 index 000000000..59b8cdef2 --- /dev/null +++ b/utils/soca/fig_gallery/vrfy_jobcard.sh.j2 @@ -0,0 +1,58 @@ +#!/bin/bash +#SBATCH --job-name={{ job_name | default("marine_vrfy") }} # Assign a name to the job (customize as needed) +#SBATCH --account={{ account | default("da-cpu") }} +#SBATCH --qos={{ qos | default("debug") }} +{% set OUTPUT = "vrfy_jobcard." + pslot + "." + pdy + "." + cyc + ".log" %} +#SBATCH --output={{ OUTPUT }} +#SBATCH --nodes={{ nodes | default(1) }} # Request 1 node +#SBATCH --tasks={{ ntasks | default(20) }} # Request total tasks (processors across nodes) +{% set HPC = hpc | default("hera") %} +{% if HPC == "hera" %} +#SBATCH --partition={{ partition | default("hera") }} # Specify the partition (cluster) +{% endif %} +#SBATCH --mem={{ memory | default("24GB") }} # Request memory +#SBATCH --time={{ walltime | default("00:30:00") }} # Set the walltime limit + +# Define HOMEgdas +export HOMEgdas="{{ homegdas }}" + +# Load EVA module +module use ${HOMEgdas}/modulefiles +module load {{ eva_module | default("EVA/hera") }} + +# Set PYTHONPATH using HOMEgfs +export PYTHONPATH="${HOMEgdas}/ush/:\ +${HOMEgdas}/ush/eva/:\ +${HOMEgdas}/ush/soca/:\ +$PYTHONPATH" + +# Set flags to control plotConfig in the Python script +export PLOT_ENSEMBLE_B={{ plot_ensemble_b | default("OFF") }} +export PLOT_PARAMETRIC_B={{ plot_parametric_b | default("ON") }} +export PLOT_BACKGROUND={{ plot_background | default("ON") }} +export PLOT_INCREMENT={{ plot_increment | default("ON") }} +export PLOT_ANALYSIS={{ plot_analysis | default("OFF") }} +export PLOT_ANALYSIS={{ plot_analysis | default("OFF") }} +export EVA_PLOTS={{ eva_plots | default("OFF") }} + +# Define and export the environment variables +export cyc="{{ cyc }}" +export RUN="{{ run | default("gdas") }}" +export PSLOT="{{ pslot }}" +export PDY="{{ pdy }}" + +# Define base experiment path +BASE_EXP_PATH="{{ base_exp_path }}" # path to the gdas.pdy directory + +# Calculate previous date and cycle +PREV_CYC=$(date -d "{{ pdy }} {{ cyc }} -6 hours" +"%Y%m%d %H") +PREV_PDY=$(echo $PREV_CYC | cut -d' ' -f1) +PREV_CYC_HOUR=$(echo $PREV_CYC | cut -d' ' -f2) + +# Define and export environment variables with paths +export COM_OCEAN_ANALYSIS="${BASE_EXP_PATH}/gdas.{{ pdy }}/{{ cyc }}/analysis/ocean" +export COM_ICE_HISTORY_PREV="${BASE_EXP_PATH}/gdas.${PREV_PDY}/${PREV_CYC_HOUR}/model/ice/history" +export COM_OCEAN_HISTORY_PREV="${BASE_EXP_PATH}/gdas.${PREV_PDY}/${PREV_CYC_HOUR}/model/ocean/history" + +# Execute Marine Verify Analysis +python3 ${HOMEgdas}/utils/soca/fig_gallery/vrfy_script.py diff --git a/utils/soca/fig_gallery/exgdas_global_marine_analysis_vrfy_manual.py b/utils/soca/fig_gallery/vrfy_script.py similarity index 82% rename from utils/soca/fig_gallery/exgdas_global_marine_analysis_vrfy_manual.py rename to utils/soca/fig_gallery/vrfy_script.py index 5a3580543..ba6abd585 100644 --- a/utils/soca/fig_gallery/exgdas_global_marine_analysis_vrfy_manual.py +++ b/utils/soca/fig_gallery/vrfy_script.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - import os import numpy as np import gen_eva_obs_yaml @@ -13,51 +11,54 @@ com_ice_history = os.getenv('COM_ICE_HISTORY_PREV') com_ocean_history = os.getenv('COM_OCEAN_HISTORY_PREV') cyc = os.getenv('cyc') -bcyc = os.getenv('bcyc') -gcyc = os.getenv('gcyc') RUN = os.getenv('RUN') -# Construct the first potential grid_file path -vrfy_grid_file = os.path.join(comout, f'{RUN}.t'+bcyc+'z.ocngrid.nc') +bcyc = str((int(cyc) - 3) % 24).zfill(2) +gcyc = str((int(cyc) - 6) % 24).zfill(2) +grid_file = os.path.join(comout, f'{RUN}.t'+bcyc+'z.ocngrid.nc') +layer_file = os.path.join(comout, f'{RUN}.t'+cyc+'z.ocninc.nc') # Check if the file exists, then decide on grid_file -if os.path.exists(vrfy_grid_file): - grid_file = vrfy_grid_file -else: +if not os.path.exists(grid_file): + # TODO: Make this work on other HPC grid_file = '/scratch1/NCEPDEV/da/common/validation/vrfy/gdas.t21z.ocngrid.nc' -layer_file = os.path.join(comout, f'{RUN}.t'+cyc+'z.ocninc.nc') - # for eva diagdir = os.path.join(comout, 'diags') -HOMEgfs = os.getenv('HOMEgfs') +HOMEgdas = os.getenv('HOMEgdas') # Get flags from environment variables (set in the bash driver) -run_ensemble_analysis = os.getenv('RUN_ENSENBLE_ANALYSIS', 'OFF').upper() == 'ON' -run_bkgerr_analysis = os.getenv('RUN_BACKGROUND_ERROR_ANALYSIS', 'OFF').upper() == 'ON' -run_bkg_analysis = os.getenv('RUN_BACKGROUND_ANALYSIS', 'OFF').upper() == 'ON' -run_increment_analysis = os.getenv('RUN_INCREMENT_ANALYSIS', 'OFF').upper() == 'ON' -run_eva_analysis = os.getenv('RUN_EVA_ANALYSIS', 'OFF').upper() == 'ON' +plot_ensemble_b = os.getenv('PLOT_ENSEMBLE_B', 'OFF').upper() == 'ON' +plot_parametric_b = os.getenv('PLOT_PARAMETRIC_B', 'OFF').upper() == 'ON' +plot_background = os.getenv('PLOT_BACKGROUND', 'OFF').upper() == 'ON' +plot_increment = os.getenv('PLOT_INCREMENT', 'OFF').upper() == 'ON' +plot_analysis = os.getenv('PLOT_ANALYSIS', 'OFF').upper() == 'ON' +eva_plots = os.getenv('EVA_PLOTS', 'OFF').upper() == 'ON' # Initialize an empty list for the main config -configs = [plotConfig(grid_file=grid_file, - data_file=os.path.join(comout, f'{RUN}.t'+cyc+'z.ocnana.nc'), - variables_horiz={'ave_ssh': [-1.8, 1.3], - 'Temp': [-1.8, 34.0], - 'Salt': [32, 40]}, - colormap='nipy_spectral', - comout=os.path.join(comout, 'vrfy', 'ana')), # ocean surface analysis - plotConfig(grid_file=grid_file, - data_file=os.path.join(comout, f'{RUN}.t'+cyc+'z.iceana.nc'), - variables_horiz={'aice_h': [0.0, 1.0], - 'hi_h': [0.0, 4.0], - 'hs_h': [0.0, 0.5]}, - colormap='jet', - projs=['North', 'South', 'Global'], - comout=os.path.join(comout, 'vrfy', 'ana'))] # sea ice analysis - -# Define each config and add to main_config if its flag is True -if run_ensemble_analysis: +configs = [] + +# Analysis plotting configuration +if plot_analysis: + configs_ana = [plotConfig(grid_file=grid_file, + data_file=os.path.join(comout, f'{RUN}.t'+cyc+'z.ocnana.nc'), + variables_horiz={'ave_ssh': [-1.8, 1.3], + 'Temp': [-1.8, 34.0], + 'Salt': [32, 40]}, + colormap='nipy_spectral', + comout=os.path.join(comout, 'vrfy', 'ana')), # ocean surface analysis + plotConfig(grid_file=grid_file, + data_file=os.path.join(comout, f'{RUN}.t'+cyc+'z.iceana.nc'), + variables_horiz={'aice_h': [0.0, 1.0], + 'hi_h': [0.0, 4.0], + 'hs_h': [0.0, 0.5]}, + colormap='jet', + projs=['North', 'South', 'Global'], + comout=os.path.join(comout, 'vrfy', 'ana'))] # sea ice analysis + configs.extend(config_ana) + +# Ensemble B plotting configuration +if plot_ensemble_b: config_ens = [plotConfig(grid_file=grid_file, data_file=os.path.join(comout, f'{RUN}.t{cyc}z.ocn.recentering_error.nc'), variables_horiz={'ave_ssh': [-1, 1]}, @@ -85,7 +86,8 @@ comout=os.path.join(comout, 'vrfy', 'bkgerr', 'steric_explained_variance'))] # steric explained variance configs.extend(config_ens) -if run_bkgerr_analysis: +# Parametric B plotting configuration +if plot_parametric_b: config_bkgerr = [plotConfig(grid_file=grid_file, data_file=os.path.join(comout, os.path.pardir, os.path.pardir, 'bmatrix', 'ice', f'{RUN}.t'+cyc+'z.ice.bkgerr_stddev.nc'), @@ -94,11 +96,11 @@ 'hs_h': [0.0, 0.2]}, colormap='jet', projs=['North', 'South', 'Global'], - comout=os.path.join(comout, 'vrfy', 'bkgerr')), # sea ice baigerr stddev + comout=os.path.join(comout, 'vrfy', 'bkgerr')), # sea ice bkgerr stddev plotConfig(grid_file=grid_file, layer_file=layer_file, data_file=os.path.join(comout, os.path.pardir, os.path.pardir, - 'bmatrix', 'ocean', f'{RUN}.t'+cyc+'z.ocean.bkgerr_stddev.nc'), + 'bmatrix', 'ocean', f'{RUN}.t'+cyc+'z.ocean.bkgerr_stddev.nc'), lats=np.arange(-60, 60, 10), lons=np.arange(-280, 80, 30), variables_zonal={'Temp': [0, 2], @@ -118,7 +120,8 @@ comout=os.path.join(comout, 'vrfy', 'bkgerr'))] # ocn bkgerr stddev configs.extend(config_bkgerr) -if run_bkg_analysis: +# Background plotting configuration +if plot_background: config_bkg = [plotConfig(grid_file=grid_file, data_file=os.path.join(com_ice_history, f'{RUN}.ice.t{gcyc}z.inst.f006.nc'), variables_horiz={'aice_h': [0.0, 1.0], @@ -149,7 +152,8 @@ comout=os.path.join(comout, 'vrfy', 'bkg'))] configs.extend(config_bkg) -if run_increment_analysis: +# Increment plotting configuration +if plot_increment: config_incr = [plotConfig(grid_file=grid_file, layer_file=layer_file, data_file=os.path.join(comout, f'{RUN}.t'+cyc+'z.ocninc.nc'), @@ -185,13 +189,11 @@ configs.extend(config_incr) -# plot marine analysis vrfy - +# Plot the marine verification figures def plot_marine_vrfy(config): ocnvrfyPlotter = statePlotter(config) ocnvrfyPlotter.plot() - # Number of processes num_processes = len(configs) @@ -208,11 +210,9 @@ def plot_marine_vrfy(config): for process in processes: process.join() -####################################### -# eva plots -####################################### -if run_eva_analysis: - evadir = os.path.join(HOMEgfs, 'sorc', f'{RUN}.cd', 'ush', 'eva') +# Run EVA +if eva_plots: + evadir = os.path.join(HOMEgdas, 'ush', 'eva') marinetemplate = os.path.join(evadir, 'marine_gdas_plots.yaml') varyaml = os.path.join(comout, 'yaml', 'var.yaml') @@ -236,8 +236,7 @@ def plot_marine_vrfy(config): infile = os.path.join('evayamls', file) print('running eva on', infile) subprocess.run(['eva', infile], check=True) -else: - print("RUN_EVA_PLOT is set to OFF. Skipping EVA plot generation.") + ####################################### # calculate diag statistics #######################################