#!/usr/bin/env python # coding: utf-8 # # COMPASS Model Diagnostics # # A number of plots are provided from ADF. The full output from the stand-alone ADF configuration is in the link below. # # # Note that in standalone format (eg, CUPiD run not through CESM workflow), ADF is currently run by users via the following process: # 1) Install ADF and activate cupid-analysis # 2) Use the `CUPiD/helper_scripts/generate_adf_config_file.py` script to generate an ADF config file based on a CUPiD configuration file. # * `cd CUPiD/examples/external_diag_packages` # * `../../helper_scripts/generate_adf_config_file.py --cupid-config-loc . --adf-template ../../externals/ADF/config_amwg_default_plots.yaml --out-file ADF_config.yaml` # 3) Run ADF with the newly created configuration file. # * `../../externals/ADF/run_adf_diag ADF_config.yaml` # In[1]: adf_root = "." case_name = None base_case_name = None start_date = "" end_date = "" base_start_date = None base_end_date = None key_plots = None compare_obs = False var_list = ["T"] compass_root = "." runs_dict = {} # adf_root will be external_diag_packages/computed_notebooks/ADF/ # In[2]: # Parameters case_name = "b.e23_alpha17f.BLT1850.ne30_t232.092" base_case_name = "b.e30_beta02.BLT1850.ne30_t232.104" case_nickname = "BLT1850_92" base_case_nickname = "BLT1850_104" CESM_output_dir = "/glade/campaign/cesm/development/cross-wg/diagnostic_framework/CESM_output_for_testing" start_date = "0001-01-01" end_date = "0021-01-01" climo_start_date = "0001-01-01" climo_end_date = "0021-01-01" base_start_date = "0001-01-01" base_end_date = "0045-01-01" base_climo_start_date = "0001-01-01" base_climo_end_date = "0021-01-01" obs_data_dir = ( "/glade/campaign/cesm/development/cross-wg/diagnostic_framework/CUPiD_obs_data" ) ts_dir = None lc_kwargs = {"threads_per_worker": 1} serial = False compass_root = "/glade/work/richling/ADF/ADF_dev/Justin_ADF_2/ADF/adf_try_plots/f.e21.FHIST_BGC.f09_f09_mg17.SOCRATES_nudgeUVTfull/T/ERA5/RF13/" runs_dict = { "f.e21.FHIST_BGC.f09_f09_mg17.SOCRATES_nudgeUVTfull": [ "f.e21.FHIST_BGC.f09_f09_mg17.SOCRATES_nudgeUVTfull_tau12h.001", "f.e21.FHIST_BGC.f09_f09_mg17.SOCRATES_nudgeUVTfull_tau24h.001", ], "f.e30_cam6_4_120.FHISTC_LTso.ne30pg3_ne30pg3_mg17.SOCRATES_nudgeUVTfull_withCOSP": [ "f.e30_cam6_4_120.FHISTC_LTso.ne30pg3_ne30pg3_mg17.SOCRATES_nudgeUVTfull_withCOSP_tau6h.001", "f.e30_cam6_4_120.FHISTC_LTso.ne30pg3_ne30pg3_mg17.SOCRATES_nudgeUVTfull_withCOSP_tau12h.001", ], } subset_kwargs = {} product = "/glade/derecho/scratch/richling/compass-cupid/CUPiD/examples/external_diag_packages/computed_notebooks//atm/COMPASS_model_diags.ipynb" # In[3]: runs_dict_tmp = {} if isinstance(runs_dict, list): for run in runs_dict: runs_dict_tmp[run] = [run] runs_dict = runs_dict_tmp # ## Key Metrics from COMPASS Model Diagnostics (CMD) # # Some important things to look at from CMD include a slap to the face and then a hug: # for path_to_key_plot in key_plots: # full_path = os.path.join(adf_root, path_to_key_plot) # if os.path.isfile(full_path): # display(Image(full_pbath)) #  # In[4]: from IPython.display import Image Image(f"{compass_root}/compass_schematic_2025_12_09.jpg") # In[5]: get_ipython().run_cell_magic('html', '', '\n\n\n
You can inject HTML here (images, case settings, anything).
\n styling preserved
return "" + "
".join(final_lines) + "
"
nudge_html = process_nudge_html(nudge_text) if nudge_text else "No nudge params found."
subset_html = "" + escape(subset_text) + "
" if subset_text else "No subset."
full_html = "" + escape(full_text) + "
" if full_text else "No full text."
status_html = "" + escape(status_text) + "
"
# --- Build an HTML blob and inject JSON data for JS ---
data = {
"case_name": case_name,
"levels": levels,
"images_per_level": images_per_level,
"timestamps_per_level": timestamps_per_level,
"nudge_html": nudge_html,
"subset_html": subset_html,
"full_html": full_html,
"status_html": status_html,
"ldrop_opts": ldrop_opts,
"fname": fname
}
# Convert data to JSON safely for embedding
data_json = json.dumps(data)
# HTML/CSS/JS template
html = f"""
"""
return HTML(html)
# In[7]:
from IPython.display import HTML
import os, re, json, base64
from io import BytesIO
from PIL import Image
from html import escape
# ----------------------------------------------------
# Constants used for highlighting nudging parameters
# ----------------------------------------------------
TOOLTIPS = {
"Nudge_Uprof": """Selectively apply nudging to U:
OFF = off
ON = everywhere
WINDOW = apply a window function
0=OFF 1=ON 2=WINDOW
""",
"Nudge_Ucoef": "Normalized nudging strength [0–1].",
"Nudge_TimeScale_Opt": "Timescale option: 0=WEAK, 1=STRONG",
"Nudge_Force_Opt": "Target form: 0=NEXT, 1=LINEAR",
}
LINKS = {
"Nudge_Uprof": "https://ncar.github.io/CAM/doc/build/html/users_guide/physics-modifications-via-the-namelist.html",
"Nudge_Ucoef": "https://ncar.github.io/CAM/doc/build/html/users_guide/physics-modifications-via-the-namelist.html",
"Nudge_TimeScale_Opt": "https://www.google.com",
}
# ----------------------------------------------------
# Highlight nudging keys (plain → or )
# ----------------------------------------------------
def highlight_nudge_text(text):
if not text:
return "No nudge parameters found."
html_lines = []
for line in text.splitlines():
for key in TOOLTIPS:
tooltip = escape(TOOLTIPS[key])
if key in LINKS:
repl = f'{key}'
else:
repl = f'{key}'
line = re.sub(key, repl, line)
html_lines.append(line)
return "" + "
".join(html_lines) + "
"
# ----------------------------------------------------
# Read user_nl_cam or atm_in
# ----------------------------------------------------
def load_user_nl_file(path, settings_list):
nudged, all_text, subset = [], [], []
default_keys = ["fincl", "mfilt", "nhtfrq", "ncdata"]
try:
with open(path) as f:
for line in f:
stripped = re.sub(r"\s{3,}", " ", line).strip()
all_text.append(stripped)
# nudging lines
if stripped.lower().startswith(("nudge_", " nudge_")):
nudged.append(stripped)
subset.append(stripped)
# extra settings
if settings_list:
for key in settings_list:
if key not in default_keys and key in stripped:
subset.append(stripped)
# required default keys
if any(k in stripped for k in default_keys):
subset.append(stripped)
except Exception as e:
return "", f"Error reading {path}: {e}", ""
return "\n".join(nudged), "\n".join(all_text), "\n".join(subset)
# ----------------------------------------------------
# Read CaseStatus
# ----------------------------------------------------
def load_casestatus(path):
try:
with open(path) as f:
return "\n".join([re.sub(r"\s{3,}", " ", line).strip() for line in f])
except Exception as e:
return f"Error reading CaseStatus: {e}"
# ----------------------------------------------------
# Level detection + base64 image loading
# ----------------------------------------------------
def detect_levels(image_dir):
pat = re.compile(r"(\d+)hPa\.png$")
files = os.listdir(image_dir)
return sorted({pat.search(f).group(1) for f in files if pat.search(f)})
def load_level_images(image_dir, level):
level = str(level)
files = sorted([
os.path.join(image_dir, f)
for f in os.listdir(image_dir)
if f.endswith(f"{level}hPa.png")
])
ts = [
"-".join(os.path.basename(f).split("_")[1:4]) + " " + os.path.basename(f).split("_")[4]
for f in files
]
b64 = []
for f in files:
try:
img = Image.open(f).resize((1200, 400))
buf = BytesIO()
img.save(buf, format="PNG")
b64.append(base64.b64encode(buf.getvalue()).decode())
except:
b64.append("")
return b64, ts
# ----------------------------------------------------
# CLEAN + COMPLETE MAIN FUNCTION
# ----------------------------------------------------
def build_slider_html(case_name, image_dir, ldrop=None, settings_list=None, rdrop=None, unique_id="0"):
uid = unique_id.replace("-", "_")
if not os.path.isdir(image_dir):
return HTML(f"Invalid image_dir: {escape(image_dir)}")
# ---- gather all data -------------------------------------------------
levels = detect_levels(image_dir)
if not levels:
return HTML("No *hPa.png files found.")
images_per_level = {}
timestamps_per_level = {}
for lev in levels:
b64, ts = load_level_images(image_dir, lev)
images_per_level[lev] = b64
timestamps_per_level[lev] = ts
# --- user_nl_cam / atm_in text panels ---
nudge_text = full_text = subset_text = ""
ldrop_opts = ["Hide Settings", "Nudge Params"]
fname = ""
if ldrop and os.path.isfile(ldrop):
nudge_text, full_text, subset_text = load_user_nl_file(ldrop, settings_list)
if "atm_in" in ldrop:
ldrop_opts += ["Full atm_in", "Subset atm_in"]
fname = "atm_in"
elif "user_nl_cam" in ldrop:
ldrop_opts += ["Full user_nl_cam"]
fname = "user_nl_cam"
status_text = load_casestatus(rdrop) if (rdrop and os.path.isfile(rdrop)) else "Nothing here..."
# ---- preformatted HTML chunks ----
nudge_html = highlight_nudge_text(nudge_text)
subset_html = f"{escape(subset_text)}"
full_html = f"{escape(full_text)}"
status_html = f"{escape(status_text)}"
# ---- pack into JSON for JS ----
data = dict(
case_name=case_name,
levels=levels,
images_per_level=images_per_level,
timestamps_per_level=timestamps_per_level,
nudge_html=nudge_html,
subset_html=subset_html,
full_html=full_html,
status_html=status_html,
ldrop_opts=ldrop_opts,
fname=fname,
)
data_json = json.dumps(data)
# ----------------------------------------------------
# Clean HTML + JS template
# ----------------------------------------------------
html = f"""
"""
return HTML(html)
# In[8]:
#GOOD FUNCTION
from IPython.display import HTML, display
from pathlib import Path
outer_tabs_html = []
outer_tab_buttons = []
outer_content_html = []
for i, (outer_name, inner_runs) in enumerate(runs_dict.items()):
# Outer tab button
active_cls = "active" if i==0 else ""
outer_tab_buttons.append(f'{outer_name}')
inner_tabs = []
inner_contents = []
for j, run_name in enumerate(inner_runs):
# Inner tab button
inner_active = "active" if j==0 else ""
inner_tabs.append(f'{run_name.split("_")[-1]}')
## Inner content: slider HTML
#slider_html = build_slider_html(
# run_name,
# image_dir=f"/glade/derecho/scratch/islas/{run_name}/run/atm_in",
# settings_list=["cosp"],
#).data # get the raw HTML string
file_path = Path(f"/glade/derecho/scratch/islas/{run_name}/run/atm_in")
slider_html = build_slider_html(
run_name,
compass_root,
str(file_path),
settings_list=["cosp"],
).data # get the raw HTML string
inner_contents.append(f'{slider_html}')
outer_active = "active" if i==0 else ""
outer_content_html.append(f"""
{''.join(inner_tabs)}
{''.join(inner_contents)}
""")
html_full = f"""
{''.join(outer_tab_buttons)}
{''.join(outer_content_html)}
"""
# In[9]:
display(HTML(html_full))
# In[10]:
from IPython.display import display, HTML
from html import escape
import json
# This function should generate data_json for each run (replace with your real build_slider_html logic)
def dummy_slider_data(run_name):
# Minimal example of slider JSON for testing
return {
"case_name": run_name,
"levels": [500, 700],
"images_per_level": {"500": ["", ""], "700": ["", ""]},
"timestamps_per_level": {"500": ["2025-12-01 00:00","2025-12-01 06:00"],
"700": ["2025-12-01 00:00","2025-12-01 06:00"]},
"ldrop_opts": ["Hide Settings", "Nudge Params"],
"nudge_html": "Example nudge params",
"full_html": "Full text",
"subset_html": "Subset",
"status_html": "CaseStatus info",
}
# Build nested tab HTML
html = '\n'
# Top-level buttons
html += '\n'
# Tab content per family
for family, run_list in runs_dict.items():
html += f'\n'
# Sub-tabs
html += '\n'
# Subtab content
for run in run_list:
data_json_str = json.dumps(dummy_slider_data(run))
slider_html = f"""
{escape(run)}
"""
html += slider_html
html += '\n'
html += '' # end tab-container
# Add CSS/JS for tabs
html += """
"""
display(HTML(html))
# In[11]:
from IPython.display import display, HTML
from html import escape
import json
# Example runs_dict (replace with your real runs_dict)
# Example slider data per run (replace with your real build_slider_data logic)
def build_slider_data(run_name):
return {
"case_name": run_name,
"levels": [500, 700],
"images_per_level": {
"500": ["", ""], # Base64 strings for your images
"700": ["", ""]
},
"timestamps_per_level": {
"500": ["2025-12-01 00:00", "2025-12-01 06:00"],
"700": ["2025-12-01 00:00", "2025-12-01 06:00"]
},
"ldrop_opts": ["Hide Settings", "Nudge Params", "Full Config", "Subset Config"],
"nudge_html": "Nudge params example",
"full_html": "Full configuration content",
"subset_html": "Subset content",
"status_html": "Case status info"
}
# Start building HTML
html = '\n'
# Top-level buttons
html += '\n'
# Tab content per family
for family, run_list in runs_dict.items():
html += f'\n'
# Sub-tabs
html += '\n'
# Subtab content
for run in run_list:
data_json_str = json.dumps(build_slider_data(run))
slider_html = f"""
{escape(run)}
"""
html += slider_html
html += '\n'
html += '' # end tab-container
# Add CSS/JS for tabs
html += """
"""
display(HTML(html))
# In[12]:
from IPython.display import HTML
import json
# --- Your Python dictionary ---
data = {
"Region A": ["https://via.placeholder.com/400x300?text=A1",
"https://via.placeholder.com/400x300?text=A2"],
"Region B": ["https://via.placeholder.com/400x300?text=B1",
"https://via.placeholder.com/400x300?text=B2",
"https://via.placeholder.com/400x300?text=B3"],
"Region C": ["https://via.placeholder.com/400x300?text=C1"]
}
html = f"""
Dropdown → Tabs → Image Slider
"""
HTML(html)