Skip to content

Conversation

@forsyth2
Copy link
Collaborator

@forsyth2 forsyth2 commented Jun 3, 2025

Summary

Corresponding zppy PR: E3SM-Project/zppy#722

Objectives:

  • Separate out the Classic PDF and Viewer implementations for global_time_series. These two output formats deviate substantially and so separating them can help clean up the code.
  • Improve performance

Issue resolution:

Select one: This pull request is...

  • 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

Big Change

  • To merge, I will use "Create a merge commit". That is, this change is large enough to require multiple units of work (i.e., it should be multiple commits).

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

Required:

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

2. Are the implementation details accurate & efficient?

Required:

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

If applicable:

  • Dependencies: This pull request introduces a new dependency. I have discussed this requirement with at least one other team member. The dependency is noted in zppy-interfaces/conda, not just an import statement.

3. Is this well documented?

Required:

  • Documentation: by looking at the docs, a new user could easily understand the functionality introduced by this pull request.

4. Is this code clean?

Required:

  • Readability: The code is as simple as possible and well-commented, such that a new team member could understand what's happening.
  • Pre-commit checks: All the pre-commits checks have passed.

If applicable:

  • Software architecture: I have discussed relevant trade-offs in design decisions with at least one other team member. It is unlikely that this pull request will increase tech debt.

@forsyth2 forsyth2 self-assigned this Jun 3, 2025
@forsyth2 forsyth2 added the semver: new feature New feature (will increment minor version) label Jun 3, 2025
@forsyth2 forsyth2 changed the title Issue 23 gts split Split global_time_series task Jun 3, 2025
@forsyth2
Copy link
Collaborator Author

forsyth2 commented Jun 3, 2025

The first 4 commits form an initial implementation. The 4th commit, b0fb5f2, effectively replaces #22 (to close #21).

Remaining TODO:

  • Run unit tests and change anything that needs to be changed.
  • Figure out how to not modify shared spaces, which is essential for the next step below.
  • Try to get subtasks working in zppy, so we can run multiple instances of global_time_series from a single cfg. That will help with testing.
  • Separate out parameter spaces between Classic and Viewer
    • Update 6/4: I organized the parameters a bit, but I don't think a further refactoring is immediately necessarily -- perhaps something to consider later.
  • Reduce duplicated code by moving out any common code to utility functions. (The code as of now is "cleaner" in the sense that the output types of Classic PDF vs. Viewer have distinct workflows, but is "messier" in the sense that there are decent amounts of duplicated code.)
    • Update 6/4: It turns out most of the code that can be pulled out has been. Remaining duplicated code is mostly a few lines of boilerplate here and there, which would probably not be worth pulling into common functions.
  • Run zppy's integration tests (perhaps using the reorganized image checker code of Organize and extend image checker zppy#720) using this version of zppy-interfaces.
  • Add integration tests of the global_time_series task to zppy's test suite.
    • Perhaps it would be good to make a dedicated subdirectory zppy/tests/global_time_series/integration for more focused cfgs
  • See if the changes of Performance improvement attempt #19 can be included more easily now that the two output types are separated.

@forsyth2
Copy link
Collaborator Author

forsyth2 commented Jun 4, 2025

My latest plan is:

  1. Test and merge Organize and extend image checker zppy#720. I need this testing functionality set up so that I can be sure changes in this PR and the corresponding zppy PR don't break backwards compatibility.
  2. Test and merge this PR, along with the corresponding zppy PR Split global_time_series task zppy#722.
  3. Work on Performance improvement attempt #19 rebased off latest main. This PR is complex enough as-is, so it makes sense to keep that performance work in its distinct PR. The performance work of Only read requested vars #22, however, has already been included as part of this PR.

@forsyth2
Copy link
Collaborator Author

forsyth2 commented Jun 6, 2025

The Classic PDF and Viewer modes are now mostly separated. I also added legacy cfg tests. I also have subtasks working for global_time_series, which has been a great help to testing multiple cases simultaneously. I cleaned up the plotting code for viewers and I made updates to load rgn data at once per variable (similar to #19). Not re-loading the same data brought a test on a subset of land vars down from 124 seconds to 93 seconds.

I'm currently testing the true all-land-variable case, where we use NCO to get every land variable we can, and then go through the csv of land vars.

Remaining TODO as of 6/6:

  • It's contradictory to have both plots_original and any of the plots_{component} parameters set. In particular, since plots_original defaults to a list with dependencies on ocn data, it will by default cause Viewer mode land plots to have dependencies they don't need. So, this should be fixed. This also segues into a larger design decision below.
  • Check that all land variables from the csv (that are available) can be plotted using plots_lnd="all" (currently running this cfg)
  • Run the other weekly cfgs, including the new legacy ones.
  • Run the integration test suite on the output of those cfgs.
  • Send to code review

Major design decisions to make/confirm:

  1. How to properly handle parameters in the new 2-mode system:
        # TODO: it may make sense in the future to refactor this class. Options:
        # 1. Have this be the base class, for paramters used by both output types
        # and then have distinct ClassicParameters and ViewerParameters subclasses.
        # One potential issue is that the type checker will get confused if a
        # common utility function returns Parameters when we know in context
        # it's returning a specific subtype.
        # 2. Have 3 distinct classes: CoreParameters, ClassicParameters, ViewerParameters.
        # The downside here is that there will need to be two variables to hold the parameters:
        # core_parameters and either classic_parameters or viewer_parameters.
        # 3. The simplest thing is just to add input validation --
        # e.g., if in viewer mode, make sure these parameters are set and
        # if in classic mode, make sure these other parameters are set.
        #
        # This would be useful if we want to split `zi-global-time-series` into 2 distinct entry points,
        # each with specialized parameters.
  1. Per the last point there, do we want the 2 modes to have separate entry points or for the mode split to happen after the entry point to zppy-interfaces?
  2. Will the Classic PDF always be in the 4x2 grid? That is, do we need user-specified nrows and ncols still?
  3. The Classic PDF will never be used for plots other than the original 8, correct?
  4. We never want a viewer for the original 8 plots, correct?

@forsyth2
Copy link
Collaborator Author

forsyth2 commented Jun 9, 2025

Another possible performance improvement: while the Classic PDF mode puts multiple plots on a single page, the Viewer mode is only plotting one variable per PNG. That means we could compute the globalAnnual for a variable and then immediately plot it, rather than storing all the globalAnnual values and then plotting all of them. That would at least help with space, if that's an issue. I'm not sure if that would improve time or not though.

@forsyth2
Copy link
Collaborator Author

forsyth2 commented Jun 9, 2025

How to properly handle parameters in the new 2-mode system:

Per the last point there, do we want the 2 modes to have separate entry points or for the mode split to happen after the entry point to zppy-interfaces?

To further expand, there are several points in the workflow where we can separate into the 2 modes. From least to most change:

  1. Current implementation -- one zppy [global_time_series] task, one zppy-interfaces entry point. Classic PDF vs Viewer mode is determined based on make_viewer parameter. Because of the combined entry point, some parameters passed in are superfluous.
  2. Separate zppy-interfaces entry points. That is, global_time_series.bash in zppy would have two distinct zppy-interfaces calls -- one for the case of Classic PDF and one for the case of Viewer. Each entry point could have specialized parameters.
  3. Distinct tasks in the zppy cfg, e.g., [global_time_series_classic] and [global_time_series_viewer] tasks. This level of change would almost certainly be a breaking change (i.e., not-backward-compatible, increment major version number). That is, at this level, the user has two distinct tasks they want to run.

(Please note that the new subtask functionality allows both a Classic PDF job and a Viewer job to run simultaneously.)

@forsyth2
Copy link
Collaborator Author

forsyth2 commented Jun 9, 2025

Noting design decisions made after discussion with @chengzhuzhang @golaz @tomvothecoder :

  1. How to properly handle parameters in the new 2-mode system:

Not visible to the user, so I can make a determination of what makes the most sense. Probably option 3 or 1.

  1. Per the last point there, do we want the 2 modes to have separate entry points or for the mode split to happen after the entry point to zppy-interfaces?

Current implementation -- one zppy [global_time_series] task, one zppy-interfaces entry point. Classic PDF vs Viewer mode is determined based on make_viewer parameter. Because of the combined entry point, some parameters passed in are superfluous.

Let's keep the change at the lowest level possible, as to not affect users -- that is, keep the current implementation.

  1. Will the Classic PDF always be in the 4x2 grid? That is, do we need user-specified nrows and ncols still?

Yes, it will always be that grid, as we want a standard view. By extension, we can ignore the nrows and ncols parameters.

  1. The Classic PDF will never be used for plots other than the original 8, correct?

Correct. We want the Classic PDF to be pretty standard; we don’t anticipate adding arbitrary variables.

One caveat is if we want to start doing more sophisticated plotting with arbitrary variables. The Classic PDF mode has the advantage of using custom formulas/plotting functions whereas the Viewer Mode code just plots the globalAnnual result for a var. Even then though, any common code would probably just be pulled out and those new plots would be added to the viewer, not the standard view PDF.

  1. We never want a viewer for the original 8 plots, correct?

No viewer needed for the classic PDF plots.

Copy link
Collaborator Author

@forsyth2 forsyth2 left a comment

Choose a reason for hiding this comment

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

@chengzhuzhang This PR and its corresponding zppy PR E3SM-Project/zppy#722 are ready for review.

@@ -1,184 +0,0 @@
import os
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Removed this file because integration testing has been moved to zppy, per the design decision made at a recent meeting. That is, since zppy 1) produces input data needed for global_time_series and 2) has the image checker functionality, it makes sense to do integration testing there.

# NOTE: PRODUCES OUTPUT IN THE CURRENT DIRECTORY (not necessarily the case directory)
# Creates the directory parameters.results_dir
coupled_global(parameters)
# Determine if we want the Classic PDF or the Viewer
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is where the mode split happens (as discussed in #26 (comment)).

"shorten_year": True,
"title": "Change in sea level",
"use_getmoc": False,
"var": lambda exp: (
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Notice how "var" is often a formula for the Classic PDF mode plots. This is a major distinction from Viewer mode, which plots simple variables.

ts_num_years: int,
):
path_out = f"{case_dir}/post/ocn/glb/ts/monthly/{ts_num_years}yr"
path_out = f"{case_dir}/post/{subtask_name}/ocn/glb/ts/monthly/{ts_num_years}yr"
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This change is crucial to allowing multiple subtasks of global_time_series to run in parallel. Without this, they'd be overwriting each other.

Comment on lines 82 to 84
self.dataset = xcdat.open_mfdataset(file_path_list, center_times=True)
else:
self.dataset = xcdat.open_mfdataset(f"{directory}*.nc", center_times=True)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In Viewer mode, we look for specific files to load. In Classic PDF mode, we load everything, because we don't necessarily know what to look for (i.e., we may have to derive variables).

Comment on lines 320 to 327
exp["annual"][var_str] = {"glb": (data_array.isel(rgn=0), units)}
if data_array.sizes["rgn"] > 1:
# data_array.shape => number of years x 3 regions
# 3 regions = global, northern hemisphere, southern hemisphere
# We get here if we used the updated `ts` task
# (using `rgn_avg` rather than `glb_avg`).
exp["annual"][var_str]["n"] = (data_array.isel(rgn=1), units)
exp["annual"][var_str]["s"] = (data_array.isel(rgn=2), units)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We add another layer to the dict to hold all the region data, so we don't have to re-process for n and s.

Comment on lines 80 to 82
if self.plots_lnd:
logger.warning(
f"plots_lnd={self.plots_lnd} will not be plotted in Classic PDF mode."
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is also the reason for "case 3 above, the lnd plots aren't made in Classic PDF mode." "errors" noted in E3SM-Project/zppy#722 (comment). Expected test results will need to be updated to reflect that Classic and Viewer plots aren't placed together anymore.

@forsyth2 forsyth2 marked this pull request as ready for review June 11, 2025 19:00
@forsyth2 forsyth2 requested a review from chengzhuzhang June 11, 2025 19:00
@forsyth2
Copy link
Collaborator Author

Web results from latest test run

  1. bundles
  2. v2
  3. v3 -- 4 subtasks, so 4 subdirectories
  4. Legacy bundles
  5. Legacy v2
  6. Legacy v3

@chengzhuzhang
Copy link
Collaborator

@forsyth2 thanks for the PR, I have some initial review with questions:
Since the update is tested with true all-land-variable case, as well as including the classic coupled figures, how the performance look like?
The new scripts under /viewer seem to have some duplicated code for component wise anlysis and plotting, can use some refactoring to make the code more maintainable.
For refactor at this scale, it would be helpful to have @tomvothecoder to also have a review..

@forsyth2
Copy link
Collaborator Author

Since the update is tested with true all-land-variable case, as well as including the classic coupled figures, how the performance look like?

I put some initial performance numbers in E3SM-Project/zppy#722 (comment).

For this latest run, we find:

cd /lcrc/group/e3sm/ac.forsyth2/zppy_weekly_comprehensive_v3_output/test-zppy-gts-split-20250610-try3/v3.LR.historical_0051/post/scripts/ && grep -H "Elapsed time" global_time_series_*.o*
# global_time_series_all_lnd_var_viewer_1985-1995.o759913:Elapsed time: 5526 seconds
# global_time_series_classic_original_8_1985-1995.o759915:Elapsed time: 65 seconds
# global_time_series_classic_original_8_no_ocn_1985-1995.o759914:Elapsed time: 63 seconds
# global_time_series_specific_var_viewers_1985-1995.o759912:Elapsed time: 79 seconds

# We can see here that the all_lnd var case took 5526 seconds = 92.1 minutes = 1.5 hours
# While we're here, let's also check how long it takes NCO to process all the lnd variables:
grep -H "Elapsed time" ts_lnd_monthly_glb_*.o*
# ts_lnd_monthly_glb_1985-1989-0005.o759899:Elapsed time 1m26s
# ts_lnd_monthly_glb_1985-1989-0005.o759899:Elapsed time: 91 seconds
# ts_lnd_monthly_glb_1990-1994-0005.o759900:Elapsed time 1m25s
# ts_lnd_monthly_glb_1990-1994-0005.o759900:Elapsed time: 90 seconds

# None of the other tests have the subtasks, so they're only running the Classic PDF mode.

cd /lcrc/group/e3sm/ac.forsyth2/zppy_weekly_comprehensive_v2_output/test-zppy-gts-split-20250610-try3/v2.LR.historical_0201/post/scripts/ && grep -H "Elapsed time" global_time_series_*.o*
# global_time_series_1980-1990.o759882:Elapsed time: 92 seconds

cd /lcrc/group/e3sm/ac.forsyth2/zppy_weekly_legacy_3.0.0_comprehensive_v3_output/test-zppy-gts-split-20250610-try3/v3.LR.historical_0051/post/scripts/ && grep -H "Elapsed time" global_time_series_*.o*
# global_time_series_1985-1995.o759978:Elapsed time: 89 seconds

cd /lcrc/group/e3sm/ac.forsyth2/zppy_weekly_legacy_3.0.0_comprehensive_v2_output/test-zppy-gts-split-20250610-try3/v2.LR.historical_0201/post/scripts/ && grep -H "Elapsed time" global_time_series_*.o*
# global_time_series_1980-1990.o759948:Elapsed time: 85 seconds

The new scripts under /viewer seem to have some duplicated code for component wise anlysis and plotting, can use some refactoring to make the code more maintainable.

Can you point to a specific code block (i.e., comment on it in the PR)? You mean code is duplicated amongst components in Viewer mode, correct? (i.e., not that code is duplicated between Viewer and Classic PDF modes). I'm looking at the /viewer subdir and it seems pretty abstracted to me. E.g., construct_generic_variables and set_var are used by multiple components.

For refactor at this scale, it would be helpful to have @tomvothecoder to also have a review..

Sure, @tomvothecoder please review if you have the bandwidth.

@chengzhuzhang in the meantime, can you review E3SM-Project/zppy#720 so we can at least get that part merged? (The first two commits of that PR are shared with E3SM-Project/zppy#722.)

@forsyth2 forsyth2 requested a review from tomvothecoder June 12, 2025 16:03
@tomvothecoder
Copy link

For refactor at this scale, it would be helpful to have @tomvothecoder to also have a review..
Sure, @tomvothecoder please review if you have the bandwidth.

I have a few high priority items for e3sm_diags to try to close out before vacation. If I complete them and have time this afternoon, I'll try to squeeze in a quick review for this PR.

]
for rgn in parameters.regions:
for component, plot_list in mapping:
make_plot_pdfs(
Copy link
Collaborator

Choose a reason for hiding this comment

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

I asked Copilot for potential performance enhancement. One thing stands out is that the pdfs now are generated in serial, if we use multiprocessing to generate pdfs in parallel, it potentially can save a good amount of run time. And Copilot is able to provide suggested code patch , which include:
Add plot_single_variable as a helper for plotting one variable.
Use ProcessPoolExecutor to map over plot_list in parallel.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

#27

Comment on lines 178 to 209
requested_variables.vars_atm = set_var(
exp,
"atmos",
requested_variables.vars_atm,
valid_vars,
invalid_vars,
parameters,
)
requested_variables.vars_ice = set_var(
exp,
"ice",
requested_variables.vars_ice,
valid_vars,
invalid_vars,
parameters,
)
requested_variables.vars_land = set_var(
exp,
"land",
requested_variables.vars_land,
valid_vars,
invalid_vars,
parameters,
)
requested_variables.vars_ocn = set_var(
exp,
"ocean",
requested_variables.vars_ocn,
valid_vars,
invalid_vars,
parameters,
)
Copy link
Collaborator

@chengzhuzhang chengzhuzhang Jun 12, 2025

Choose a reason for hiding this comment

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

Here is an example of duplicated code for components.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm, I would say the code formatting simply make this appear more duplicative than it really is. It's a little tricky because we'd need to iterate through attributes rather than dictionary keys here.

The following at first appears like it would work, but it would not:

for (exp_key, var_list) in zip(["atm", "ice", "land", "ocean"], [requested_variables.vars_atm, requested_variables.vars_ice, requested_variables.vars_land, requested_variables.vas_ocn]):
  var_list = set_var(exp, exp_key, var_list, valid_vars, invalid_vars, parameters)

Notice then that while var_list is updated, the requested_variables.vars_{component} is absolutely not being updated!

We could try to dive deeper and have set_var simply mutate the var_list parameter, but that is also impractical. We create new_var_list by iterating through var_list. And then at that point, var_list = new_var_list would have exactly the same problem as above, i.e., the variable we want updated isn't getting updated.

And we can't simply mutate var_list while iterating through the for loop because that can cause issues and is thus bad practice. Really we'd need to do an element-wise copy from new_var_list to var_list.

traceback.print_exc()
logger.error(f"plot_generic failed. Invalid plot={plot_name}, rgn={rgn}")
invalid_plots.append(plot_name)
i += 1
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is probably not needed?

Copy link
Collaborator

Choose a reason for hiding this comment

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

sorry, I mean i+=1 is not needed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, it appears this didn't cause any issues because i gets rewritten on each iteration, but yes it should be removed. Probably a holdover from the original combined plotting.

@chengzhuzhang
Copy link
Collaborator

Can you point to a specific code block (i.e., comment on it in the PR)? You mean code is duplicated amongst components in Viewer mode, correct? (i.e., not that code is duplicated between Viewer and Classic PDF modes)

I point to an example that duplicated code among components just in viewer mode.

I think if the PR is just for separation (not concerning performance), it looks good to me. It does feel like we should be able to get speed up using multiprocessing because for the Viewer mode, the variables can be processed and plotted in parallel. This can probably addressed by another PR. I will move on the zppy PR now, just to clarify, the previous using configuration should still work?

@forsyth2
Copy link
Collaborator Author

I think if the PR is just for separation (not concerning performance), it looks good to me. It does feel like we should be able to get speed up using multiprocessing because for the Viewer mode, the variables can be processed and plotted in parallel. This can probably addressed by another PR.

I agree. It's a good improvement but it will add more complexity, so it's a good idea to implement it as a distinct PR.

just to clarify, the previous using configuration should still work?

Yes, the legacy cfg files work fine. The only caveat is that it is no longer possible to run both the Classic PDF and the Viewer mode under the same job. So, the legacy cfg's default to the Classic PDF mode -- that is they, they no longer produce lnd plots. The non-legacy cfg does produce all plots, because they are run by different subtasks and placed in different subdirectories. So, that is, the legacy cfgs run fine, but produce less output than previously.

@tomvothecoder tomvothecoder requested a review from Copilot June 12, 2025 23:27
@tomvothecoder
Copy link

tomvothecoder commented Jun 12, 2025

I tagged Copilot to do a review. Refer to it with discretion of course. I won't be able to review, but can do so when I get back from vacation (6/25) if this PR is still open.

Copy link

Copilot AI left a comment

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 refactors the global_time_series task by splitting the Classic PDF generation and Viewer implementations, updates CLI argument handling, and cleans up shared utilities to support both modes.

  • Introduces a dedicated Viewer driver and plotting modules
  • Updates Parameters to centralize shared and mode-specific configuration
  • Adjusts __main__.py to dispatch between Classic and Viewer workflows and updates tests accordingly

Reviewed Changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
zppy_interfaces/global_time_series/viewer/driver.py New Viewer entry point invoking run_coupled_global
zppy_interfaces/global_time_series/viewer/coupled_global_plotting.py Viewer-specific plotting logic
zppy_interfaces/global_time_series/viewer/coupled_global.py Core Viewer data-processing and run flow
zppy_interfaces/global_time_series/utils.py Enhanced Parameters class with shared/mode flags
zppy_interfaces/global_time_series/main.py Updated CLI to select Classic PDF vs. Viewer mode
tests/unit/global_time_series/test_global_time_series.py Tests extended to cover both classic and viewer paths
Comments suppressed due to low confidence (3)

zppy_interfaces/global_time_series/utils.py:70

  • The deprecation warning for ncols mistakenly labels the parameter as nrows; consider updating it to ncols={self.ncols} for accuracy.
logger.warning(f"nrows={self.ncols} is DEPRECATED. It will be overridden as 2.")

zppy_interfaces/global_time_series/viewer/coupled_global.py:108

  • [nitpick] The variable name vars shadows the built-in vars() function; consider renaming it (e.g., component_vars) to avoid confusion.
vars = get_vars(requested_variables, component)

zppy_interfaces/global_time_series/viewer/driver.py:8

  • [nitpick] The new viewer run entry point doesn't have corresponding unit tests; adding tests to cover this function will help ensure correct mode dispatching.
def run(parameters: Parameters):

traceback.print_exc()
logger.error(f"plot_generic failed. Invalid plot={plot_name}, rgn={rgn}")
invalid_plots.append(plot_name)
i += 1
Copy link

Copilot AI Jun 12, 2025

Choose a reason for hiding this comment

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

The manual increment of 'i' inside the for-loop is redundant because the loop variable is automatically updated; you can remove this line for clarity.

Suggested change
i += 1

Copilot uses AI. Check for mistakes.
valid_plots,
invalid_plots,
)
logger.info(f"These {rgn} region plots generated successfully: {valid_plots}")
Copy link

Copilot AI Jun 12, 2025

Choose a reason for hiding this comment

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

The valid_plots (and invalid_plots) lists accumulate entries across regions, so log messages include plots from previous regions; consider resetting these lists at the start of each region loop.

Copilot uses AI. Check for mistakes.
@forsyth2 forsyth2 force-pushed the issue-23-gts-split branch 2 times, most recently from 733854d to 64e1450 Compare July 1, 2025 23:09
@forsyth2
Copy link
Collaborator Author

forsyth2 commented Jul 1, 2025

@tomvothecoder @xylar The CI/CD Build Workflow is failing for a reason I can't work out: https://github.com/E3SM-Project/zppy-interfaces/actions/runs/16012365805/job/45172523746?pr=26

Relevant pieces of the stack trace:

Updating 'zppy-interfaces-dev' env from conda env update...
  /usr/share/miniconda3/condabin/mamba env update --name zppy-interfaces-dev --file conda/dev.yml
  Warning: info     libmamba ****************** Backtrace Start ******************
  debug    libmamba Loading configuration
[...]
  
  info     libmamba ****************** Backtrace Start ******************
  debug    libmamba Loading configuration
[...]
  Warning: trace    libmamba Configuration not found at '/etc/conda/condarc.d/'
[...]
  error    libmamba No prefix found at: /usr/share/miniconda3/envs/zppy-interfaces-dev
  error    libmamba Environment must first be created with "mamba create -n {env_name} ..."
  critical libmamba Aborting.
  info     libmamba ****************** Backtrace End ********************
  
[...]
  debug    libmamba No env 'name' specified in YAML spec file '/home/runner/work/zppy-interfaces/zppy-interfaces/conda/dev.yml'
[...]
  error    libmamba No prefix found at: /usr/share/miniconda3/envs/zppy-interfaces-dev
  error    libmamba Environment must first be created with "mamba create -n {env_name} ..."
  critical libmamba Aborting.
  info     libmamba ****************** Backtrace End ********************

Error: The process '/usr/share/miniconda3/condabin/mamba' failed with exit code 1

It was originally failing with No prefix found at: /usr/share/miniconda3/envs/zppy_interfaces_dev, so after asking LivChat, it said the dev name needed to match what's in dev.yml. I was skeptical of this because the build workflow and the release workflow were each last modified 8 months ago & the dev.yml was last modified 6 months ago, yet this error is just occurring now. I figured it was worth a try, so I made the commit ecb4f4e, but as expected that didn't actually do anything.

I'm also confused where mamba came from when cd ~/ez/zppy-interfaces; git grep mamba returns no matches.

forsyth2 and others added 15 commits October 1, 2025 16:24
…dling

Multiprocessing with 128+ processes was causing deadlocks and memory issues
when processing 300+ NetCDF variables. Sequential processing provides better
reliability and memory management for this I/O-intensive workload.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Adds run_zi_global-time-series.py to demonstrate usage of the global time
series functionality with real data paths and performance timing.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@forsyth2 forsyth2 force-pushed the issue-23-gts-split branch from 02927db to 3909dfa Compare October 1, 2025 22:51
@forsyth2
Copy link
Collaborator Author

forsyth2 commented Oct 1, 2025

could you test again?

Testing process

Expand
cd ~/ez/zppy-interfaces
git status
# Branch: issue-23-gts-split
# No uncommitted changes
git log
# Last commit (9/30): fix directory generation for component only config
# Bad, missing latest commis from https://github.com/E3SM-Project/zppy-interfaces/pull/26/commits
# First, make a backup
git checkout -b issue-23-gts-split-backup20251001
git checkout issue-23-gts-split
git fetch upstream
git reset --hard upstream/issue-23-gts-split
git log
# Good, latest commit: Merge remote changes and fix single-variable plot generation bug
# Good, commit hashs match https://github.com/E3SM-Project/zppy-interfaces/pull/26/commits
# Now, to rebase off the latest `main`
git fetch upstream
git rebase upstream/main
# We have some merge conflicts
git status
# Last commands done (19 commands done) 
git grep -n "<<<<<<<" 
# zppy_interfaces/global_time_series/coupled_global/plots_component.py:10:<<<<<<< HEAD
# zppy_interfaces/global_time_series/coupled_global/plots_component.py:56:<<<<<<< HEAD
# zppy_interfaces/global_time_series/coupled_global/utils.py:4:<<<<<<< HEAD
# zppy_interfaces/global_time_series/coupled_global/utils.py:235:<<<<<<< HEAD
# zppy_interfaces/global_time_series/coupled_global/utils.py:389:<<<<<<< HEAD
# zppy_interfaces/global_time_series/coupled_global/utils.py:715:<<<<<<< HEAD
# 
# For all: Accept Current Change 
git add zppy_interfaces/global_time_series/coupled_global/plots_component.py zppy_interfaces/global_time_series/coupled_global/utils.py
git rebase --continue
git status
# Last commands done (20 commands done)
#
# More merge conflicts
# At this point, let's just cherry-pick that commit on the original working branch...
git rebase --abort
# Let's rename this branch
git branch -m issue-23-gts-split-GitHub-0251001
# And use our backup branch instead:
git checkout issue-23-gts-split-backup20251001
git checkout -b issue-23-gts-split
git log
# Last commit: fix directory generation for component only config
# Now, we need to make this match https://github.com/E3SM-Project/zppy-interfaces/pull/26/commits
# That means adding the "fix for single variable" commit.
# (The merge commit adds no diffs)
git fetch upstream issue-23-gts-split
git cherry-pick 63599cb1e0dbe3255fa0089f5581f08615999364
git log
# Last commit: fix for single variable
# Good
git fetch upstream main
git rebase upstream/main
rm -rf build
conda clean --all --y
conda env create -f conda/dev.yml -n zi-gts-updates-20251001
conda activate zi-gts-updates-20251001
pre-commit run --all-files
python -m pip install .
pytest tests/unit/global_time_series/test_*.py
# 10 passed in 24.12s

cd ~/ez/zppy
git status
# Branch: update-python
# No uncommitted changes
git checkout issue-23-gts-split
git log
# Last commit (9/5): remove subsection to align with zi
# Good, matches https://github.com/E3SM-Project/zppy/pull/722/commits
# Hashes match too
# We do want to get the testing fixes commit though
git fetch upstream further-test-fixes
git cherry-pick b0132f0600cb6f867e6f45b37f2acf2d32a49c59
git log
# Good, commit added
rm -rf build
conda clean --all --y
conda env create -f conda/dev.yml -n zppy-gts-updates-20251001
conda activate zppy-gts-updates-20251001
pytest tests/test_*.py
# 25 passed in 0.69s

# Edit tests/integration/utils.py
# TEST_SPECIFICS: Dict[str, Any] = {
#     "diags_environment_commands": "source /gpfs/fs1/home/ac.forsyth2/miniforge3/etc/profile.d/conda.sh; conda activate e3sm-diags-main-20250926-rerun",
#     "global_time_series_environment_commands": "source /gpfs/fs1/home/ac.forsyth2/miniforge3/etc/profile.d/conda.sh; conda activate zi-gts-updates-20251001",
#     "cfgs_to_run": [
#         "weekly_bundles",
#         "weekly_comprehensive_v2",
#         "weekly_comprehensive_v3",
#         "weekly_legacy_3.0.0_bundles",
#         "weekly_legacy_3.0.0_comprehensive_v2",
#         "weekly_legacy_3.0.0_comprehensive_v3",
#     ],
#     "tasks_to_run": ["global_time_series"],
#     "unique_id": "test_gts_updates_20251001",
# }
python tests/integration/utils.py
pre-commit run --all-files
python -m pip install .
zppy -c tests/integration/generated/test_weekly_bundles_chrysalis.cfg
zppy -c tests/integration/generated/test_weekly_comprehensive_v2_chrysalis.cfg
zppy -c tests/integration/generated/test_weekly_comprehensive_v3_chrysalis.cfg
zppy -c tests/integration/generated/test_weekly_legacy_3.0.0_bundles_chrysalis.cfg
zppy -c tests/integration/generated/test_weekly_legacy_3.0.0_comprehensive_v2_chrysalis.cfg
zppy -c tests/integration/generated/test_weekly_legacy_3.0.0_comprehensive_v3_chrysalis.cfg

# Check on bundles status
cd /lcrc/group/e3sm/ac.forsyth2/zppy_weekly_bundles_output/test_gts_updates_20251001/v3.LR.historical_0051/post/scripts
grep -v "OK" *status
# Good, no errors
cd /lcrc/group/e3sm/ac.forsyth2/zppy_weekly_legacy_3.0.0_bundles_output/test_gts_updates_20251001/v3.LR.historical_0051/post/scripts
grep -v "OK" *status
# Good, no errors
# Now, run bundles part 2
cd ~/ez/zppy
git status
# No uncommitted changes
# Branch: issue-23-gts-split
zppy -c tests/integration/generated/test_weekly_bundles_chrysalis.cfg
zppy -c tests/integration/generated/test_weekly_legacy_3.0.0_bundles_chrysalis.cfg
# Actually, nothing new to run when we're only running global_time_series

### v2  ###
cd /lcrc/group/e3sm/ac.forsyth2/zppy_weekly_comprehensive_v2_output/test_gts_updates_20251001/v2.LR.historical_0201/post/scripts
grep -v "OK" *status
# Good, no errors
cd /lcrc/group/e3sm/ac.forsyth2/zppy_weekly_legacy_3.0.0_comprehensive_v2_output/test_gts_updates_20251001/v2.LR.historical_0201/post/scripts
grep -v "OK" *status
# Good, no errors
### v3 ###
cd /lcrc/group/e3sm/ac.forsyth2/zppy_weekly_comprehensive_v3_output/test_gts_updates_20251001/v3.LR.historical_0051/post/scripts
grep -v "OK" *status
# Good, no errors
cd /lcrc/group/e3sm/ac.forsyth2/zppy_weekly_legacy_3.0.0_comprehensive_v3_output/test_gts_updates_20251001/v3.LR.historical_0051/post/scripts
grep -v "OK" *status
# Good, no errors

cd ~/ez/zppy
ls tests/integration/test_*.py
git status
# Branch: issue-23-gts-split
pytest tests/integration/test_bash_generation.py
# 1 failed in 2.40s
# Diffs appear to be expected, based on this PR's changes to zppy/templates/global_time_series.bash
pytest tests/integration/test_campaign.py
# 6 passed in 4.14s
pytest tests/integration/test_defaults.py
# 1 passed in 0.62s
pytest tests/integration/test_last_year.py
# 1 passed in 0.60s
pytest tests/integration/test_bundles.py
# 2 failed in 0.52s
# Failure is because we didn't run the entire cfg and 
# this test isn't customized like test_images.py is.
pytest tests/integration/test_images.py
# 1 failed in 13.92s
cat test_images_summary.md

git log
# Matches https://github.com/E3SM-Project/zppy/pull/722/commits
# + https://github.com/E3SM-Project/zppy/pull/738/commits
# So, nothing to add here

cd ~/ez/zppy-interfaces
git log
# Doesn't match https://github.com/E3SM-Project/zppy-interfaces/pull/26/commits
# Need to push rebased commits
git push -f upstream issue-23-gts-split

Complete results

Expand

Diff subdir is where to find the lists of missing/mismatched images, the image diff grid, and the individual diffs.
Note image diff grids can not yet be constructed automatically.

Test name Total images Correct images Missing images Mismatched images Diff subdir
comprehensive_v2_global_time_series 6 6 0 0 /lcrc/group/e3sm/public_html/diagnostic_output/ac.forsyth2/zppy_weekly_comprehensive_v2_www/test_gts_updates_20251001/v2.LR.historical_0201/image_check_failures_comprehensive_v2/global_time_series
comprehensive_v3_global_time_series 15 0 15 0 /lcrc/group/e3sm/public_html/diagnostic_output/ac.forsyth2/zppy_weekly_comprehensive_v3_www/test_gts_updates_20251001/v3.LR.historical_0051/image_check_failures_comprehensive_v3/global_time_series
bundles_global_time_series 3 3 0 0 /lcrc/group/e3sm/public_html/diagnostic_output/ac.forsyth2/zppy_weekly_bundles_www/test_gts_updates_20251001/v3.LR.historical_0051/image_check_failures_bundles/global_time_series
legacy_3.0.0_comprehensive_v2_global_time_series 6 6 0 0 /lcrc/group/e3sm/public_html/diagnostic_output/ac.forsyth2/zppy_weekly_legacy_3.0.0_comprehensive_v2_www/test_gts_updates_20251001/v2.LR.historical_0201/image_check_failures_legacy_3.0.0_comprehensive_v2/global_time_series
legacy_3.0.0_comprehensive_v3_global_time_series 15 15 0 0 /lcrc/group/e3sm/public_html/diagnostic_output/ac.forsyth2/zppy_weekly_legacy_3.0.0_comprehensive_v3_www/test_gts_updates_20251001/v3.LR.historical_0051/image_check_failures_legacy_3.0.0_comprehensive_v3/global_time_series
legacy_3.0.0_bundles_global_time_series 3 3 0 0 /lcrc/group/e3sm/public_html/diagnostic_output/ac.forsyth2/zppy_weekly_legacy_3.0.0_bundles_www/test_gts_updates_20251001/v3.LR.historical_0051/image_check_failures_legacy_3.0.0_bundles/global_time_series

Concise results

Test name Total images Correct images Missing images Mismatched images Diff subdir
comprehensive_v3_global_time_series 15 0 15 0 /lcrc/group/e3sm/public_html/diagnostic_output/ac.forsyth2/zppy_weekly_comprehensive_v3_www/test_gts_updates_20251001/v3.LR.historical_0051/image_check_failures_comprehensive_v3/global_time_series

And those failures are because we changed what subtasks run for the v3 test, so all good.

Test cases

make_viewer = True make_viewer = False
both original & component viewer results dir
original only viewer results dir
component only viewer results dir
All land variables viewer N/A
Original 8 plots N/A results dir

Ok, it looks like the atmosphere plots are now linked, good.

Ready to merge

I think E3SM-Project/zppy#722 && #26 are now ready to merge.

I tested on rebased branches and have pushed the rebased commits. The atm plots and links are now showing up -- example.

@chengzhuzhang Do you want to check anything else or can I go ahead and merge these? Thanks for all the effort on this!

@chengzhuzhang
Copy link
Collaborator

Great news. I'm not very clear about the cases you tested: what is the difference between original only and Original 8 plots? Otherwise, I think this PR is good to merge.

@forsyth2
Copy link
Collaborator Author

forsyth2 commented Oct 1, 2025

what is the difference between original only and Original 8 plots?

Sorry, the last row should actually be "Original 8 plots, no ocn"

@chengzhuzhang
Copy link
Collaborator

Sorry, the last row should actually be "Original 8 plots, no ocn"

In this case, it’s a bit odd that “Original 8 plots, no ocn” produces no output when make_viewer = True, while Original does produce output. Let me quickly check the logic that disables output in the “Original 8 plots, no ocn” and “All land variables” cases. It might be better to always generate output, regardless of whether make_viewer is set to True or False.

@forsyth2
Copy link
Collaborator Author

forsyth2 commented Oct 1, 2025

In this case, it’s a bit odd that “Original 8 plots, no ocn” produces no output when make_viewer = True, while Original does produce output.

That is because I didn't run that case. N/A meant it wasn't one of the test cases.

@forsyth2
Copy link
Collaborator Author

forsyth2 commented Oct 1, 2025

Those last two rows are sort of extra add-on cases. The top 3 rows are the basics.

@forsyth2
Copy link
Collaborator Author

forsyth2 commented Oct 1, 2025

The test cases are defined in the test cfg https://github.com/E3SM-Project/zppy/blob/69c0a18edef13f058b21c190483d611123005291/tests/integration/generated/test_weekly_comprehensive_v3_chrysalis.cfg:

[global_time_series]
active = True
climo_years = "1985-1989", "1990-1995",
environment_commands = "source <INSERT PATH TO CONDA>/conda.sh; conda activate <INSERT ENV NAME>"
experiment_name = "v3.LR.historical_0051"
figstr = "v3.LR.historical_0051"
#moc_file=mocTimeSeries_1985-1995.nc
# plots_lnd = "FSH,RH2M,LAISHA,LAISUN,QINTR,QOVER,QRUNOFF,QSOIL,QVEGE,QVEGT,SOILWATER_10CM,TSA,H2OSNO,TOTLITC,CWDC,SOIL1C,SOIL2C,SOIL3C,SOIL4C,WOOD_HARVESTC,TOTVEGC,NBP,GPP,AR,HR"
ts_num_years = 5
ts_years = "1985-1989", "1985-1995",
walltime = "00:30:00"
years = "1985-1995",

  # Important parameter combinations ##########################################

  [[ viewer_both ]]
  # 1. make_viewer = True, plots_original set, >= 1 plots_<component> set
  # NOTE: This is the case displayed in examples/post.v3.LR.historical_zppy_v3.cfg
  make_viewer = True
  moc_file = "mocTimeSeries_1985-1995.nc"
  plots_atm = "TREFHT"
  plots_lnd = "FSH,RH2M,LAISHA,LAISUN,QINTR,QOVER,QRUNOFF,QSOIL,QVEGE,QVEGT,SOILWATER_10CM,TSA,H2OSNO,TOTLITC,CWDC,SOIL1C,SOIL2C,SOIL3C,SOIL4C,WOOD_HARVESTC,TOTVEGC,NBP,GPP,AR,HR"
  # Default plots_original = "net_toa_flux_restom,global_surface_air_temperature,toa_radiation,net_atm_energy_imbalance,change_ohc,max_moc,change_sea_level,net_atm_water_imbalance",

  [[ viewer_original ]]
  # 2. make_viewer = True, plots_original set, 0 plots_<component> set
  make_viewer = True
  moc_file = "mocTimeSeries_1985-1995.nc"
  # Default plots_original = "net_toa_flux_restom,global_surface_air_temperature,toa_radiation,net_atm_energy_imbalance,change_ohc,max_moc,change_sea_level,net_atm_water_imbalance",

  [[ viewer_component ]]
  # 3. make_viewer = True, plots_original set, >= 1 plots_<component> set
  make_viewer = True
  plots_atm = "TREFHT"
  plots_lnd = "FSH,RH2M,LAISHA,LAISUN,QINTR,QOVER,QRUNOFF,QSOIL,QVEGE,QVEGT,SOILWATER_10CM,TSA,H2OSNO,TOTLITC,CWDC,SOIL1C,SOIL2C,SOIL3C,SOIL4C,WOOD_HARVESTC,TOTVEGC,NBP,GPP,AR,HR"
  plots_original = ""

  # We can ignore this case for image checking.
  # [[ viewer_none ]]
  # # 4. make_viewer = True, plots_original not set, 0 plots_<component> set
  # make_viewer = True
  # plots_original = ""

  [[ classic_pdf_both ]]
  # 5. make_viewer = False, plots_original set, >= 1 plots_<component> set
  # Default make_viewer = False
  moc_file = "mocTimeSeries_1985-1995.nc"
  plots_atm = "TREFHT"
  plots_lnd = "FSH,RH2M,LAISHA,LAISUN,QINTR,QOVER,QRUNOFF,QSOIL,QVEGE,QVEGT,SOILWATER_10CM,TSA,H2OSNO,TOTLITC,CWDC,SOIL1C,SOIL2C,SOIL3C,SOIL4C,WOOD_HARVESTC,TOTVEGC,NBP,GPP,AR,HR"
  # Default plots_original = "net_toa_flux_restom,global_surface_air_temperature,toa_radiation,net_atm_energy_imbalance,change_ohc,max_moc,change_sea_level,net_atm_water_imbalance",

  [[ classic_pdf_original ]]
  # 6. make_viewer = False, plots_original set, 0 plots_<component> set
  # NOTE: This is the default case -- where all of the above parameters are at their default values.
  # Default make_viewer = False
  moc_file = "mocTimeSeries_1985-1995.nc"
  # Default plots_original = "net_toa_flux_restom,global_surface_air_temperature,toa_radiation,net_atm_energy_imbalance,change_ohc,max_moc,change_sea_level,net_atm_water_imbalance",

  [[ classic_pdf_component ]]
  # 7. make_viewer = False, plots_original set, >= 1 plots_<component> set
  # Default make_viewer = False
  plots_atm = "TREFHT"
  plots_lnd = "FSH,RH2M,LAISHA,LAISUN,QINTR,QOVER,QRUNOFF,QSOIL,QVEGE,QVEGT,SOILWATER_10CM,TSA,H2OSNO,TOTLITC,CWDC,SOIL1C,SOIL2C,SOIL3C,SOIL4C,WOOD_HARVESTC,TOTVEGC,NBP,GPP,AR,HR"
  plots_original = ""

  # We can ignore this case for image checking.
  # [[ classic_pdf_none ]]
  # # 8. make_viewer = False, plots_original not set, 0 plots_<component> set
  # # Default make_viewer = False
  # plots_original = ""

  # Special cases #############################################################

  [[ all_lnd_var_viewer ]]
  # 1. plot ALL land variables
  make_viewer = True
  partition = "compute"
  plots_lnd = "all"
  plots_original = ""
  walltime = "03:00:00"

  [[ classic_original_8_no_ocn ]]
  # 2. exclude ocean from original plots
  plots_original = "net_toa_flux_restom,global_surface_air_temperature,toa_radiation,net_atm_energy_imbalance,net_atm_water_imbalance"

@chengzhuzhang
Copy link
Collaborator

OH, I mis interpreted the table. Thanks for clarifying. If N/A just means the case was not tested, then we are okay.

@forsyth2
Copy link
Collaborator Author

forsyth2 commented Oct 1, 2025

Great, I'll merge the PRs then, thanks!!

@forsyth2 forsyth2 merged commit 70ece18 into main Oct 1, 2025
6 checks passed
@forsyth2 forsyth2 deleted the issue-23-gts-split branch October 1, 2025 23:46
This was referenced Oct 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: high High priority task (for next release) semver: new feature New feature (will increment minor version)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Separate original and component plots of global_time_series [Bug]: Potential performance bottleneck with DatasetWrapper.dataset

4 participants