Skip to content

Commit

Permalink
Update tests, add additional access driver test, modify variable name…
Browse files Browse the repository at this point in the history
…s for clarity
  • Loading branch information
blimlim committed Aug 20, 2024
1 parent 7bf99e6 commit c974561
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 27 deletions.
2 changes: 1 addition & 1 deletion payu/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def seconds_between_dates(start_date, end_date, caltype_int):
delta = (date_to_cftime(end_date, calendar_str)
- date_to_cftime(start_date, calendar_str))

return delta.total_seconds()
return int(delta.total_seconds())


def date_to_cftime(date, calendar):
Expand Down
49 changes: 33 additions & 16 deletions payu/models/access.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,30 @@ def __init__(self, expt, name, config):
# Structure of model coupling namelist
model.cpl_fname = 'input_ice.nml'
model.cpl_group = 'coupling'
model.runtime0_key = 'runtime0'
model.start_date_nml_name = "restart_date.nml"
# Experiment initialisation date
model.init_date_key = "init_date"
# Start date for new run
model.inidate_key = "inidate"
# Total time in seconds since initialisation date
model.runtime0_key = 'runtime0'
# Simulation length in seconds for new run
model.runtime_key = "runtime"

if model.model_type == 'matm':
# Structure of model coupling namelist
model.cpl_fname = 'input_atm.nml'
model.cpl_group = 'coupling'
model.runtime0_key = 'truntime0'
model.start_date_nml_name = "restart_date.nml"
# Experiment initialisation date
model.init_date_key = "init_date"
# Start date for new run
model.inidate_key = "inidate"
# Total time in seconds since initialisation date
model.runtime0_key = 'truntime0'
# Simulation length in seconds for new run
model.runtime_key = "runtime"


def setup(self):
if not self.top_level_model:
Expand Down Expand Up @@ -131,11 +146,13 @@ def setup(self):

# Experiment initialisation date
init_date = cal.int_to_date(
start_date_nml["init_date"]
start_date_nml[model.init_date_key]
)

# Start date of new run
run_start_date = cal.int_to_date(start_date_nml["inidate"])
run_start_date = cal.int_to_date(
start_date_nml[model.inidate_key]
)

# run_start_date must be after initialisation date
if run_start_date < init_date:
Expand All @@ -144,8 +161,8 @@ def setup(self):
f"{model.start_date_nml_name} must not be "
"before initialisation date `init_date. "
"Values provided: \n"
f"inidate = {start_date_nml['inidate']}\n"
f"init_date = {start_date_nml['init_date']}"
f"inidate={start_date_nml[model.inidate_key]}\n"
f"init_date={start_date_nml[model.init_date_key]}"
)
raise ValueError(msg)

Expand All @@ -160,7 +177,7 @@ def setup(self):

else:
init_date = cal.int_to_date(
cpl_group['init_date']
cpl_group[model.init_date_key]
)
previous_runtime = 0
run_start_date = init_date
Expand All @@ -176,14 +193,14 @@ def setup(self):
self.expt.runtime.get('seconds', 0),
caltype)
else:
run_runtime = cpl_group['runtime']
run_runtime = cpl_group[model.runtime_key]

# 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[model.init_date_key] = cal.date_to_int(init_date)
cpl_group[model.inidate_key] = cal.date_to_int(run_start_date)
cpl_group[model.runtime0_key] = previous_runtime
cpl_group['runtime'] = int(run_runtime)
cpl_group[model.runtime_key] = int(run_runtime)

if model.model_type == 'cice':
if self.expt.counter and not self.expt.repeat_run:
Expand Down Expand Up @@ -257,9 +274,9 @@ def archive(self):
work_cpl_grp = work_cpl_nml[model.cpl_group]

# Timing information on the completed run.
run_init_date_int = work_cpl_grp["init_date"]
run_start_date_int = work_cpl_grp["inidate"]
run_runtime = work_cpl_grp["runtime"]
exp_init_date_int = work_cpl_grp[model.init_date_key]
run_start_date_int = work_cpl_grp[model.inidate_key]
run_runtime = work_cpl_grp[model.runtime_key]
run_caltype = work_cpl_grp["caltype"]

# Calculate end date of completed run
Expand All @@ -271,8 +288,8 @@ def archive(self):

end_date_dict = {
model.cpl_group: {
"init_date": run_init_date_int,
"inidate": cal.date_to_int(run_end_date)
model.init_date_key: exp_init_date_int,
model.inidate_key: cal.date_to_int(run_end_date)
}
}

Expand Down
101 changes: 98 additions & 3 deletions test/models/test_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
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
from payu.calendar import GREGORIAN, NOLEAP
import f90nml


Expand All @@ -23,6 +24,7 @@

INPUT_ICE_FNAME = "input_ice.nml"
RESTART_DATE_FNAME = "restart_date.nml"
SEC_PER_DAY = 24*60*60


def setup_module(module):
Expand Down Expand Up @@ -178,7 +180,7 @@ def initial_start_date_file(restart_dir):
# Teardown handled by restart_dir fixture


def test_esm_calendar_cycling_1000(
def test_access_cice_calendar_cycling_500(
access_1year_config,
ice_control_directory,
default_input_ice,
Expand All @@ -190,8 +192,8 @@ def test_esm_calendar_cycling_1000(
over a large number of runs.
"""

n_years = 1000
expected_end_date = 11010101
n_years = 500
expected_end_date = 6010101
expected_end_init_date = 10101
# Setup the experiment
with cd(ctrldir):
Expand Down Expand Up @@ -243,3 +245,96 @@ def test_esm_calendar_cycling_1000(

assert final_end_date == expected_end_date
assert final_init_date == expected_end_init_date


@pytest.mark.parametrize(
"start_date_int, caltype, expected_runtime",
[(1010101, GREGORIAN, 365*SEC_PER_DAY),
(1010101, NOLEAP, 365*SEC_PER_DAY),
(1040101, GREGORIAN, 366*SEC_PER_DAY),
(1040101, NOLEAP, 365*SEC_PER_DAY),
(3000101, GREGORIAN, 365*SEC_PER_DAY),
(3000101, NOLEAP, 365*SEC_PER_DAY),
(4000101, GREGORIAN, 366*SEC_PER_DAY),
(4000101, NOLEAP, 365*SEC_PER_DAY)]
)
def test_access_cice_1year_runtimes(
access_1year_config,
ice_control_directory,
fake_cice_in,
restart_dir,
initial_start_date_file,
start_date_int,
caltype,
expected_runtime
):
"""
The large setup/archive cycling test won't pick up situations
where the calculations dyring setup and archive are simultaneously
wrong, e.g. if they both used the wrong calendar.
Hence test seperately that the correct runtimes for cice are
written by the access.setup() step for a range of standard
and generally tricky years.
"""
# Write an input_ice.nml namelist to the control directory
# with the specified calendar type.
ctrl_input_ice_path = ice_control_directory / INPUT_ICE_FNAME
input_ice_nml = {
"coupling":
{
"caltype": caltype
}
}
f90nml.write(input_ice_nml, ctrl_input_ice_path)

# Reset the start date in the initial_start_date_file
initial_start_nml = f90nml.read(initial_start_date_file)
# Make sure our start date doesn't occur before the init_date
assert start_date_int >= initial_start_nml["coupling"]["init_date"]

initial_start_nml["coupling"]["inidate"] = start_date_int
f90nml.write(initial_start_nml, initial_start_date_file, force=True)

# Setup the experiment
with cd(ctrldir):
lab = payu.laboratory.Laboratory(lab_path=str(labdir))
expt = payu.experiment.Experiment(lab, reproduce=False)

# For the purposes of the test, use one year runtime
expt.runtime["years"] = 1
expt.runtime["months"] = 0
expt.runtime["days"] = 0
expt.runtime["seconds"] = 0

# 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.
cice_model.prior_restart_path = restart_dir
cice_model.restart_path = restart_dir

# 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(ctrl_input_ice_path, cice_model.work_path)

access_model.setup()

# Check that the correct runtime is written to the work directory's
# input ice namelist.
work_input_ice = f90nml.read(cice_model.work_path/INPUT_ICE_FNAME)
written_runtime = work_input_ice["coupling"]["runtime"]
assert written_runtime == expected_runtime
44 changes: 37 additions & 7 deletions test/test_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,9 @@ def test_parse_date_offset_no_offset_magnitude():
),
(
datetime.datetime(year=400, month=1, day=1),
datetime.datetime(year=401, month=1, day=1),
datetime.datetime(year=400, month=12, day=31),
GREGORIAN,
366 * SEC_PER_DAY
365 * SEC_PER_DAY
),
(
datetime.datetime(year=12, month=7, day=22),
Expand Down Expand Up @@ -214,7 +214,7 @@ def test_seconds_between_dates(start_date, end_date, caltype_int, expected):
[
(10101, datetime.date(1, 1, 1)),
(100321, datetime.date(10, 3, 21)),
(38211130, datetime.date(3821, 11, 30))
(99991231, datetime.date(9999, 12, 31))
]
)
def test_int_to_date(date_int, expected):
Expand All @@ -226,19 +226,49 @@ def test_int_to_date(date_int, expected):
assert converted_date == expected


@pytest.mark.parametrize(
"bad_date_int",
[0, 100000000, 101, -5, 11119153]
)
def test_int_to_date_failures(bad_date_int):
"""
Check that int_to_date does not allow non existent
or out of range dates.
"""
with pytest.raises(ValueError):
int_to_date(bad_date_int)


@pytest.mark.parametrize(
"start_date, years, months, days, seconds, caltype, expected",
[
# Normal year
(datetime.date(101, 1, 1), 1, 0, 0, 0, GREGORIAN, 365*SEC_PER_DAY),
(datetime.date(101, 1, 1), 1, 0, 0, 0, NOLEAP, 365*SEC_PER_DAY),
# Leap year
(datetime.date(4, 1, 1), 1, 0, 0, 0, GREGORIAN, 366*SEC_PER_DAY),
(datetime.date(4, 1, 1), 1, 0, 0, 0, NOLEAP, 365*SEC_PER_DAY),
# Non-leap year due to 100 year rule
(datetime.date(100, 1, 1), 1, 0, 0, 0, GREGORIAN, 365*SEC_PER_DAY),
(datetime.date(100, 1, 1), 1, 0, 0, 0, NOLEAP, 365*SEC_PER_DAY),
# Leap year due to 400 year rule
(datetime.date(400, 1, 1), 1, 0, 0, 0, GREGORIAN, 366*SEC_PER_DAY),
(datetime.date(500, 1, 1), 1, 0, 0, 0, NOLEAP, 365*SEC_PER_DAY),
# Febraury in leap years
(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),
# Misc
(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)
(datetime.date(1, 1, 1), 9998, 11, 30, 0,
NOLEAP, (9998 * 365 + 364) * SEC_PER_DAY),
(datetime.date(1, 1, 1), 9998, 11, 30, 0,
GREGORIAN, (9998 * 365 + 2424 + 364) * SEC_PER_DAY),
(datetime.date(1, 1, 1), 0, 0, 0, 1,
GREGORIAN, 1),
(datetime.date(1, 1, 1), 0, 0, 0, 1,
NOLEAP, 1),
]
)
def test_runtime_from_date(
Expand Down

0 comments on commit c974561

Please sign in to comment.