1
1
"""Specific Access-OM3 Model setup and post-processing"""
2
2
3
- import re
4
3
from collections import defaultdict
5
4
from pathlib import Path
6
5
from typing import Any
7
6
7
+ import f90nml
8
+ from netCDF4 import Dataset
8
9
from payu .models .cesm_cmeps import Runconfig
9
10
10
11
from model_config_tests .models .model import (
11
12
DEFAULT_RUNTIME_SECONDS ,
12
13
SCHEMA_VERSION_1_0_0 ,
13
14
Model ,
14
15
)
15
- from model_config_tests .util import DAY_IN_SECONDS
16
16
17
17
18
18
class AccessOm3 (Model ):
19
19
def __init__ (self , experiment ):
20
20
super ().__init__ (experiment )
21
- self .output_file = self .experiment .output000 / "ocean.stats"
22
21
22
+ # ACCESS-OM3 uses restarts for repro testing
23
+ self .output_0 = self .experiment .restart000
24
+ self .output_1 = self .experiment .restart001
25
+
26
+ self .mom_restart_pointer = self .output_0 / "rpointer.ocn"
23
27
self .runconfig = experiment .control_path / "nuopc.runconfig"
24
- self .mom_override = experiment .control_path / "MOM_override"
25
- self .ocean_config = experiment .control_path / "input.nml"
28
+ self .wav_in = experiment .control_path / "wav_in"
26
29
27
30
def set_model_runtime (
28
31
self , years : int = 0 , months : int = 0 , seconds : int = DEFAULT_RUNTIME_SECONDS
@@ -31,19 +34,20 @@ def set_model_runtime(
31
34
Default is 3 hours"""
32
35
runconfig = Runconfig (self .runconfig )
33
36
37
+ # Check that ocean model component is MOM since checksums are obtained from
38
+ # MOM6 restarts. Fail early if not
39
+ ocn_model = runconfig .get ("ALLCOMP_attributes" , "OCN_model" )
40
+ if ocn_model != "mom" :
41
+ raise ValueError (
42
+ "ACCESS-OM3 reproducibility checks utilize checksums written in MOM6 "
43
+ "restarts and hence can only be used with ACCESS-OM3 configurations that "
44
+ f"use MOM6. This configuration uses OCN_model = { ocn_model } ."
45
+ )
46
+
34
47
if years == months == 0 :
35
48
freq = "nseconds"
36
49
n = str (seconds )
37
50
38
- # Ensure that ocean.stats are written at the end of the run
39
- if seconds < DAY_IN_SECONDS :
40
- with open (self .mom_override , "a" ) as f :
41
- f .writelines (
42
- [
43
- f"\n #override TIMEUNIT = { n } " ,
44
- "\n #override ENERGYSAVEDAYS = 1.0" ,
45
- ]
46
- )
47
51
elif seconds == 0 :
48
52
freq = "nmonths"
49
53
n = str (12 * years + months )
@@ -59,38 +63,42 @@ def set_model_runtime(
59
63
60
64
runconfig .write ()
61
65
66
+ # Unfortunately WW3 doesn't (yet) obey the nuopc.runconfig. This should change in a
67
+ # future release, but for now we have to set WW3 runtime in wav_in. See
68
+ # https://github.com/COSIMA/access-om3/issues/239
69
+ if self .wav_in .exists ():
70
+ with open (self .wav_in ) as f :
71
+ nml = f90nml .read (f )
72
+
73
+ nml ["output_date_nml" ]["date" ]["restart" ]["stride" ] = int (n )
74
+ nml .write (self .wav_in , force = True )
75
+
62
76
def output_exists (self ) -> bool :
63
77
"""Check for existing output file"""
64
- return self .output_file .exists ()
78
+ return self .mom_restart_pointer .exists ()
65
79
66
80
def extract_checksums (
67
81
self , output_directory : Path = None , schema_version : str = None
68
82
) -> dict [str , Any ]:
69
83
"""Parse output file and create checksum using defined schema"""
70
84
if output_directory :
71
- output_filename = output_directory / "ocean.stats "
85
+ mom_restart_pointer = output_directory / "rpointer.ocn "
72
86
else :
73
- output_filename = self .output_file
74
-
75
- # ocean.stats is used for regression testing in MOM6's own test suite
76
- # See https://github.com/mom-ocean/MOM6/blob/2ab885eddfc47fc0c8c0bae46bc61531104428d5/.testing/Makefile#L495-L501
77
- # Rows in ocean.stats look like:
78
- # 0, 693135.000, 0, En 3.0745627134675957E-23, CFL 0.00000, ...
79
- # where the first three columns are Step, Day, Truncs and the remaining
80
- # columns include a label for what they are (e.g. En = Energy/Mass)
81
- # Header info is only included for new runs so can't be relied on
87
+ mom_restart_pointer = self .mom_restart_pointer
88
+
89
+ # MOM6 saves checksums for each variable in its restart files. Extract these
90
+ # attributes for each restart
82
91
output_checksums : dict [str , list [any ]] = defaultdict (list )
83
92
84
- with open (output_filename ) as f :
85
- lines = f .readlines ()
86
- # Skip header if it exists (for new runs)
87
- istart = 2 if "Step" in lines [0 ] else 0
88
- for line in lines [istart :]:
89
- for col in line .split ("," ):
90
- # Only keep columns with labels (ie not Step, Day, Truncs)
91
- col = re .split (" +" , col .strip ().rstrip ("\n " ))
92
- if len (col ) > 1 :
93
- output_checksums [col [0 ]].append (col [- 1 ])
93
+ with open (mom_restart_pointer ) as f :
94
+ for restart_file in f .readlines ():
95
+ restart = mom_restart_pointer .parent / restart_file .rstrip ()
96
+ rootgrp = Dataset (restart , "r" )
97
+ for variable in sorted (rootgrp .variables ):
98
+ var = rootgrp [variable ]
99
+ if "checksum" in var .ncattrs ():
100
+ output_checksums [variable .strip ()].append (var .checksum .strip ())
101
+ rootgrp .close ()
94
102
95
103
if schema_version is None :
96
104
schema_version = self .default_schema_version
0 commit comments