Skip to content
48 changes: 48 additions & 0 deletions CrocoDash/case.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,27 @@ def __init__(
Must be in the form hh:mm:ss. If None, defaults to the CESM defaults
"""

# Capture scalar init args for state serialization before any local vars are added.
# Excludes: objects (stored as paths), args resolved to derived values, and ephemeral flags.
_locals = locals()
_SERIALIZABLE_EXCLUDE = frozenset(
{
"self",
"ocn_grid",
"ocn_topo",
"ocn_vgrid",
"compset",
"machine",
"cesmroot",
"caseroot",
"inputdir",
"override",
}
)
self._init_args = {
k: v for k, v in _locals.items() if k not in _SERIALIZABLE_EXCLUDE
}

# Initialize visualCaseGen system and get the CIME interface
self.cime = initialize_visualCaseGen(cesmroot)

Expand Down Expand Up @@ -125,11 +146,13 @@ def __init__(
)

# Set instance attributes
self.cesmroot = Path(cesmroot)
self.caseroot = Path(caseroot)
self.inputdir = Path(inputdir)
self.ocn_grid = ocn_grid
self.ocn_topo = ocn_topo
self.ocn_vgrid = ocn_vgrid
self.atm_grid_name = atm_grid_name
self.ninst = ninst
self.override = override
self.ProductRegistry = ProductRegistry
Expand All @@ -139,6 +162,10 @@ def __init__(
self.compset_lname = compset_lname
self.machine = machine or self.cime.machine
self.project = project
self.rof_grid_name = rof_grid_name
self.ntasks_ocn = ntasks_ocn
self.job_queue = job_queue
self.job_wallclock_time = job_wallclock_time

# Using visualCaseGen's configuration system, set the configuration variables for the case
# based on the provided arguments. This includes setting the compset, grid, and launch variables.
Expand All @@ -165,6 +192,8 @@ def __init__(

self._apply_final_xmlchanges(ntasks_ocn, job_queue, job_wallclock_time)

self._write_state()

required_configurators = ForcingConfigRegistry.find_required_configurators(
self.compset_lname
)
Expand Down Expand Up @@ -872,6 +901,25 @@ def _configure_launch(self):
# Variables that are not included in a stage:
cvars["NINST"].value = self.ninst

def _write_state(self):
"""Write case creation parameters to crocodash_state.json in caseroot."""
state = {
# Derived / resolved fields that can't come from init args directly
"inputdir": str(self.inputdir),
"cesmroot": str(self.cesmroot),
"supergrid_path": self.supergrid_path,
"topo_path": self.topo_path,
"vgrid_path": self.vgrid_path,
"grid_name": self.ocn_grid.name,
"session_id": cvars["MB_ATTEMPT_ID"].value,
"compset_lname": self.compset_lname,
"machine": self.machine,
# Scalar init args captured at construction time
**self._init_args,
}
with open(self.caseroot / "crocodash_state.json", "w") as f:
json.dump(state, f, indent=2)

def _apply_final_xmlchanges(
self, ntasks_ocn=None, job_queue=None, job_wallclock_time=None
):
Expand Down
81 changes: 43 additions & 38 deletions CrocoDash/cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
import argparse
import json
import sys


def _create(args):
from CrocoDash.recipe import load_config, create_case_from_yaml

config = load_config(args.config)
create_case_from_yaml(config, override=args.override)


def _dump(args):
from CrocoDash.recipe import case_to_yaml
import yaml

config = case_to_yaml(args.caseroot)
yaml.dump(config, sys.stdout, default_flow_style=False, sort_keys=False)


def _bundle(args):
Expand Down Expand Up @@ -30,20 +46,9 @@ def _duplicate_case(args):


def _fork(args):

from CrocoDash.shareable.fork import ForkCrocoDashBundle

plan = json.loads(args.plan) if args.plan else None
extra_configs = (
[x.strip() for x in args.extra_configs.split(",") if x.strip()]
if args.extra_configs
else None
)
remove_configs = (
[x.strip() for x in args.remove_configs.split(",") if x.strip()]
if args.remove_configs
else None
)

forker = ForkCrocoDashBundle(args.bundle)
forker.fork(
Expand All @@ -53,17 +58,39 @@ def _fork(args):
new_caseroot=args.caseroot,
new_inputdir=args.inputdir,
plan=plan,
compset=args.compset,
extra_configs=extra_configs,
remove_configs=remove_configs,
extra_forcing_args_path=args.extra_forcing_args,
)


def main():
parser = argparse.ArgumentParser(prog="crocodash")
subparsers = parser.add_subparsers(dest="command", required=True)

# --- create ---
create_parser = subparsers.add_parser(
"create",
help="Create a new CrocoDash case from a YAML config file.",
)
create_parser.add_argument(
"--config", required=True, help="Path to the YAML case config file."
)
create_parser.add_argument(
"--override",
action="store_true",
default=False,
help="Overwrite existing caseroot and inputdir if they exist.",
)
create_parser.set_defaults(func=_create)

# --- dump ---
dump_parser = subparsers.add_parser(
"dump",
help="Print a YAML representation of an existing CrocoDash case to stdout.",
)
dump_parser.add_argument(
"--caseroot", required=True, help="Path to the existing CESM caseroot."
)
dump_parser.set_defaults(func=_dump)

# --- bundle ---
bundle_parser = subparsers.add_parser(
"bundle",
Expand Down Expand Up @@ -136,32 +163,10 @@ def main():
"--machine", required=True, help="Machine name (e.g. derecho)."
)
fork_parser.add_argument("--project", required=True, help="Project/account number.")
# optional bypass flags
fork_parser.add_argument(
"--compset", default=None, help="Override the compset from the bundle."
)
fork_parser.add_argument(
"--plan",
default=None,
help='JSON object controlling what to copy, e.g. \'{"xml_files": true, "user_nl": true, "source_mods": false, "xmlchanges": true}\'.',
)
fork_parser.add_argument(
"--extra-configs",
default=None,
dest="extra_configs",
help="Comma-separated forcing configs to add.",
)
fork_parser.add_argument(
"--remove-configs",
default=None,
dest="remove_configs",
help="Comma-separated forcing configs to drop.",
)
fork_parser.add_argument(
"--extra-forcing-args",
default=None,
dest="extra_forcing_args",
help="Path to JSON file with extra forcing arguments.",
help='JSON object controlling what non-standard CESM state to copy, e.g. \'{"xml_files": true, "user_nl": true, "source_mods": false, "xmlchanges": true}\'.',
)
fork_parser.set_defaults(func=_fork)

Expand Down
Loading
Loading