Skip to content

Zppy pcmdi enhancement - #815

Merged
forsyth2 merged 24 commits into
mainfrom
zppy_pcmdi_enhancement
Sep 25, 2026
Merged

forsyth2 merged 24 commits into
mainfrom
zppy_pcmdi_enhancement

Conversation

@zhangshixuan1987

Copy link
Copy Markdown
Collaborator

Summary

Objectives:

  • Enhance robustness of the PCMDI diagnostics workflow.
  • Add adaptive num_workers safeguard for resource-intensive diagnostics.
  • Improve extraction and handling of observations.
  • Bug fixes to NCO processing and general workflow reliability.

Issue resolution:

This pull request is to:

  • a bug fix: increment the patch version
  • a small improvement: increment the minor version
  • a new feature: increment the minor version
  • an incompatible (non-backwards compatible) API change: increment the major version
  • Please fill out either the "Small Change" or "Big Change" section (the latter includes the numbered subsections), and delete the other.

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?

  • I have added or modified at least one "min-case" configuration file to test this change. Every objective above is represented in at least one cfg. Note: I have tested the changes, but did not add a new or modified "min-case" config file. Please advise if this is required.

2. Are the implementation details accurate & efficient?

  • I have visually inspected the entire pull request myself.
  • I have left comments highlighting important pieces of code logic in the commit messages. I have had these code blocks reviewed by at least one other team member.

3. Is this well documented?

  • Documentation: Key usage notes and explanations are included in the commit messages. The code changes are also commented for clarity. Please advise if further documentation updates are needed.

4. Is this code clean?

-Pre-commit checks: All the pre-commits checks have passed.

@zhangshixuan1987
zhangshixuan1987 requested a review from forsyth2 May 5, 2026 23:18
@zhangshixuan1987 zhangshixuan1987 self-assigned this May 5, 2026
@zhangshixuan1987
zhangshixuan1987 marked this pull request as draft May 5, 2026 23:18
@zhangshixuan1987

Copy link
Copy Markdown
Collaborator Author

@forsyth2: I submit this pull request for some changes I made based on my observations during the testing I did when I try to resolve the reported issues for #807. I noticed in my log file for the mean climate metrics calculation:

  • /compyfs/zhan391/e3sm_project/E3SMv3_testings/v3.LR.historical_0051/post/scripts/fix2/pcmdi_diags_mean_climate_model_vs_obs_1985-1994.o755476

ncra: WARNING nco_fl_lst_stdin() tried and failed to get input filename(s) from stdin ncra: ERROR received 1 positional filename(s); need at least two ncra Command line options cheatsheet (full details at http://nco.sf.net/nco.html#ncra): ncra [-3] [-4] [-5] [-6] [-7] [-A] [--bfr byt] [-C] [-c] [--cb ...] [--cmp sng] [--cnk_byt byt] [--cnk_csh byt] [--cnk_dmn nm,lmn] [--cnk_map map] [--cnk_min byt] [--cnk_plc plc] [--cnk_scl sz] [-D dbg_lvl] [-d ...] [--dbl|flt] [-F] [--fl_fmt fmt] [-G grp:lvl] [-g ...] [--gaa ...] [--gad ...] [-H] [-h] [--hdf] [--hdr_pad nbr] [--hpss] [-L lvl] [-l path] [--mro] [--msa] [-N] [-n ...] [--no_cll_msr] [--no_cll_mth] [--no_frm_trm] [--no_tmp_fl] [-O] [-o out.nc] [-p path] [--prm_ints] [--prw] [--qnt ...] [--qnt_alg alg_nm] [-R] [-r] [--ram_all] [--rec_apn] [-t thr_nbr] [--uio] [--unn] [-w wgt] [-v ...] [-X box] [-x] [-y op_typ] [-Y prg_nm] in1.nc in2.nc [...] [out.nc]

I did some debugging and found that there is a missing condition that is not fully handled in https://github.com/E3SM-Project/zppy/pull/815/changes#diff-47eb0fa6e3e0e191a714b640eddb33424a2516b9dab2c7ec57e46193d8e160bdL238-R301

Specifically, for the mean climate metrics calculation of radiative flux variables such as rsut, when CERES-EBAF is used as the observational reference, the observational dataset only covers 2001–2018. If the target model output period is, for example, 1985–1994, which is completely outside the observational coverage period, then the current code path is not able to correctly perform the observational subselection and can lead to unexpected behavior in the downstream metrics calculation. Although the metrics processing will still proceed and produce outputs, this is not the expected behavior. Instead, when this situation occurs, we would expect the workflow to fall back to using the full available observational period (2001–2018 in this case) to calculate the observational climatology for comparison, rather than attempting an invalid temporal subselection.

@chengzhuzhang
chengzhuzhang requested a review from Copilot May 6, 2026 17:01
@chengzhuzhang
chengzhuzhang marked this pull request as ready for review May 6, 2026 17:02
@zhangshixuan1987
zhangshixuan1987 force-pushed the zppy_pcmdi_enhancement branch from ace60e0 to f50333f Compare May 11, 2026 05:24
@chengzhuzhang

Copy link
Copy Markdown
Collaborator

@zhangshixuan1987 Should we convert this PR back to draft? From another thread, we decided that the pcmdi feature will remain as beta version until enso set is being finalized and integrated. We are at the end of e3sm-unified testing period, if you'd like to make more changes, we can choose to review and merge this PR after this e3sm-unified release.

@zhangshixuan1987

Copy link
Copy Markdown
Collaborator Author

@chengzhuzhang : Sure, I converted this to a draft. Please take any necessary actions and let me know if you need any help from me.

@zhangshixuan1987
zhangshixuan1987 marked this pull request as draft May 11, 2026 16:35
@chengzhuzhang

Copy link
Copy Markdown
Collaborator

Thank you @zhangshixuan1987. Let's plan to keep this PR open and integrate in the next release. Thanks for the testing and keeping enhance this feature.

@zhangshixuan1987
zhangshixuan1987 force-pushed the zppy_pcmdi_enhancement branch 2 times, most recently from 1057e0f to cd718e4 Compare May 16, 2026 01:33
@forsyth2

Copy link
Copy Markdown
Collaborator

@zhangshixuan1987 Can you please rebase this off the latest main? There are enough conflicts that it'd be worthwhile to rebase before I do my code review.

Options to rebase:

# Option 1:
git fetch upstream main # Assuming you named your remote "upstream", might be "origin"
git rebase upstream/main # Address conflicts here
# When I tried this, I didn't hit conflicts until 10 commits in (out of 18 added here)

# Option 2:
git fetch upstream main # Assuming you named your remote "upstream", might be "origin"
# Check what came before the 18 commits of this branch:
git log --oneline | head -n 19 # We see this branch is based off the commit 17ce9335
git rebase -i 17ce9335
# Keep "pick" for the first commit
# Use "f" for fixup for the 17 later commits
git fetch upstream main # Assuming you named your remote "upstream", might be "origin"
git rebase upstream/main
# Address conflicts here

Option 1 allows you to keep the commit history intact, but you'll have to address conflicts commit-by-commit which could be tedious. Option 2 allows you to address conflicts in one go, but it puts the work from all 18 commits under the first commit.

@zhangshixuan1987
zhangshixuan1987 force-pushed the zppy_pcmdi_enhancement branch from cd718e4 to c35d90e Compare June 19, 2026 17:53
@zhangshixuan1987

Copy link
Copy Markdown
Collaborator Author

@forsyth2 : Hi Ryan, I did a rebased following your suggestions. Please take a look and let me know if you have any questions. Thank you!

@forsyth2

Copy link
Copy Markdown
Collaborator

Great, thanks @zhangshixuan1987! I'll work on reviewing this PR and E3SM-Project/zppy-interfaces#49 today.

@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-interfaces PR together. I had Claude do an initial review (for some reason, Copilot won't let me request a review). I then did a very-high level visual inspection based on its comments. I already posted a similar review for that PR.

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

A few notes:

  1. Re: the rebase -- you can combine changes rather than picking one or the other. It looks like all the vrt changes of #827 were removed here.
  2. Some of the error numbers need fixing, but I do like the new count-by-10s incrementing system
  3. It looks like this PR enables ENSO support. Is that correct? That is, we can use ENSO after this merges?

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 print → logger.debug in pcmdi_diags.py print → logger.info in viewer.py
Tests — New tests for synthetic_metrics_plotter, expanded test_viewer.py

zppy PR

1. New MPAS component support (utils.py)

set_component_and_prc_typ() gains two new cases:

  • mpaso → component="ocn", prc_typ="mpasocean"
  • mpassi → component="ice", prc_typ="mpasseaice"

This is the foundation that enables the rest of the MPAS work.

2. New configuration parameters (default.ini)

Added to [climo]:

  • mpas_rst_template — MPAS restart file template to guide time-series processing.

Added to [ts]:

  • mpas_calendar (default: "noleap") — Calendar type for constructing a CF-style numeric time coordinate.
  • mpas_start_time (default: "0001-01-01 00:00:00") — Reference time for time:units = "days since ...".

Removed from [ts]:

  • vrt_remap_vars, vrt_remap_file, vrt_in_file — The vertical remap parameters are removed from [ts]. ⚠️ Breaking change for existing users who configured vertical remapping via these keys. Confirm whether this functionality has been moved elsewhere or intentionally dropped. The template still contains a {%- if vrt_remap_vars != '' %} block (visible at the end of the diff), suggesting some vertical remap logic may still be active — this inconsistency should be clarified.

Modified in [e3sm_to_cmip]:

  • vrt_in_file removed.
  • interp_vars default changed from "U,V,T,Q,RELHUM,OMEGA,Z3" to "" (empty). ⚠️ Behavioral change: previously, vertical interpolation ran by default for a standard set of 3D variables. Now users must opt in explicitly. Existing workflows that relied on the default will silently stop interpolating. This deserves prominent documentation.

Modified in [pcmdi_diags]:

  • enso_vars default updated: prsn removed, hflx → hfls. These look like correctness fixes (matching standard CMIP variable names), but should be confirmed.

3. pcmdi_diags.py: enso set handling changed

⚠️ Concern. The enso set previously triggered a break to skip the task entirely, with a warning message saying it was "not yet supported." This PR changes the warning to say it is "currently a testing mode" and comments out the continue (which was itself commented out, not active). The net result is that the enso set now runs. Whether this is intentional and whether enso is actually stable enough to run by default should be confirmed with the author.

4. Multi-subsection dependency support (pcmdi_diags.py)

add_ts_dependencies() now supports comma-separated ts_subsection values, iterating over each and registering dependencies for all of them. Similarly, a new add_e3sm_to_cmip_dependencies() function was extracted and applies the same pattern for e3sm_to_cmip_atm_subsection. Both functions also defensively initialize the subsection key if it is absent before calling set_value_of_parameter_if_undefined() (which requires the key to exist). This is a solid improvement for multi-component workflows.

5. MPAS time-series shell template (large addition)

The most substantial change is a new block in the ts shell template for processing MPAS ocean and sea-ice variables. Key steps:

  1. SGS (sub-grid-scale) helper construction for mpasseaice: Derives a static binary mask (sgs_msk_static) from sgs_frc_static (static ice fraction), avoiding reliance on timeMonthly_avg_icePresentIntMask which may not be present in every split file. Falls back gracefully if timeMonthly_avg_iceAreaCell is missing.

  2. ncremap invocation: Two code paths — with and without SGS helpers — both using --d2f, --no_stagger, and a provided mapping file. The SGS path additionally passes --sgs_frc, --sgs_msk, and --sgs_nrm.

  3. Post-remap cleanup: Removes helper variables (sgs_frc_static, sgs_msk_static, and conditionally timeMonthly_avg_iceAreaCell) from the output, but only if they are actually present. The presence check uses a grep against ncks -m output.

  4. MPAS time metadata restoration: After remap, restores xtime_startMonthly, xtime_endMonthly, and timeMonthly_avg_daysSinceStartOfSim from the pre-remap temp file if they were dropped.

  5. CF-style time coordinate construction: If the output lacks a standard time variable, derives one from timeMonthly_avg_daysSinceStartOfSim, renames it, and attaches units, calendar, long_name, and standard_name attributes. Uses mpas_start_time and mpas_calendar from config.

  6. missing_value attribute: Adds missing_value = 1.0e36 to the main floating-point variable if it is absent, without touching _FillValue.

  7. Cleanup: All temp files removed at the end.

Concerns:

  • The grep pattern used to check variable presence — grep -qE "^[[:space:]]*[^:]+[[:space:]]+${main_var}(\(|\[)" — is somewhat fragile. A variable whose name is a substring of another could potentially match incorrectly. Consider using ncks --var or a more precise pattern.
  • Error code numbering: The existing "move output ts files" step was bumped from ERROR (3) to ERROR (5) to make room for the new MPAS error codes (3 and 4). Ensure all error code documentation and any monitoring/alerting is updated accordingly.
  • The {%- if vrt_remap_vars != '' %} block visible at the end of the diff suggests vertical remap logic is still present in the template, even though vrt_remap_vars was removed from default.ini. This is either dead code or an inconsistency that needs resolution.

Comment thread zppy/defaults/default.ini Outdated
Comment thread zppy/defaults/default.ini Outdated
Comment thread zppy/pcmdi_diags.py Outdated
Comment thread zppy/pcmdi_diags.py Outdated
Comment thread zppy/templates/ts.bash Outdated
Comment thread zppy/templates/e3sm_to_cmip.bash Outdated
Comment thread zppy/templates/ts.bash Outdated
Comment thread zppy/templates/ts.bash Outdated
Comment thread zppy/templates/pcmdi_diags.bash Outdated
Comment thread zppy/templates/pcmdi_diags.bash Outdated
@zhangshixuan1987
zhangshixuan1987 force-pushed the zppy_pcmdi_enhancement branch from c35d90e to 740f231 Compare June 29, 2026 03:02
@zhangshixuan1987
zhangshixuan1987 force-pushed the zppy_pcmdi_enhancement branch 2 times, most recently from 8e1d72d to 06e5602 Compare September 10, 2026 20:41
@zhangshixuan1987

Copy link
Copy Markdown
Collaborator Author

Hi @forsyth2 : I rebuilt this branch from the latest main and narrowed PR #815 to the ENSO-related changes only. The PR now enables the experimental PCMDI ENSO path, corrects the default ENSO variables, updates the documentation, and adds a regression test. The earlier MPAS, dependency-refactoring, PCMDI robustness, and error-code changes are no longer included. The vertical-remapping functionality from #827 remains intact and unchanged from main. Therefore, the earlier review threads attached to the removed changes are now outdated. The current diff is limited to four ENSO-focused files and is ready for a fresh review.

@forsyth2

Copy link
Copy Markdown
Collaborator

Accidentally closed; reopened

@zhangshixuan1987

Copy link
Copy Markdown
Collaborator Author

Thanks @czender. I opened a new issue #875 to track this on the zppy-side, since we've determined it's an independent issue to this PR.

@forsyth2: Hi Ryan, based on what you responded here, I assume there is no further action needed from me regarding the PCMDI-related changes in this PR. Please let me know if I misunderstood anything or if there are any remaining issues that require additional work on my side.

@forsyth2

Copy link
Copy Markdown
Collaborator

I assume there is no further action needed from me regarding the PCMDI-related changes in this PR.

Hi @zhangshixuan1987 That's correct. Once I get #875 sorted out, I'll run a new test.

@zhangshixuan1987

Copy link
Copy Markdown
Collaborator Author

I assume there is no further action needed from me regarding the PCMDI-related changes in this PR.

Hi @zhangshixuan1987 That's correct. Once I get #875 sorted out, I'll run a new test.

Thank you, Ryan.

@forsyth2

Copy link
Copy Markdown
Collaborator

@zhangshixuan1987 After a great deal of test setup modification*, it looks like we're producing results again. See here. Please let me know how that looks. Unfortunately, something went wrong with my test script, so it didn't run the tests on the output. I'm retesting** now but I won't be able to look at the results until tomorrow.

* Constrained NCO to be less than 5.4.0 in e3sm_to_cmip and in zppy_interfaces to filter out the NCO bug
** I'm retesting now with Charlie's latest NCO version, which should hopefully also not have the bug.

Assuming the tests pass (or fail with acceptable diffs), I'll try to do visual inspections of this PR and E3SM-Project/zppy-interfaces#49. I think we're still on track to get this merged by Xylar's E3SM Unified release candidate deadline of Oct. 5, but ideally we'd get it merged much sooner.

@zhangshixuan1987

zhangshixuan1987 commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator Author

@zhangshixuan1987 After a great deal of test setup modification*, it looks like we're producing results again. See here. Please let me know how that looks. Unfortunately, something went wrong with my test script, so it didn't run the tests on the output. I'm retesting** now but I won't be able to look at the results until tomorrow.

@forsyth2: Hi Ryan, I checked the results from the link you shared above. All of the expected results are now showing correctly, and the output looks consistent with what I expected. I think this looks good from my side.

I’d also like to tag Jiwoo (@lee1043) here in case he has a chance to do a quick review of the diagnostic output.

Your remaining plan also works for me. After the NCO update and the e3sm_to_cmip fix, if the new PCMDI test produces results consistent with what we are seeing here, I think it should be ready to merge.

@lee1043

lee1043 commented Sep 22, 2026

Copy link
Copy Markdown

@zhangshixuan1987 Thank you for pinging me on this. I skimmed through the diagnostics in from the viewer page that was pointed from the comment in the PR.

I don’t see any diagnostics that are obviously wrong, so it seems good to go! Nice organization for the results in the viewer page!

This is not necessarily a technical issue from this zppy work, but during the inspection I just noticed that for ta-200 DJF, it shows strong warm bias over the polar region in the model, which might be an interest for model developers unless already known issue.

@zhangshixuan1987

zhangshixuan1987 commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator Author

@zhangshixuan1987 Thank you for pinging me on this. I skimmed through the diagnostics in from the viewer page that was pointed from the comment in the PR.

I don’t see any diagnostics that are obviously wrong, so it seems good to go! Nice organization for the results in the viewer page!

@lee1043: Thank you, Jiwoo, for the quick review. I am glad that the current results look reasonable to you.

This is not necessarily a technical issue from this zppy work, but during the inspection I just noticed that for ta-200 DJF, it shows strong warm bias over the polar region in the model, which might be an interest for model developers unless already known issue.

@lee1043: nice catch. I think this is something not simple to answer with the test here. The test here only used a short period (1985-1994), and it may not be robust enough to draw scientific conclusions. However, it seems that similar features also showed in e3sm_diag output https://web.lcrc.anl.gov/public/e3sm/diagnostic_output/ac.wlin/E3SMv3/v3.LR.historical_0051/e3sm_diags/atm_monthly_180x360_aave/model_vs_obs_1985-2014/viewer/lat_lon/era5/t-200mb-global-era5/ann.html

We can keep monitoring this when we apply the zppy-pcmdi for the upcoming model evaluation.

@forsyth2

Copy link
Copy Markdown
Collaborator

2026-09-23 Run2 zppy pcmdi_diags ENSO support test

Set up and run the test

Set up e3sm_to_cmip and zppy-interfaces to use pinned NCO

See #875. We can't have e3sm_to_cmip (or presumably zppy-interfaces) use NCO 5.4.0 yet. climo and ts were already using E3SM Unified 1.13.0, which doesn't have NCO 5.4.0.

cd ~/ez/e3sm_to_cmip
git checkout constrain-nco
git diff HEAD^ HEAD
# nco >=5.1.4, <5.4.0

# For zppy, we've been pushing updated branches to GitHub 
# so the script can fetch the latest main/master branch.
# We don't want to push a just-for-testing branch to the e3sm_to_cmip repo though.
# So, we'll make the environment ourselves.

lcrc_conda
rm -rf build
conda clean --all --y
conda env create -f conda-env/dev.yml -n test-e3sm-to-cmip-master-20260923_run2
conda activate test-e3sm-to-cmip-master-20260923_run2
pre-commit run --all-files
python -m pip install .
conda list | grep nco
# nco                       5.3.9                h06dfe6a_2    conda-forge
# Good, it's not on 5.4.0
cd ~/ez/zppy-interfaces
git checkout enhance_robust_pcmdi
# 34 commits from https://github.com/E3SM-Project/zppy-interfaces/pull/49/commits
git fetch upstream enhance_robust_pcmdi 
git reset --hard upstream/enhance_robust_pcmdi
git checkout -b combined-pcmdi-test-20260923-run2
# 1 commit from https://github.com/E3SM-Project/zppy-interfaces/pull/62/commits
git fetch upstream nco-dev 
git rebase upstream/nco-dev
git log --oneline | head -n 36
# git log --oneline | head -n 36
# ...
# 41b13dc Clude-augment enhancement on the pcmdi_diags
# 9d4e8aa Add NCO spec to conda dev file
# 477aa64 Merge pull request #61 from E3SM-Project/update-pre-commit-deps

# NOTE: For `ZI_BASE_BRANCH="combined-pcmdi-test-20260923-run2"` 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-20260923-run2

Set up the zppy branch

cd ~/ez/zppy
git status
# nothing to commit, working tree clean
git checkout zppy_pcmdi_enhancement
# 24 commits from https://github.com/E3SM-Project/zppy/pull/815/commits
git fetch upstream zppy_pcmdi_enhancement
git reset --hard upstream/zppy_pcmdi_enhancement
git checkout -b combined-pcmdi-test-20260923-run2
# 30 commits from https://github.com/E3SM-Project/zppy/pull/871/commits
git fetch upstream issue-869-improve-test-automation
git rebase upstream/issue-869-improve-test-automation
git log --oneline | head -n 56 # 24+30+2
# 4e8ef955 pre-commit fix
# ...
# 94fa057b Enable experimental PCMDI ENSO diagnostics
# 5735aefd One authoritative production path per case; nest development by user (#868)
# 2127adc3 Add pcmdi_diags to legacy 3.1.0 image checks
# ...
# 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-20260923-run2
# nothing to commit, working tree clean

# NOTE: For `ZPPY_BASE_BRANCH="combined-pcmdi-test-20260923-run2"` 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-20260923-run2

# 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_20260923_run2
cd ~/ez/zppy_main_branch_tests/test_20260923_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

Set up the test cfg

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_NUMBER=2

ZI_BASE_BRANCH="combined-pcmdi-test-20260923-run2" # zi PR #849
ZPPY_BASE_BRANCH="combined-pcmdi-test-20260923-run2" # 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" # Keep as-is

# We don't need to spend time building dev environments for these:
DIAGS_ENV_TYPE="unified"
MPAS_ENV_TYPE="unified"

NCO_PATH="" # Keep as-is

# 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"

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_20260923_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_20260923_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-23 15:31:35] Starting zppy integration test automation
# Ctrl-A D to detach from screen
screen -ls # See what screen sessions you have
tail -f integration_test_run2.log
[2026-09-23 16:42:42] ✗ Image checker job failed with state FAILED (ExitCode 1:0)
[2026-09-23 16:42:42] ✗ Image checker job did not pass. Output: /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260923_run2/image_checker_20260923_run2.o1293931
[2026-09-23 16:42:42] ✓ Copied test_images_summary.md -> /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260923_run2/test_images_summary_20260923_run2.md
[2026-09-23 16:42:42] ✗ Phase 3 automated tests completed with failures.
[2026-09-23 16:42:42] Generating Markdown report...
[2026-09-23 16:42:53] ✓ Markdown report written: /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260923_run2/test_report_20260923_run2.md
[2026-09-23 16:42:53] ✓ Integration test automation complete!
[2026-09-23 16:42:53] ✓ Markdown report: /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260923_run2/test_report_20260923_run2.md

D. Review the output

# CTRL C # Exit tail
screen -R
# [2026-09-23 16:42:53] ✓ Markdown report: /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260923_run2/test_report_20260923_run2.md

# real    71m17.167s
# user    6m12.473s
# sys     1m23.184s
exit # Exit screen

Copying the auto-generated /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260923_run2/test_report_20260923_run2.md below.

AUTO-GENERATED REPORT

Auto-generated report

20260923 zppy test

See automated testing docs page for info on setup.

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

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 #1090, #1085, #1082, #903, #1080
mpas_analysis develop (same as baseline) 2026-08-28 1dfa1da1f, e4ecec81b
zppy-interfaces (global_time_series) combined-pcmdi-test-20260923-run2 2026-08-12 477aa64, 87adf0d, 3f62bda, 0dd1305, 4e6861a, 25fb510, 3a8d66c, 5a8dc10, ad1f4e2, ef4bec8, #55
zppy-interfaces (pcmdi_diags) combined-pcmdi-test-20260923-run2 2026-09-04 None
zppy combined-pcmdi-test-20260923-run2 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_20260923_run2/env_descriptions_20260923_run2/e3sm_diags.txt
e3sm_to_cmip /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260923_run2/env_descriptions_20260923_run2/e3sm_to_cmip.txt
global_time_series /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260923_run2/env_descriptions_20260923_run2/global_time_series.txt
mpas_analysis /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260923_run2/env_descriptions_20260923_run2/mpas_analysis.txt
pcmdi_diags /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260923_run2/env_descriptions_20260923_run2/pcmdi_diags.txt

Each task's env_description.txt above is also copied to <prefix>/<case>/<task>/env_description.txt under that cfg's _www output dir, for each of the following per-cfg prefixes:

Cfg _www prefix
weekly_comprehensive_v3 /lcrc/group/e3sm/public_html/diagnostic_output/ac.forsyth2/zppy_weekly_comprehensive_v3_www/zppy_main_branch_test_20260923_run2
weekly_legacy_3.1.0_comprehensive_v3 /lcrc/group/e3sm/public_html/diagnostic_output/ac.forsyth2/zppy_weekly_legacy_3.1.0_comprehensive_v3_www/zppy_main_branch_test_20260923_run2

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: passed
  • Integration tests:
    • test_last_year.py: passed
    • test_bash_generation.py: passed
    • test_campaign.py: passed
    • test_defaults.py: passed
    • test_bundles.py: skipped (no _bundles cfg in CFGS_TO_RUN)

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: 1293931

  • Terminal state: FAILED

  • Exit code: 1:0

  • Summary source: final

  • Full output: /home/ac.forsyth2/ez/zppy_main_branch_tests/test_20260923_run2/image_checker_20260923_run2.o1293931

Complete summary table

Test name Total images Correct images Identical Cosmetic only Missing images Needs review Severity
comprehensive_v3_pcmdi_diags 647 321 55 266 (sample) 156 (list) 170 (list, grid), ranked 156 missing, 142 structural, 28 minor, 266 negligible, 55 identical
legacy_3.1.0_comprehensive_v3_pcmdi_diags 617 321 55 266 (sample) 126 (list) 170 (list, grid), ranked 126 missing, 142 structural, 28 minor, 266 negligible, 55 identical

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 321 55 266 (sample) 156 (list) 170 (list, grid), ranked 156 missing, 142 structural, 28 minor, 266 negligible, 55 identical
legacy_3.1.0_comprehensive_v3_pcmdi_diags 617 321 55 266 (sample) 126 (list) 170 (list, grid), ranked 126 missing, 142 structural, 28 minor, 266 negligible, 55 identical

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 321 55 266 (sample) 156 (list) 170 (list, grid), ranked 156 missing, 142 structural, 28 minor, 266 negligible, 55 identical
legacy_3.1.0_comprehensive_v3_pcmdi_diags 617 321 55 266 (sample) 126 (list) 170 (list, grid), ranked 126 missing, 142 structural, 28 minor, 266 negligible, 55 identical

@zhangshixuan1987 Please review these diffs. The "expected" is based off the 9/4 test run results. If these diffs look good to you, then I'll do a visual inspection of this PR and E3SM-Project/zppy-interfaces#49. If those turn out ok, we can merge.

  • Cosmetic-only diffs: these do appear to be negligible.
  • Missing images:
    • Most are because of the typo fix in the word "pattern". we'll just have to update the expected results after merging.
    • ERROR_METRIC also has some missing plots; perhaps we're no longer interested in those for this test?
  • Mismatched images: severity of diff goes all the way to the "structual" rank. I will need this to be approved as the new expectation if they are all acceptable.

Did ENSO plots get produced?

Yes, https://web.lcrc.anl.gov/public/e3sm/diagnostic_output/ac.forsyth2/zppy_weekly_comprehensive_v3_www/zppy_main_branch_test_20260923_run2/v3.LR.historical_0051/pcmdi_diags/model_vs_obs/ENSO_metric/ exists.

Furthermore, the viewer includes ENSO plots now.

Did any jobs fail?

No, the AUTO-GENERATED REPORT notes:

Output directory status files: passed

@zhangshixuan1987

zhangshixuan1987 commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator Author

@forsyth2 : Hi Ryan, thank you for running the new tests. Below is some review comments from me:

  1. The missing figures in 156 (list) and 126 (list) in terms of pcmdi_diags/model_vs_obs/CLIM_patttern/land/ and pcmdi_diags/model_vs_obs/CLIM_patttern/ocean/ as well as pcmdi_diags/model_vs_obs/ERROR_metric/mean_climate/land and pcmdi_diags/model_vs_obs/ERROR_metric/mean_climate/ocean are expected to me. This is because the test current by default have following setup:
  [[ mean_climate ]]
  clim_regions = "global"

namely, we only process global region. This is not an issue, and I would recommend this default setup, while leaving the users to customize other regions like ocean and land when needed. Overall, this is good.

  1. However, the missing list also include pcmdi_diags/model_vs_obs/CLIM_patttern/global, which is a little confusing to me, as we indeed have figures shown on the viewer page:
Screenshot 2026-09-23 at 4 19 42 PM

So, I think that this is either a misreport, or the change of the organization after the update on this PR. Therefore, I do not think these are issues.

  1. For the differences in 170 (grid). I think that they are expected. Specifically, I expect that the actual figure is generated by the new test with code under this PR, and the expected are the comparing reference from an old test. If this understanding is correct, then the actual figure from this PR is more correct: it added more variables in the sythentic metrics plot (recommend default); it fixed the missing panel labels in the figures related to the MOV patterns.

Overall, I think that the results from this PR with the new test here are good, which indicates that the code is good to go.

@forsyth2

Copy link
Copy Markdown
Collaborator

Hi @zhangshixuan1987

expected to me. [...] Overall, this is good.

Great!

However, the missing list also include pcmdi_diags/model_vs_obs/CLIM_patttern/global, which is a little confusing to me

Sorry, that's what my comment 'Most are because of the typo fix in the word "pattern". we'll just have to update the expected results after merging.' was referring to.

Overall, I think that the results from this PR with the new test here are good, which indicates that the code is good to go.

Great! Are you able to request a Copilot review on this PR? This one won't let me, but I was able to request one on the zppy-interfaces PR E3SM-Project/zppy-interfaces#49. In any case, I'll begin visual inspections of the two PR's diffs.

@zhangshixuan1987

zhangshixuan1987 commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator Author

Great! Are you able to request a Copilot review on this PR? This one won't let me, but I was able to request one on the zppy-interfaces PR E3SM-Project/zppy-interfaces#49. In any case, I'll begin visual inspections of the two PR's diffs.

I wasn't included in the E3SM project to use Copilot, so I can not issue a Copilot review. It is not clear to me why you can not issue it, as I think you have permission to use E3SM Copilot resource.

Would it be possible because this is still in draft mode? I just clicked Request Review; maybe you can try to request the Copilot again and see if it works now.

@zhangshixuan1987
zhangshixuan1987 marked this pull request as ready for review September 24, 2026 00:28
@forsyth2

Copy link
Copy Markdown
Collaborator

@zhangshixuan1987 Ah ok, it still doesn't seem to let me choose it. I can do what I did before and give the diff to Claude to comment on. I'll try to do both PR reviews tomorrow and hopefully we can merge it. Minor changes after review are probably ok, because we can always catch possible errors on the main branch testing.

@zhangshixuan1987

zhangshixuan1987 commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator Author

@zhangshixuan1987 Ah ok, it still doesn't seem to let me choose it. I can do what I did before and give the diff to Claude to comment on. I'll try to do both PR reviews tomorrow and hopefully we can merge it. Minor changes after review are probably ok, because we can always catch possible errors on the main branch testing.

Another possibility is that you were not listed as an assignee. I removed the Copilot request that I submitted and added you as an assignee. Could you try requesting Copilot from your side and give it one last try?

If that still does not work, your Plan B using Claude sounds good to me. Please let me know if you need any further action from my side.

@forsyth2

Copy link
Copy Markdown
Collaborator

give the diff to Claude to comment on

I asked it to review only for serious issues and it confirmed there's no truly serious issues to address.

Another possibility is that you were not listed as an assignee.

Ah that seems to be it! I can request it now. Ok let me do that, thanks.

I also want to do at least a high-level read-through of this PR (+1,065 -327 lines) and E3SM-Project/zppy-interfaces#49 (+2,980 -435 lines). That's a total of +4,045 -762 lines. We can proceed with this since we're so far along, but I think going forward we should try to avoid such large PRs (e.g., by splitting them up)

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.

Copilot review overview

🟡 Changes recommended

Critical backward-compatibility issues and unresolved worker-safeguard and date-parsing defects remain.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 2 High severity · 2 Medium severity

Open (4)

Comment thread zppy/pcmdi_diags.py
Comment on lines +218 to +222
if "obs_sets" in c:
raise ValueError(
f"obs_sets is no longer supported for {c['current_set']}; "
f"use {obs_sets_parameter} instead."
)

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.

This has already been discussed and deemed acceptable since pcmdi_diags is currently a beta release. Can't seem to find the comment to link to.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

If I understand correctly, the change here is to rename obs_sets to obs_sets_parameter, right? I think this should be acceptable in our case. Since the previous beta releases have only been used by us for testing, we do not really need to maintain backward compatibility with those configurations. For the new release, users should follow the updated examples/documentation and use obs_sets_parameter, so this error should normally not be encountered.

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.

If I understand correctly, the change here is to rename obs_sets to obs_sets_parameter, right?

I think Copilot is saying don't error out if someone still sets obs_sets to maintain backwards compatibility. But we had already agreed it's fine to break that here.

For the new release, users should follow the updated examples/documentation and use obs_sets_parameter, so this error should normally not be encountered.

Yes, correct.

Comment on lines +5 to +6
"alternatc1" : "ceres_ebaf_v4.0",
"alternatc2" : "ceres_ebaf_v2.8",

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.

As noted above, backwards compatibility is not being considered high-priority now since pcmdi_diags is a beta release.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks, Ryan. That makes sense to me. Since pcmdi_diags is still in beta and we are not prioritizing backward compatibility at this stage, I think removing the old alternate alias is fine as long as the updated examples/documentation consistently use the new aliases.

Comment thread zppy/defaults/default.ini
# Sea-surface height is opt-in. Append ssh to enso_vars and default (AVISO) to
# enso_obs_sets only when an ssh model time series is available; zppy does not
# translate zos to ssh.
enso_obs_sets = string(default="alternate3,default,alternatd4,alternate3,alternate3,alternate3,alternate3,alternate3,alternate3,alternate3,alternate3,alternate3,alternate3")

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.

@zhangshixuan1987 I think this comment and the next are referring to "Add adaptive num_workers safeguard for resource-intensive diagnostics." from the PR description. I think maybe that was something left over from an earlier iteration? Do we need that for this PR?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think this may have been left over from an earlier iteration of the PR description. In our testing so far, the PCMDI diagnostics, including ENSO, have completed successfully, and we have not encountered any issues related to num_workers. So I don't think the adaptive num_workers safeguard is necessary for this PR. We can remove that item from the PR description to keep the stated scope consistent with the implementation.

Comment thread zppy/pcmdi_diags.py
Comment on lines 62 to +63
if c["current_set"] == "enso":
logger.warning(
"The 'enso' set is not yet supported in PCMDI Diags. Skipping launching of associated jobs."
)
break # Skip this task
logger.warning("The 'enso' set is currently experimental in PCMDI Diags.")

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think this is the same issue as the previous comment. The adaptive num_workers safeguard was likely left over from an earlier iteration of the PR description. In our testing so far, including the ENSO diagnostics, we have not encountered any problems related to num_workers. I therefore do not think an additional safeguard is needed for this PR, and we can remove that objective from the PR description.

The idea of adapting num_workers came from a possible performance optimization rather than from a correctness or stability issue. For mean_climate, users can select different numbers of variables; for ENSO, they can select different sets of metrics; and for modes of variability, they can choose different numbers of modes to process. Since these options are customizable, the computational workload can vary substantially. In principle, dynamically increasing or reducing num_workers based on the selected workload could improve overall efficiency by reducing both queue time and processing time. However, I think this can be treated as a separate optimization for future work rather than as a requirement for this PR.

@zhangshixuan1987

Copy link
Copy Markdown
Collaborator Author

@forsyth2 : Hi Ryan, see my response above, and please let me know if I misunderstood anything or if you have a different opinion.

@zhangshixuan1987

Copy link
Copy Markdown
Collaborator Author

I also want to do at least a high-level read-through of this PR (+1,065 -327 lines) and E3SM-Project/zppy-interfaces#49 (+2,980 -435 lines). That's a total of +4,045 -762 lines. We can proceed with this since we're so far along, but I think going forward we should try to avoid such large PRs (e.g., by splitting them up)

@forsyth2: I agree with you. As you mentioned, these PRs are large partly because this work evolved through several rounds of testing and debugging, and related changes accumulated in both zppy and zppy-interfaces as we worked through the PCMDI integration. In retrospect, we could have separated some of the functionality and fixes into smaller PRs.

For this round, since the two PRs are already fairly mature and closely connected, I agree that it makes sense to proceed with them as they are. Going forward, I should definitely attempt to split larger developments into smaller, more focused PRs—for example, separating configuration/interface changes, individual diagnostic enhancements, and unrelated bug fixes where possible. That should make the review process easier and also make the purpose and testing scope of each PR clearer.

@chengzhuzhang chengzhuzhang added this to the v3.3.0 milestone Sep 24, 2026

@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.

Given the size of the diffs, I've done extremely high-level visual inspections of this PR and its companion PR E3SM-Project/zppy-interfaces#49, looking for anything obviously out of place. A large part of the confidence to merge comes from the multiple iterations of both testing and Copilot/Claude reviews.

(Note Shixuan and I agreed above to try to reduce PR scope in the future.)

@forsyth2
forsyth2 merged commit 64bffc9 into main Sep 25, 2026
12 checks passed
@forsyth2

Copy link
Copy Markdown
Collaborator

Thank you @zhangshixuan1987 for your efforts on this and the companion PR E3SM-Project/zppy-interfaces#49. It's great that we'll have ENSO supported in pcmdi_diags now. They are both merged now.

@zhangshixuan1987

zhangshixuan1987 commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator Author

@forsyth2 Hi Ryan, thank you for your work on the PR. This was a great team effort, and I really appreciate your help!

@forsyth2
forsyth2 deleted the zppy_pcmdi_enhancement branch September 25, 2026 20:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants