Skip to content

Commit

Permalink
Add tests to cice calendar cycling, and calendar functions
Browse files Browse the repository at this point in the history
  • Loading branch information
blimlim committed Aug 19, 2024
1 parent 6d76956 commit 8f18f24
Show file tree
Hide file tree
Showing 3 changed files with 299 additions and 5 deletions.
6 changes: 2 additions & 4 deletions payu/models/access.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,8 @@ def setup(self):

# Now write out new run start date and total runtime into the
# work directory namelist.
cpl_group["init_date"] = cal.date_to_int(
init_date)
cpl_group['inidate'] = cal.date_to_int(
run_start_date)
cpl_group["init_date"] = cal.date_to_int(init_date)
cpl_group['inidate'] = cal.date_to_int(run_start_date)
cpl_group[model.runtime0_key] = previous_runtime
cpl_group['runtime'] = int(run_runtime)

Expand Down
242 changes: 242 additions & 0 deletions test/models/test_access.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
import copy
import os
import shutil

import pytest
import cftime

import payu

from test.common import cd
from test.common import tmpdir, ctrldir, labdir, workdir, archive_dir
from test.common import config as config_orig
from test.common import write_config
from test.common import make_all_files
from test.common import list_expt_archive_dirs
from test.common import make_expt_archive_dir, remove_expt_archive_dirs
from test.common import config_path
import f90nml


verbose = True


INPUT_ICE_FNAME = "input_ice.nml"
RESTART_DATE_FNAME = "restart_date.nml"

def setup_module(module):
"""
Put any test-wide setup code in here, e.g. creating test files
"""
if verbose:
print("setup_module module:%s" % module.__name__)

# Should be taken care of by teardown, in case remnants lying around
try:
shutil.rmtree(tmpdir)
except FileNotFoundError:
pass

try:
tmpdir.mkdir()
labdir.mkdir()
ctrldir.mkdir()
workdir.mkdir()
archive_dir.mkdir()
make_all_files()
except Exception as e:
print(e)


def teardown_module(module):
"""
Put any test-wide teardown code in here, e.g. removing test outputs
"""
if verbose:
print("teardown_module module:%s" % module.__name__)

try:
shutil.rmtree(tmpdir)
print('removing tmp')
except Exception as e:
print(e)


@pytest.fixture
def access_1year_config():
# Write an access model config file with 1 year runtime

# Create a config.yaml file with the cice submodel and 1 year run length

# Global config
config = copy.deepcopy(config_orig)
config['model'] = 'access'
config['submodels'] = [{"name": "ice",
"model": "cice"}]

config["calendar"] = {'start': {'year': 101, 'month': 1, 'days': 1},
'runtime': {'years': 1, 'months': 0, 'days': 0}}
write_config(config)

# Run test
yield

# Teardown
os.remove(config_path)

@pytest.fixture
def ice_control_directory():
# Make a cice control subdirectory
ice_ctrl_dir = ctrldir / "ice"
ice_ctrl_dir.mkdir()

# Run test
yield ice_ctrl_dir

# Teardown
shutil.rmtree(ice_ctrl_dir)


@pytest.fixture
def default_input_ice(ice_control_directory):
# Create base input_ice.nml namelist
ctrl_input_ice_path = ice_control_directory / INPUT_ICE_FNAME

# Default timing values from the input_ice.nml namelist that will be
# overwritten by the calendar calculations.
default_input_nml = {
"coupling":
{
"caltype": 1,
"jobnum": 2,
"inidate": "01010101",
"init_date": "00010101",
"runtime0": 3155673600,
"runtime": 86400
}
}
f90nml.write(default_input_nml, ctrl_input_ice_path)

# Run test
yield ctrl_input_ice_path

# Teardown handled by ice_control_directory fixture


@pytest.fixture
def fake_cice_in(ice_control_directory):
# Create a fake cice_in.nml file. This is irrelevant for the tests,
# however is required to exist for the experiment initialisation.
fake_cice_in_nml = {
"setup_nml": {
"restart_dir": "",
"history_dir": ""
},
"grid_nml": {
"grid_file": "",
"kmt_file": ""
}
}
fake_cice_in_path = ice_control_directory / "cice_in.nml"
f90nml.write(fake_cice_in_nml, fake_cice_in_path)

yield fake_cice_in_path

# Teardown handled by ice_control_directory fixture


@pytest.fixture
def restart_dir():
# Create restart directory for ice timing tests
restart_path = archive_dir / "restart"
restart_path.mkdir()

# Run test
yield restart_path

# Teardown
shutil.rmtree(restart_path)

@pytest.fixture
def initial_start_date_file(restart_dir):
# Initital start date for testing calendar cycling
initial_res_date = {
"coupling": {
"init_date": 10101,
"inidate": 1010101
}
}
res_date_path = restart_dir / RESTART_DATE_FNAME
f90nml.write(initial_res_date, res_date_path)

# Run test
yield res_date_path

# Teardown handled by restart_dir fixture


def test_esm_calendar_cycling_1000(
access_1year_config,
ice_control_directory,
default_input_ice,
fake_cice_in,
restart_dir,
initial_start_date_file):
"""
Test that cice run date calculations remain correct when cycling
over a large number of runs.
"""

n_years = 1000
expected_end_date = 11010101
expected_end_init_date = 10101
# Setup the experiment
with cd(ctrldir):
lab = payu.laboratory.Laboratory(lab_path=str(labdir))
expt = payu.experiment.Experiment(lab, reproduce=False)
# Get the models
for model in expt.models:
if model.model_type == "cice":
cice_model = model
# There are two access models within the experiment. The top level
# model, expt.model, and the one under expt.models. The top level
# model's setup and archive steps are the ones that actually run.
access_model = expt.model

# Overwrite cice model paths created during experiment initialisation.
# It's simpler to just set them here than rely on the ones collected
# from the fake namelist files.
cice_model.work_path = workdir

# Path to read and write restart dates from. In a real experiment
# The read and write restart dirs would be different and increment
# each run. However we will just use one for the tests to avoid
# creating hundreds of directories.
cice_model.prior_restart_path = restart_dir
cice_model.restart_path = restart_dir

for i in range(n_years):
if i % 100 == 0:
print(f"Access setup/archive cycle: {i}")

# Manually copy the input_ice.nml file from the control directory
# to the work directory. This would normally happen in cice.setup()
# which we are trying to bypass.
shutil.copy(default_input_ice, cice_model.work_path)

access_model.setup()
access_model.archive()

end_date_fpath = os.path.join(
cice_model.restart_path,
cice_model.start_date_nml_name
)

end_date_nml = f90nml.read(end_date_fpath)[
cice_model.cpl_group]

final_end_date = end_date_nml["inidate"]
final_init_date = end_date_nml["init_date"]

assert final_end_date == expected_end_date
assert final_init_date == expected_end_init_date
56 changes: 55 additions & 1 deletion test/test_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import pytest

from payu.calendar import parse_date_offset, DatetimeOffset
from payu.calendar import seconds_between_dates
from payu.calendar import seconds_between_dates, int_to_date
from payu.calendar import runtime_from_date
from payu.calendar import GREGORIAN, NOLEAP

SEC_PER_DAY = 24*60*60
Expand Down Expand Up @@ -205,3 +206,56 @@ def test_parse_date_offset_no_offset_magnitude():
)
def test_seconds_between_dates(start_date, end_date, caltype_int, expected):
assert seconds_between_dates(start_date, end_date, caltype_int) == expected


@pytest.mark.parametrize(
"date_int, expected",
[
(10101, datetime.date(1, 1, 1)),
(100321, datetime.date(10, 3, 21)),
(38211130, datetime.date(3821, 11, 30))
]
)
def test_int_to_date(date_int, expected):
"""
Check that integers typically read in from namelists
are correctly converted to datetime.date objects.
"""
converted_date = int_to_date(date_int)
assert converted_date == expected


@pytest.mark.parametrize(
"start_date, years, months, days, seconds, caltype, expected",
[
(datetime.date(101, 1, 1), 1, 0, 0, 0, GREGORIAN, 365*SEC_PER_DAY),
(datetime.date(40, 2, 8), 0, 1, 0, 0, GREGORIAN, 29*SEC_PER_DAY),
(datetime.date(40, 2, 8), 0, 1, 0, 0, NOLEAP, 28*SEC_PER_DAY),
(datetime.date(153, 8, 21), 2, 7, 510, 5321,
GREGORIAN, 1453*SEC_PER_DAY + 5321),
# Extreme cases, unlikely to ever happen
(datetime.date(1, 1, 1), 9998, 0, 0, 0,
NOLEAP, 9998 * 365 * SEC_PER_DAY),
(datetime.date(1, 1, 1), 9998, 0, 0, 0,
GREGORIAN, (9998 * 365 + 2424) * SEC_PER_DAY)
]
)
def test_runtime_from_date(
start_date,
years,
months,
days,
seconds,
caltype,
expected):
"""
Test that the number of seconds calculated for run lengths is correct.
"""
runtime = runtime_from_date(start_date,
years,
months,
days,
seconds,
caltype)

assert runtime == expected

0 comments on commit 8f18f24

Please sign in to comment.