Skip to content

Enhance robust pcmdi - #49

Draft
zhangshixuan1987 wants to merge 34 commits into
mainfrom
enhance_robust_pcmdi
Draft

zhangshixuan1987 wants to merge 34 commits into
mainfrom
enhance_robust_pcmdi

Conversation

@zhangshixuan1987

Copy link
Copy Markdown
Contributor

Summary

This pull request enhances the robustness of the PCMDI diagnostics utilities by fixing bugs related to ENSO metric extraction and synthetic plot handling, improving logging clarity and parallel computing reliability, and making incremental enhancements to the viewer and utility modules.

Objectives:

  • Enhance robustness of PCMDI diagnostics utilities.
  • Fix bugs related to ENSO metric extraction and synthetic plot handling.
  • Improve logging clarity and parallel computing reliability.
  • Incremental enhancements to viewer and utility modules.

Issue resolution:

  • No related GitHub issue; changes are based on code review and internal quality improvements.

This pull request is

  • a bug fix: increment the patch version
  • a small improvement: increment the minor version

Small Change

  • To merge, I will use "Squash and merge". That is, this change should be a single commit.
  • Logic: I have visually inspected the entire pull request myself.
  • Pre-commit checks: All the pre-commits checks have passed.

1. Does this do what we want it to do?

-Product Management: I have confirmed with the stakeholders that the objectives above are correct and complete.
-Testing: I have considered likely and/or severe edge cases and have included them in testing.
Note: Testing was performed together with another bug fix change to address .

2. Are the implementation details accurate & efficient?

  • I have visually inspected the entire pull request myself.

3. Is this well documented?

  • A comment is put on each commit to explain the changes clearly.

4. Is this code clean?

  • The code is as simple as possible and well-commented.
    -All the pre-commits checks have passed.

@zhangshixuan1987
zhangshixuan1987 marked this pull request as draft May 5, 2026 22:48
@zhangshixuan1987
zhangshixuan1987 requested a review from forsyth2 May 5, 2026 22:48
@zhangshixuan1987 zhangshixuan1987 self-assigned this May 5, 2026
@zhangshixuan1987 zhangshixuan1987 added the semver: bug Bug fix (will increment patch version) label May 5, 2026
@zhangshixuan1987

Copy link
Copy Markdown
Contributor Author

@forsyth2: Hi Ryan, I am submitting this pull request with several bug fixes identified during the code review process while addressing the reported issues in E3SM-Project/zppy#807. I also included a few fixes related to the ENSO code path that is planned to be activated in the near future. I decided to address them now because the relevant code is already part of the current code base, so it would be better to resolve these issues before the functionality becomes fully enabled and more widely used.

@zhangshixuan1987 zhangshixuan1987 added the semver: small improvement Small improvement (will increment patch version) label May 5, 2026
@forsyth2
forsyth2 requested a review from Copilot June 17, 2026 23:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the robustness of the PCMDI diagnostics interface by tightening input validation, making viewer/collector output generation more deterministic, and improving failure diagnostics (logging and error handling) across ENSO, mean-climate, and variability-modes workflows.

Changes:

  • Hardened viewer generation and synthetic plot handling (directory creation, deterministic file selection, safer config coercion) and added/expanded unit coverage.
  • Improved job execution reliability and error observability (better logging, clearer failures, basic batch cleanup on parallel failures).
  • Enhanced ENSO / modes collectors with stricter argument validation, safer file moves/renames, and more defensive parsing of produced outputs.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
zppy_interfaces/pcmdi_diags/viewer.py Adds logger usage, safer output-dir creation, deterministic glob selection, and mode normalization for viewer tables.
zppy_interfaces/pcmdi_diags/utils.py Improves parallel/serial subprocess failure reporting and validates worker count.
zppy_interfaces/pcmdi_diags/synthetic_plots/synthetic_metrics_plotter.py Fixes ENSO collection extraction per-stat and improves mean-climate portrait handling when variables/seasons are missing.
zppy_interfaces/pcmdi_diags/synthetic_plots/enso_metrics_reader.py Adds input validation and safer JSON structure checks while collecting ENSO metric JSON paths.
zppy_interfaces/pcmdi_diags/pcmdi_variability_modes.py Strengthens parameter validation, improves warnings for missing outputs, and moves from prints to logging.
zppy_interfaces/pcmdi_diags/pcmdi_synthetic_plots.py Uses safer bool parsing, guards missing logo copy, and improves argument parsing for debug.
zppy_interfaces/pcmdi_diags/pcmdi_setup.py Adds required-argument checks, improves error handling for unexpected file formats, and standardizes logging.
zppy_interfaces/pcmdi_diags/pcmdi_mean_climate.py Adds required-argument checks, fixes CLIM path typo, improves missing-output warnings, and hardens filename parsing.
zppy_interfaces/pcmdi_diags/pcmdi_enso.py Enables ENSO flow (removes early exit), adds catalogue normalization, hardens file collection/moves, and improves output validation.
zppy_interfaces/pcmdi_diags/link_observation.py Uses context manager for reading JSON alias file.
tests/unit/pcmdi_diags/test_viewer.py Adds targeted tests for coupled-mode EOF tagging, config normalization, and out_dir creation behavior.
tests/unit/pcmdi_diags/test_synthetic_metrics_plotter.py Adds tests for drop_vars behavior and mean-climate portrait variable consistency.
tests/unit/pcmdi_diags/test_pcmdi_variability_modes.py Updates expectations for quoted reference_data_path.
tests/unit/pcmdi_diags/test_pcmdi_mean_climate.py Fixes import path typo in test.
pyproject.toml Fixes console script entrypoint for mean-climate module name.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread zppy_interfaces/pcmdi_diags/utils.py Outdated
Comment thread zppy_interfaces/pcmdi_diags/viewer.py
Comment thread zppy_interfaces/pcmdi_diags/viewer.py
Comment thread zppy_interfaces/pcmdi_diags/synthetic_plots/enso_metrics_reader.py Outdated

@forsyth2 forsyth2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm reviewing this PR and the corresponding zppy PR together. I had both Copilot and Claude do initial reviews. I then did a visual inspection based on their comments. (I will post a similar review for that PR).

Once these comments are addressed, I can run zppy's integration tests on it. I'm also adding zppy-interfaces docs in #51, so I'll want to update those to reflect important changes from this PR.

Claude's summary

These two PRs were made in conjunction and should be reviewed together. At a high level, they extend the zppy/zppy-interfaces pipeline with MPAS ocean and sea-ice component support, while also cleaning up configuration, fixing bugs, and expanding test coverage.


High-Level Summary

Area zppy zppy-interfaces
New feature MPAS ocean/sea-ice time-series processing
Config changes New MPAS parameters; removal of vertical remap params from [ts] Default EMOV modes list updated
Bug fixes enso set handling; dependency wiring for e3sm_to_cmip Typos in module names, paths, and variable names
Robustness Multi-subsection dependency support; richer error codes in shell templates Output dir auto-creation; Jinja2 template validation; sorted glob; normalized mode inputs
Logging printlogger.debug in pcmdi_diags.py printlogger.info in viewer.py
Tests New tests for synthetic_metrics_plotter, expanded test_viewer.py

zppy-interfaces PR

1. Typo fixes (bugfixes)

Three typos were silently breaking things:

  • pcmdi_mean_cimatepcmdi_mean_climate in both pyproject.toml (CLI entry point) and test_pcmdi_mean_climate.py (import). Would have caused the zi-pcmdi-mean-climate command to fail at install time.
  • CLIM_pattternCLIM_pattern in MeanClimateTableBuilder. Would have produced broken figure links.
  • seasons variable renamed to regions in map_regions(). No behavior change, but removes a misleading name.

2. EOF compose fix in CMVARGroupBuilder

A copy-paste bug caused "EOF(Yearly)" and "EOF(Monthly)" entries to use the cbf tag instead of the appropriate eof tag. The fix introduces a map_coupled_mode_to_eof_tag() static method that maps NPGO → eof2 and all other modes → eof1. Mode names are now stripped and uppercased before lookup.

3. Default EMOV modes list changed

⚠️ Behavioral breaking change. The default modes for generate_emovs_table changed from ["PDO", "NPGO", "AMO"] to ["NAM", "PNA", "NPO", "NAO", "SAM", "PSA1", "PSA2"]. This is presumably intentional — PDO/NPGO/AMO now belong to the coupled modes table — but it is easy to miss buried among other changes. This should be clearly documented in a changelog or release note, and any existing user configs that relied on the default should be reviewed.

4. Robustness improvements in viewer.py

  • setup_jinja_env() now raises FileNotFoundError if the template directory is missing (fail-fast).
  • generate_methodology_html(), generate_data_html(), and generate_viewer_html() all call Path(...).mkdir(parents=True, exist_ok=True) before writing output, preventing crashes on missing directories.
  • glob.glob() results are now sorted before selecting the first match, ensuring deterministic behavior across filesystems.
  • join_list() now passes strings through directly if the config value is already a string. Minor concern: this silently absorbs a likely misconfiguration. A warning log would be more appropriate.
  • All print(...) calls replaced with logger.info(...). Consistent with standard Python logging.

5. run_serial_jobs error logging improvements

Error messages now include the job index, return code, and full stdout/stderr — a meaningful debuggability improvement. The stdout.strip() / stderr.strip() are now applied once before the error check and stored in results. Verify no downstream consumers relied on trailing newlines in those values.

6. Test coverage expansion

  • New test_synthetic_metrics_plotter.py adds unit tests for drop_vars and mean_climate_plot_driver. Well-structured, but could add edge cases for all-missing or none-missing variables. The shape assertion values.shape == (1, 2) would benefit from a comment explaining the dimensions.
  • test_viewer.py grows from 2 tests to ~12+, covering CMVARGroupBuilder, generate_cmvar_table, generate_emovs_table, and others. Tests use tmp_path and monkeypatch appropriately.

7. Variability modes test: quoted refpath

Expected CLI strings now wrap --reference_data_path in quotes ("refpath"). ⚠️ Needs investigation: this implies the production code changed to quote path arguments. When using shell=True, quoting strategy inside command strings can be fragile and platform-dependent. Confirm the quoting is applied consistently in the actual implementation.


Summary of Concerns

Severity Location Issue
⚠️ Medium zppy-interfaces/viewer.py Default EMOV modes list changed — behavioral breaking change for users relying on defaults
⚠️ Low zppy-interfaces Quoted refpath in variability modes tests — confirm quoting strategy is robust with shell=True
⚠️ Low zppy-interfaces/viewer.py join_list string passthrough absorbs misconfiguration silently — consider a warning log
💡 Suggestion zppy-interfaces tests test_synthetic_metrics_plotter.py could cover more edge cases

Comment thread tests/unit/pcmdi_diags/test_pcmdi_variability_modes.py Outdated
Comment thread zppy_interfaces/pcmdi_diags/viewer.py
Comment thread zppy_interfaces/pcmdi_diags/viewer.py
Comment thread zppy_interfaces/pcmdi_diags/viewer.py
Comment thread tests/unit/pcmdi_diags/test_synthetic_metrics_plotter.py
Comment thread tests/unit/pcmdi_diags/test_synthetic_metrics_plotter.py
Comment thread zppy_interfaces/pcmdi_diags/viewer.py
Comment thread zppy_interfaces/pcmdi_diags/viewer.py
- pcmdi_setup.py: raise ValueError in _extract_metadata() when filename
  parts are missing, instead of logging and falling through to IndexError;
  fix typo "dervied" -> "derived" in log message

- link_observation.py: use context manager for obs_alias_file to ensure
  file handle is closed

- enso_metrics_reader.py: guard nested RESULTS.model dict access with
  .get() and raise a descriptive KeyError when structure is missing,
  instead of an opaque KeyError at runtime

- utils.py: replace Popen(shell=True) with shlex.split() + shell=False
  in run_parallel_jobs() and run_serial_jobs() to eliminate shell
  injection risk; add shlex import

- pcmdi_enso.py, pcmdi_synthetic_plots.py: replace bare json.load(open())
  calls with context managers to prevent file descriptor leaks

All 7 unit tests pass (zi-pcmdi-diags-20260430).
- ENSOParameters: validate enso_groups is not None at construction time
  instead of crashing with AttributeError on .split() downstream

- EnsoDiagnosticsCollector.__init__: validate model_name_parts has
  exactly 4 elements before tuple unpack, giving a descriptive error

- collect_figures: guard os.listdir(fdir) with os.path.isdir() before
  calling it; guard error-log dir listing the same way to prevent
  FileNotFoundError inside the logger call itself

- collect_figures: warn when model/relm marker is absent from filename
  before splitting, preventing silent wrong output filenames

- collect_metrics / collect_diags: replace bare os.listdir() inside
  logger error f-strings with isdir-guarded dir_contents variable

- main(): check obs_dict is non-empty before [0] index to avoid
  IndexError on empty obs_catalogue.json

- check_enso_input: guard both os.symlink() calls with
  os.path.exists() to prevent FileExistsError on re-run/retry

- check_vars: add re.DOTALL flag to list_variables regex so multi-line
  driver stdout does not produce a false "no variable list found"
failure

All 7 unit tests pass (zi-pcmdi-diags-20260430).
- rename pcmdi_mean_cimate.py -> pcmdi_mean_climate.py (correct
  spelling);
  update entry point in pyproject.toml and import in test file

- MeanClimateParameters: validate --regions is not None before .split()
  to prevent AttributeError on missing CLI argument

- MeanClimateMetricsCollector.__init__: validate model_info has exactly
  4 elements before tuple unpack, giving a descriptive error

- _collect_figures: fix output directory key typo "CLIM_patttern" ->
  "CLIM_pattern"; add logger.warning when no figures are found for a
  var/region/season combination instead of silently skipping

- _collect_metrics: guard parts[1] access with len(parts) < 2 check
  and log+skip on unexpected filename format instead of IndexError

- main(): re-raise RuntimeError from job runners instead of swallowing
  it with print(), preventing silent wrong results after job failure;
  replace all print() calls with logger.info()

- generate_mean_clim_cmds: add logger.warning when a variable is not
  found in obs_dic instead of silently omitting its command

All 7 unit tests pass (zi-pcmdi-diags-20260430).
- VariabilityModesParameters: validate --var_modes and --vars are not
  None at construction time instead of crashing with AttributeError
  or KeyError: None downstream

- main(): validate model_name has exactly 4 dot-separated parts before
  index access to prevent IndexError

- main(): re-raise RuntimeError from job runners instead of swallowing
  it with print(), preventing silent wrong results after job failure;
  replace all print() calls with logger.info()

- _collect_figures: add logger.warning when no files match a
  mode/season combination instead of silently skipping

- _classify_output_name: add logger.warning when filename does not
  match any known pattern and suffix falls back to "unknown"

- generate_varmode_cmds: quote refpath in command string so paths
  containing spaces are handled correctly by shlex.split

- test_generate_varmode_cmds: update expected strings to reflect
  quoted refpath

All 7 unit tests pass (zi-pcmdi-diags-20260430).
- SyntheticPlotsParameters: replace all bare args["x"].split(",") calls
  with None-safe guards so missing optional list arguments (clim_vars,
  clim_regions, mova_modes, mova_vars, movc_modes, movc_vars, enso_vars)
  no longer raise AttributeError

- SyntheticPlotsParameters: replace all str(args["x"]).lower() in (...)
  boolean checks with str2bool(args.get("x", False)) to use the existing
  helper consistently; previously str("None") silently evaluated to
False,
  masking missing viewer flags (clim_viewer, mova_viewer, movc_viewer,
  enso_viewer, save_all_data)

- main(): guard shutil.copy for e3sm_pmp_logo.png with os.path.exists
  check; log a warning and skip instead of raising FileNotFoundError
  when pcmdi_external_prefix is misconfigured

- _get_args(): change --debug to type=str2bool with default=False and
  simplify check to `if args.debug:`; previously only "true" was
  recognised, silently ignoring "1", "yes", etc.

All 7 unit tests pass (zi-pcmdi-diags-20260430).
with logger

- CoreParameters: validate num_workers and multiprocessing are not None
  before int() and .lower() calls to prevent cryptic TypeError/
  AttributeError on missing CLI arguments

- CoreParameters: guard --vars with args.get() before .split(",") to
  prevent AttributeError on missing argument

- set_up(): split model_name once into model_name_parts and validate
  len >= 2 before index access; eliminates IndexError on malformed
  model name and removes two redundant .split() calls in input_template
  construction

- set_up(): validate model_name_ref is non-None and has >= 2 parts in
  model_vs_model branch to prevent None.split() and IndexError

- _process_group(): replace print() warning with logger.warning() so
  missing catalogue warning respects log-level control

- _generate_mask(): replace both print() calls with logger.info() so
  mask method info is captured in log output

- derive_missing_variable(): replace print() with logger.info() for
  derived variable write confirmation

- Fix typo "assigining" -> "assigning" in two log messages

All 7 unit tests pass (zi-pcmdi-diags-20260430).
- run_parallel_jobs: raise ValueError if num_workers < 1 instead of
  silently degrading to serial execution

- run_parallel_jobs: rename inner loop variables cmd/proc to
  batch_cmd/batch_proc to eliminate shadowing of the outer cmd variable

- run_parallel_jobs: terminate all remaining running batch processes
  before raising RuntimeError on job failure, preventing orphaned
  subprocesses from running indefinitely after an error

- run_parallel_jobs: improve batch log message from misleading
  "Running {count_child_processes()} subprocesses" (counted before
  launch) to "Running batch of {len(procs)} subprocesses"

All 7 unit tests pass (zi-pcmdi-diags-20260430).
- MeanClimateTableBuilder.map_regions(): rename local variable from
  `seasons` to `regions` throughout the method; copy-paste bug caused
  no runtime error but was misleading and error-prone

- build_table(): update figure path from "CLIM_patttern" to
  "CLIM_pattern" to match the corrected output directory name from
  pcmdi_mean_climate.py; viewer was silently finding no files

- setup_jinja_env(): add os.path.isdir() check before creating the
  Jinja2 environment; previously a missing template directory produced
  an unhelpful TemplateNotFound error with no indication the directory
  itself was absent

- Add module-level `import logging` and `logger = logging.getLogger`
  at the top of the file; previously the file had no logger

- generate_methodology_html(), generate_data_html(),
  generate_viewer_html(): replace print() calls with logger.info() so
  HTML write confirmations respect log-level control and appear in log
  output

All 7 unit tests pass (zi-pcmdi-diags-20260430).
Copilot AI requested a review from forsyth2 September 11, 2026 19:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved ENSO setup, catalogue, case-handling, and subprocess-cleanup issues block approval.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (4)

zppy_interfaces/pcmdi_diags/pcmdi_enso.py:183

  • These lines treat any falsy metric value as incomplete. A valid ENSO metric can be numeric 0, so it will be removed from the copied JSON before the collector moves it, causing zero-valued results to disappear. Check for a missing/None/empty metric payload rather than truthiness so valid zeroes are preserved.
                        incomplete = [
                            m for m, v in value_block.items() if not v.get("metric")
                        ]

zppy_interfaces/pcmdi_diags/pcmdi_enso.py:611

  • This new underscore-style filename branch is not supported by the shared setup: DataCatalogueBuilder still searches only *.{var}.*.nc (pcmdi_setup.py:91-97 and 316-318). Such a file is symlinked here but produces no model catalogue metadata, so the subsequent ENSO driver cannot use the accepted input. Extend catalogue discovery to the same convention before treating it as supported.
        found_nc_file = glob.glob(
            os.path.join(ts_dir, f"*.{cmip_var_name}.*.nc")
        ) + glob.glob(os.path.join(ts_dir, f"{cmip_var_name}_*.nc"))

zppy_interfaces/pcmdi_diags/pcmdi_setup.py:293

  • All three command entry points document and validate model_name as mip.exp.model.relm, but this shared setup now accepts any value with at least two parts. A 3- or 5-part value can perform catalogue or derived-file work and only fail later in a collector, so reject non-4-part names here.
    if len(model_name_parts) < 2:
        raise ValueError(
            f"model_name must have at least 2 dot-separated parts, "
            f"got: {parameters.model_name}"
        )

zppy_interfaces/pcmdi_diags/pcmdi_variability_modes.py:25

  • var_modes is later used directly by VariabilityMetricsCollector, whereas generate_varmode_cmds strips each mode. With a value such as --var_modes 'NPO, PDO', the PDO job runs but collection searches for a directory named ' PDO' and leaves its outputs uncollected. Normalize and strip the list here as well.
        self.var_modes: List[str] = var_modes.split(",")
  • Files reviewed: 18/18 changed files
  • Comments generated: 7
  • Review effort level: Lite

Comment thread zppy_interfaces/pcmdi_diags/pcmdi_enso.py Outdated
Comment thread zppy_interfaces/pcmdi_diags/pcmdi_enso.py Outdated
Comment thread zppy_interfaces/pcmdi_diags/pcmdi_enso.py Outdated
Comment thread zppy_interfaces/pcmdi_diags/synthetic_plots/enso_metrics_reader.py Outdated
Comment thread zppy_interfaces/pcmdi_diags/utils.py Outdated
Comment thread zppy_interfaces/pcmdi_diags/utils.py Outdated
Co-authored-by: forsyth2 <30700190+forsyth2@users.noreply.github.com>

Copilot AI commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

🟡 Changes recommended

Unresolved ENSO setup, catalogue, case-handling, and subprocess-cleanup issues block approval....

Fixed in 2fd895b.

@forsyth2 forsyth2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had Claude summarize the changes in this PR, since it is adding nearly 2,000 lines of code. I'm pasting its individual file diff summaries as part of this review.

(This is just a summary review; no action items)

Comment thread docs/source/developer_pcmdi_diags.rst
Comment thread docs/source/pcmdi_diags.rst
Comment thread tests/unit/pcmdi_diags/test_pcmdi_enso.py
Comment thread tests/unit/pcmdi_diags/test_pcmdi_mean_climate.py Outdated
Comment thread tests/unit/pcmdi_diags/test_pcmdi_setup.py
Comment thread zppy_interfaces/pcmdi_diags/pcmdi_setup.py Outdated
Comment thread zppy_interfaces/pcmdi_diags/pcmdi_synthetic_plots.py Outdated
Comment thread zppy_interfaces/pcmdi_diags/pcmdi_variability_modes.py
Comment thread zppy_interfaces/pcmdi_diags/utils.py
Comment thread pyproject.toml

@forsyth2 forsyth2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now for actual items from Claude

Comment thread zppy_interfaces/pcmdi_diags/viewer.py
Comment thread zppy_interfaces/pcmdi_diags/pcmdi_enso.py Outdated
Comment thread zppy_interfaces/pcmdi_diags/pcmdi_enso.py
Comment thread zppy_interfaces/pcmdi_diags/pcmdi_enso.py
Comment thread zppy_interfaces/pcmdi_diags/pcmdi_enso.py Outdated
Comment thread zppy_interfaces/pcmdi_diags/pcmdi_enso.py
@forsyth2

Copy link
Copy Markdown
Collaborator

@zhangshixuan1987 Can you address Claude's notes in my review above. They seem to be mostly confirming design decisions -- so if you could just confirm the decisions were intentional (or note if the code should be fixed).

Once that is done, I'll do a test run of just the pcmdi_diags task using E3SM-Project/zppy#815 and this PR. If that works, we'll probably be ok to merge.

@zhangshixuan1987

Copy link
Copy Markdown
Contributor Author

@zhangshixuan1987 Can you address Claude's notes in my review above. They seem to be mostly confirming design decisions -- so if you could just confirm the decisions were intentional (or note if the code should be fixed).

Once that is done, I'll do a test run of just the pcmdi_diags task using E3SM-Project/zppy#815 and this PR. If that works, we'll probably be ok to merge.

Hi @forsyth2 : thank you for putting these together. I will follow the comments and attempt to review and adjust as needed. I will notify you once I push the changes to the PR.

@forsyth2

Copy link
Copy Markdown
Collaborator

Great, thanks @zhangshixuan1987!

@zhangshixuan1987

Copy link
Copy Markdown
Contributor Author

@zhangshixuan1987 Can you address Claude's notes in my review above. They seem to be mostly confirming design decisions -- so if you could just confirm the decisions were intentional (or note if the code should be fixed).

Once that is done, I'll do a test run of just the pcmdi_diags task using E3SM-Project/zppy#815 and this PR. If that works, we'll probably be ok to merge.

@forsyth2 Hi Ryan, I followed up on the review comments and confirmed or refined the code further where needed. I have pushed the latest changes to this PR.

In addition, I ran a standalone test for ENSO using the E3SM data I had collected and tested previously, with the final revised code in this PR together with the corresponding changes in the zppy branch. The standalone test completed successfully on my side.

Could you please take another look and test it on your side to see whether any additional issues arise? If so, I would be happy to help further refine the implementation.

@forsyth2

Copy link
Copy Markdown
Collaborator

Thanks @zhangshixuan1987. Chrysalis isn't available today, so I'll do a visual inspection, and once it's back up I'll run the test suite to see if any issues come up.

Also, Xylar let us know the release candidate (RC) deadline for E3SM-Unified 1.14.0 is Oct. 5. That means we need to have this PR and E3SM-Project/zppy#815 merged by then, so I can make a zppy-interfaces RC.

@forsyth2

Copy link
Copy Markdown
Collaborator

2026-09-14 zppy pcmdi_diags ENSO support test

I just updated the expected results as noted here

Using the test script

A. Set up the test script

cd ~/ez/zppy
git status
# On branch partial-update-expectations
# nothing to commit, working tree clean

git fetch upstream main 
git checkout -b test-zppy-pr815-20260914 upstream/main
git log --oneline | head -n 1
# 5232fe30 Rank image check failures by severity (#865))
# Good, matches https://github.com/E3SM-Project/zppy/commits/main

# Now, copy the test script and cfg from the zppy repo into the directory
# that you'll be running the test script from.
mkdir -p ~/ez/zppy_main_branch_tests/test_20260914_run2
cd ~/ez/zppy_main_branch_tests/test_20260914_run2
cp ~/ez/zppy/tests/main_branch_testing/run_integration_test.bash .
cp ~/ez/zppy/tests/main_branch_testing/zppy_test.cfg .

# Now, edit the test cfg as needed
emacs zppy_test.cfg

B. Set up the test cfg

RUN_NUMBER=2

ZI_BASE_BRANCH="enhance_robust_pcmdi"
ZPPY_BASE_BRANCH="zppy_pcmdi_enhancement"

DIAGS_ENV_TYPE="unified"
E3SM_TO_CMIP_ENV_TYPE="unified"
MPAS_ENV_TYPE="unified"

CFGS_TO_RUN="weekly_comprehensive_v3,weekly_legacy_3.1.0_comprehensive_v3"
TASKS_TO_RUN="pcmdi_diags"

EZ_DIR="/lcrc/group/e3sm/ac.forsyth2/zppy_main_branch_test_dirs"

C. Run the test script

# NOTE: This part is not listed in the docs, as the script should check this.
# To be on the safer side though, it's a good idea to check that these directories
# don't have uncommitted changes.
cd /lcrc/group/e3sm/ac.forsyth2/zppy_main_branch_test_dirs/e3sm_to_cmip && git status
# nothing to commit, working tree clean
cd /lcrc/group/e3sm/ac.forsyth2/zppy_main_branch_test_dirs/e3sm_diags && git status
# nothing to commit, working tree clean
cd /lcrc/group/e3sm/ac.forsyth2/zppy_main_branch_test_dirs/MPAS-Analysis && git status
# nothing to commit, working tree clean
cd /lcrc/group/e3sm/ac.forsyth2/zppy_main_branch_test_dirs/zppy-interfaces && git status
# nothing to commit, working tree clean
cd /lcrc/group/e3sm/ac.forsyth2/zppy_main_branch_test_dirs/zppy && git status
# nothing to commit, working tree clean
cd ~/ez/zppy_main_branch_tests/test_20260914_run2

screen # Use `screen`` so that even if the terminal connection is interrupted, the script will keep running.
ulimit -s unlimited # This is necessary for MPAS-Analysis to work inside `screen`
cd ~/ez/zppy_main_branch_tests/test_20260914_run2
cat zppy_test.cfg # Make sure changes are there
time ./run_integration_test.bash --config zppy_test.cfg 2>&1 | tee integration_test_run2.log
# [2026-09-14 22:17:18] Starting zppy integration test automation
# Ctrl-A D to detach from screen
screen -ls # See what screen sessions you have
# 1792866.pts-29.chrlogin1	(Detached) # For this run
# 1735354.pts-35.chrlogin1	(Detached) # From run 1
tail -f integration_test_run2.log
# Jobs remaining: 29 (elapsed: 600s / max: 14400s)[2026-09-14 23:35:42] ✗ Jobs with DependencyNeverSatisfied:
#            1287157     debug pcmdi_di ac.forsy PD       0:00      1 (DependencyNeverSatisfied) 
# Jobs remaining: 7 (elapsed: 1200s / max: 14400s)

# Jobs remaining: 29 (elapsed: 600s / max: 14400s)[2026-09-14 23:35:42] ✗ Jobs with DependencyNeverSatisfied:
#            1287157     debug pcmdi_di ac.forsy PD       0:00      1 (DependencyNeverSatisfied) 
# Jobs remaining: 1 (elapsed: 3000s / max: 14400s)

# Jobs remaining: 29 (elapsed: 600s / max: 14400s)[2026-09-14 23:35:42] ✗ Jobs with DependencyNeverSatisfied:
#            1287157     debug pcmdi_di ac.forsy PD       0:00      1 (DependencyNeverSatisfied) 
# Jobs remaining: 1 (elapsed: 3600s / max: 14400s)

Looks like the script isn't recognizing there's only one job left and it has "DependencyNeverSatisfied". We'll have to terminate that job ourselves.

D. Review the output

# CTRL C # Exit tail
scancel 1287157
screen -R 1792866.pts-29.chrlogin1
# CTRL C -- no point letting it run now

# real    125m23.824s
# user    4m30.471s
# sys     1m15.671s
exit # Exit screen
cd ~/ez/zppy_main_branch_tests/test_20260914_run2

# First, the unit tests.
grep -n "Running zppy-interfaces unit tests..." integration_test_run2.log # Line 1183
grep -n "zppy-interfaces unit tests passed" integration_test_run2.log # Line 1212

grep -n "Running zppy unit tests..." integration_test_run2.log # Line 2138
grep -n "zppy unit tests passed" integration_test_run2.log # Line 2158

# Second, the output directories status.
grep -n "Checking all status files..." integration_test_run2.log # No matches
grep -n "All status files clean!" integration_test_run2.log # No matches
# By cancelling early, we accidentally skipped the directory status checking steps
# We'll have to check manually.

cd /lcrc/group/e3sm/ac.forsyth2/zppy_weekly_legacy_3.1.0_comprehensive_v3_output/zppy_main_branch_test_20260914_run2/v3.LR.historical_0051/post/scripts
grep -v "OK" *status # Good, no errors

cd /lcrc/group/e3sm/ac.forsyth2/zppy_weekly_comprehensive_v3_output/zppy_main_branch_test_20260914_run2/v3.LR.historical_0051/post/scripts
grep -v "OK" *status
# pcmdi_diags_enso_model_vs_obs_1985-1994.status:ERROR (9)
# pcmdi_diags_synthetic_plots_model_vs_obs.status:WAITING 1287157
emacs pcmdi_diags_enso_model_vs_obs_1985-1994.o1287156
# ncrcat: ERROR No records lay within specified hyperslab

# Let's at least continue on to the image checker test.

Image checker test

cd /lcrc/group/e3sm/ac.forsyth2/zppy_main_branch_test_dirs/zppy
git status
# Good: On branch test_zppy_20260914_run2

# The image checker test, which we'll run from a compute node:
salloc --nodes=1 --partition=debug --time=02:00:00 --account=e3sm
source /home/ac.forsyth2/miniforge3/etc/profile.d/conda.sh
conda activate test-zppy-zppy_pcmdi_enhancement-20260914_run2
cd /lcrc/group/e3sm/ac.forsyth2/zppy_main_branch_test_dirs/zppy
pytest tests/integration/test_images.py
# Paste `Captured stdout call` below

cat test_images_summary.md
# Paste below

exit # Exit compute note
Output
-------------------------------------------------- Captured stdout call ---------------------------------------------------
Preparing weekly cfg tests
Preparing legacy 3.1.0 cfg tests
Preparing legacy 3.0.0 cfg tests
Running 2 tests in parallel
Individual test logs will be written to test_<name>.log files
✓ Completed: legacy_3.1.0_comprehensive_v3 (0 tasks) (log: test_legacy_3.1.0_comprehensive_v3.log)
✓ Completed: comprehensive_v3 (1 tasks) (log: test_comprehensive_v3.log)
Copy the output of test_images_summary.md to a Pull Request comment

Test Summary:
Test                                                    Total    Correct     Status
----------------------------------------------------------------------------------
comprehensive_v3_pcmdi_diags                              647        480     ✗ FAIL
----------------------------------------------------------------------------------

⚠ Some tests had mismatched or missing images. Check individual log files for details.
================================================= short test summary info =================================================
FAILED tests/integration/test_images.py::test_images - assert 647 == 480
============================================== 1 failed in 71.46s (0:01:11) ===============================================

Complete summary table

cat test_images_summary.md:

Test name Total images Correct images Identical Cosmetic only Missing images Needs review Severity
comprehensive_v3_pcmdi_diags 647 480 480 0 167 (list) 0 167 missing, 480 identical

Results analysis:

167 images are now missing.

Claude notes this typo fix:

-    clim_path = safe_join(str(self.fig_dir), "CLIM_patttern")
+    clim_path = safe_join(str(self.fig_dir), "CLIM_pattern")

If we look at the list of missing images above, we can see they're misnamed. That just means we need to update the expected results now that the code is fixed.

Are ENSO plots showing up now?

It doesn't look like it. https://web.lcrc.anl.gov/public/e3sm/diagnostic_output//ac.forsyth2/zppy_weekly_comprehensive_v3_www/zppy_main_branch_test_20260914_run2/v3.LR.historical_0051/pcmdi_diags/model_vs_obs/ shows:

CLIM_pattern/
MOV_metric/
metris_data/

Indeed, pcmdi_diags_enso_model_vs_obs_1985-1994 is the job that failed in the output directory we looked at earlier.

From Claude:

Yes — the [[ enso ]] section is the one that broke, and it's this line:

enso_vars = "psl,pr,ts,tas,tauu,tauv,hfls,hfss,rlds,rsds,rlus,rlut,rsdt"

combined with:

ref_start_yr = 1985
ref_final_yr = 1994
ref_years = "1985-1994",

From the log, psl, ts, tas, tauu, tauv come from ERA5 (1979–2019) and pr from GPCP (1979–2017) — both cover 1985–1994 fine. But rlds, rsds, rlus, rsdt (and presumably rlut) get linked from CERES EBAF v4.1, which only spans 2001–2018. Requesting the 1985-1994 hyperslab out of a file that starts in 2001 gives ncrcat: ERROR No records lay within specified hyperslab — exactly what you hit.

What needs changing: drop the CERES-only radiation variables from enso_vars for this reference period, since CERES EBAF simply doesn't have data before 2001:

enso_vars = "psl,pr,ts,tas,tauu,tauv,hfls,hfss"

(hfls/hfss are fine — those were linked from ERA5 too, per the earlier log, not CERES.)

Conclusion

@zhangshixuan1987 So, we're still not quite ready to merge.

  1. pcmdi_diags_enso_model_vs_obs_1985-1994 fails.
  2. This means the dependency is never satisfied to run pcmdi_diags_synthetic_plots_model_vs_obs.
  3. And when we do merge, we'll have to update the expected results to account for the file name typo fix.

We need to either fix the ENSO code or, perhaps more likely, fix what data I'm asking for in the test cfg.

@zhangshixuan1987

zhangshixuan1987 commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

@forsyth2 Hi Ryan, I think this test failure is related to the observational dataset configuration rather than the zppy-interfaces PCMDI implementation. Specifically, your test focused on 1985-1994. From the log, psl, ts, tas, tauu, tauv come from ERA5 (1979–2019) and pr from GPCP (1979–2017) — both cover 1985–1994 fine. But rlds, rsds, rlus, rsdt (and presumably rlut) are linked from CERES EBAF v4.1, which only spans 2001–2018. In this way, the ENSO diagnostics are not able to be processed, as the radiative fluxes do not exist for 1985-1994.

One vulnerability in the current workflow is that, by default, we use the observations best for model evaluation, say CERES EBAF for rlds, rsds, rlus, rsdt, which have limited coverage. This is perfectly fine for mean climate evaluation, but it is not good for diagnostics such as ENSO. To better address this in the workflow, I proposed to use a separate setup for the observations for each diagnostic section in zppy-pcmdi. I’ve updated the configuration in PR #815 with the following changes:

  • Assigned consistent, unique aliases to observational datasets in reference_alias.json and removed empty aliases.
  • Replaced the shared obs_sets configuration with diagnostic-specific options: clim_obs_sets, enso_obs_sets, mova_obs_sets, and movc_obs_sets.
  • Kept the downstream interface unchanged: zppy resolves the appropriate setting internally and continues passing it as --obs_sets.
  • Updated default.ini, the examples, documentation, and relevant tests.

If possible, could you also share the testing setup you used? I’d be happy to run the same test on my side using the same setup so that we can compare results and troubleshoot any remaining issues. After that, I could hand it to you for the final test.

@forsyth2

Copy link
Copy Markdown
Collaborator

@zhangshixuan1987

I’ve updated the configuration in PR #815

Great, thanks I'll review the diff of the latest commits.

could you also share the testing setup you used? I’

I used a test cfg based off tests/integration/template_weekly_comprehensive_v3.cfg, which I see you updated in your commits to the zppy PR.

You can see the output directories here:

cd /lcrc/group/e3sm/ac.forsyth2/zppy_weekly_legacy_3.1.0_comprehensive_v3_output/zppy_main_branch_test_20260914_run2/v3.LR.historical_0051/post/scripts
grep -v "OK" *status # Good, no errors

cd /lcrc/group/e3sm/ac.forsyth2/zppy_weekly_comprehensive_v3_output/zppy_main_branch_test_20260914_run2/v3.LR.historical_0051/post/scripts
grep -v "OK" *status
# pcmdi_diags_enso_model_vs_obs_1985-1994.status:ERROR (9)
# pcmdi_diags_synthetic_plots_model_vs_obs.status:WAITING 1287157
emacs pcmdi_diags_enso_model_vs_obs_1985-1994.o1287156
# ncrcat: ERROR No records lay within specified hyperslab

# Provenance cfg:
# /lcrc/group/e3sm/ac.forsyth2/zppy_weekly_comprehensive_v3_output/zppy_main_branch_test_20260914_run2/v3.LR.historical_0051/post/scripts/provenance.20260915_041531_545669.cfg

using the same setup

I'm not entirely sure that's possible at the moment. I've been making a lot of improvements to the testing process (notably E3SM-Project/zppy#774 and now E3SM-Project/zppy#871). While the goal is for anyone to run this updated test script, I'm not sure we're there yet. (I'm mainly just trying to 100% automate a test run for myself before I have others try it out).

That said, you could still run a zppy cfg with a matching ENSO section.

@zhangshixuan1987

zhangshixuan1987 commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

@forsyth2 Hi Ryan, I wanted to follow up on the testing I did on my side for the code changes associated with this PR and the companion zppy PR #815. Based on my testing, I think the failure you encountered is mainly related to several issues in the zppy workflow addressed in PR #815, rather than to the zppy-interface changes in this PR.

  1. Model-data preprocessing bug. There was a bug in the preprocessing step for model data used by the PCMDI diagnostics. I identified and fixed this in the latest commit pushed to zppy PR #815. This affects how the time-series data are prepared before being passed to the PCMDI diagnostics.

  2. Diagnostic-specific variables and observational datasets. For the ENSO diagnostics, the required variables include: "psl, pr, ts, tas, tauu, tauv, hfls, hfss, rlds, rsds, rlus, rlut, rsdt". These variables need to be available from the [ts] and [e3sm_to_cmip] preprocessing steps before the ENSO diagnostics can run successfully. zppy PR #815 now reflects this ENSO-specific variable set. This also exposed a broader issue with how zppy specifies variables and observational references for the different PCMDI diagnostic subsets (mean_climate, variability_modes_atm, variability_modes_cpl, and enso). Previously, I used the common obs_sets and vars options across the subsections, which does not allow each diagnostic set to have its own appropriate defaults. To make this more robust, I refactored the configuration so that each diagnostic subset has its own observational-dataset setting, such as clim_obs_sets, enso_obs_sets, mova_obs_sets, and movc_obs_sets. zppy then selects the appropriate setting based on current_set and continues passing it downstream as obs_sets, so the downstream zppy-interface behavior does not need to change. The related commits for this refactoring include: Screenshot 2026-09-16 at 10 15 53 PM

  3. Changes are also needed in the cfg file to accommodate the test and the code changes we have made in 1 and 2. The sample file with changes on top of the Provenance cfg you shared with me can be found at: /lcrc/group/e3sm/ac.szhang/acme_scratch/e3sm_project/zppy_implement_test/post.v3.LR.historical.0051.cfg (note that I removed the unrelated sections such as e3sm_diags and mpas_analysis to focus only on pcmdi test). I also put necessary notes to explain the changes and modifications I made.

With these changes in PR #815, my test on the same E3SM dataset is now able to proceed successfully through the PCMDI workflow. See

So at this point, I think it would be useful to rerun your test with the latest version of PR #815 together with the current zppy-interface PR. If there are still failures after that, I would be happy to compare the logs and continue debugging from there.

@forsyth2

Copy link
Copy Markdown
Collaborator

2026-09-17 zppy pcmdi_diags ENSO support test

I'm going to use the test improvements of E3SM-Project/zppy#871 here.

Set up and run the test

Set up the zppy branch

cd ~/ez/zppy
git status
# nothing to commit, working tree clean
git checkout zppy_pcmdi_enhancement
git checkout -b zppy_pcmdi_enhancement_backup20260917
git checkout zppy_pcmdi_enhancement
git fetch upstream zppy_pcmdi_enhancement
git reset --hard upstream/zppy_pcmdi_enhancement
git log --oneline | head -n 17
# e36096c0 Fix PCMDI diags observation date extraction and period clamping
# ...
# fc2df2d8 One authoritative production path per case; nest development by user (#868)

# Good, has the 16 commits from https://github.com/E3SM-Project/zppy/pull/815/commits

lcrc_conda # Activate conda
rm -rf build
conda clean --all --y
conda env create -f conda/dev.yml -n zppy-pcmdi-test-20260917
conda activate zppy-pcmdi-test-20260917
pre-commit run --all-files
python -m pip install .

git checkout -b combined-pcmdi-test
git fetch upstream issue-869-improve-test-automation
git rebase upstream/issue-869-improve-test-automation # Add the 24 commits from PR 871
git log --oneline | head -n 42
# 8f3724a2 Fix PCMDI diags observation date extraction and period clamping
# ...
# 00216ed6 Enable experimental PCMDI ENSO diagnostics
# 83d5644d One authoritative production path per case; nest development by user (#868)
# c754ea47 Split zppy-interfaces and rename e3sm_to_cmip/zppy expected-results date tracking
# ...
# 827cb4c6 Automate image checker, add per-task env descriptions, and auto-generate test report
# 5232fe30 Rank image check failures by severity (#865)

# Now, we have the PCMDI Diags PR (#815) built on top of the improved test script (#871)

Set up the test script

cd ~/ez/zppy
git status
# On branch combined-pcmdi-test
# nothing to commit, working tree clean

# NOTE: For `ZPPY_BASE_BRANCH="combined-pcmdi-test"` to work,
# we need to push that branch to GitHub so the script can fetch that branch.
# It doesn't need a PR made though.
git push upstream combined-pcmdi-test

# Now, copy the test script and cfg from the zppy repo into the directory
# that you'll be running the test script from.
mkdir -p ~/ez/zppy_main_branch_tests/test_20260917_run1
cd ~/ez/zppy_main_branch_tests/test_20260917_run1
cp ~/ez/zppy/tests/main_branch_testing/run_integration_test.bash .
cp ~/ez/zppy/tests/main_branch_testing/zppy_test.cfg .

# Now, edit the test cfg as needed
emacs zppy_test.cfg

Set up the test cfg

ZI_BASE_BRANCH="enhance_robust_pcmdi" # zi PR #849
ZPPY_BASE_BRANCH="combined-pcmdi-test" # zppy PR #815 + PR #871

# Because we're using different base branches for these two, 
# we need to make sure we specify a different expected_results_branch.
ZI_EXPECTED_RESULTS_BRANCH="main"
ZPPY_EXPECTED_RESULTS_BRANCH="main"

# We only care about running pcmdi_diags here.
CFGS_TO_RUN="weekly_comprehensive_v3,weekly_legacy_3.1.0_comprehensive_v3"
TASKS_TO_RUN="pcmdi_diags"

EXPECTED_RESULTS_DIR="/lcrc/group/e3sm/public_html/zppy_test_resources"

DIAGS_EXPECTED_RESULTS_DATE="2026-08-28"
MPAS_EXPECTED_RESULTS_DATE="2026-08-28"
ZI_GLOBAL_TIME_SERIES_EXPECTED_RESULTS_DATE="2026-08-12"
ZI_PCMDI_DIAGS_EXPECTED_RESULTS_DATE="2026-09-04"
E3SM_TO_CMIP_LAST_TESTED_DATE="2026-09-15"
ZPPY_LAST_TESTED_DATE="2026-09-15"

EZ_DIR="/lcrc/group/e3sm/ac.forsyth2/zppy_main_branch_test_dirs"
ls -lt /lcrc/group/e3sm/public_html/zppy_test_resources/expected_comprehensive_v3/
# Sep 14 21:11 pcmdi_diags
# Sep  4 11:55 mpas_analysis
# Sep  4 11:53 e3sm_diags
# Aug 14 17:01 global_time_series
# May 20 13:52 livvkit
# May 20 13:51 ilamb

shows the date results were promoted to be official expected results. We can look at the testing log to see what the run before that date was.

  • Before 9/14: 9/4 test
  • Before 9/4: 8/28 test
  • Before 8/14: 8/12 test
  • Before 5/20: These were the results from E3SM Unified 1.13.0

Run the test script

# NOTE: This part is not listed in the docs, as the script should check this.
# To be on the safer side though, it's a good idea to check that these directories
# don't have uncommitted changes.
cd /lcrc/group/e3sm/ac.forsyth2/zppy_main_branch_test_dirs/e3sm_to_cmip && git status
# nothing to commit, working tree clean
cd /lcrc/group/e3sm/ac.forsyth2/zppy_main_branch_test_dirs/e3sm_diags && git status
# nothing to commit, working tree clean
cd /lcrc/group/e3sm/ac.forsyth2/zppy_main_branch_test_dirs/MPAS-Analysis && git status
# nothing to commit, working tree clean
cd /lcrc/group/e3sm/ac.forsyth2/zppy_main_branch_test_dirs/zppy-interfaces && git status
# nothing to commit, working tree clean
cd /lcrc/group/e3sm/ac.forsyth2/zppy_main_branch_test_dirs/zppy && git status
# Has uncommitted changes
git add -A
git commit -m "Testing" --no-verify
git status
# nothing to commit, working tree clean
cd ~/ez/zppy_main_branch_tests/test_20260917_run1

screen # Use `screen`` so that even if the terminal connection is interrupted, the script will keep running.
ulimit -s unlimited # This is necessary for MPAS-Analysis to work inside `screen`
cd ~/ez/zppy_main_branch_tests/test_20260917_run1
cat zppy_test.cfg # Make sure changes are there
time ./run_integration_test.bash --config zppy_test.cfg 2>&1 | tee integration_test_run1.log
# [2026-09-17 12:27:18] Starting zppy integration test automation
# Ctrl-A D to detach from screen
screen -ls # See what screen sessions you have
tail -f integration_test_run1.log

# Note: already started run, but in general it's faster 
# to set the branches you're not testing to use Unified.
# That way a dev env doesn't have to be built.

# 14:30 CT:
# Jobs remaining: 9 (elapsed: 600s / max: 14400s)[2026-09-17 14:08:52] ✗ Jobs with DependencyNeverSatisfied:
#            1289577     debug pcmdi_di ac.forsy PD       0:00      1 (DependencyNeverSatisfied) 
# Jobs remaining: 1 (elapsed: 2400s / max: 14400s)

# Let's cancel that job. In another tab:
scancel 1289577
# We'll now wait for the script to continue.
[2026-09-17 15:00:26] ✗ Image checker job failed with state FAILED (ExitCode 1:0)
[2026-09-17 15:00:26] ✗ Image checker job did not pass. Output: /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260917_run1/image_checker_20260917_run1.o1289601
[2026-09-17 15:00:26] ✓ Copied test_images_summary.md -> /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260917_run1/test_images_summary_20260917_run1.md
[2026-09-17 15:00:26] ✗ Phase 3 automated tests completed with failures.
[2026-09-17 15:00:26] Generating Markdown report...
[2026-09-17 15:00:43] ✓ Markdown report written: /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260917_run1/test_report_20260917_run1.md

D. Review the output

# CTRL C # Exit tail
screen -R
# [2026-09-17 15:00:43] ✓ Markdown report written: /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260917_run1/test_report_20260917_run1.md

# real    153m25.620s
# user    9m52.678s
# sys     2m36.317s
exit # Exit screen

Copying the auto-generated /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260917_run1/test_report_20260917_run1.md below.

AUTO-GENERATED REPORT

Auto-generated report

20260917 zppy test

Below, I follow the steps of the automated testing docs page.

Step 1: Determine what the current expected results are

Promotion date for each cfg/task under /lcrc/group/e3sm/public_html/zppy_test_resources -- i.e. when its expected-results files were last copied into place. This is not necessarily when those results were actually produced (see Step 2, which uses a different, content-based date for exactly that reason).

expected_comprehensive_v3:

Task Last promoted
pcmdi_diags 2026-09-14

expected_legacy_3.1.0_comprehensive_v3:

Task Last promoted
pcmdi_diags 2026-09-14

Step 2: Review changes since expected results were updated

Commits merged on each repo's expected-results baseline branch (see the "Branch tested" column when it differs from the branch this run actually tested) since that dependency's expected results were actually produced (for e3sm_to_cmip/zppy, which have no per-task expected results of their own, since they were last tested instead -- see the *_LAST_TESTED_DATE config variables). The "Since" date is read from the Generated: line of the promoted env_description.txt (the earliest one found across every cfg being tested) -- deliberately not the promotion date from Step 1 above, since results normally sit under review before being promoted, so the promotion date routinely lags well behind the run that actually produced them (set the matching *_EXPECTED_RESULTS_DATE/*_LAST_TESTED_DATE in the config to override any date below when you know better, e.g. from a discussion thread). zppy-interfaces bundles two independently-refreshed tasks, so its row is split into global_time_series and pcmdi_diags, each with its own date.

Package Branch tested Since Changes since expected results were produced
e3sm_to_cmip master (same as baseline) 2026-09-15 None
e3sm_diags main (same as baseline) 2026-08-28 #1085, #1082, #903, #1080
mpas_analysis develop (same as baseline) 2026-08-28 1dfa1da1f, e4ecec81b
zppy-interfaces (global_time_series) enhance_robust_pcmdi 2026-08-12 477aa64, 87adf0d, 3f62bda, 0dd1305, 4e6861a, 25fb510, 3a8d66c, 5a8dc10, ad1f4e2, ef4bec8
zppy-interfaces (pcmdi_diags) enhance_robust_pcmdi 2026-09-04 None
zppy combined-pcmdi-test 2026-09-15 #868

Environment descriptions

An env_description.txt (commit hash + conda package list) was written for each task alongside its diagnostic output. Locally cached copies:

Task env_description.txt
e3sm_diags /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260917_run1/env_descriptions_20260917_run1/e3sm_diags.txt (also copied to each cfg's _www output dir)
e3sm_to_cmip /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260917_run1/env_descriptions_20260917_run1/e3sm_to_cmip.txt (also copied to each cfg's _www output dir)
global_time_series /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260917_run1/env_descriptions_20260917_run1/global_time_series.txt (also copied to each cfg's _www output dir)
mpas_analysis /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260917_run1/env_descriptions_20260917_run1/mpas_analysis.txt (also copied to each cfg's _www output dir)
pcmdi_diags /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260917_run1/env_descriptions_20260917_run1/pcmdi_diags.txt (also copied to each cfg's _www output dir)

Automated test script results

  • zppy-interfaces unit tests: passed
  • zppy unit tests: passed
  • Image-checker/report unit tests: passed (tests/images/test_image_checker.py, tests/images/test_image_severity.py, tests/images/test_image_summary_report.py)
  • Output directory status files: failed
  • Integration tests:
    • test_last_year.py: passed
    • test_bash_generation.py: passed
    • test_campaign.py: passed
    • test_defaults.py: passed
    • test_bundles.py: failed

TODO: one or more steps above did not pass -- review the full log captured from this script's stdout (for example, integration_test_runN.log if you used tee integration_test_runN.log) and note any unexpected failures here.

Step 8: Run Python tests

The image checker (pytest tests/integration/test_images.py) was launched automatically as a SLURM job and no longer requires a manual compute-node step.

  • SLURM job ID: 1289601

  • Terminal state: FAILED

  • Exit code: 1:0

  • Summary source: final

  • Full output: /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260917_run1/image_checker_20260917_run1.o1289601

Complete summary table

Summary of test results

Test name Total images Correct images Identical Cosmetic only Missing images Needs review Severity
comprehensive_v3_pcmdi_diags 647 154 0 154 (sample) 167 (list) 326 (list, grid), ranked 167 missing, 138 structural, 17 major, 48 moderate, 123 minor, 154 negligible

Summary table -- only failing image-check tests, sorted by task

pcmdi_diags

Test name Total images Correct images Identical Cosmetic only Missing images Needs review Severity
comprehensive_v3_pcmdi_diags 647 154 0 154 (sample) 167 (list) 326 (list, grid), ranked 167 missing, 138 structural, 17 major, 48 moderate, 123 minor, 154 negligible

Results analysis

TODO: fill in analysis of any failures above (expected vs. unexpected, whether expected results should be updated, etc.).


Manual review of results

Test name Total images Correct images Identical Cosmetic only Missing images Needs review Severity
comprehensive_v3_pcmdi_diags 647 154 0 154 (sample) 167 (list) 326 (list, grid), ranked 167 missing, 138 structural, 17 major, 48 moderate, 123 minor, 154 negligible

The sample of cosmetic diffs and the grid of bigger diffs appear to only have small diffs, however some of those do involve plots looking slightly different.

The list of missing images was previously identified to be simply because the expected results need to be updated to point to the new file names.

Did ENSO plots get produced?

No, https://web.lcrc.anl.gov/public/e3sm/diagnostic_output/ac.forsyth2/zppy_weekly_comprehensive_v3_www/zppy_main_branch_test_20260917_run1/v3.LR.historical_0051/pcmdi_diags/model_vs_obs/metrics_data/ does not contain ENSO plots.

Did any jobs fail?

cd /lcrc/group/e3sm/ac.forsyth2/zppy_weekly_legacy_3.1.0_comprehensive_v3_output/zppy_main_branch_test_20260917_run1/v3.LR.historical_0051/post/scripts
grep -v "OK" *status
# Good, no errors

cd /lcrc/group/e3sm/ac.forsyth2/zppy_weekly_comprehensive_v3_output/zppy_main_branch_test_20260917_run1/v3.LR.historical_0051/post/scripts
grep -v "OK" *status
# pcmdi_diags_enso_model_vs_obs_1985-1994.status:ERROR (12)
# pcmdi_diags_synthetic_plots_model_vs_obs.status:WAITING 1289577
cat pcmdi_diags_enso_model_vs_obs_1985-1994.o1289576

gives:

Traceback (most recent call last):
  File "/gpfs/fs1/home/ac.forsyth2/miniforge3/envs/test-zi-enhance_robust_pcmdi-20260917_run1/bin/zi-pcmdi-enso", line 6, in <module>
    sys.exit(main())
             ~~~~^^
  File "/gpfs/fs1/home/ac.forsyth2/miniforge3/envs/test-zi-enhance_robust_pcmdi-20260917_run1/lib/python3.14/site-packages/zppy_interfaces/pcmdi_diags/pcmdi_enso.py", line 287, in main
    build_enso_obsvar_catalog(core_output.obs_dic, requested_variables)
    ~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/gpfs/fs1/home/ac.forsyth2/miniforge3/envs/test-zi-enhance_robust_pcmdi-20260917_run1/lib/python3.14/site-packages/zppy_interfaces/pcmdi_diags/pcmdi_enso.py", line 404, in build_enso_obsvar_catalog
    catalogue_key = _resolve_enso_catalogue_key(obs_dic, vkey)
  File "/gpfs/fs1/home/ac.forsyth2/miniforge3/envs/test-zi-enhance_robust_pcmdi-20260917_run1/lib/python3.14/site-packages/zppy_interfaces/pcmdi_diags/pcmdi_enso.py", line 473, in _resolve_enso_catalogue_key
    raise KeyError(
    ...<2 lines>...
    )
KeyError: "Variable key 'hfls' not found in observation dictionary. Available keys are ['psl', 'pr', 'ts', 'tas', 'tauu', 'tauv', 'hfss']"

Synthetics plots can't start because ENSO failed, because hfls was not found.

@zhangshixuan1987 It looks like we need to remove hfls from enso_vars = "psl,pr,ts,tas,tauu,tauv,hfls,hfss,rlds,rsds,rlus,rlut,rsdt" in tests/integration/template_weekly_comprehensive_v3.cfg on E3SM-Project/zppy#815. Or perhaps the simulation I'm using as input data simply does not have the required data to produce ENSO plots?

@zhangshixuan1987

zhangshixuan1987 commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

@forsyth2: Hi Ryan, unfortunately, the hfls is mandatorily needed by pcmdi-enso to generate a complete list of metrics data and figures. Simply removing hfls from enso_vars will not be preferred and will lead to errors in the subsequent enso metrics processing step.

I think the error here is simply because you did not process the hfls data, as I did not find the data file under the following directory: /lcrc/group/e3sm/ac.forsyth2/zppy_weekly_comprehensive_v3_output/zppy_main_branch_test_20260917_run1/v3.LR.historical_0051/post/atm/180x360_aave/cmip_ts/monthly/

To trace back the cause, in your /lcrc/group/e3sm/ac.forsyth2/zppy_weekly_comprehensive_v3_output/zppy_main_branch_test_20260917_run1/v3.LR.historical_0051/post/scripts/provenance.20260917_184833_773843.cfg

[e3sm_to_cmip]
active = True
environment_commands = "source /home/ac.forsyth2/miniforge3/etc/profile.d/conda.sh; conda activate test-e3sm-to-cmip-master-20260917_run1"
frequency = "monthly"
ts_num_years=2
walltime = "00:30:00"

  [[ atm_monthly_180x360_aave ]]
  active = True
  cmip_plevdata = "/lcrc/group/e3sm/diagnostics/e3sm_to_cmip_data/maps/vrt_remap_plev19.nc"
  cmip_vars = "ua, va, ta, wap, zg, hur, tas, ts, psl, ps, sfcWind, huss, pr, prc, prsn, evspsbl, tauu, tauv, hfls, clt, rlus, rsds, rsus, hfss, clivi, clwvi, rlut, rsdt, rsuscs, rsut, rtmt, abs550aer, od550aer, rsdscs, tasmax, tasmin"
  input_files = "eam.h0"
  ts_subsection = "atm_monthly_180x360_aave"
  vars = "ICEFRAC,LANDFRAC,OCNFRAC,PSL,FSNTC,FSNTOAC,SWCF,LWCF,FLUT,FSNT,FSNTOA,FLNT,FLNTC,FSNS,FLNS,FSNS,SHFLX,QFLX,LHFLX,TAUX,TAUY,PRECC,PRECL,PRECSC,PRECSL,TS,TREFHT,U10,QREFHT,TMQ,CLDTOT,CLDHGH,CLDMED,CLDLOW,FLDS,FSDS,TGCLDIWP,TGCLDCWP,TGCLDLWP,FLNSC,FLUTC,FSDSC,SOLIN,FSNSC,AODABS,AODVIS,AODDUST,AREL,TREFMNAV,TREFMXAV,PS,PHIS,U,V,T,Z3"
  years = "1985:1995:2", # Need 10 years for pcmdi_diags task

This is correct as hfls is indeed included in the cmip_vars. However, when we further trace back to your [ts] section

  [[ atm_monthly_180x360_aave ]]
  active = True
  frequency = "monthly"
  input_files = "eam.h0"
  input_subdir = "archive/atm/hist"
  years = "1985:1995:2", # Need 10 years for pcmdi_diags task
  # We need to add extra vars to the default `vars=""` value
  # in order to enable the e3sm_diags mp_partition set.
  vars = "FSNTOA,FLUT,FSNT,FLNT,FSNS,FLNS,SHFLX,QFLX,TAUX,TAUY,PRECC,PRECL,PRECSC,PRECSL,TS,TREFHT,CLDTOT,CLDHGH,CLDMED,CLDLOW,U,PSL,LANDFRAC,CLD_CAL_TMPICE,CLD_CAL_TMPLIQ,CLDLIQ,T"

You did not add LHFLX as part of the time series processing. So the LHFLX data from e3sm is never produced; therefore, [e3sm_to_cmip] will not produce hfls as there is no upstream LHFLX passed in. Here is what I used in my setup:

  [[ atm_monthly_180x360_aave ]]
  active = True
  frequency = "monthly"
  input_files = "eam.h0"
  input_subdir = "archive/atm/hist"
  years = "1985:1995:2", # Need 10 years for pcmdi_diags task
  # We need to add extra vars to the default `vars=""` value
  # in order to enable the e3sm_diags mp_partition set.
  vars = "ICEFRAC,LANDFRAC,OCNFRAC,PSL,FSNTC,FSNTOAC,SWCF,LWCF,FLUT,FSNT,FSNTOA,FLNT,FLNTC,FSNS,FLNS,FSNS,SHFLX,QFLX,LHFLX,TAUX,TAUY,PRECC,PRECL,PRECSC,PRECSL,TS,TREFHT,U10,QREFHT,TMQ,CLDTOT,CLDHGH,CLDMED,CLDLOW,FLDS,FSDS,TGCLDIWP,TGCLDCWP,TGCLDLWP,FLNSC,FLUTC,FSDSC,SOLIN,FSNSC,AODABS,AODVIS,AODDUST,AREL,TREFMNAV,TREFMXAV,PS,PHIS,U,V,T,Z3"

A comment from me: I think it is a good idea to make the [ts], [e3sm_to_cmip], and diagnostic sections, including [e3sm_diags] and [pcmdi_diags], fully independent. This would make the workflow more flexible and modular. However, this also places greater responsibility on the user. In particular, the user needs to understand which variables are required by each diagnostic package and make sure those variables are properly included in both the [ts] and [e3sm_to_cmip] sections. To me, it may be worth thinking about a good "default" setup in the example cfg file to ensure that a default list of variables required by each set of diagnostics supported by zppy can be processed anyway.

@forsyth2

Copy link
Copy Markdown
Collaborator

Thanks @zhangshixuan1987, I've replied on the zppy PR.

@zhangshixuan1987

zhangshixuan1987 commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

@forsyth2: Hi Ryan, as your AUTO-GENERATED REPORT seems to perform a comparison with some existing pcmdi diagnostic results you perhaps generated before. If the target is to do a fair comparison with the old results like what you showed in the table above, please consider following extra modifications in your cfg file (/lcrc/group/e3sm/ac.forsyth2/zppy_weekly_legacy_3.1.0_comprehensive_v3_output/zppy_main_branch_test_20260917_run1/v3.LR.historical_0051/post/scripts/provenance.20260917_184844_958693.cfg)

  1. [[ mean_climate ]] section:
  clim_regions = "global,ocean,land"

Simply remove "Tropics,NHEX,SHEX" so that we do not process it for this test to save time. Also, for sample run, we do not need these regions.

  1. [[ variability_modes_cpl ]] section:
  # default setup: HadISST2, user can omit it as this is default setup. 
  movc_obs_sets = "alternatd4"  

This change is due to the update in the reference dictionary which now assign a unique key for each observations for each variable. By default, HadISST2 observations is used, and is now referred to as alternatd4 (previously is alternate1). We note that the use of HadISST2 is to make the portrait and parallel coordinate metrics figure more meaningful because the CMIP6 reference datasets for comparison also used the HadISST2 observations,

  1. [[ variability_modes_atm ]] section:
  # default setup: NOAA-2OC, user can omit it as this is default setup. 
  mova_obs_sets = "alternate4"  

Similar to what we described in 2, the changes here is also due to the change in reference dictionray. By default, NOAA-20C observations is used, and is now referred to as alternate4 (previously is alternate1). We note that the use of NOAA-20C is to make the portrait and parallel coordinate metrics figure more meaningful because the CMIP6 reference datasets for comparison also used the NOAA-20C,

  1. [[ enso ]] section:
  current_set = "enso" # Can be inferred if needed
  # Default setup: ERA-Interim for surface state and fluxes, GPCP for precipitation
  # HadISST2 for sst/ts. user can omit it to use default setup
  enso_obs_sets = "alternate3,default,alternatd4,alternate3,alternate3,alternate3,alternate3,alternate3,alternate3,alternate3,alternate3,alternate3,alternate3"
  ref_final_yr = 1994
  ref_start_yr = 1985
  ref_years = "1985-1994",
  enso_groups = "ENSO_perf,ENSO_proc,ENSO_tel"
  # Note: needs to ensure these variables are added and processed by e3sm_to_cmip step
  enso_vars = "psl,pr,ts,tas,tauu,tauv,hfls,hfss,rlds,rsds,rlus,rlut,rsdt"

I checked the directory :

/lcrc/group/e3sm/ac.forsyth2/zppy_weekly_legacy_3.1.0_comprehensive_v3_output/zppy_main_branch_test_20260917_run1/v3.LR.historical_0051/post/atm/180x360_aave/cmip_ts/monthly/
and I think that "hfls", "rlds", "rsds", "rlus" and "rsdt" are all missings, which needs to be processed via [ts] and [e3sm_to_cmip] sections. For this case, the following changes needs to be made:

[ts]
...
  [[ atm_monthly_180x360_aave ]]
  vars = "ICEFRAC,LANDFRAC,OCNFRAC,PSL,FSNTC,FSNTOAC,SWCF,LWCF,FLUT,FSNT,FSNTOA,FLNT,FLNTC,FSNS,**FLNS**,FSNS,**SHFLX**,QFLX,**LHFLX**,TAUX,TAUY,PRECC,PRECL,PRECSC,PRECSL,TS,TREFHT,U10,QREFHT,TMQ,CLDTOT,CLDHGH,CLDMED,CLDLOW,**FLDS**,**FSDS**,TGCLDIWP,TGCLDCWP,TGCLDLWP,FLNSC,FLUTC,FSDSC,**SOLIN**,FSNSC,AODABS,AODVIS,AODDUST,AREL,TREFMNAV,TREFMXAV,PS,PHIS,U,V,T,Z3"

[e3sm_to_cmip]
....
  [[ atm_monthly_180x360_aave ]]
   ....
  cmip_vars = "ua, va, ta, wap, zg, hur, tas, ts, psl, ps, sfcWind, huss, pr, prc, prsn, evspsbl, tauu, tauv, hfls, clt, rlus, rsds, rsus, hfss, clivi, clwvi, **rlds,** rlut, rsdt, rsuscs, rsut, rtmt, abs550aer, od550aer, rsdscs, tasmax, tasmin"

Note that: [[ atm_monthly_180x360_aave ]] does not explicitly specify the vars in the test. So it may not include enought variables for downstream [e3sm_to_cmip] process (bold variables in list above are madatorily required for enso diagnostics). Specfically, [e3sm_to_cmip] requires (1) LHFLX to process hfls; (2) FLDS and FLNS to process rlds and rlus; (3) FSDS to process rsds, and (4) SOLIN to process rsdt; (5) PSL, PRECT, TS, THREFHT, SHFLX, TAUX, TAUY for psl,pr,ts,tas,hfss, tauu, and tauv, respectively.

  1. [[ synthetic_plots ]] section:
  [[ synthetic_plots ]]
  clim_vars = "pr,psl,rsus,rsuscs,rlus,rlut,rsds,rsdscs,rsdt,rsut,sfcWind,tas,tauu,tauv,ts,ta-200,ta-850,ua-200,ua-850,va-200,va-850"
  clim_regions = "global,ocean,land"
  enso_viewer = True
  enso_years = "1985-1994"
  cmip_enso_dir = /lcrc/group/e3sm/diagnostics/pcmdi_data/metrics_data/enso_metric

@forsyth2

Copy link
Copy Markdown
Collaborator

Thanks @zhangshixuan1987, responded here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

semver: bug Bug fix (will increment patch version) semver: small improvement Small improvement (will increment patch version)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants