Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support cesm workflow #131

Merged
merged 11 commits into from
Sep 20, 2024
113 changes: 113 additions & 0 deletions helper_scripts/generate_cupid_config_for_cesm_case.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/usr/bin/env python3
from __future__ import annotations

import argparse
import os
import sys

import yaml


def _parse_args():
"""Parse command line arguments"""

parser = argparse.ArgumentParser(
description="Generate config.yml based on an existing CUPID example",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)

# Command line argument for location of CESM source code (required)
parser.add_argument(
"--cesm-root",
action="store",
dest="cesm_root",
required=True,
help="Location of CESM source code",
)

# Command line argument for CUPiD example from which to get config.yml
parser.add_argument(
"--cupid-example",
action="store",
dest="cupid_example",
default="key_metrics",
help="CUPiD example to use as template for config.yml",
Copy link
Collaborator

Choose a reason for hiding this comment

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

The default as key_metrics is great. Is it worthwhile to mention that the example choices are coupled_model and key_metrics? I'm not sure how many different examples we'll end up having?

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 a tough one -- I'd love to use the choices argument of add_argument() along with the os.walk() function to list the subdirectories of CUPiD/examples/, but we don't know where the examples/ directory is until we resolve --cesm-root. The best option is probably to enforce this immediately after entering generate_cupid_config() by ensuring that os.path.join(cupid_root, "examples", cupid_example) is a directory and printing out a useful error message if it isn't?

Copy link
Collaborator

Choose a reason for hiding this comment

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

That makes sense that adding the choices would entail using information we don't have yet. The suggestion you shared sounds great!

Copy link
Collaborator Author

@mnlevy1981 mnlevy1981 Sep 19, 2024

Choose a reason for hiding this comment

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

The code in bb3f37d has the check and gives the following:

$ ./generate_cupid_config_for_cesm_case.py --cesm-root ${CESM_ROOT} --case-root ${CESM_ROOT}/cases/test_cupid_workflow.007 --cupid-example foo
Traceback (most recent call last):
  File "${CESM_ROOT}/tools/CUPiD/helper_scripts/./generate_cupid_config_for_cesm_case.py", line 107, in <module>
    generate_cupid_config(**args)
  File "${CESM_ROOT}/tools/CUPiD/helper_scripts/./generate_cupid_config_for_cesm_case.py", line 62, in generate_cupid_config
    raise KeyError(
KeyError: "--cupid-example must be a subdirectory of ${CESM_ROOT}/tools/CUPiD/examples (['coupled_model', 'key_metrics'])"

Note that both ilamb and nblibrary are left off the list of valid_examples; eventually we'll probably reinstate ilamb, but for now it doesn't have a config.yml file. I'd really like to move nblibrary/ out of examples/ but that seems like a big thing to tackle in this PR :)

I should probably add text about 'foo' is not a valid option for --cupid-examples; I'll get to that in the morning

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sounds good!

Yes, let's move nblibrary out of examples after this PR is merged in.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

0888657 has the improved check:

$ ./generate_cupid_config_for_cesm_case.py --cesm-root ${CESM_ROOT} --cupid-example foo
Traceback (most recent call last):
  File "${CESM_ROOT}/tools/CUPiD/helper_scripts/./generate_cupid_config_for_cesm_case.py", line 108, in <module>
    generate_cupid_config(**args)
  File "${CESM_ROOT}/tools/CUPiD/helper_scripts/./generate_cupid_config_for_cesm_case.py", line 63, in generate_cupid_config
    raise KeyError(
KeyError: "argument --cupid-example: invalid choice 'foo' (choose from subdirectories of ${CESM_ROOT}/tools/CUPiD/examples: ['coupled_model', 'key_metrics'])"

Copy link
Collaborator

Choose a reason for hiding this comment

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

Fantastic!

)

# Command line argument location of CESM case directory
parser.add_argument(
"--case-root",
action="store",
dest="case_root",
default=os.getcwd(),
Copy link
Collaborator

Choose a reason for hiding this comment

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

It may also be worth logging information on what arguments were used?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

dba6bea adds information about the arguments to the comment section at the head of the new config.yml; I think that's the best place to put it but I'm open to other suggestions. (If we use a print() statement, then I think it will show up in the PBS log file generated by the case.cupid job, but other queue managers might treat that output differently?)

Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems like a good place-- especially since the config.yml file also says it's generated by the script, so all that information is in one area.

help="CESM case directory",
)

return parser.parse_args()


def generate_cupid_config(case_root, cesm_root, cupid_example):
sys.path.append(os.path.join(cesm_root, "cime"))
from CIME.case import Case

# Is cupid_example a valid value?
cupid_root = os.path.join(cesm_root, "tools", "CUPiD")
cupid_examples = os.path.join(cupid_root, "examples")
valid_examples = [
example
for example in next(os.walk(cupid_examples))[1]
if example not in ["ilamb", "nblibrary"]
]
if cupid_example not in valid_examples:
error_msg = f"argument --cupid-example: invalid choice '{cupid_example}'"
raise KeyError(
f"{error_msg} (choose from subdirectories of {cupid_examples}: {valid_examples})",
)

with Case(case_root, read_only=False, record=True) as cesm_case:
case = cesm_case.get_value("CASE")
dout_s_root = cesm_case.get_value("DOUT_S_ROOT")

# Additional options we need to get from env_cupid.xml
nyears = 1
start_date = "0001-01-01"
end_date = f"{nyears+1:04d}-01-01"
climo_nyears = 1
base_case_output_dir = "/glade/campaign/cesm/development/cross-wg/diagnostic_framework/CESM_output_for_testing"
base_nyears = 100
base_end_date = f"{base_nyears+1:04d}-01-01"
base_climo_nyears = 40

with open(os.path.join(cupid_root, "examples", cupid_example, "config.yml")) as f:
my_dict = yaml.safe_load(f)

my_dict["global_params"]["case_name"] = case
my_dict["global_params"]["start_date"] = start_date
my_dict["global_params"]["end_date"] = end_date
my_dict["global_params"]["climo_nyears"] = climo_nyears
my_dict["global_params"]["base_case_output_dir"] = base_case_output_dir
my_dict["global_params"]["base_end_date"] = base_end_date
my_dict["global_params"]["base_climo_nyears"] = base_climo_nyears
my_dict["timeseries"]["case_name"] = case
my_dict["timeseries"]["atm"]["end_years"] = [nyears, base_nyears]

# replace with environment variable
my_dict["global_params"]["CESM_output_dir"] = dout_s_root

# create new file, make it writeable
with open("config.yml", "w") as f:
# Header of file is a comment logging provenance
f.write(f"# This file has been auto-generated for use with {case}\n")
f.write(f"# It is based off of examples/{cupid_example}/config.yml\n")
f.write("# Arguments used:\n")
f.write(f"# cesm_root = {cesm_root}\n")
f.write(f"# case_root = {case_root}\n")
f.write(f"# cupid_example= {cupid_example}\n")

# enter in each element of the dictionary into the new file
yaml.dump(my_dict, f, sort_keys=False)


if __name__ == "__main__":
args = vars(_parse_args())
TeaganKing marked this conversation as resolved.
Show resolved Hide resolved
generate_cupid_config(**args)
Loading