diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 47600cb2..9a1ce0b2 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -1,3 +1,10 @@ +# TODO: Revert tests back to NOAA-GFDL version (not jjuyeonkim) after +# NDSL/issues#64's PRs close and are all merged. These PRs include: +# https://github.com/NOAA-GFDL/NDSL/pull/218 +# https://github.com/NOAA-GFDL/PyFV3/pull/83 +# https://github.com/NOAA-GFDL/PySHiELD/pull/58 +# https://github.com/NOAA-GFDL/pace/pull/145 +# name: "Lint" # Run these these whenever ... @@ -7,30 +14,60 @@ on: push: branches: - main # ... when merging into the main branch + - 20250908_namelist_2 jobs: lint: runs-on: ubuntu-latest steps: - - name: Checkout repository + - name: Checkout Pace + uses: actions/checkout@v5 + with: + submodules: 'false' + repository: jjuyeonkim/pace + path: pace + ref: 20250908_namelist_2 + + - name: "Checkout NDSL" + uses: actions/checkout@v5 + with: + submodules: 'recursive' + repository: jjuyeonkim/ndsl + path: pace/NDSL + ref: 20250908_namelist_2 + + - name: "Checkout pyFV3" uses: actions/checkout@v5 with: submodules: 'recursive' + repository: jjuyeonkim/pyfv3 + path: pace/pyFV3 + ref: 20250908_namelist_2 + + - name: "Checkout pySHiELD" + uses: actions/checkout@v5 + with: + submodules: 'recursive' + repository: jjuyeonkim/pyshield + path: pace/pySHiELD + ref: 20250908_namelist_2 + + - name: "Git log checks" + run: | + git -C ${GITHUB_WORKSPACE}/pace log -1 + git -C ${GITHUB_WORKSPACE}/pace/NDSL log -1 + git -C ${GITHUB_WORKSPACE}/pace/pyFV3 log -1 + git -C ${GITHUB_WORKSPACE}/pace/pySHiELD log -1 - name: Step Python 3.11 uses: actions/setup-python@v5 with: python-version: '3.11' - # Only restore (don't save) caches on PRs. New caches created from PRs won't be - # accessible from other PRs, see workflows/create-cache.yaml. - - uses: actions/cache/restore@v4 - with: - path: ~/.cache/pre-commit - key: pre-commit_${{ env.pythonLocation }}_${{ hashFiles('.pre-commit-config.yaml') }} - - name: Install pre-commit run: pip install pre-commit - name: Run lint via pre-commit - run: pre-commit run --all-files + run: | + cd ${GITHUB_WORKSPACE}/pace + pre-commit run --all-files diff --git a/.github/workflows/main_unit_tests.yaml b/.github/workflows/main_unit_tests.yaml index c0b01a96..20937a99 100644 --- a/.github/workflows/main_unit_tests.yaml +++ b/.github/workflows/main_unit_tests.yaml @@ -1,3 +1,10 @@ +# TODO: Revert tests back to NOAA-GFDL version (not jjuyeonkim) after +# NDSL/issues#64's PRs close and are all merged. These PRs include: +# https://github.com/NOAA-GFDL/NDSL/pull/218 +# https://github.com/NOAA-GFDL/PyFV3/pull/83 +# https://github.com/NOAA-GFDL/PySHiELD/pull/58 +# https://github.com/NOAA-GFDL/pace/pull/145 +# name: "pace main unit tests" # Run these these whenever ... @@ -16,7 +23,8 @@ on: merge_group: # ... the PR is added to the merge queue push: branches: - - main # ... when merging into the main branch + - main + - 20250908_namelist_2 # cancel running jobs if theres a newer push concurrency: @@ -26,19 +34,7 @@ concurrency: jobs: pace_main_unit_tests: runs-on: ubuntu-latest - defaults: - run: - shell: bash -el {0} steps: - - name: "External trigger: Checkout pace/develop" - if: ${{ inputs.component_trigger }} - uses: actions/checkout@v5 - with: - submodules: 'recursive' - repository: NOAA-GFDL/pace - path: pace - ref: develop - - name: Setup Miniconda uses: conda-incubator/setup-miniconda@v3 with: @@ -48,42 +44,77 @@ jobs: activate-environment: pace_test_env - name: Install mpi (MPICH flavor) + shell: bash -l {0} run: pip install mpich - - name: "External trigger: Remove existing component in pace/develop" - if: ${{ inputs.component_trigger }} - run: rm -rf ${GITHUB_WORKSPACE}/pace/${{inputs.component_name}} + - name: Checkout Pace + uses: actions/checkout@v5 + with: + submodules: 'false' + repository: jjuyeonkim/pace + path: pace + ref: 20250908_namelist_2 + + - name: "Checkout NDSL" + uses: actions/checkout@v5 + with: + submodules: 'recursive' + repository: jjuyeonkim/ndsl + path: pace/NDSL + ref: 20250908_namelist_2 - - name: Checkout out hash that triggered CI + - name: "Checkout pyFV3" uses: actions/checkout@v5 with: submodules: 'recursive' - path: pace/${{inputs.component_name}} + repository: jjuyeonkim/pyfv3 + path: pace/pyFV3 + ref: 20250908_namelist_2 + + - name: "Checkout pySHiELD" + uses: actions/checkout@v5 + with: + submodules: 'recursive' + repository: jjuyeonkim/pyshield + path: pace/pySHiELD + ref: 20250908_namelist_2 + + - name: "Git log checks" + run: | + git -C ${GITHUB_WORKSPACE}/pace log -1 + git -C ${GITHUB_WORKSPACE}/pace/NDSL log -1 + git -C ${GITHUB_WORKSPACE}/pace/pyFV3 log -1 + git -C ${GITHUB_WORKSPACE}/pace/pySHiELD log -1 - name: Install packages + shell: bash -l {0} run: | cd ${GITHUB_WORKSPACE}/pace pip install .[test] pip install numpy==1.26.4 - name: Print versions + shell: bash -l {0} run: | python --version pip --version pip list - name: Prepare input files + shell: bash -l {0} run: | cd ${GITHUB_WORKSPACE}/pace mkdir tests/main/input python examples/generate_eta_files.py tests/main/input - name: Run tests + shell: bash -l {0} run: | cd ${GITHUB_WORKSPACE}/pace python -m pytest -x tests/main - name: Run baroclinic test case + shell: bash -l {0} run: | cd ${GITHUB_WORKSPACE}/pace mpiexec -np 6 python -m pace.run examples/configs/baroclinic_c48_no_out.yaml diff --git a/examples/configs/baroclinic_stable_c48_input.nml b/examples/configs/baroclinic_stable_c48_input.nml new file mode 100644 index 00000000..770bfa7f --- /dev/null +++ b/examples/configs/baroclinic_stable_c48_input.nml @@ -0,0 +1,179 @@ +! Example analytic test case for baroclinic_stable + +&diag_manager_nml + !flush_nc_files = .false. + prepend_date = .F. +! this diag table creates a lot of files +! next three lines are necessary + max_num_axis_sets = 100 + max_files = 100 + max_axes = 240 + !do_diag_field_log = .T. +/ + + &fms_affinity_nml + affinity = .false. +/ + + &fms_io_nml + checksum_required = .false. +/ + + &fms_nml + clock_grain = 'ROUTINE', + domains_stack_size = 16000000, + print_memory_usage = .false. +/ + + &fv_core_nml + layout = 2,2 + io_layout = 1,1 + npx = 49 + npy = 49 + ntiles = 6 + npz = 32 + npz_type = 'gfs' + grid_type = 0 + make_nh = .T. + fv_debug = .F. + range_warn = .F. + reset_eta = .F. + sg_cutoff = 150.e2 !replaces old "n_sponge" + fv_sg_adj = 3600 + nudge_qv = .T. + RF_fast = .T. + tau_h2o = 0. + tau = 10. + rf_cutoff = 3000. + d2_bg_k1 = 0.20 + d2_bg_k2 = 0.10 ! z2: increased + kord_tm = -9 + kord_mt = 9 + kord_wz = 9 + kord_tr = 9 + hydrostatic = .F. + phys_hydrostatic = .F. + use_hydro_pressure = .F. + beta = 0. + a_imp = 1. + p_fac = 0.05 + k_split = 1 + n_split = 1 + nwat = 6 + na_init = 0 + d_ext = 0.0 + dnats = 1 + d2_bg = 0. + nord = 3 + dddmp = 0.5 + d4_bg = 0.15 + do_vort_damp = .T. + external_ic = .F. !COLD START + is_ideal_case = .T. + mountain = .F. + hord_mt = 6 + hord_vt = 6 + hord_tm = 6 + hord_dp = 6 + hord_tr = 8 ! z2: changed + adjust_dry_mass = .F. + consv_te = 0. + consv_am = .T. + fill = .T. + dwind_2d = .F. + print_freq = 6 + warm_start = .F. + z_tracer = .T. + fill_dp = .T. + adiabatic = .F. +/ + + &integ_phys_nml + do_inline_mp = .T. + do_sat_adj = .T. +/ + +&fv_diag_plevs_nml + nplev=7 + levs = 50,200,300,500,700,850,1000 + levs_ave = 10,100,300,500,700,850,1000 +/ + +&test_case_nml ! cold start + test_case = 12 +/ + + &main_nml + days = 0 + hours = 0 + minutes = 0 + seconds = 0 + dt_atmos = 225 + current_time = 0,0,0,0 + atmos_nthreads = 2 + use_hyper_thread = .true. +/ + + &external_ic_nml + filtered_terrain = .T. + levp = 64 + gfs_dwinds = .T. + checker_tr = .F. + nt_checker = 0 +/ + + &sim_phys_nml + do_GFDL_sim_phys = .F. +/ + + + &gfdl_mp_nml + do_sedi_heat = .T. + do_sedi_w = .T. + rad_snow = .true. + rad_graupel = .true. + rad_rain = .true. + const_vi = .F. + const_vs = .F. + const_vg = .F. + const_vr = .F. + vi_max = 1. + vs_max = 2. + vg_max = 16. + vr_max = 16. + qi_lim = 1. + prog_ccn = .false. + do_qa = .true. + tau_l2v = 300. + tau_v2l = 90. ! z7: enabled + do_cond_timescale = .true. ! z7: enabled + rthresh = 10.e-6 ! 10.e-6 ! This is a key parameter for cloud water + dw_land = 0.15 + dw_ocean = 0.10 + ql_mlt = 2.0e-3 + qs_mlt = 1.e-6 + qi0_crt = 8.E-5 + qs0_crt = 3.0e-3 + tau_i2s = 1000. + c_psaci = 0.05 + c_pgacs = 0.01 + rh_inc = 0.0 + rh_inr = 0.0 + ccn_l = 300. + ccn_o = 100. + c_paut = 0.55 + z_slope_liq = .T. + z_slope_ice = .T. + fix_negative = .true. + irain_f = 0 + icloud_f = 0 +/ + +!# From LJZ mar 2019 + &cld_eff_rad_nml + reiflag = 4 + rewmin = 5.0 + rewmax = 10.0 + reimin = 10.0 + reimax = 150.0 +/ diff --git a/tests/main/fv3core/test_config.py b/tests/main/fv3core/test_config.py index 59a106a8..c8a72dab 100644 --- a/tests/main/fv3core/test_config.py +++ b/tests/main/fv3core/test_config.py @@ -1,10 +1,14 @@ import collections import dataclasses +import os import typing +import f90nml import pytest +import yaml import pyfv3._config +from ndsl import Namelist CONFIG_CLASSES = [ @@ -85,3 +89,51 @@ def test_types_match(): Checks both dataclass attributes and property methods. """ assert_types_match(CONFIG_CLASSES) + + +def test_dycore_config_from_yaml(): + """Sanity Spot Checks from yaml example config""" + yaml_path = os.path.join("examples", "configs", "baroclinic_c12.yaml") + with open(os.path.abspath(yaml_path), "r") as f: + yaml_config = yaml.safe_load(f) + dcconfig1 = pyfv3.DynamicalCoreConfig.from_yaml(yaml_path) + + # Check parameters that exist in yaml root level + assert yaml_config["dt_atmos"] == getattr(dcconfig1, "dt_atmos") + assert yaml_config["nz"] == getattr(dcconfig1, "npz") + + # Check parameters that exist in yaml_config["dycore_config"] + dycore_specific_params = ["hydrostatic", "hord_tr", "hord_tm"] + for param in dycore_specific_params: + assert yaml_config["dycore_config"][param] == getattr(dcconfig1, param) + + # Check default parameters, not specified in the yaml + dcconfig_default = pyfv3.DynamicalCoreConfig() + default_params = ["adiabatic", "rad_snow"] + for param in default_params: + assert param not in yaml_config.keys() + assert param not in yaml_config["dycore_config"].keys() + assert getattr(dcconfig1, param) == getattr(dcconfig_default, param) + + +def test_dycore_config_from_f90nml(): + """Sanity Checks from example nml""" + f90_namelist_path = os.path.join( + "examples", "configs", "baroclinic_stable_c48_input.nml" + ) + + f90_namelist = f90nml.read(f90_namelist_path) + dcconfig1 = pyfv3.DynamicalCoreConfig.from_f90nml(f90_namelist) + + namelist = Namelist(f90_namelist) + dcconfig2 = pyfv3.DynamicalCoreConfig.from_namelist(namelist) + + assert dcconfig1.__dataclass_fields__ == dcconfig2.__dataclass_fields__ + assert dcconfig1.dt_atmos == dcconfig2.dt_atmos + assert dataclasses.asdict(dcconfig1) == dataclasses.asdict(dcconfig2) + + for attr in dcconfig1.__dataclass_fields__: + if attr in f90_namelist["fv_core_nml"]: + assert getattr(dcconfig1, attr) == f90_namelist["fv_core_nml"][attr] + elif attr in f90_namelist["main_nml"]: + assert getattr(dcconfig1, attr) == f90_namelist["main_nml"][attr] diff --git a/tests/main/fv3core/test_init_from_geos.py b/tests/main/fv3core/test_init_from_geos.py index 2def9028..27573acf 100644 --- a/tests/main/fv3core/test_init_from_geos.py +++ b/tests/main/fv3core/test_init_from_geos.py @@ -18,7 +18,7 @@ def test_geos_wrapper(): "device_sync": False, } }, - "initialization": {"type": "baroclinic"}, + "initialization": {"type": "baroclinic_instability"}, "nx_tile": 12, "nz": 91, "dt_atmos": 225,