Skip to content

Commit d989d68

Browse files
authored
Merge pull request #631 from xylar/add-algorithm-for-global-ocean-cores-dt
Get global ocean resources and time steps based on mesh size
2 parents f12807b + fd68d99 commit d989d68

File tree

23 files changed

+314
-299
lines changed

23 files changed

+314
-299
lines changed

compass/ocean/tests/global_ocean/forward.py

Lines changed: 98 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import os
2+
import time
23
from importlib.resources import contents
34

45
from compass.model import run_model
56
from 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
810
from compass.step import Step
911
from 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

168226
class ForwardTestCase(TestCase):

compass/ocean/tests/global_ocean/global_ocean.cfg

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,31 @@ cull_mesh_max_memory = 1000
1515

1616
## each mesh should replace these with appropriate values in its config file
1717

18+
# the number of cells per core to aim for
19+
goal_cells_per_core = 200
20+
# the approximate maximum number of cells per core (the test will fail if too
21+
# few cores are available)
22+
max_cells_per_core = 2000
23+
24+
# the approximate number of cells in the mesh, to be estimated for each mesh
25+
approx_cell_count = <<<Missing>>>
26+
27+
# time step per resolution (s/km), since dt is proportional to resolution
28+
dt_per_km = 30
29+
# barotropic time step per resolution (s/km)
30+
btr_dt_per_km = 1.5
31+
1832
## config options related to the initial_state step
1933
# number of cores to use
20-
init_ntasks = 4
34+
init_ntasks = 36
2135
# minimum of cores, below which the step fails
22-
init_min_tasks = 1
23-
# maximum memory usage allowed (in MB)
24-
init_max_memory = 1000
36+
init_min_tasks = 8
2537
# number of threads
2638
init_threads = 1
2739

2840
## config options related to the forward steps
29-
# number of cores to use
30-
forward_ntasks = 4
31-
# minimum of cores, below which the step fails
32-
forward_min_tasks = 1
3341
# number of threads
3442
forward_threads = 1
35-
# maximum memory usage allowed (in MB)
36-
forward_max_memory = 1000
3743

3844
## metadata related to the mesh
3945
# whether to add metadata to output files
Lines changed: 5 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
from importlib.resources import contents
2-
31
from compass.ocean.iceshelf import adjust_ssh
4-
from compass.step import Step
2+
from compass.ocean.tests.global_ocean.forward import ForwardStep
53

64

7-
class SshAdjustment(Step):
5+
class SshAdjustment(ForwardStep):
86
"""
97
A step for iteratively adjusting the pressure from the weight of the ice
108
shelf to match the sea-surface height as part of ice-shelf 2D test cases
@@ -18,21 +16,9 @@ def __init__(self, test_case):
1816
test_case : compass.ocean.tests.global_ocean.init.Init
1917
The test case this step belongs to
2018
"""
21-
super().__init__(test_case=test_case, name='ssh_adjustment')
22-
23-
# make sure output is double precision
24-
self.add_streams_file('compass.ocean.streams', 'streams.output')
25-
26-
self.add_namelist_file(
27-
'compass.ocean.tests.global_ocean', 'namelist.forward')
28-
29-
mesh_package = test_case.mesh.package
30-
mesh_package_contents = list(contents(mesh_package))
31-
mesh_namelists = ['namelist.forward',
32-
'namelist.split_explicit']
33-
for mesh_namelist in mesh_namelists:
34-
if mesh_namelist in mesh_package_contents:
35-
self.add_namelist_file(mesh_package, mesh_namelist)
19+
super().__init__(test_case=test_case, mesh=test_case.mesh,
20+
time_integrator='split_explicit',
21+
name='ssh_adjustment')
3622

3723
self.add_namelist_options({'config_AM_globalStats_enable': '.false.'})
3824
self.add_namelist_file('compass.ocean.namelists',
@@ -55,24 +41,8 @@ def __init__(self, test_case):
5541
filename='graph.info',
5642
work_dir_target=f'{mesh_path}/culled_graph.info')
5743

58-
self.add_model_as_input()
59-
6044
self.add_output_file(filename='adjusted_init.nc')
6145

62-
def setup(self):
63-
"""
64-
Set up the test case in the work directory, including downloading any
65-
dependencies
66-
"""
67-
self._get_resources()
68-
69-
def constrain_resources(self, available_resources):
70-
"""
71-
Update resources at runtime from config options
72-
"""
73-
self._get_resources()
74-
super().constrain_resources(available_resources)
75-
7646
def run(self):
7747
"""
7848
Run this step of the testcase
@@ -81,10 +51,3 @@ def run(self):
8151
iteration_count = config.getint('ssh_adjustment', 'iterations')
8252
adjust_ssh(variable='landIcePressure', iteration_count=iteration_count,
8353
step=self)
84-
85-
def _get_resources(self):
86-
# get the these properties from the config options
87-
config = self.config
88-
self.ntasks = config.getint('global_ocean', 'forward_ntasks')
89-
self.min_tasks = config.getint('global_ocean', 'forward_min_tasks')
90-
self.openmp_threads = config.getint('global_ocean', 'forward_threads')

compass/ocean/tests/global_ocean/mesh/arrm10to60/arrm10to60.cfg

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,9 @@ max_layer_thickness = 150.0
2525
init_ntasks = 256
2626
# minimum of cores, below which the step fails
2727
init_min_tasks = 64
28-
# maximum memory usage allowed (in MB)
29-
init_max_memory = 1000
3028

31-
## config options related to the forward steps
32-
# number of cores to use
33-
forward_ntasks = 2000
34-
# minimum of cores, below which the step fails
35-
forward_min_tasks = 200
36-
# maximum memory usage allowed (in MB)
37-
forward_max_memory = 1000
29+
# the approximate number of cells in the mesh
30+
approx_cell_count = 600000
3831

3932
## metadata related to the mesh
4033
# the prefix (e.g. QU, EC, WC, SO)

0 commit comments

Comments
 (0)