11import os
2+ import time
23from importlib .resources import contents
34
45from compass .model import run_model
56from compass .ocean .tests .global_ocean .metadata import (
67 add_mesh_and_init_metadata ,
78)
9+ from compass .ocean .tests .global_ocean .tasks import get_ntasks_from_cell_count
810from compass .step import Step
911from compass .testcase import TestCase
1012
@@ -25,13 +27,18 @@ class ForwardStep(Step):
2527 time_integrator : {'split_explicit', 'RK4'}
2628 The time integrator to use for the forward run
2729
28- resources_from_config : bool
29- Whether to get ``ntasks``, ``min_tasks`` and ``openmp_threads`` from
30- config options
30+ dynamic_ntasks : bool
31+ Whether the target and minimum number of MPI tasks (``ntasks`` and
32+ ``min_tasks``) are computed dynamically from the number of cells
33+ in the mesh
34+
35+ get_dt_from_min_res : bool
36+ Whether to automatically compute `config_dt` and `config_btr_dt`
37+ namelist options from the minimum resolution of the mesh
3138 """
32- def __init__ (self , test_case , mesh , init , time_integrator , name = 'forward' ,
33- subdir = None , ntasks = None , min_tasks = None ,
34- openmp_threads = None ):
39+ def __init__ (self , test_case , mesh , time_integrator , init = None ,
40+ name = 'forward' , subdir = None , ntasks = None , min_tasks = None ,
41+ openmp_threads = None , get_dt_from_min_res = True ):
3542 """
3643 Create a new step
3744
@@ -43,12 +50,12 @@ def __init__(self, test_case, mesh, init, time_integrator, name='forward',
4350 mesh : compass.ocean.tests.global_ocean.mesh.Mesh
4451 The test case that produces the mesh for this run
4552
46- init : compass.ocean.tests.global_ocean.init.Init
47- The test case that produces the initial condition for this run
48-
4953 time_integrator : {'split_explicit', 'RK4'}
5054 The time integrator to use for the forward run
5155
56+ init : compass.ocean.tests.global_ocean.init.Init, optional
57+ The test case that produces the initial condition for this run
58+
5259 name : str, optional
5360 the name of the step
5461
@@ -66,6 +73,10 @@ def __init__(self, test_case, mesh, init, time_integrator, name='forward',
6673
6774 openmp_threads : int, optional
6875 the number of OpenMP threads the step will use
76+
77+ get_dt_from_min_res : bool
78+ Whether to automatically compute `config_dt` and `config_btr_dt`
79+ namelist options from the minimum resolution of the mesh
6980 """
7081 self .mesh = mesh
7182 self .init = init
@@ -79,7 +90,9 @@ def __init__(self, test_case, mesh, init, time_integrator, name='forward',
7990 if (ntasks is None ) != (openmp_threads is None ):
8091 raise ValueError ('You must specify both ntasks and openmp_threads '
8192 'or neither.' )
82- self .resources_from_config = ntasks is None
93+
94+ self .dynamic_ntasks = (ntasks is None and min_tasks is None )
95+ self .get_dt_from_min_res = get_dt_from_min_res
8396
8497 # make sure output is double precision
8598 self .add_streams_file ('compass.ocean.streams' , 'streams.output' )
@@ -93,7 +106,7 @@ def __init__(self, test_case, mesh, init, time_integrator, name='forward',
93106 self .add_namelist_file (
94107 'compass.ocean.tests.global_ocean' , 'namelist.wisc' )
95108
96- if init .with_bgc :
109+ if init is not None and init .with_bgc :
97110 self .add_namelist_file (
98111 'compass.ocean.tests.global_ocean' , 'namelist.bgc' )
99112 self .add_streams_file (
@@ -113,38 +126,75 @@ def __init__(self, test_case, mesh, init, time_integrator, name='forward',
113126 if mesh_stream in mesh_package_contents :
114127 self .add_streams_file (mesh_package , mesh_stream )
115128
116- if mesh .with_ice_shelf_cavities :
117- initial_state_target = \
118- f'{ init .path } /ssh_adjustment/adjusted_init.nc'
119- else :
120- initial_state_target = \
121- f'{ init .path } /initial_state/initial_state.nc'
122- self .add_input_file (filename = 'init.nc' ,
123- work_dir_target = initial_state_target )
124- self .add_input_file (
125- filename = 'forcing_data.nc' ,
126- work_dir_target = f'{ init .path } /initial_state/'
127- f'init_mode_forcing_data.nc' )
128- self .add_input_file (
129- filename = 'graph.info' ,
130- work_dir_target = f'{ init .path } /initial_state/graph.info' )
129+ if init is not None :
130+ if mesh .with_ice_shelf_cavities :
131+ initial_state_target = \
132+ f'{ init .path } /ssh_adjustment/adjusted_init.nc'
133+ else :
134+ initial_state_target = \
135+ f'{ init .path } /initial_state/initial_state.nc'
136+ self .add_input_file (filename = 'init.nc' ,
137+ work_dir_target = initial_state_target )
138+ self .add_input_file (
139+ filename = 'forcing_data.nc' ,
140+ work_dir_target = f'{ init .path } /initial_state/'
141+ f'init_mode_forcing_data.nc' )
142+ self .add_input_file (
143+ filename = 'graph.info' ,
144+ work_dir_target = f'{ init .path } /initial_state/graph.info' )
131145
132146 self .add_model_as_input ()
133147
134148 def setup (self ):
135149 """
136- Set up the test case in the work directory, including downloading any
137- dependencies
150+ Set the number of MPI tasks and the time step based on config options
138151 """
139- self ._get_resources ()
152+ config = self .config
153+ self .dynamic_ntasks = (self .ntasks is None and self .min_tasks is None )
154+
155+ if self .dynamic_ntasks :
156+ mesh_filename = os .path .join (self .work_dir , 'init.nc' )
157+ self .ntasks , self .min_tasks = get_ntasks_from_cell_count (
158+ config = config , at_setup = True , mesh_filename = mesh_filename )
159+ self .openmp_threads = config .getint ('global_ocean' ,
160+ 'forward_threads' )
161+
162+ if self .get_dt_from_min_res :
163+ dt , btr_dt = self ._get_dts ()
164+ if self .time_integrator == 'split_explicit' :
165+ self .add_namelist_options ({'config_dt' : dt ,
166+ 'config_btr_dt' : btr_dt })
167+ else :
168+ # RK4, so use the smaller time step
169+ self .add_namelist_options ({'config_dt' : btr_dt })
140170
141171 def constrain_resources (self , available_resources ):
142172 """
143173 Update resources at runtime from config options
144174 """
145- self ._get_resources ()
175+ config = self .config
176+ if self .dynamic_ntasks :
177+ mesh_filename = os .path .join (self .work_dir , 'init.nc' )
178+ self .ntasks , self .min_tasks = get_ntasks_from_cell_count (
179+ config = config , at_setup = False , mesh_filename = mesh_filename )
180+ self .openmp_threads = config .getint ('global_ocean' ,
181+ 'forward_threads' )
146182 super ().constrain_resources (available_resources )
147183
184+ def runtime_setup (self ):
185+ """
186+ Update the time step based on config options that a user might have
187+ changed
188+ """
189+ if self .get_dt_from_min_res :
190+ dt , btr_dt = self ._get_dts ()
191+ if self .time_integrator == 'split_explicit' :
192+ self .update_namelist_at_runtime ({'config_dt' : dt ,
193+ 'config_btr_dt' : btr_dt })
194+ else :
195+ # RK4, so use the smaller time step
196+ self .update_namelist_at_runtime ({'config_dt' : btr_dt })
197+
148198 def run (self ):
149199 """
150200 Run this step of the testcase
@@ -153,16 +203,24 @@ def run(self):
153203 add_mesh_and_init_metadata (self .outputs , self .config ,
154204 init_filename = 'init.nc' )
155205
156- def _get_resources (self ):
157- # get the these properties from the config options
158- if self .resources_from_config :
159- config = self .config
160- self .ntasks = config .getint (
161- 'global_ocean' , 'forward_ntasks' )
162- self .min_tasks = config .getint (
163- 'global_ocean' , 'forward_min_tasks' )
164- self .openmp_threads = config .getint (
165- 'global_ocean' , 'forward_threads' )
206+ def _get_dts (self ):
207+ """
208+ Get the time step and barotropic time steps
209+ """
210+ config = self .config
211+ # dt is proportional to resolution: default 30 seconds per km
212+ dt_per_km = config .getfloat ('global_ocean' , 'dt_per_km' )
213+ btr_dt_per_km = config .getfloat ('global_ocean' , 'btr_dt_per_km' )
214+ min_res = config .getfloat ('global_ocean' , 'min_res' )
215+
216+ dt = dt_per_km * min_res
217+ # https://stackoverflow.com/a/1384565/7728169
218+ dt = time .strftime ('%H:%M:%S' , time .gmtime (dt ))
219+
220+ btr_dt = btr_dt_per_km * min_res
221+ btr_dt = time .strftime ('%H:%M:%S' , time .gmtime (btr_dt ))
222+
223+ return dt , btr_dt
166224
167225
168226class ForwardTestCase (TestCase ):
0 commit comments