diff --git a/visualCaseGen/config_vars/launcher_vars.py b/visualCaseGen/config_vars/launcher_vars.py index dbe662d..241c662 100644 --- a/visualCaseGen/config_vars/launcher_vars.py +++ b/visualCaseGen/config_vars/launcher_vars.py @@ -4,6 +4,7 @@ from ProConPy.out_handler import handler as owh from ProConPy.config_var import cvars from ProConPy.config_var_str import ConfigVarStr +from ProConPy.config_var_int import ConfigVarInt logger = logging.getLogger("\t" + __name__.split(".")[-1]) @@ -17,4 +18,6 @@ def initialize_launcher_variables(cime): default_value = cime.machine, ) ConfigVarStr("PROJECT", widget_none_val="") # Project ID for the machine - ConfigVarStr("CASE_CREATOR_STATUS", widget_none_val="") # a status variable to prevent the completion of the stage \ No newline at end of file + ConfigVarStr("CASE_CREATOR_STATUS", widget_none_val="") # a status variable to prevent the completion of the stage + ConfigVarInt("NINST", default_value=1) # Number of model instances (Currently, can only be controlled in the backend, + # particularly when visualCaseGen is used as an external library) \ No newline at end of file diff --git a/visualCaseGen/custom_widget_types/case_creator.py b/visualCaseGen/custom_widget_types/case_creator.py index 1dc45c4..8659ab0 100644 --- a/visualCaseGen/custom_widget_types/case_creator.py +++ b/visualCaseGen/custom_widget_types/case_creator.py @@ -24,7 +24,7 @@ class CaseCreator: def __init__(self, cime, output=None, allow_xml_override=False): """Initialize CaseCreator object. - + Parameters ---------- cime : CIME @@ -149,10 +149,10 @@ def create_case(self, do_exec): print(f"cd {caseroot}\n") # Apply case modifications, e.g., xmlchanges and user_nl changes - self._apply_all_xmlchanges(caseroot, do_exec) + self._apply_all_xmlchanges(do_exec) # Run case.setup - self._run_case_setup(caseroot, do_exec) + run_case_setup(do_exec, self._is_non_local(), self._out) # Apply user_nl changes self._apply_all_namelist_changes(do_exec) @@ -167,11 +167,11 @@ def create_case(self, do_exec): f"{SUCCESS}Case created successfully at {caseroot}.{RESET}\n\n" f"{COMMENT}To further customize, build, and run the case, " f"navigate to the case directory in your terminal. To create " - f"another case, restart the notebook.{RESET}\n" + f"another case, restart the notebook.{RESET}\n" ) def _update_ccs_config(self, do_exec): - """Update the modelgrid_aliases and component_grids xml files with custom grid + """Update the modelgrid_aliases and component_grids xml files with custom grid information if needed. This function is called before running create_newcase.""" # If Custom grid is selected, update modelgrid_aliases and component_grids xml files: @@ -206,7 +206,7 @@ def _update_ccs_config(self, do_exec): def _update_modelgrid_aliases(self, custom_grid_path, ocn_grid, do_exec): """Update the modelgrid_aliases xml file with custom resolution information. This function is called before running create_newcase. - + Parameters ---------- custom_grid_path : Path @@ -309,7 +309,7 @@ def _update_component_grids( ): """Update the component_grids xml file with custom ocnice grid information. This function is called before running create_newcase. - + Parameters ---------- custom_grid_path : Path @@ -420,7 +420,7 @@ def _update_component_grids( def _run_create_newcase(self, caseroot, compset, resolution, do_exec): """Run CIME's create_newcase tool to create a new case instance. - + Parameters ---------- caseroot : Path @@ -450,6 +450,11 @@ def _run_create_newcase(self, caseroot, compset, resolution, do_exec): if project := cvars["PROJECT"].value: cmd += f"--project {project} " + # append number of model instances if needed: + ninst = 1 if cvars["NINST"].value is None else cvars["NINST"].value + if ninst != 1: + cmd += f"--ninst {ninst} " + # append --nonlocal if needed: if self._is_non_local(): cmd += "--non-local " @@ -473,7 +478,7 @@ def _run_create_newcase(self, caseroot, compset, resolution, do_exec): if runout.returncode != 0: raise RuntimeError("Error creating case.") - def _apply_all_xmlchanges(self, caseroot, do_exec): + def _apply_all_xmlchanges(self, do_exec): lnd_grid_mode = cvars["LND_GRID_MODE"].value if lnd_grid_mode == "Modified": @@ -485,7 +490,7 @@ def _apply_all_xmlchanges(self, caseroot, do_exec): # component_grids_nuopc.xml and modelgrid_aliases_nuopc.xml (just like how we handle new ocean grids) # lnd domain mesh - xmlchange("LND_DOMAIN_MESH", cvars["INPUT_MASK_MESH"].value, caseroot, do_exec, self._is_non_local(), self._out) + xmlchange("LND_DOMAIN_MESH", cvars["INPUT_MASK_MESH"].value, do_exec, self._is_non_local(), self._out) # mask mesh (if modified) base_lnd_grid = cvars["CUSTOM_LND_GRID"].value @@ -493,22 +498,17 @@ def _apply_all_xmlchanges(self, caseroot, do_exec): lnd_dir = custom_grid_path / "lnd" modified_mask_mesh = lnd_dir / f"{base_lnd_grid}_mesh_mask_modifier.nc" # TODO: the way we get this filename is fragile assert modified_mask_mesh.exists(), f"Modified mask mesh file {modified_mask_mesh} does not exist." - xmlchange("MASK_MESH", modified_mask_mesh, caseroot, do_exec, self._is_non_local(), self._out) + xmlchange("MASK_MESH", modified_mask_mesh, do_exec, self._is_non_local(), self._out) else: assert lnd_grid_mode in [None, "", "Standard"], f"Unknown land grid mode: {lnd_grid_mode}" - def _run_case_setup(self, caseroot, do_exec): - """Run the case.setup script to set up the case instance.""" - run_case_setup(caseroot, do_exec, self._is_non_local(), self._out) - - def _apply_user_nl_changes(self, user_nl_filename, var_val_pairs, do_exec, comment=None, log_title=True): + def _apply_user_nl_changes(self, model, var_val_pairs, do_exec, comment=None, log_title=True): """Apply changes to a given user_nl file.""" - caseroot = cvars["CASEROOT"].value - append_user_nl(user_nl_filename, var_val_pairs, caseroot, do_exec, comment, log_title, self._out) + append_user_nl(model, var_val_pairs, do_exec, comment, log_title, self._out) def _apply_all_namelist_changes(self, do_exec): """Apply all the necessary user_nl changes to the case. - + Parameters ---------- caseroot : Path @@ -565,7 +565,7 @@ def _apply_mom_namelist_changes(self, do_exec): # apply custom MOM6 grid changes: self._apply_user_nl_changes( - "user_nl_mom", + "mom", [ ("INPUTDIR", ocn_grid_path), ("TRIPOLAR_N", "False"), @@ -589,7 +589,7 @@ def _apply_mom_namelist_changes(self, do_exec): ) self._apply_user_nl_changes( - "user_nl_mom", + "mom", [ ("DT", str(dt)), ("DT_THERM", str(dt_therm)), @@ -602,7 +602,7 @@ def _apply_mom_namelist_changes(self, do_exec): # Set MOM6 Initial Conditions parameters: if cvars["OCN_IC_MODE"].value == "Simple": self._apply_user_nl_changes( - "user_nl_mom", + "mom", [ ("TS_CONFIG", "fit"), ("T_REF", cvars["T_REF"].value), @@ -626,7 +626,7 @@ def _apply_mom_namelist_changes(self, do_exec): shutil.copy(temp_salt_z_init_file, ocn_grid_path / temp_salt_z_init_file.name) # Apply the user_nl changes: self._apply_user_nl_changes( - "user_nl_mom", + "mom", [ ("INIT_LAYERS_FROM_Z_FILE", "True"), ("TEMP_SALT_Z_INIT_FILE", temp_salt_z_init_file.name), @@ -653,7 +653,7 @@ def _apply_cice_namelist_changes(self, do_exec): cice_grid_file_path = MOM6BathyLauncher.cice_grid_file_path() self._apply_user_nl_changes( - "user_nl_cice", + "cice", [ ("grid_format", '"nc"'), ("grid_file", f'"{cice_grid_file_path}"'), @@ -685,7 +685,7 @@ def _apply_clm_namelist_changes(self, do_exec): inittime = cvars["INITTIME"].value if inittime is None: inittime = cvars["COMPSET_LNAME"].value.split("_")[0] - + # For transient runs, we need to: # - set check_dynpft_consistency to .false. # - set flanduse_timeseries @@ -702,5 +702,4 @@ def _apply_clm_namelist_changes(self, do_exec): # cft dimensions included in clm namelist xml files don't match the dimensions that clm expects: 64 vs 2. ]) - self._apply_user_nl_changes("user_nl_clm", user_nl_clm_changes, do_exec) - \ No newline at end of file + self._apply_user_nl_changes("clm", user_nl_clm_changes, do_exec) diff --git a/visualCaseGen/custom_widget_types/case_tools.py b/visualCaseGen/custom_widget_types/case_tools.py index d01e2c8..433b88f 100644 --- a/visualCaseGen/custom_widget_types/case_tools.py +++ b/visualCaseGen/custom_widget_types/case_tools.py @@ -1,26 +1,27 @@ from pathlib import Path import subprocess +from ProConPy.config_var import cvars from visualCaseGen.custom_widget_types.dummy_output import DummyOutput COMMENT = "\033[01;96m" # bold, cyan RESET = "\033[0m" -def run_case_setup(caseroot, do_exec, is_non_local=False, out=None): +def run_case_setup(do_exec, is_non_local=False, out=None): """Run the case.setup script to set up the case instance. - + Parameters ---------- - caseroot : Path - The path to the case directory. do_exec : bool - If True, execute the commands. If False, only print them. + If True, execute the commands. If False, only print them. is_non_local : bool, optional If True, the case has been created on a machine different from the one that runs visualCaseGen. """ + caseroot = cvars["CASEROOT"].value + # Run ./case.setup cmd = "./case.setup" if is_non_local: @@ -37,17 +38,15 @@ def run_case_setup(caseroot, do_exec, is_non_local=False, out=None): if runout.returncode != 0: raise RuntimeError(f"Error running {cmd}.") -def append_user_nl(user_nl_filename, var_val_pairs, caseroot, do_exec, comment=None, log_title=True, out=None): +def append_user_nl(model, var_val_pairs, do_exec, comment=None, log_title=True, out=None): """Apply changes to a given user_nl file. - + Parameters ---------- - user_nl_filename : str - The name of the user_nl file to modify. + model : str + The model whose user_nl file will be modified. var_val_pairs : list of tuples A list of tuples, where each tuple contains a variable name and its value. - caseroot: Path or str - The path to the case directory do_exec : bool If True, execute the commands. If False, only print them. comment : str, optional @@ -62,35 +61,44 @@ def append_user_nl(user_nl_filename, var_val_pairs, caseroot, do_exec, comment=N assert isinstance(var_val_pairs, list) assert all(isinstance(pair, tuple) for pair in var_val_pairs) - # Print the changes to the user_nl file: out = DummyOutput() if out is None else out - with out: - if log_title: - print(f"{COMMENT}Adding parameter changes to {user_nl_filename}:{RESET}\n") - if comment: - print(f" ! {comment}") - for var, val in var_val_pairs: - print(f" {var} = {val}") - print("") - - if not do_exec: - return - - # Apply the changes to the user_nl file: - with open(Path(caseroot) / user_nl_filename, "a") as f: - if comment: - f.write(f"\n! {comment}\n") - for var, val in var_val_pairs: - f.write(f"{var} = {val}\n") + caseroot = cvars["CASEROOT"].value + ninst = cvars["NINST"].value -def xmlchange(var, val, caseroot, do_exec=True, is_non_local=False, out=None): + def _do_append_user_nl(user_nl_filename): + # Print the changes to the user_nl file: + with out: + if log_title: + print(f"{COMMENT}Adding parameter changes to {user_nl_filename}:{RESET}\n") + if comment: + print(f" ! {comment}") + for var, val in var_val_pairs: + print(f" {var} = {val}") + print("") + + if not do_exec: + return + + # Apply the changes to the user_nl file: + with open(Path(caseroot) / user_nl_filename, "a") as f: + if comment: + f.write(f"\n! {comment}\n") + for var, val in var_val_pairs: + f.write(f"{var} = {val}\n") + + ninst = 1 if ninst is None else ninst + if ninst==1: + _do_append_user_nl(f"user_nl_{model}") + else: + for i in range(1, ninst+1): + _do_append_user_nl(f"user_nl_{model}_{str(i).zfill(4)}") + +def xmlchange(var, val, do_exec=True, is_non_local=False, out=None): """Apply custom xml changes to the case. - + Parameters ---------- - caseroot : Path - The path to the case directory. do_exec : bool If True, execute the commands. If False, only print them. is_non_local : bool @@ -100,6 +108,8 @@ def xmlchange(var, val, caseroot, do_exec=True, is_non_local=False, out=None): The output widget to use for displaying log messages. """ + caseroot = cvars["CASEROOT"].value + cmd = f"./xmlchange {var}={val}" if is_non_local is True: cmd += " --non-local"