|  | 
| 17 | 17 | import sys | 
| 18 | 18 | import shutil | 
| 19 | 19 | import datetime | 
|  | 20 | +import struct | 
| 20 | 21 | import re | 
| 21 | 22 | import tarfile | 
| 22 | 23 | 
 | 
| @@ -173,27 +174,7 @@ def setup(self): | 
| 173 | 174 |         setup_nml = self.ice_in['setup_nml'] | 
| 174 | 175 | 
 | 
| 175 | 176 |         if self.prior_restart_path: | 
| 176 |  | -            # Generate ice.restart_file | 
| 177 |  | -            # TODO: better check of restart filename | 
| 178 |  | -            iced_restart_file = None | 
| 179 |  | -            iced_restart_files = [f for f in self.get_prior_restart_files() | 
| 180 |  | -                                  if f.startswith('iced.')] | 
| 181 |  | - | 
| 182 |  | -            if len(iced_restart_files) > 0: | 
| 183 |  | -                iced_restart_file = sorted(iced_restart_files)[-1] | 
| 184 |  | - | 
| 185 |  | -            if iced_restart_file is None: | 
| 186 |  | -                raise FileNotFoundError( | 
| 187 |  | -                    f'No iced restart file found in {self.prior_restart_path}') | 
| 188 |  | - | 
| 189 |  | -            res_ptr_path = os.path.join(self.work_init_path, | 
| 190 |  | -                                        'ice.restart_file') | 
| 191 |  | -            if os.path.islink(res_ptr_path): | 
| 192 |  | -                # If we've linked in a previous pointer it should be deleted | 
| 193 |  | -                os.remove(res_ptr_path) | 
| 194 |  | -            with open(res_ptr_path, 'w') as res_ptr: | 
| 195 |  | -                res_dir = self.get_ptr_restart_dir() | 
| 196 |  | -                print(os.path.join(res_dir, iced_restart_file), file=res_ptr) | 
|  | 177 | +            self._make_restart_ptr() | 
| 197 | 178 | 
 | 
| 198 | 179 |             # Update input namelist | 
| 199 | 180 |             setup_nml['runtype'] = 'continue' | 
| @@ -394,3 +375,130 @@ def link_restart(self, fpath): | 
| 394 | 375 |             ) | 
| 395 | 376 | 
 | 
| 396 | 377 |         make_symlink(input_path, input_work_path) | 
|  | 378 | + | 
|  | 379 | +    def _make_restart_ptr(self): | 
|  | 380 | +        """ | 
|  | 381 | +        CICE4 restart pointers are created in the access driver, where | 
|  | 382 | +        the correct run start dates are available. | 
|  | 383 | +        """ | 
|  | 384 | +        pass | 
|  | 385 | + | 
|  | 386 | +    def overwrite_restart_ptr(self, | 
|  | 387 | +                              run_start_date, | 
|  | 388 | +                              previous_runtime, | 
|  | 389 | +                              calendar_file): | 
|  | 390 | +        """ | 
|  | 391 | +        Generate restart pointer file 'ice.restart_file' pointing to | 
|  | 392 | +        'iced.YYYYMMDD' with the correct start date. | 
|  | 393 | +        Additionally check that the `iced.YYYYMNDD` restart file's header | 
|  | 394 | +        has the correct previous runtime. | 
|  | 395 | +        Typically called from the access driver, which provides the | 
|  | 396 | +        the correct date and runtime. | 
|  | 397 | +
 | 
|  | 398 | +        Parameters | 
|  | 399 | +        ---------- | 
|  | 400 | +        run_start_date: datetime.date | 
|  | 401 | +            Start date of the new simulation | 
|  | 402 | +        previous_runtime:  int | 
|  | 403 | +            Seconds between experiment initialisation date and start date | 
|  | 404 | +        calendar_file:  str | 
|  | 405 | +            Calendar restart file used to calculate timing information | 
|  | 406 | +        """ | 
|  | 407 | +        # Expected iced restart file name | 
|  | 408 | +        iced_restart_file = self.find_matching_iced(self.prior_restart_path, | 
|  | 409 | +                                                    run_start_date) | 
|  | 410 | + | 
|  | 411 | +        res_ptr_path = os.path.join(self.work_init_path, | 
|  | 412 | +                                    'ice.restart_file') | 
|  | 413 | +        if os.path.islink(res_ptr_path): | 
|  | 414 | +            # If we've linked in a previous pointer it should be deleted | 
|  | 415 | +            os.remove(res_ptr_path) | 
|  | 416 | + | 
|  | 417 | +        iced_path = os.path.join(self.prior_restart_path, | 
|  | 418 | +                                 iced_restart_file) | 
|  | 419 | + | 
|  | 420 | +        # Check binary restart has correct time | 
|  | 421 | +        self._cice4_check_date_consistency(iced_path, | 
|  | 422 | +                                           previous_runtime, | 
|  | 423 | +                                           calendar_file) | 
|  | 424 | + | 
|  | 425 | +        with open(res_ptr_path, 'w') as res_ptr: | 
|  | 426 | +            res_dir = self.get_ptr_restart_dir() | 
|  | 427 | +            res_ptr.write(os.path.join(res_dir, iced_restart_file)) | 
|  | 428 | + | 
|  | 429 | +    def _cice4_check_date_consistency(self, | 
|  | 430 | +                                      iced_path, | 
|  | 431 | +                                      previous_runtime, | 
|  | 432 | +                                      calendar_file): | 
|  | 433 | +        """ | 
|  | 434 | +        Check that the previous runtime in iced restart file header | 
|  | 435 | +        matches the runtime calculated from the calendar restart file. | 
|  | 436 | +
 | 
|  | 437 | +        Parameters | 
|  | 438 | +        ---------- | 
|  | 439 | +        iced_path: str or Path | 
|  | 440 | +            Path to iced restart file | 
|  | 441 | +        previous_runtime:  int | 
|  | 442 | +            Seconds between experiment initialisation date and start date | 
|  | 443 | +        calendar_file:  str or Path | 
|  | 444 | +            Calendar restart file used to calculate timing information | 
|  | 445 | +        """ | 
|  | 446 | +        _, _, cice_iced_runtime, _ = read_binary_iced_header(iced_path) | 
|  | 447 | +        if previous_runtime != cice_iced_runtime: | 
|  | 448 | +            msg = (f"Previous runtime from calendar file " | 
|  | 449 | +                   f"{calendar_file}: {previous_runtime} " | 
|  | 450 | +                   "does not match previous runtime in restart" | 
|  | 451 | +                   f"file {iced_path}: {cice_iced_runtime}.") | 
|  | 452 | +            raise RuntimeError(msg) | 
|  | 453 | + | 
|  | 454 | +    def find_matching_iced(self, dir_path, date): | 
|  | 455 | +        """ | 
|  | 456 | +        Check a directory for an iced.YYYYMMDD restart file matching a | 
|  | 457 | +        specified date. | 
|  | 458 | +        Raises an error if the expected file is not found. | 
|  | 459 | +
 | 
|  | 460 | +        Parameters | 
|  | 461 | +        ---------- | 
|  | 462 | +        dir_path: str or Path | 
|  | 463 | +            Path to directory containing iced restart files | 
|  | 464 | +        date: datetime.date | 
|  | 465 | +            Date for matching iced file names | 
|  | 466 | +
 | 
|  | 467 | +        Returns | 
|  | 468 | +        ------- | 
|  | 469 | +        iced_file_name: str | 
|  | 470 | +            Name of iced restart file found in dir_path matching | 
|  | 471 | +            the specified date | 
|  | 472 | +        """ | 
|  | 473 | +        # Expected iced restart file name | 
|  | 474 | +        date_int = cal.date_to_int(date) | 
|  | 475 | +        iced_restart_file = f"iced.{date_int:08d}" | 
|  | 476 | + | 
|  | 477 | +        dir_files = [f for f in os.listdir(dir_path) | 
|  | 478 | +                     if os.path.isfile(os.path.join(dir_path, f))] | 
|  | 479 | + | 
|  | 480 | +        if iced_restart_file not in dir_files: | 
|  | 481 | +            msg = (f"CICE restart file not found in {dir_path}. Expected " | 
|  | 482 | +                   f"{iced_restart_file} to exist. Is 'dumpfreq' set " | 
|  | 483 | +                   f"in {self.ice_nml_fname} consistently with the run-length?" | 
|  | 484 | +                   ) | 
|  | 485 | +            raise FileNotFoundError(msg) | 
|  | 486 | + | 
|  | 487 | +        return iced_restart_file | 
|  | 488 | + | 
|  | 489 | + | 
|  | 490 | +CICE4_RESTART_HEADER_SIZE = 24 | 
|  | 491 | +CICE4_RESTART_HEADER_FORMAT = '>iidd' | 
|  | 492 | + | 
|  | 493 | + | 
|  | 494 | +def read_binary_iced_header(iced_path): | 
|  | 495 | +    """ | 
|  | 496 | +    Read header information from a CICE4 binary restart file. | 
|  | 497 | +    """ | 
|  | 498 | +    with open(iced_path, 'rb') as iced_file: | 
|  | 499 | +        header = iced_file.read(CICE4_RESTART_HEADER_SIZE) | 
|  | 500 | +        bint, istep0, time, time_forc = struct.unpack( | 
|  | 501 | +                                            CICE4_RESTART_HEADER_FORMAT, | 
|  | 502 | +                                            header) | 
|  | 503 | + | 
|  | 504 | +    return (bint, istep0, time, time_forc) | 
0 commit comments