Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check CICE4 restart file dates #539

Merged
merged 15 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions payu/models/access.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,13 @@ def setup(self):
f90nml.write(cpl_nml, nml_work_path + '~')
shutil.move(nml_work_path + '~', nml_work_path)

if model.model_type == 'cice':
if model.prior_restart_path and not self.expt.repeat_run:
# Set up and check the cice restart files.
model.overwrite_restart_ptr(run_start_date,
previous_runtime,
start_date_fpath)

# Now change the oasis runtime. This needs to be done after the others.
for model in self.expt.models:
if model.model_type == 'oasis':
Expand Down
124 changes: 101 additions & 23 deletions payu/models/cice.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import sys
import shutil
import datetime
import struct

# Extensions
import f90nml
Expand Down Expand Up @@ -162,29 +163,10 @@ def setup(self):
self.ice_in.patch(history_nml)

setup_nml = self.ice_in['setup_nml']
self._calc_runtime()

if self.prior_restart_path:
# Generate ice.restart_file
# TODO: better check of restart filename
iced_restart_file = None
iced_restart_files = [f for f in self.get_prior_restart_files()
if f.startswith('iced.')]

if len(iced_restart_files) > 0:
iced_restart_file = sorted(iced_restart_files)[-1]

if iced_restart_file is None:
raise FileNotFoundError(
f'No iced restart file found in {self.prior_restart_path}')

res_ptr_path = os.path.join(self.work_init_path,
'ice.restart_file')
if os.path.islink(res_ptr_path):
# If we've linked in a previous pointer it should be deleted
os.remove(res_ptr_path)
with open(res_ptr_path, 'w') as res_ptr:
res_dir = self.get_ptr_restart_dir()
print(os.path.join(res_dir, iced_restart_file), file=res_ptr)
self._make_restart_ptr()

# Update input namelist
setup_nml['runtype'] = 'continue'
Expand All @@ -198,8 +180,6 @@ def setup(self):
if setup_nml['restart']:
self.link_restart(setup_nml['pointer_file'])

self._calc_runtime()

# Write any changes to the work directory copy of the cice
# namelist
nml_path = os.path.join(self.work_path, self.ice_nml_fname)
Expand Down Expand Up @@ -351,3 +331,101 @@ def link_restart(self, fpath):
)

make_symlink(input_path, input_work_path)

def _make_restart_ptr(self):
"""
CICE4 restart pointers are created in the access driver, where
the correct run start dates are available.
"""
pass

def overwrite_restart_ptr(self,
run_start_date,
previous_runtime,
calendar_file):
"""
Generate restart pointer file 'ice.restart_file' pointing to
'iced.YYYYMMDD' with the correct start date.=
Additionally check that the `iced.YYYYMNDD` restart file's header
has the correct previous runtime.
Typically called from the access driver, which provides the
the correct date and runtime.

Parameters
----------
run_start_date: datetime.date
Start date of the new simulation
previous_runtime: int
Seconds between experiment initialisation date and start date
calendar_file: str
Calendar restart file used to calculate timing information
"""
# Expected iced restart file name
run_start_date_int = cal.date_to_int(run_start_date)
iced_restart_file = f"iced.{run_start_date_int:08d}"

if iced_restart_file not in self.get_prior_restart_files():
msg = (f"Restart file {iced_restart_file} matching "
anton-seaice marked this conversation as resolved.
Show resolved Hide resolved
f"{calendar_file} run start date {run_start_date_int} "
f"not found in {self.prior_restart_path}.")
raise FileNotFoundError(msg)

res_ptr_path = os.path.join(self.work_init_path,
'ice.restart_file')
if os.path.islink(res_ptr_path):
# If we've linked in a previous pointer it should be deleted
os.remove(res_ptr_path)

iced_path = os.path.join(self.prior_restart_path,
iced_restart_file)

# Check binary restart has correct time
self._cice4_check_date_consistency(iced_path,
previous_runtime,
calendar_file)

with open(res_ptr_path, 'w') as res_ptr:
res_dir = self.get_ptr_restart_dir()
res_ptr.write(os.path.join(res_dir, iced_restart_file))

def _cice4_check_date_consistency(self,
iced_path,
previous_runtime,
calendar_file):
"""
Check that the previous runtime in iced restart file header
matches the runtime calculated from the calendar restart file.

Parameters
----------
iced_path: str or Path
Path to iced restart file
previous_runtime: int
Seconds between experiment initialisation date and start date
calendar_file: str or Path
Calendar restart file used to calculate timing information
"""
_, _, cice_iced_runtime, _ = read_binary_iced_header(iced_path)
if previous_runtime != cice_iced_runtime:
msg = (f"Previous runtime from calendar file "
f"{calendar_file}: {previous_runtime} "
"does not match previous runtime in restart"
f"file {iced_path}: {cice_iced_runtime}.")
raise RuntimeError(msg)


CICE4_RESTART_HEADER_SIZE = 24
CICE4_RESTART_HEADER_FORMAT = '>iidd'


def read_binary_iced_header(iced_path):
"""
Read header information from a CICE4 binary restart file.
"""
with open(iced_path, 'rb') as iced_file:
header = iced_file.read(CICE4_RESTART_HEADER_SIZE)
bint, istep0, time, time_forc = struct.unpack(
CICE4_RESTART_HEADER_FORMAT,
header)

return (bint, istep0, time, time_forc)
25 changes: 25 additions & 0 deletions payu/models/cice5.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,28 @@ def _calc_runtime(self):
the timing information in the cice_in.nml namelist.
"""
pass

def _make_restart_ptr(self):
"""
Generate restart pointer which points to the latest iced.YYYYMMDD
restart file.
"""
iced_restart_file = None
iced_restart_files = [f for f in self.get_prior_restart_files()
if f.startswith('iced.')]

if len(iced_restart_files) > 0:
iced_restart_file = sorted(iced_restart_files)[-1]

if iced_restart_file is None:
raise FileNotFoundError(
f'No iced restart file found in {self.prior_restart_path}')

res_ptr_path = os.path.join(self.work_init_path,
'ice.restart_file')
if os.path.islink(res_ptr_path):
# If we've linked in a previous pointer it should be deleted
os.remove(res_ptr_path)
with open(res_ptr_path, 'w') as res_ptr:
res_dir = self.get_ptr_restart_dir()
res_ptr.write(os.path.join(res_dir, iced_restart_file))
22 changes: 18 additions & 4 deletions test/models/test_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import pytest
import cftime
import f90nml
from unittest.mock import patch

import payu

Expand All @@ -16,7 +18,6 @@
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


verbose = True
Expand Down Expand Up @@ -229,7 +230,14 @@ def test_access_cice_calendar_cycling_500(
# which we are trying to bypass.
shutil.copy(default_input_ice, cice_model.work_path)

access_model.setup()
# Skip writing restart pointer as it requires iced file
# with valid header
with patch(
anton-seaice marked this conversation as resolved.
Show resolved Hide resolved
'payu.models.cice.Cice.overwrite_restart_ptr',
return_value=None
):
access_model.setup()

access_model.archive()

end_date_fpath = os.path.join(
Expand Down Expand Up @@ -269,7 +277,7 @@ def test_access_cice_1year_runtimes(
expected_runtime
):
"""
The large setup/archive cycling test won't pick up situations
The large setup/archive cycling test won't pick up situations
where the calculations during 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
Expand Down Expand Up @@ -331,7 +339,13 @@ def test_access_cice_1year_runtimes(
# which we are trying to bypass.
shutil.copy(ctrl_input_ice_path, cice_model.work_path)

access_model.setup()
# Skip writing restart pointer as it requires iced file
# with valid header
with patch(
'payu.models.cice.Cice.overwrite_restart_ptr',
return_value=None
):
access_model.setup()

# Check that the correct runtime is written to the work directory's
# input ice namelist.
Expand Down
Loading