diff --git a/src/model_config_tests/exp_test_helper.py b/src/model_config_tests/exp_test_helper.py index a936075..54f32c6 100644 --- a/src/model_config_tests/exp_test_helper.py +++ b/src/model_config_tests/exp_test_helper.py @@ -27,6 +27,8 @@ def __init__(self, control_path: Path, lab_path: Path, disable_payu_run=False): self.work_path = lab_path / "work" / self.exp_name self.output000 = self.archive_path / "output000" self.output001 = self.archive_path / "output001" + self.restart000 = self.archive_path / "restart000" + self.restart001 = self.archive_path / "restart001" with open(self.config_path) as f: self.config = yaml.safe_load(f) diff --git a/src/model_config_tests/models/accessesm1p5.py b/src/model_config_tests/models/accessesm1p5.py index 7e58b37..d010b8e 100644 --- a/src/model_config_tests/models/accessesm1p5.py +++ b/src/model_config_tests/models/accessesm1p5.py @@ -19,7 +19,7 @@ def __init__(self, experiment): # Override model default runtime self.default_runtime_seconds = DEFAULT_RUNTIME_SECONDS - self.output_file = self.experiment.output000 / "access.out" + self.output_file = self.output_0 / "access.out" def set_model_runtime( self, years: int = 0, months: int = 0, seconds: int = DEFAULT_RUNTIME_SECONDS diff --git a/src/model_config_tests/models/accessom2.py b/src/model_config_tests/models/accessom2.py index 9cec2d6..1968d8d 100644 --- a/src/model_config_tests/models/accessom2.py +++ b/src/model_config_tests/models/accessom2.py @@ -16,7 +16,7 @@ class AccessOm2(Model): def __init__(self, experiment): super().__init__(experiment) - self.output_file = self.experiment.output000 / "access-om2.out" + self.output_file = self.output_0 / "access-om2.out" self.accessom2_config = experiment.control_path / "accessom2.nml" self.ocean_config = experiment.control_path / "ocean" / "input.nml" diff --git a/src/model_config_tests/models/accessom3.py b/src/model_config_tests/models/accessom3.py index be69372..a8f4028 100644 --- a/src/model_config_tests/models/accessom3.py +++ b/src/model_config_tests/models/accessom3.py @@ -1,10 +1,11 @@ """Specific Access-OM3 Model setup and post-processing""" -import re from collections import defaultdict from pathlib import Path from typing import Any +import f90nml +from netCDF4 import Dataset from payu.models.cesm_cmeps import Runconfig from model_config_tests.models.model import ( @@ -12,17 +13,19 @@ SCHEMA_VERSION_1_0_0, Model, ) -from model_config_tests.util import DAY_IN_SECONDS class AccessOm3(Model): def __init__(self, experiment): super().__init__(experiment) - self.output_file = self.experiment.output000 / "ocean.stats" + # ACCESS-OM3 uses restarts for repro testing + self.output_0 = self.experiment.restart000 + self.output_1 = self.experiment.restart001 + + self.mom_restart_pointer = self.output_0 / "rpointer.ocn" self.runconfig = experiment.control_path / "nuopc.runconfig" - self.mom_override = experiment.control_path / "MOM_override" - self.ocean_config = experiment.control_path / "input.nml" + self.wav_in = experiment.control_path / "wav_in" def set_model_runtime( self, years: int = 0, months: int = 0, seconds: int = DEFAULT_RUNTIME_SECONDS @@ -31,19 +34,20 @@ def set_model_runtime( Default is 3 hours""" runconfig = Runconfig(self.runconfig) + # Check that ocean model component is MOM since checksums are obtained from + # MOM6 restarts. Fail early if not + ocn_model = runconfig.get("ALLCOMP_attributes", "OCN_model") + if ocn_model != "mom": + raise ValueError( + "ACCESS-OM3 reproducibility checks utilize checksums written in MOM6 " + "restarts and hence can only be used with ACCESS-OM3 configurations that " + f"use MOM6. This configuration uses OCN_model = {ocn_model}." + ) + if years == months == 0: freq = "nseconds" n = str(seconds) - # Ensure that ocean.stats are written at the end of the run - if seconds < DAY_IN_SECONDS: - with open(self.mom_override, "a") as f: - f.writelines( - [ - f"\n#override TIMEUNIT = {n}", - "\n#override ENERGYSAVEDAYS = 1.0", - ] - ) elif seconds == 0: freq = "nmonths" n = str(12 * years + months) @@ -59,38 +63,42 @@ def set_model_runtime( runconfig.write() + # Unfortunately WW3 doesn't (yet) obey the nuopc.runconfig. This should change in a + # future release, but for now we have to set WW3 runtime in wav_in. See + # https://github.com/COSIMA/access-om3/issues/239 + if self.wav_in.exists(): + with open(self.wav_in) as f: + nml = f90nml.read(f) + + nml["output_date_nml"]["date"]["restart"]["stride"] = int(n) + nml.write(self.wav_in, force=True) + def output_exists(self) -> bool: """Check for existing output file""" - return self.output_file.exists() + return self.mom_restart_pointer.exists() def extract_checksums( self, output_directory: Path = None, schema_version: str = None ) -> dict[str, Any]: """Parse output file and create checksum using defined schema""" if output_directory: - output_filename = output_directory / "ocean.stats" + mom_restart_pointer = output_directory / "rpointer.ocn" else: - output_filename = self.output_file - - # ocean.stats is used for regression testing in MOM6's own test suite - # See https://github.com/mom-ocean/MOM6/blob/2ab885eddfc47fc0c8c0bae46bc61531104428d5/.testing/Makefile#L495-L501 - # Rows in ocean.stats look like: - # 0, 693135.000, 0, En 3.0745627134675957E-23, CFL 0.00000, ... - # where the first three columns are Step, Day, Truncs and the remaining - # columns include a label for what they are (e.g. En = Energy/Mass) - # Header info is only included for new runs so can't be relied on + mom_restart_pointer = self.mom_restart_pointer + + # MOM6 saves checksums for each variable in its restart files. Extract these + # attributes for each restart output_checksums: dict[str, list[any]] = defaultdict(list) - with open(output_filename) as f: - lines = f.readlines() - # Skip header if it exists (for new runs) - istart = 2 if "Step" in lines[0] else 0 - for line in lines[istart:]: - for col in line.split(","): - # Only keep columns with labels (ie not Step, Day, Truncs) - col = re.split(" +", col.strip().rstrip("\n")) - if len(col) > 1: - output_checksums[col[0]].append(col[-1]) + with open(mom_restart_pointer) as f: + for restart_file in f.readlines(): + restart = mom_restart_pointer.parent / restart_file.rstrip() + rootgrp = Dataset(restart, "r") + for variable in sorted(rootgrp.variables): + var = rootgrp[variable] + if "checksum" in var.ncattrs(): + output_checksums[variable.strip()].append(var.checksum.strip()) + rootgrp.close() if schema_version is None: schema_version = self.default_schema_version diff --git a/src/model_config_tests/models/model.py b/src/model_config_tests/models/model.py index 90dfc68..5199742 100644 --- a/src/model_config_tests/models/model.py +++ b/src/model_config_tests/models/model.py @@ -23,6 +23,9 @@ def __init__(self, experiment): self.default_runtime_seconds = DEFAULT_RUNTIME_SECONDS + self.output_0 = self.experiment.output000 + self.output_1 = self.experiment.output001 + def extract_checksums(self, output_directory: Path, schema_version: str): """Extract checksums from output directory""" raise NotImplementedError diff --git a/src/model_config_tests/test_bit_reproducibility.py b/src/model_config_tests/test_bit_reproducibility.py index 9414fa8..b41144b 100644 --- a/src/model_config_tests/test_bit_reproducibility.py +++ b/src/model_config_tests/test_bit_reproducibility.py @@ -179,7 +179,7 @@ def test_restart_repro(self, output_path: Path, control_path: Path): # Now compare the output between our two short and one long run. checksums_1d_0 = exp_2x1day.extract_checksums() - checksums_1d_1 = exp_2x1day.extract_checksums(exp_2x1day.output001) + checksums_1d_1 = exp_2x1day.extract_checksums(exp_2x1day.model.output_1) checksums_2d = exp_2day.extract_checksums() diff --git a/tests/qa/test_test_access_esm1p5_config.py b/tests/qa/test_test_access_esm1p5_config.py index 0858da4..1244d53 100644 --- a/tests/qa/test_test_access_esm1p5_config.py +++ b/tests/qa/test_test_access_esm1p5_config.py @@ -9,7 +9,8 @@ def test_test_access_esm1p5_config_release_release_preindustrial(): access_esm1p5_configs = RESOURCES_DIR / "access" / "configurations" test_config = access_esm1p5_configs / "release-preindustrial+concentrations" - assert test_config.exists() + if not test_config.exists(): + raise FileNotFoundError(f"The test configuration {test_config} does not exist.") test_cmd = ( "model-config-tests -s " diff --git a/tests/qa/test_test_access_esm1p6_config.py b/tests/qa/test_test_access_esm1p6_config.py index a37ce1e..581a295 100644 --- a/tests/qa/test_test_access_esm1p6_config.py +++ b/tests/qa/test_test_access_esm1p6_config.py @@ -11,7 +11,8 @@ def test_test_access_esm1p6_config_release_release_preindustrial(): # access_esm1p6_configs = RESOURCES_DIR / "access" / "configurations" # test_config = access_esm1p6_configs / "release-preindustrial+concentrations" - # assert test_config.exists() + # if not test_config.exists(): + # raise FileNotFoundError(f"The test configuration {test_config} does not exist.") # test_cmd = ( # "model-config-tests -s " diff --git a/tests/qa/test_test_access_om2_config.py b/tests/qa/test_test_access_om2_config.py index 286814b..12bf918 100644 --- a/tests/qa/test_test_access_om2_config.py +++ b/tests/qa/test_test_access_om2_config.py @@ -12,7 +12,8 @@ def test_test_access_om2_config_release_1deg_jra55_ryf(): access_om2_configs = RESOURCES_DIR / "access-om2" / "configurations" test_config = access_om2_configs / "release-1deg_jra55_ryf" - assert test_config.exists() + if not test_config.exists(): + raise FileNotFoundError(f"The test configuration {test_config} does not exist.") test_cmd = ( "model-config-tests -s " diff --git a/tests/qa/test_test_access_om3_config.py b/tests/qa/test_test_access_om3_config.py new file mode 100644 index 0000000..939099a --- /dev/null +++ b/tests/qa/test_test_access_om3_config.py @@ -0,0 +1,73 @@ +import shlex +import shutil +import subprocess + +import yaml + +from tests.common import RESOURCES_DIR + + +def test_test_access_om3_config_release_1deg_jra55_ryf(): + """Test ACCESS-OM3 specific config tests""" + access_om3_configs = RESOURCES_DIR / "access-om3" / "configurations" + test_config = access_om3_configs / "om3-dev-1deg_jra55do_ryf" + + if not test_config.exists(): + raise FileNotFoundError(f"The test configuration {test_config} does not exist.") + + test_cmd = ( + "model-config-tests -s " + # Run all access_om3 specific tests + "-m access_om3 " + f"--control-path {test_config} " + # Use target branch as can't mock get_git_branch function in utils + f"--target-branch om3-dev-1deg_jra55do_ryf" + ) + + result = subprocess.run(shlex.split(test_cmd), capture_output=True, text=True) + + # Expect the tests to have passed + if result.returncode: + # Print out test logs if there are errors + print(f"Test stdout: {result.stdout}\nTest stderr: {result.stderr}") + + assert result.returncode == 0 + + +def test_test_access_om3_config_modified_module_version(tmp_path): + """Test changing model module version in config.yaml, + will cause tests to fail if paths in exe manifests don't + match released spack.location file""" + access_om3_configs = RESOURCES_DIR / "access-om3" / "configurations" + + # Copy test configuration + test_config = access_om3_configs / "om3-dev-1deg_jra55do_ryf" + mock_control_path = tmp_path / "mock_control_path" + shutil.copytree(test_config, mock_control_path) + + mock_config = mock_control_path / "config.yaml" + + with open(mock_config) as f: + config = yaml.safe_load(f) + + # Use a different released version of access-om3 module + config["modules"]["load"] = ["access-om3/2024.09.0"] + + with open(mock_config, "w") as f: + yaml.dump(config, f) + + test_cmd = ( + "model-config-tests -s " + # Only test the manifest exe in release spack location test + "-k test_access_om3_manifest_exe_in_release_spack_location " + f"--control-path {mock_control_path} " + # Use target branch as can't mock get_git_branch function in utils + f"--target-branch om3-dev-1deg_jra55do_ryf" + ) + + result = subprocess.run(shlex.split(test_cmd), capture_output=True, text=True) + + # Expect test to have failed + assert result.returncode == 1 + error_msg = "Expected exe path in exe manifest to match an install path in released spack.location" + assert error_msg in result.stdout diff --git a/tests/qa/test_test_config.py b/tests/qa/test_test_config.py index ce6169c..2c5a42f 100644 --- a/tests/qa/test_test_config.py +++ b/tests/qa/test_test_config.py @@ -10,7 +10,8 @@ def test_test_config_access_om2(): access_om2_configs = RESOURCES_DIR / "access-om2" / "configurations" test_config = access_om2_configs / branch_name - assert test_config.exists() + if not test_config.exists(): + raise FileNotFoundError(f"The test configuration {test_config} does not exist.") test_cmd = ( "model-config-tests -s " diff --git a/tests/resources/access-om3/checksums/1-0-0.json b/tests/resources/access-om3/checksums/1-0-0.json index 61eeb82..aa01ad7 100644 --- a/tests/resources/access-om3/checksums/1-0-0.json +++ b/tests/resources/access-om3/checksums/1-0-0.json @@ -1,32 +1,89 @@ { "schema_version": "1-0-0", "output": { - "En": [ - "3.0745627134675957E-23" + "CAu": [ + "A4C51EC2A34D8EF1" ], - "CFL": [ - "0.00000" + "CAv": [ + "DA7F07794C0B7279" ], - "SL": [ - "1.5112E-10" + "DTBT": [ + "4055968059DD63AF" ], - "M": [ - "1.36404E+21" + "First_direction": [ + "0" ], - "S": [ - "34.7263" + "Kd_shear": [ + "8A1CB3488BF7FAC7" ], - "T": [ - "3.6362" + "Kv_shear": [ + "55036CBF887185D4" ], - "Me": [ - "0.00E+00" + "MEKE": [ + "A205FF94D0736FD0" ], - "Se": [ - "0.00E+00" + "MEKE_Kh": [ + "C5E23A24466765D4" ], - "Te": [ - "0.00E+00" + "MEKE_Kh_diff": [ + "C62261CF4678ACEE" + ], + "MEKE_Ku": [ + "A9959752C4D3DE41" + ], + "MLD_MLE_filtered": [ + "7463C5D823501487" + ], + "Salt": [ + "8E3D02158AE4EAED" + ], + "Temp": [ + "E441AC22DEDE8930" + ], + "age": [ + "B64AD181B58D3F4B" + ], + "ave_ssh": [ + "40E5A21D6F0D1F87" + ], + "diffu": [ + "749F723BA63A013C" + ], + "diffv": [ + "CD7614674E679DA8" + ], + "frazil": [ + "5FE9C9F905239FC4" + ], + "h": [ + "9138701970F7E8A" + ], + "h_ML": [ + "A815BF54039C2D82" + ], + "p_surf_EOS": [ + "161DADB852A006F1" + ], + "sfc": [ + "68FCD5E9DE45D1DF" + ], + "u": [ + "803BF5B7E9C239C1" + ], + "u2": [ + "32A58A90C668C32D" + ], + "ubtav": [ + "2E16365E105FFB9" + ], + "v": [ + "7D9693C22E4D52B5" + ], + "v2": [ + "DA212F15199DC960" + ], + "vbtav": [ + "A6344B9A6D8CB3A9" ] } -} +} \ No newline at end of file diff --git a/tests/resources/access-om3/configurations/README.md b/tests/resources/access-om3/configurations/README.md new file mode 100644 index 0000000..b8ced97 --- /dev/null +++ b/tests/resources/access-om3/configurations/README.md @@ -0,0 +1,8 @@ +# Example test configuration + +This contains some files from configurations from the +[ACCESS-NRI/access-om3-configs](https://github.com/ACCESS-NRI/access-om3-configs) +and +[ACCESS-NRI/access-om3-wav-configs](https://github.com/ACCESS-NRI/access-om3-wav-configs) +repositories. The configurations contain only the files checked with QA checks, +so they are incomplete configurations. diff --git a/tests/resources/access-om3/configurations/om3-dev-1deg_jra55do_ryf/config.yaml b/tests/resources/access-om3/configurations/om3-dev-1deg_jra55do_ryf/config.yaml new file mode 100644 index 0000000..9887f5d --- /dev/null +++ b/tests/resources/access-om3/configurations/om3-dev-1deg_jra55do_ryf/config.yaml @@ -0,0 +1,53 @@ +# PBS configuration + +# If submitting to a different project to your default, uncomment line below +# and change project code as appropriate; also set shortpath below +# project: x77 + +# Force payu to always find, and save, files in this scratch project directory +# (you may need to add the corresponding PBS -l storage flag in sync_data.sh) +# shortpath: /scratch/v45 + +queue: normal +ncpus: 240 +jobfs: 10GB +mem: 960GB + +walltime: 02:00:00 +jobname: 1deg_jra55do_ryf + +model: access-om3 + +exe: access-om3-MOM6-CICE6 +input: + - /g/data/vk83/configurations/inputs/access-om3/share/meshes/global.1deg/2024.01.25/access-om2-1deg-ESMFmesh.nc + - /g/data/vk83/configurations/inputs/access-om3/share/meshes/global.1deg/2024.01.25/access-om2-1deg-nomask-ESMFmesh.nc + - /g/data/vk83/configurations/inputs/access-om3/share/meshes/share/2024.09.16/JRA55do-datm-ESMFmesh.nc + - /g/data/vk83/configurations/inputs/access-om3/share/meshes/share/2024.09.16/JRA55do-drof-ESMFmesh.nc + - /g/data/vk83/configurations/inputs/access-om3/share/grids/global.1deg/2020.10.22/topog.nc + - /g/data/vk83/configurations/inputs/access-om3/mom/grids/mosaic/global.1deg/2020.05.30/ocean_hgrid.nc + - /g/data/vk83/configurations/inputs/access-om3/mom/grids/vertical/global.1deg/2023.07.28/ocean_vgrid.nc + - /g/data/vk83/configurations/inputs/access-om3/mom/initial_conditions/global.1deg/2020.10.22/ocean_temp_salt.res.nc + - /g/data/vk83/configurations/inputs/access-om3/mom/surface_salt_restoring/global.1deg/2020.05.30/salt_sfc_restore.nc + - /g/data/vk83/configurations/inputs/access-om3/cice/grids/global.1deg/2024.05.14/grid.nc + - /g/data/vk83/configurations/inputs/access-om3/cice/grids/global.1deg/2024.05.14/kmt.nc + - /g/data/vk83/configurations/inputs/access-om3/cice/initial_conditions/global.1deg/2023.07.28/iced.1900-01-01-10800.nc + - /g/data/vk83/configurations/inputs/JRA-55/RYF/v1-4/data + +collate: false +runlog: false +metadata: + enable: false + +userscripts: + setup: /usr/bin/bash /g/data/vk83/apps/om3-scripts/payu_config/setup.sh + archive: /usr/bin/bash /g/data/vk83/apps/om3-scripts/payu_config/archive.sh + +modules: + use: + - /g/data/vk83/modules + load: + - access-om3/2025.01.0 + - nco/5.0.5 + +payu_minimum_version: 1.1.6 diff --git a/tests/resources/access-om3/configurations/om3-dev-1deg_jra55do_ryf/manifests/exe.yaml b/tests/resources/access-om3/configurations/om3-dev-1deg_jra55do_ryf/manifests/exe.yaml new file mode 100644 index 0000000..256eb9a --- /dev/null +++ b/tests/resources/access-om3/configurations/om3-dev-1deg_jra55do_ryf/manifests/exe.yaml @@ -0,0 +1,8 @@ +format: yamanifest +version: 1.0 +--- +work/access-om3-MOM6-CICE6: + fullpath: /g/data/vk83/apps/spack/0.22/release/linux-rocky8-x86_64/intel-2021.10.0/access-om3-nuopc-git.0.4.0_0.4.0-bmecwy5vdmpxapbgqwbqsoi2y7rglaie/bin/access-om3-MOM6-CICE6 + hashes: + binhash: 2edb3ca163da9ef9ef082a1fbdb98493 + md5: 815f23c0339742798d5db37deb7b798e diff --git a/tests/resources/access-om3/configurations/om3-dev-1deg_jra55do_ryf/nuopc.runconfig b/tests/resources/access-om3/configurations/om3-dev-1deg_jra55do_ryf/nuopc.runconfig new file mode 100644 index 0000000..7b8d9cf --- /dev/null +++ b/tests/resources/access-om3/configurations/om3-dev-1deg_jra55do_ryf/nuopc.runconfig @@ -0,0 +1,371 @@ +DRIVER_attributes:: + Verbosity = off + cime_model = cesm + drv_restart_pointer = rpointer.cpl + logFilePostFix = .log + outPathRoot = ./ + pio_blocksize = -1 + pio_buffer_size_limit = -1 + pio_debug_level = 0 + pio_rearr_comm_enable_hs_comp2io = .true. + pio_rearr_comm_enable_hs_io2comp = .false. + pio_rearr_comm_enable_isend_comp2io = .false. + pio_rearr_comm_enable_isend_io2comp = .true. + pio_rearr_comm_fcd = 2denable + pio_rearr_comm_max_pend_req_comp2io = -2 + pio_rearr_comm_max_pend_req_io2comp = 64 + pio_rearr_comm_type = p2p + reprosum_diffmax = -1.0e-8 + reprosum_recompute = .false. + reprosum_use_ddpdd = .false. + tchkpt_dir = ./timing/checkpoints + timing_dir = ./timing + wv_sat_scheme = GoffGratch + wv_sat_table_spacing = 1.0D0 + wv_sat_transition_start = 20.0D0 + wv_sat_use_tables = .false. +:: + +PELAYOUT_attributes:: + atm_ntasks = 24 + atm_nthreads = 1 + atm_pestride = 1 + atm_rootpe = 0 + cpl_ntasks = 24 + cpl_nthreads = 1 + cpl_pestride = 1 + cpl_rootpe = 0 + esmf_logging = ESMF_LOGKIND_NONE + ice_ntasks = 24 + ice_nthreads = 1 + ice_pestride = 1 + ice_rootpe = 0 + ninst = 1 + ocn_ntasks = 216 + ocn_nthreads = 1 + ocn_pestride = 1 + ocn_rootpe = 24 + pio_asyncio_ntasks = 0 + pio_asyncio_rootpe = 1 + pio_asyncio_stride = 0 + rof_ntasks = 24 + rof_nthreads = 1 + rof_pestride = 1 + rof_rootpe = 0 +:: + +component_list: MED ATM ICE OCN ROF +ALLCOMP_attributes:: + ATM_model = datm + GLC_model = sglc + ICE_model = cice + LND_model = slnd + MED_model = cesm + OCN_model = mom + ROF_model = drof + WAV_model = swav + Profiling = 0 + ScalarFieldCount = 4 + ScalarFieldIdxGridNX = 1 + ScalarFieldIdxGridNY = 2 + ScalarFieldIdxNextSwCday = 3 + ScalarFieldIdxPrecipFactor = 0 + ScalarFieldName = cpl_scalars + brnch_retain_casename = .false. + case_desc = UNSET + case_name = access-om3 + cism_evolve = .false. + coldair_outbreak_mod = .false. + data_assimilation_atm = .false. + data_assimilation_cpl = .false. + data_assimilation_ice = .false. + data_assimilation_ocn = .false. + data_assimilation_rof = .false. + flds_bgc_oi = .false. + flds_co2a = .false. + flds_co2b = .false. + flds_co2c = .false. + flds_i2o_per_cat = .false. + flds_r2l_stream_channel_depths = .false. + flds_wiso = .false. + add_gusts = .false. + flux_convergence = 0.01 + flux_max_iteration = 5 + glc_nec = 10 + histaux_l2x1yrg = .false. + history_n = -999 + history_option = never + hostname = gadi + ice_ncat = 5 + mediator_present = true + mesh_atm = ./INPUT/access-om2-1deg-nomask-ESMFmesh.nc + mesh_ice = ./INPUT/access-om2-1deg-ESMFmesh.nc + mesh_lnd = UNSET + mesh_mask = ./INPUT/access-om2-1deg-ESMFmesh.nc + mesh_ocn = ./INPUT/access-om2-1deg-ESMFmesh.nc + model_version = unknown + ocn2glc_coupling = .false. + ocn2glc_levels = 1:10:19:26:30:33:35 + orb_eccen = 1.e36 + orb_iyear = 2000 + orb_iyear_align = 2000 + orb_mode = fixed_year + orb_mvelp = 1.e36 + orb_obliq = 1.e36 + scol_lat = -999.99 + scol_lon = -999.99 + single_column_lnd_domainfile = UNSET + start_type = startup + tfreeze_option = linear_salt + wav_coupling_to_cice = .false. + restart_pointer_append_date = .false. +:: + +MED_attributes:: + Verbosity = off + aoflux_grid = ogrid + atm2ice_map = unset + atm2lnd_map = unset + atm2ocn_map = unset + atm2wav_map = unset + atm_nx = 360 + atm_ny = 300 + budget_ann = 1 + budget_daily = 0 + budget_inst = 0 + budget_ltann = 1 + budget_ltend = 0 + budget_month = 1 + budget_table_version = v1 + check_for_nans = .true. + coupling_mode = cesm + do_budgets = .true. + flux_albav = .true. + glc2ice_rmapname = idmap + glc2ocn_ice_rmapname = idmap + glc2ocn_liq_rmapname = idmap + glc_renormalize_smb = on_if_glc_coupled_fluxes + gust_fac = 0.0D0 + histaux_atm2med_file1_auxname = atm.1h.inst + histaux_atm2med_file1_doavg = .false. + histaux_atm2med_file1_enabled = .false. + histaux_atm2med_file1_flds = Faxa_swndr:Faxa_swvdr:Faxa_swndf:Faxa_swvdf + histaux_atm2med_file1_history_n = 1 + histaux_atm2med_file1_history_option = nhours + histaux_atm2med_file1_ntperfile = 24 + histaux_atm2med_file2_auxname = atm.1h.avrg + histaux_atm2med_file2_doavg = .true. + histaux_atm2med_file2_enabled = .false. + histaux_atm2med_file2_flds = Sa_u:Sa_v + histaux_atm2med_file2_history_n = 1 + histaux_atm2med_file2_history_option = nhours + histaux_atm2med_file2_ntperfile = 24 + histaux_atm2med_file3_auxname = atm.3hprec.avrg + histaux_atm2med_file3_doavg = .true. + histaux_atm2med_file3_enabled = .false. + histaux_atm2med_file3_flds = Faxa_rainc:Faxa_rainl:Faxa_snowc:Faxa_snowl + histaux_atm2med_file3_history_n = 3 + histaux_atm2med_file3_history_option = nhours + histaux_atm2med_file3_ntperfile = 8 + histaux_atm2med_file4_auxname = atm.3h.avrg + histaux_atm2med_file4_doavg = .true. + histaux_atm2med_file4_enabled = .false. + histaux_atm2med_file4_flds = Sa_z:Sa_topo:Sa_u:Sa_v:Sa_tbot:Sa_ptem:Sa_shum:Sa_dens:Sa_pbot:Sa_pslv:Faxa_lwdn:Faxa_rainc:Faxa_rainl:Faxa_snowc:Faxa_snowl:Faxa_swndr:Faxa_swvdr:Faxa_swndf:Faxa_swvdf:Sa_co2diag:Sa_co2prog + histaux_atm2med_file4_history_n = 3 + histaux_atm2med_file4_history_option = nhours + histaux_atm2med_file4_ntperfile = 8 + histaux_atm2med_file5_auxname = atm.24h.avrg + histaux_atm2med_file5_doavg = .true. + histaux_atm2med_file5_enabled = .false. + histaux_atm2med_file5_flds = Faxa_bcph:Faxa_ocph:Faxa_dstwet:Faxa_dstdry:Sa_co2prog:Sa_co2diag + histaux_atm2med_file5_history_n = 3 + histaux_atm2med_file5_history_option = nhours + histaux_atm2med_file5_ntperfile = 2 + histaux_lnd2med_file1_auxname = lnd.ncpl.inst + histaux_lnd2med_file1_doavg = .false. + histaux_lnd2med_file1_enabled = .false. + histaux_lnd2med_file1_flds = all + histaux_lnd2med_file1_history_n = 1 + histaux_lnd2med_file1_history_option = nsteps + histaux_lnd2med_file1_ntperfile = 1 + histaux_ocn2med_file1_auxname = ocn.24h.avg + histaux_ocn2med_file1_doavg = .true. + histaux_ocn2med_file1_enabled = .false. + histaux_ocn2med_file1_flds = So_bldepth:So_t:So_u:So_v + histaux_ocn2med_file1_history_n = 1 + histaux_ocn2med_file1_history_option = ndays + histaux_ocn2med_file1_ntperfile = 30 + histaux_rof2med_file1_auxname = rof.24h.avrg + histaux_rof2med_file1_doavg = .true. + histaux_rof2med_file1_enabled = .false. + histaux_rof2med_file1_flds = all + histaux_rof2med_file1_history_n = 3 + histaux_rof2med_file1_history_option = nhours + histaux_rof2med_file1_ntperfile = 2 + history_n_atm_avg = -999 + history_n_atm_inst = -999 + history_n_glc_avg = -999 + history_n_glc_inst = -999 + history_n_ice_avg = -999 + history_n_ice_inst = -999 + history_n_lnd_avg = -999 + history_n_lnd_inst = -999 + history_n_med_inst = -999 + history_n_ocn_avg = -999 + history_n_ocn_inst = -999 + history_n_rof_avg = -999 + history_n_rof_inst = -999 + history_n_wav_avg = -999 + history_n_wav_inst = -999 + history_option_atm_avg = never + history_option_atm_inst = never + history_option_glc_avg = never + history_option_glc_inst = never + history_option_ice_avg = never + history_option_ice_inst = never + history_option_lnd_avg = never + history_option_lnd_inst = never + history_option_med_inst = never + history_option_ocn_avg = never + history_option_ocn_inst = never + history_option_rof_avg = never + history_option_rof_inst = never + history_option_wav_avg = never + history_option_wav_inst = never + ice2atm_map = unset + ice2wav_smapname = unset + ice_nx = 360 + ice_ny = 300 + info_debug = 1 + lnd2atm_map = unset + lnd2rof_map = unset + lnd_nx = 0 + lnd_ny = 0 + mapuv_with_cart3d = .true. + ocn2atm_map = unset + ocn2wav_smapname = unset + ocn_nx = 360 + ocn_ny = 300 + ocn_surface_flux_scheme = 0 + rof2lnd_map = unset + rof2ocn_fmapname = unset + rof2ocn_ice_rmapname = unset + rof2ocn_liq_rmapname = unset + rof_nx = 360 + rof_ny = 300 + wav2ocn_smapname = unset + wav_nx = 0 + wav_ny = 0 +:: + +CLOCK_attributes:: + atm_cpl_dt = 99999 #not used + calendar = NO_LEAP + end_restart = .false. + glc_avg_period = yearly + glc_cpl_dt = 99999 #not used + history_ymd = -999 + ice_cpl_dt = 99999 #not used + lnd_cpl_dt = 99999 #not used + ocn_cpl_dt = 3600 #ignored (coupling timestep set by nuopc.runseq) unless stop_option is nsteps + restart_n = 1 + restart_option = nyears + restart_ymd = -999 + rof_cpl_dt = 99999 #not used + start_tod = 0 + start_ymd = 19000101 + stop_n = 1 + stop_option = nyears + stop_tod = 0 + stop_ymd = -999 + tprof_n = -999 + tprof_option = never + tprof_ymd = -999 + wav_cpl_dt = 99999 #not used +:: + +ATM_attributes:: + Verbosity = off + aqua_planet = .false. + perpetual = .false. + perpetual_ymd = -999 +:: + +ICE_attributes:: + eps_imesh = 1e-13 # allowed error between angles in mesh file and cice grid + Verbosity = off +:: + +OCN_attributes:: + Verbosity = off +:: + +ROF_attributes:: + Verbosity = off + mesh_rof = ./INPUT/access-om2-1deg-nomask-ESMFmesh.nc +:: + +MED_modelio:: + diro = ./log + logfile = med.log + pio_async_interface = .false. + pio_netcdf_format = nothing + pio_numiotasks = 4 + pio_rearranger = 2 + pio_root = 1 + pio_stride = 1 + pio_typename = netcdf4p +:: + +ATM_modelio:: + diro = ./log + logfile = atm.log + pio_async_interface = .false. + pio_netcdf_format = nothing + pio_numiotasks = 1 + pio_rearranger = 1 + pio_root = 1 + pio_stride = 1 + pio_typename = netcdf4p +:: + +ICE_modelio:: + diro = ./log + logfile = ice.log + pio_async_interface = .false. + pio_netcdf_format = nothing + pio_numiotasks = 1 + pio_rearranger = 1 + pio_root = 1 + pio_stride = 1 + pio_typename = netcdf4p +:: + +OCN_modelio:: + diro = ./log + logfile = ocn.log + pio_async_interface = .false. #not used + pio_netcdf_format = 64bit_offset #not used + pio_numiotasks = -99 #not used + pio_rearranger = 2 #not used + pio_root = 1 #not used + pio_stride = 48 #not used + pio_typename = netcdf #not used, set in input.nml +:: + +ROF_modelio:: + diro = ./log + logfile = rof.log + pio_async_interface = .false. + pio_netcdf_format = nothing + pio_numiotasks = 1 + pio_rearranger = 2 + pio_root = 1 + pio_stride = 1 + pio_typename = netcdf4p +:: + +DRV_modelio:: + diro = ./log + logfile = drv.log +:: diff --git a/tests/resources/access-om3/configurations/om3-wav-dev-1deg_jra55do_ryf/config.yaml b/tests/resources/access-om3/configurations/om3-wav-dev-1deg_jra55do_ryf/config.yaml new file mode 100644 index 0000000..3b9c5d7 --- /dev/null +++ b/tests/resources/access-om3/configurations/om3-wav-dev-1deg_jra55do_ryf/config.yaml @@ -0,0 +1,56 @@ +# PBS configuration + +# If submitting to a different project to your default, uncomment line below +# and change project code as appropriate; also set shortpath below +# project: x77 + +# Force payu to always find, and save, files in this scratch project directory +# (you may need to add the corresponding PBS -l storage flag in sync_data.sh) +# shortpath: /scratch/v45 + +queue: normal +ncpus: 48 +jobfs: 10GB +mem: 192GB + +walltime: 01:00:00 +jobname: 1deg_jra55do_ryf + +model: access-om3 + +exe: access-om3-MOM6-CICE6-WW3 +input: + - /g/data/vk83/configurations/inputs/access-om3/share/meshes/global.1deg/2024.01.25/access-om2-1deg-ESMFmesh.nc + - /g/data/vk83/configurations/inputs/access-om3/share/meshes/global.1deg/2024.01.25/access-om2-1deg-nomask-ESMFmesh.nc + - /g/data/vk83/configurations/inputs/access-om3/share/meshes/share/2024.09.16/JRA55do-datm-ESMFmesh.nc + - /g/data/vk83/configurations/inputs/access-om3/share/meshes/share/2024.09.16/JRA55do-drof-ESMFmesh.nc + - /g/data/vk83/configurations/inputs/access-om3/share/grids/global.1deg/2020.10.22/topog.nc + - /g/data/vk83/configurations/inputs/access-om3/mom/grids/mosaic/global.1deg/2020.05.30/ocean_hgrid.nc + - /g/data/vk83/configurations/inputs/access-om3/mom/grids/vertical/global.1deg/2023.07.28/ocean_vgrid.nc + - /g/data/vk83/configurations/inputs/access-om3/mom/initial_conditions/global.1deg/2020.10.22/ocean_temp_salt.res.nc + - /g/data/vk83/configurations/inputs/access-om3/mom/surface_salt_restoring/global.1deg/2020.05.30/salt_sfc_restore.nc + - /g/data/vk83/configurations/inputs/access-om3/cice/grids/global.1deg/2024.05.14/grid.nc + - /g/data/vk83/configurations/inputs/access-om3/cice/grids/global.1deg/2024.05.14/kmt.nc + - /g/data/vk83/configurations/inputs/access-om3/cice/initial_conditions/global.1deg/2023.07.28/iced.1900-01-01-10800.nc + - /g/data/vk83/configurations/inputs/access-om3/ww3/initial_conditions/global.1deg/2024.04.18/restart.ww3 + - /g/data/vk83/configurations/inputs/access-om3/ww3/mod_def/global.1deg/2024.04.18/mod_def.ww3 + - /g/data/vk83/configurations/inputs/JRA-55/RYF/v1-4/data + +collate: false +runlog: false + +metadata: + enable: false + +userscripts: + setup: /usr/bin/bash /g/data/vk83/apps/om3-scripts/payu_config/setup.sh + archive: /usr/bin/bash /g/data/vk83/apps/om3-scripts/payu_config/archive.sh + +modules: + use: + - /g/data/vk83/modules + load: + - access-om3/2024.09.0 + - nco/5.0.5 + +payu_minimum_version: 1.1.4 diff --git a/tests/resources/access-om3/configurations/om3-wav-dev-1deg_jra55do_ryf/manifests/exe.yaml b/tests/resources/access-om3/configurations/om3-wav-dev-1deg_jra55do_ryf/manifests/exe.yaml new file mode 100644 index 0000000..62ee5f9 --- /dev/null +++ b/tests/resources/access-om3/configurations/om3-wav-dev-1deg_jra55do_ryf/manifests/exe.yaml @@ -0,0 +1,8 @@ +format: yamanifest +version: 1.0 +--- +work/access-om3-MOM6-CICE6-WW3: + fullpath: /g/data/vk83/apps/spack/0.22/release/linux-rocky8-x86_64/intel-2021.10.0/access-om3-nuopc-git.0.3.1_0.3.1-boks2tay6p2uqrv2y3wstcdqkotfs3d2/bin/access-om3-MOM6-CICE6-WW3 + hashes: + binhash: 69008a04747020878876747c818a2f5e + md5: 69db508b3db989e4461bd31bbabb2cb1 diff --git a/tests/resources/access-om3/configurations/om3-wav-dev-1deg_jra55do_ryf/nuopc.runconfig b/tests/resources/access-om3/configurations/om3-wav-dev-1deg_jra55do_ryf/nuopc.runconfig new file mode 100644 index 0000000..3c1aaea --- /dev/null +++ b/tests/resources/access-om3/configurations/om3-wav-dev-1deg_jra55do_ryf/nuopc.runconfig @@ -0,0 +1,419 @@ +DRIVER_attributes:: + Verbosity = off + cime_model = cesm + drv_restart_pointer = rpointer.cpl + logFilePostFix = .log + outPathRoot = ./ + pio_blocksize = -1 + pio_buffer_size_limit = -1 + pio_debug_level = 0 + pio_rearr_comm_enable_hs_comp2io = .true. + pio_rearr_comm_enable_hs_io2comp = .false. + pio_rearr_comm_enable_isend_comp2io = .false. + pio_rearr_comm_enable_isend_io2comp = .true. + pio_rearr_comm_fcd = 2denable + pio_rearr_comm_max_pend_req_comp2io = -2 + pio_rearr_comm_max_pend_req_io2comp = 64 + pio_rearr_comm_type = p2p + reprosum_diffmax = -1.0e-8 + reprosum_recompute = .false. + reprosum_use_ddpdd = .false. + tchkpt_dir = ./timing/checkpoints + timing_dir = ./timing + wv_sat_scheme = GoffGratch + wv_sat_table_spacing = 1.0D0 + wv_sat_transition_start = 20.0D0 + wv_sat_use_tables = .false. +:: + +PELAYOUT_attributes:: + atm_ntasks = 48 + atm_nthreads = 1 + atm_pestride = 1 + atm_rootpe = 0 + cpl_ntasks = 48 + cpl_nthreads = 1 + cpl_pestride = 1 + cpl_rootpe = 0 + esmf_logging = ESMF_LOGKIND_NONE + esp_ntasks = 1 + esp_nthreads = 1 + esp_pestride = 1 + esp_rootpe = 0 + glc_ntasks = 48 + glc_nthreads = 1 + glc_pestride = 1 + glc_rootpe = 0 + ice_ntasks = 48 + ice_nthreads = 1 + ice_pestride = 1 + ice_rootpe = 0 + lnd_ntasks = 48 + lnd_nthreads = 1 + lnd_pestride = 1 + lnd_rootpe = 0 + ninst = 1 + ocn_ntasks = 48 + ocn_nthreads = 1 + ocn_pestride = 1 + ocn_rootpe = 0 + pio_asyncio_ntasks = 0 + pio_asyncio_rootpe = 1 + pio_asyncio_stride = 0 + rof_ntasks = 48 + rof_nthreads = 1 + rof_pestride = 1 + rof_rootpe = 0 + wav_ntasks = 48 + wav_nthreads = 1 + wav_pestride = 1 + wav_rootpe = 0 +:: + +component_list: MED ATM ICE OCN ROF WAV +ALLCOMP_attributes:: + ATM_model = datm + GLC_model = sglc + ICE_model = cice + LND_model = slnd + MED_model = cesm + OCN_model = mom + Profiling = 0 + ROF_model = drof + ScalarFieldCount = 4 + ScalarFieldIdxGridNX = 1 + ScalarFieldIdxGridNY = 2 + ScalarFieldIdxNextSwCday = 3 + ScalarFieldIdxPrecipFactor = 0 + ScalarFieldName = cpl_scalars + WAV_model = ww3dev + brnch_retain_casename = .false. + case_desc = UNSET + case_name = access-om3 + cism_evolve = .false. + coldair_outbreak_mod = .false. + data_assimilation_atm = .false. + data_assimilation_cpl = .false. + data_assimilation_glc = .false. + data_assimilation_ice = .false. + data_assimilation_lnd = .false. + data_assimilation_ocn = .false. + data_assimilation_rof = .false. + data_assimilation_wav = .false. + flds_bgc_oi = .false. + flds_co2a = .false. + flds_co2b = .false. + flds_co2c = .false. + flds_i2o_per_cat = .false. + flds_r2l_stream_channel_depths = .false. + flds_wiso = .false. + flux_convergence = 0.01 + flux_max_iteration = 5 + glc_nec = 10 + histaux_l2x1yrg = .false. + history_n = -999 + history_option = never + hostname = gadi + ice_ncat = 5 + mediator_present = true + mesh_atm = ./INPUT/access-om2-1deg-nomask-ESMFmesh.nc + mesh_glc = UNSET + mesh_ice = ./INPUT/access-om2-1deg-ESMFmesh.nc + mesh_lnd = UNSET + mesh_mask = ./INPUT/access-om2-1deg-ESMFmesh.nc + mesh_ocn = ./INPUT/access-om2-1deg-ESMFmesh.nc + model_version = unknown + ocn2glc_coupling = .false. + ocn2glc_levels = 1:10:19:26:30:33:35 + orb_eccen = 1.e36 + orb_iyear = 2000 + orb_iyear_align = 2000 + orb_mode = fixed_year + orb_mvelp = 1.e36 + orb_obliq = 1.e36 + scol_lat = -999.99 + scol_lon = -999.99 + single_column_lnd_domainfile = UNSET + start_type = startup + tfreeze_option = linear_salt + username = ds0092 + wav_coupling_to_cice = .true. + write_restart_at_endofrun = .false. +:: + +MED_attributes:: + Verbosity = off + aoflux_grid = ogrid + atm2ice_map = unset + atm2lnd_map = unset + atm2ocn_map = unset + atm2wav_map = unset + atm_nx = 360 + atm_ny = 300 + budget_ann = 1 + budget_daily = 0 + budget_inst = 0 + budget_ltann = 1 + budget_ltend = 0 + budget_month = 1 + budget_table_version = v1 + check_for_nans = .true. + coupling_mode = cesm + do_budgets = .true. + flux_albav = .true. + glc2ice_rmapname = idmap + glc2ocn_ice_rmapname = idmap + glc2ocn_liq_rmapname = idmap + glc_renormalize_smb = on_if_glc_coupled_fluxes + gust_fac = 0.0D0 + histaux_atm2med_file1_auxname = atm.1h.inst + histaux_atm2med_file1_doavg = .false. + histaux_atm2med_file1_enabled = .false. + histaux_atm2med_file1_flds = Faxa_swndr:Faxa_swvdr:Faxa_swndf:Faxa_swvdf + histaux_atm2med_file1_history_n = 1 + histaux_atm2med_file1_history_option = nhours + histaux_atm2med_file1_ntperfile = 24 + histaux_atm2med_file2_auxname = atm.1h.avrg + histaux_atm2med_file2_doavg = .true. + histaux_atm2med_file2_enabled = .false. + histaux_atm2med_file2_flds = Sa_u:Sa_v + histaux_atm2med_file2_history_n = 1 + histaux_atm2med_file2_history_option = nhours + histaux_atm2med_file2_ntperfile = 24 + histaux_atm2med_file3_auxname = atm.3hprec.avrg + histaux_atm2med_file3_doavg = .true. + histaux_atm2med_file3_enabled = .false. + histaux_atm2med_file3_flds = Faxa_rainc:Faxa_rainl:Faxa_snowc:Faxa_snowl + histaux_atm2med_file3_history_n = 3 + histaux_atm2med_file3_history_option = nhours + histaux_atm2med_file3_ntperfile = 8 + histaux_atm2med_file4_auxname = atm.3h.avrg + histaux_atm2med_file4_doavg = .true. + histaux_atm2med_file4_enabled = .false. + histaux_atm2med_file4_flds = Sa_z:Sa_topo:Sa_u:Sa_v:Sa_tbot:Sa_ptem:Sa_shum:Sa_dens:Sa_pbot:Sa_pslv:Faxa_lwdn:Faxa_rainc:Faxa_rainl:Faxa_snowc:Faxa_snowl:Faxa_swndr:Faxa_swvdr:Faxa_swndf:Faxa_swvdf:Sa_co2diag:Sa_co2prog + histaux_atm2med_file4_history_n = 3 + histaux_atm2med_file4_history_option = nhours + histaux_atm2med_file4_ntperfile = 8 + histaux_atm2med_file5_auxname = atm.24h.avrg + histaux_atm2med_file5_doavg = .true. + histaux_atm2med_file5_enabled = .false. + histaux_atm2med_file5_flds = Faxa_bcph:Faxa_ocph:Faxa_dstwet:Faxa_dstdry:Sa_co2prog:Sa_co2diag + histaux_atm2med_file5_history_n = 3 + histaux_atm2med_file5_history_option = nhours + histaux_atm2med_file5_ntperfile = 2 + histaux_lnd2med_file1_auxname = lnd.ncpl.inst + histaux_lnd2med_file1_doavg = .false. + histaux_lnd2med_file1_enabled = .false. + histaux_lnd2med_file1_flds = all + histaux_lnd2med_file1_history_n = 1 + histaux_lnd2med_file1_history_option = nsteps + histaux_lnd2med_file1_ntperfile = 1 + histaux_ocn2med_file1_auxname = ocn.24h.avg + histaux_ocn2med_file1_doavg = .true. + histaux_ocn2med_file1_enabled = .false. + histaux_ocn2med_file1_flds = So_bldepth:So_t:So_u:So_v + histaux_ocn2med_file1_history_n = 1 + histaux_ocn2med_file1_history_option = ndays + histaux_ocn2med_file1_ntperfile = 30 + histaux_rof2med_file1_auxname = rof.24h.avrg + histaux_rof2med_file1_doavg = .true. + histaux_rof2med_file1_enabled = .false. + histaux_rof2med_file1_flds = all + histaux_rof2med_file1_history_n = 3 + histaux_rof2med_file1_history_option = nhours + histaux_rof2med_file1_ntperfile = 2 + history_n_atm_avg = -999 + history_n_atm_inst = -999 + history_n_glc_avg = -999 + history_n_glc_inst = -999 + history_n_ice_avg = -999 + history_n_ice_inst = -999 + history_n_lnd_avg = -999 + history_n_lnd_inst = -999 + history_n_med_inst = -999 + history_n_ocn_avg = -999 + history_n_ocn_inst = -999 + history_n_rof_avg = -999 + history_n_rof_inst = -999 + history_n_wav_avg = -999 + history_n_wav_inst = -999 + history_option_atm_avg = never + history_option_atm_inst = never + history_option_glc_avg = never + history_option_glc_inst = never + history_option_ice_avg = never + history_option_ice_inst = never + history_option_lnd_avg = never + history_option_lnd_inst = never + history_option_med_inst = never + history_option_ocn_avg = never + history_option_ocn_inst = never + history_option_rof_avg = never + history_option_rof_inst = never + history_option_wav_avg = never + history_option_wav_inst = never + ice2atm_map = unset + ice2wav_smapname = unset + ice_nx = 360 + ice_ny = 300 + info_debug = 1 + lnd2atm_map = unset + lnd2rof_map = unset + lnd_nx = 0 + lnd_ny = 0 + mapuv_with_cart3d = .true. + ocn2atm_map = unset + ocn2wav_smapname = unset + ocn_nx = 360 + ocn_ny = 300 + ocn_surface_flux_scheme = 0 + rof2lnd_map = unset + rof2ocn_fmapname = unset + rof2ocn_ice_rmapname = unset + rof2ocn_liq_rmapname = unset + rof_nx = 360 + rof_ny = 300 + wav2ocn_smapname = unset + wav_nx = 90 + wav_ny = 50 +:: + +CLOCK_attributes:: + atm_cpl_dt = 99999 #not used + calendar = NO_LEAP + end_restart = .false. + glc_avg_period = yearly + glc_cpl_dt = 99999 #not used + history_ymd = -999 + ice_cpl_dt = 99999 #not used + lnd_cpl_dt = 99999 #not used + ocn_cpl_dt = 3600 #ignored (coupling timestep set by nuopc.runseq) unless stop_option is nsteps + restart_n = 10800 + restart_option = nseconds + restart_ymd = -999 + rof_cpl_dt = 99999 #not used + start_tod = 0 + start_ymd = 19000101 + stop_n = 10800 + stop_option = nseconds + stop_tod = 0 + stop_ymd = -999 + tprof_n = -999 + tprof_option = never + tprof_ymd = -999 + wav_cpl_dt = 99999 #not used +:: + +ATM_attributes:: + Verbosity = off + aqua_planet = .false. + perpetual = .false. + perpetual_ymd = -999 +:: + +ICE_attributes:: + eps_imesh = 1e-13 # allowed error between angles in mesh file and cice grid + Verbosity = off +:: + +GLC_attributes:: + Verbosity = off +:: + +LND_attributes:: + Verbosity = off +:: + +OCN_attributes:: + Verbosity = off +:: + +ROF_attributes:: + Verbosity = off + mesh_rof = ./INPUT/access-om2-1deg-nomask-ESMFmesh.nc +:: + +WAV_attributes:: + Verbosity = off + mesh_wav = ./INPUT/access-om2-1deg-nomask-ESMFmesh.nc +:: + +MED_modelio:: + diro = ./log + logfile = med.log + pio_async_interface = .false. + pio_netcdf_format = 64bit_offset + pio_numiotasks = -99 + pio_rearranger = 2 + pio_root = 1 + pio_stride = 48 + pio_typename = netcdf +:: + +ATM_modelio:: + diro = ./log + logfile = atm.log + pio_async_interface = .false. + pio_netcdf_format = 64bit_offset + pio_numiotasks = -99 + pio_rearranger = 1 + pio_root = 1 + pio_stride = 48 + pio_typename = netcdf +:: + +ICE_modelio:: + diro = ./log + logfile = ice.log + pio_async_interface = .false. + pio_netcdf_format = 64bit_offset + pio_numiotasks = 1 + pio_rearranger = 1 + pio_root = 1 + pio_stride = 48 + pio_typename = netcdf4p +:: + +OCN_modelio:: + diro = ./log + logfile = ocn.log + pio_async_interface = .false. #not used + pio_netcdf_format = 64bit_offset #not used + pio_numiotasks = -99 #not used + pio_rearranger = 2 #not used + pio_root = 1 #not used + pio_stride = 48 #not used + pio_typename = netcdf #not used, set in input.nml +:: + +ROF_modelio:: + diro = ./log + logfile = rof.log + pio_async_interface = .false. #not used + pio_netcdf_format = 64bit_offset #not used + pio_numiotasks = -99 #not used + pio_rearranger = 2 #not used + pio_root = 1 #not used + pio_stride = 48 #not used + pio_typename = netcdf #not used +:: + +WAV_modelio:: + diro = ./log + logfile = wav.log + pio_async_interface = .false. + pio_netcdf_format = 64bit_offset + pio_numiotasks = -99 + pio_rearranger = 2 + pio_root = 1 + pio_stride = 48 + pio_typename = netcdf + history_n = 1 + history_option = ndays +:: + +DRV_modelio:: + diro = ./log + logfile = drv.log +:: + diff --git a/tests/resources/access-om3/configurations/om3-wav-dev-1deg_jra55do_ryf/wav_in b/tests/resources/access-om3/configurations/om3-wav-dev-1deg_jra55do_ryf/wav_in new file mode 100644 index 0000000..b3ad681 --- /dev/null +++ b/tests/resources/access-om3/configurations/om3-wav-dev-1deg_jra55do_ryf/wav_in @@ -0,0 +1,36 @@ +&ww3_inparm + dtcfl = 180. + dtcfli = 300. + dtmax = 600. + dtmin = 50. + initfile = "./INPUT/restart.ww3" +/ +&input_nml + input%forcing%water_levels = 'T' + input%forcing%currents = 'C' + input%forcing%winds = 'C' + input%forcing%ice_conc = 'C' + input%forcing%ice_param1 = 'C' + input%forcing%ice_param5 = 'C' +/ +&output_type_nml + type%field%list = 'WND ICE HS T02 T0M1 T01 FP DIR EF USS TOC IC1 IC5' + type%point%file = 'ww3_points.list' +/ +&output_date_nml + date%field%outffile = '1' + date%field%start = '19000101 0' + date%field%stride = '86400' + date%field%stop = '99990101 0' + date%point%outffile = '0' + date%point%start = '19000101 0' + date%point%stride = '0' + date%point%stop = '99990101 0' + date%restart%start = '19000101 0' + date%restart%stride = '86400' + date%restart%stop = '99990101 0' + date%restart2%start = '19000101 0' + date%restart2%stride = '0' + date%restart2%stop = '99990101 0' +/ + diff --git a/tests/resources/access-om3/output000/ocean.stats b/tests/resources/access-om3/output000/ocean.stats deleted file mode 100644 index ee9e004..0000000 --- a/tests/resources/access-om3/output000/ocean.stats +++ /dev/null @@ -1,3 +0,0 @@ - Step, Day, Truncs, Energy/Mass, Maximum CFL, Mean Sea Level, Total Mass, Mean Salin, Mean Temp, Frac Mass Err, Salin Err, Temp Err - [days] [m2 s-2] [Nondim] [m] [kg] [PSU] [degC] [Nondim] [PSU] [degC] - 0, 693135.000, 0, En 3.0745627134675957E-23, CFL 0.00000, SL 1.5112E-10, M 1.36404E+21, S 34.7263, T 3.6362, Me 0.00E+00, Se 0.00E+00, Te 0.00E+00 diff --git a/tests/resources/access-om3/restart000/access-om3.mom6.r.1900-01-01-10800.nc b/tests/resources/access-om3/restart000/access-om3.mom6.r.1900-01-01-10800.nc new file mode 100644 index 0000000..1e050f3 Binary files /dev/null and b/tests/resources/access-om3/restart000/access-om3.mom6.r.1900-01-01-10800.nc differ diff --git a/tests/resources/access-om3/restart000/rpointer.ocn b/tests/resources/access-om3/restart000/rpointer.ocn new file mode 100644 index 0000000..2cb6975 --- /dev/null +++ b/tests/resources/access-om3/restart000/rpointer.ocn @@ -0,0 +1 @@ +access-om3.mom6.r.1900-01-01-10800.nc diff --git a/tests/test_model_extract_checksums.py b/tests/test_model_extract_checksums.py index af2fcb0..b4eacc2 100644 --- a/tests/test_model_extract_checksums.py +++ b/tests/test_model_extract_checksums.py @@ -19,6 +19,7 @@ def test_extract_checksums(model_name): # Mock ExpTestHelper mock_experiment = Mock() mock_experiment.output000 = resources_dir / "output000" + mock_experiment.restart000 = resources_dir / "restart000" mock_experiment.control_path = Path("test/tmp") # Create Model instance @@ -53,6 +54,7 @@ def test_extract_checksums_unsupported_version(model_name): # Mock ExpTestHelper mock_experiment = Mock() mock_experiment.output000 = resources_dir / "output000" + mock_experiment.restart000 = resources_dir / "restart000" mock_experiment.control_path = Path("test/tmp") # Create Model instance diff --git a/tests/test_test_bit_reprodubility.py b/tests/test_test_bit_reproducibility.py similarity index 55% rename from tests/test_test_bit_reprodubility.py rename to tests/test_test_bit_reproducibility.py index c70a7ff..719763b 100644 --- a/tests/test_test_bit_reprodubility.py +++ b/tests/test_test_bit_reproducibility.py @@ -8,6 +8,8 @@ import f90nml import pytest import yaml +from netCDF4 import Dataset +from payu.models.cesm_cmeps import Runconfig from tests.common import RESOURCES_DIR @@ -72,6 +74,11 @@ def write_config(self): # TODO: Could create use a test config.yaml file for each model # in test resources? This could be used to test "config" tests too? + def copy_config(self, configuration): + """Copy a minimal control directory from RESOURCES_DIR""" + mock_config = self.resources_path / "configurations" / configuration + shutil.copytree(mock_config, self.control_path) + def base_test_command(self): """Create a minimal test command""" # Minimal test command @@ -86,68 +93,35 @@ def base_test_command(self): ) return test_cmd - def create_mock_output000(self): - """Copy some expected output in the archive directory""" - resources_output000 = self.resources_path / "output000" - mock_output000 = self.test_archive_path / "output000" - shutil.copytree(resources_output000, mock_output000) - return mock_output000 - - -def test_test_bit_repro_historical_access_pass(tmp_dir): - """Test ACCESS-ESM1.5 access class with historical repro test with - some mock output and configuration directory.""" - test_name = "test_bit_repro_historical" - model_name = "access" - - # Setup test Helper - helper = CommonTestHelper(test_name, model_name, tmp_dir) - helper.write_config() - - # Compare checksums against the existing checksums in resources folder - checksum_path = helper.resources_path / "checksums" / "1-0-0.json" - - # Put some expected output in the archive directory (as we are skipping - # the actual payu run step) - helper.create_mock_output000() - - # Build test command - test_cmd = ( - f"{helper.base_test_command()} " - f"--checksum-path {checksum_path} " - f"--control-path {helper.control_path} " - ) - - # Run test in a subprocess call - result = subprocess.run(shlex.split(test_cmd), capture_output=True, text=True) - - if result.returncode: - # Print out test logs if there are errors - print(f"Test stdout: {result.stdout}\nTest stderr: {result.stderr}") - assert result.returncode == 0 - - # Check config.yaml file generated for the test - with helper.test_config_path.open("r") as f: - test_config = yaml.safe_load(f) - - # Check runtime of 24hr hours is set - assert test_config["calendar"]["runtime"] == { - "years": 0, - "months": 0, - "days": 0, - "seconds": 86400, - } - - # Check general config.yaml settings for test - assert test_config["experiment"] == test_name - assert not test_config["runlog"] - assert not test_config["metadata"]["enable"] - assert test_config["laboratory"] == str(helper.lab_path) - - # Check name of checksum file written out and contents - test_checksum = helper.output_path / "checksum" / "historical-24hr-checksum.json" - assert test_checksum.exists() - assert test_checksum.read_text() == checksum_path.read_text() + def create_mock_output(self, output="output000", modify=False): + """Copy some expected output in the archive directory, optionally modifying the output + to alter checksums""" + resources_output = self.resources_path / output + mock_output = self.test_archive_path / output + shutil.copytree(resources_output, mock_output) + + if modify: + if self.model_name in ["access", "access-om2"]: + with (mock_output / f"{self.model_name}.out").open("a") as f: + f.write("[chksum] test_checksum -1") + elif self.model_name == "access-om3": + mom_restart_pointer = mock_output / "rpointer.ocn" + with open(mom_restart_pointer) as f: + restart_file = f.readline().rstrip() + + restart = mom_restart_pointer.parent / restart_file + rootgrp = Dataset(restart, "a") + for variable in sorted(rootgrp.variables): + # Find the first var with a checksum and subtract 1 from it + var = rootgrp[variable] + if "checksum" in var.ncattrs(): + # Subtract 1 from the checksum and return as uppercase + var_p1 = format(int(var.checksum, 16) - 1, "X") + var.setncattr("checksum", var_p1) + break + rootgrp.close() + else: + raise ValueError(f"Unrecognised model: {self.model_name}") def test_test_bit_repro_historical_access_checksums_saved_on_config(tmp_dir): @@ -170,7 +144,7 @@ def test_test_bit_repro_historical_access_checksums_saved_on_config(tmp_dir): # Put some expected output in the archive directory (as we are skipping # the actual payu run step) - helper.create_mock_output000() + helper.create_mock_output() # Build test command test_cmd = helper.base_test_command() @@ -191,46 +165,6 @@ def test_test_bit_repro_historical_access_checksums_saved_on_config(tmp_dir): assert result.returncode == 0 -def test_test_bit_repro_historical_access_fail(tmp_dir): - """Check when checksums do not match, checksum file is still written out""" - test_name = "test_bit_repro_historical" - model_name = "access" - - # Setup test Helper - helper = CommonTestHelper(test_name, model_name, tmp_dir) - helper.write_config() - - # Compare checksums against the existing checksums in resources folder - checksum_path = helper.resources_path / "checksums" / "1-0-0.json" - - # Put some expected output in the archive directory (as we are skipping - # the actual payu run step) - mock_output000 = helper.create_mock_output000() - - # Modify output file to mimic a change to output file in archive - with (mock_output000 / "access.out").open("a") as f: - f.write("[chksum] test_checksum -1") - - # Build test command - test_cmd = ( - f"{helper.base_test_command()} " - f"--checksum-path {checksum_path} " - f"--control-path {helper.control_path} " - ) - - # Run test in a subprocess call - result = subprocess.run(shlex.split(test_cmd), capture_output=True, text=True) - - # Expect test to fail - assert result.returncode == 1 - - # Test when checksums aren't matched, that file are still written - test_checksum = helper.output_path / "checksum" / "historical-24hr-checksum.json" - assert test_checksum.exists() - content = test_checksum.read_text() - assert content != checksum_path.read_text() - - def test_test_bit_repro_historical_access_no_reference_checksums(tmp_dir): """Check when a reference file for checksums does not exist, that checksums from the output are written out""" @@ -243,7 +177,7 @@ def test_test_bit_repro_historical_access_no_reference_checksums(tmp_dir): # Put some expected output in the archive directory (as we are skipping # the actual payu run step) - helper.create_mock_output000() + helper.create_mock_output() # Build test command test_cmd = f"{helper.base_test_command()} " f"--control-path {helper.control_path} " @@ -255,12 +189,8 @@ def test_test_bit_repro_historical_access_no_reference_checksums(tmp_dir): assert result.returncode == 1 # Test that checksums are still written out - test_checksum = helper.output_path / "checksum" / "historical-24hr-checksum.json" - assert test_checksum.exists() - - # Test that they are equal to checksums in resource folder checksum_path = helper.resources_path / "checksums" / "1-0-0.json" - assert test_checksum.read_text() == checksum_path.read_text() + check_checksum(helper.output_path, checksum_path, helper.model_name) def test_test_bit_repro_historical_access_no_model_output(tmp_dir): @@ -292,25 +222,38 @@ def test_test_bit_repro_historical_access_no_model_output(tmp_dir): assert not test_checksum.exists() -def test_test_bit_repro_historical_access_om2_pass(tmp_dir): - """Test ACCESS-OM2 class with historical repro test with - some mock output and configuration directory.""" +@pytest.mark.parametrize( + "model_name, output_0, configuration", + [ + ("access", "output000", None), + ("access", "output000", "release-preindustrial+concentrations"), + ("access-om2", "output000", "release-1deg_jra55_ryf"), + ("access-om3", "restart000", "om3-dev-1deg_jra55do_ryf"), + ("access-om3", "restart000", "om3-wav-dev-1deg_jra55do_ryf"), + ], +) +@pytest.mark.parametrize("fail", [False, True]) +def test_test_bit_repro_historical(tmp_dir, model_name, output_0, configuration, fail): + """Test ACCESS-OM classes with historical repro test with some mock + output and configuration directory, optionally checking that things + fail when the outputs are modified to give different checksums""" test_name = "test_bit_repro_historical" - model_name = "access-om2" # Setup test Helper helper = CommonTestHelper(test_name, model_name, tmp_dir) - # Use config in resources dir - mock_config = helper.resources_path / "configurations" / "release-1deg_jra55_ryf" - shutil.copytree(mock_config, helper.control_path) + # Use config in resources dir if provided + if configuration: + helper.copy_config(configuration) + else: + helper.write_config() # Compare checksums against the existing checksums in resources folder checksum_path = helper.resources_path / "checksums" / "1-0-0.json" # Put some expected output in the archive directory (as we are skipping - # the actual payu run step) - helper.create_mock_output000() + # the actual payu run step) and modify the output if testing failure + helper.create_mock_output(output_0, modify=fail) # Build test command test_cmd = ( @@ -322,20 +265,103 @@ def test_test_bit_repro_historical_access_om2_pass(tmp_dir): # Run test in a subprocess call result = subprocess.run(shlex.split(test_cmd), capture_output=True, text=True) - if result.returncode: - # Print out test logs if there are errors - print(f"Test stdout: {result.stdout}\nTest stderr: {result.stderr}") - assert result.returncode == 0 + assert result.returncode == int(fail) - # Check runtime of 3 hours is set - with open(helper.test_control_path / "accessom2.nml") as f: - nml = f90nml.read(f) - years, months, seconds = nml["date_manager_nml"]["restart_period"] - assert years == 0 - assert months == 0 - assert seconds == 10800 + # Check runtime is set correctly + check_runtime(helper.test_control_path, helper.model_name) + + # Check general config.yaml settings for test + with open(helper.test_control_path / "config.yaml") as f: + test_config = yaml.safe_load(f) + assert test_config["experiment"] == test_name + assert not test_config["runlog"] + assert not test_config["metadata"]["enable"] + assert test_config["laboratory"] == str(helper.lab_path) # Check name of checksum file written out and contents - test_checksum = helper.output_path / "checksum" / "historical-3hr-checksum.json" + check_checksum( + helper.output_path, checksum_path, helper.model_name, match=(not fail) + ) + + +def test_test_access_om3_ocean_model(tmp_dir): + """Test that an error is thrown when the ocean model is not MOM. This should be moved into + dedicated tests for experiment setup when they exist. See + https://github.com/ACCESS-NRI/model-config-tests/issues/115""" + test_name = "test_bit_repro_historical" + + # Setup test Helper + helper = CommonTestHelper(test_name, "access-om3", tmp_dir) + + helper.copy_config("om3-dev-1deg_jra55do_ryf") + + # Set ocean model in nuopc.runconfig to something other than mom + mock_runconfig = Runconfig(helper.control_path / "nuopc.runconfig") + mock_runconfig.set("ALLCOMP_attributes", "OCN_model", "docn") + mock_runconfig.write() + + # Put some expected output in the archive directory (as we are skipping + # the actual payu run step) + helper.create_mock_output("restart000") + + # Build test command + test_cmd = f"{helper.base_test_command()} " f"--control-path {helper.control_path} " + + # Run test in a subprocess call + result = subprocess.run(shlex.split(test_cmd), capture_output=True, text=True) + + # Expect test to have failed + assert result.returncode == 1 + error_msg = ( + "ACCESS-OM3 reproducibility checks utilize checksums written in MOM6 restarts" + ) + assert error_msg in result.stdout + + +def check_runtime(control_path, model_name): + if model_name == "access": + with open(control_path / "config.yaml") as f: + test_config = yaml.safe_load(f) + # Check runtime of 24hr hours is set + assert test_config["calendar"]["runtime"] == { + "years": 0, + "months": 0, + "days": 0, + "seconds": 86400, + } + elif model_name == "access-om2": + with open(control_path / "accessom2.nml") as f: + nml = f90nml.read(f) + years, months, seconds = nml["date_manager_nml"]["restart_period"] + assert years == 0 + assert months == 0 + assert seconds == 10800 + elif model_name == "access-om3": + runconfig = Runconfig(control_path / "nuopc.runconfig") + assert runconfig.get("CLOCK_attributes", "restart_option") == "nseconds" + assert int(runconfig.get("CLOCK_attributes", "restart_n")) == 10800 + assert runconfig.get("CLOCK_attributes", "stop_option") == "nseconds" + assert int(runconfig.get("CLOCK_attributes", "stop_n")) == 10800 + + wav_in = control_path / "wav_in" + if wav_in.exists(): + with open(wav_in) as f: + nml = f90nml.read(f) + assert nml["output_date_nml"]["date"]["restart"]["stride"] == 10800 + else: + raise ValueError(f"Unrecognised model: {model_name}") + + +def check_checksum(output_path, checksum_path, model_name, match=True): + if model_name == "access": + test_checksum = output_path / "checksum" / "historical-24hr-checksum.json" + elif model_name in ["access-om2", "access-om3"]: + test_checksum = output_path / "checksum" / "historical-3hr-checksum.json" + else: + raise ValueError(f"Unrecognised model: {model_name}") assert test_checksum.exists() - assert test_checksum.read_text() == checksum_path.read_text() + + if match: + assert test_checksum.read_text() == checksum_path.read_text() + else: + assert test_checksum.read_text() != checksum_path.read_text()