Model
Running an E3SM component
Steps that run a standalone build of an E3SM component should descend from the
polaris.ModelStep
class.
By default (if the attribute update_pio = True
), at runtime, the namelist
options associated with the PIO library
will automatically be set. If the make_graph = True
, the mesh will be
partitioned across MPI tasks at runtime.
During construction or by setting attributes directly, you can can provide
non-default names for the graph, namelist and streams files. At construction
or with the polaris.ModelStep.set_model_resources()
method, you can set number of tasks, threads, etc. determined from the
ntasks
, min_tasks
, cpus_per_task
, min_cpus_per_task`
and
openmp_threads
attributes. These resources need to be set at construction or
in the setup() method (i.e. before calling run())
so that the polaris framework can ensure that the required resources are
available.
Adding yaml, namelist and streams files
Components and tasks can provide yaml config options,
namelist and streams files that are used to replace default model config
options and streams definitions before the E3SM component gets run. Namelist
and streams files within the polaris
package must start with the prefix
namelist.
and streams.
, respectively, to ensure that they are included
when we build the package. Yaml files must end with .yaml
or .yml
for the
same reason.
You can make calls to polaris.ModelStep.add_namelist_file()
,
polaris.ModelStep.add_yaml_file()
,
polaris.ModelStep.add_model_config_options()
and
polaris.ModelStep.add_streams_file()
as described below to indicate
how yaml, namelist and streams file should be built up by modifying the
defaults for the E3SM component. The yaml, namelists and streams files
themselves are generated automatically (which of these depends on the E3SM
component in question) as part of setting up the task.
Adding a yaml file
Typically, a step that runs an E3SM component will include one or more calls
to polaris.ModelStep.add_namelist_file()
or
polaris.ModelStep.add_yaml_file()
within the constructor
or setup() method. Calling one of these methods simply adds the
file to a list that will be parsed if and when the step gets set up. (This
way, it is safe to add namelist files to a step in init even if that task
will never get set up or run.)
The format of the yaml file is a hierarchical list of sections with config options and values, followed by streams:
ocean:
run_modes:
config_ocean_run_mode: forward
time_management:
config_run_duration: 0024_00:00:00
ALE_vertical_grid:
config_vert_coord_movement: impermeable_interfaces
decomposition:
config_block_decomp_file_prefix: graph.info.part.
time_integration:
config_time_integrator: RK4
streams:
mesh:
filename_template: init.nc
input:
filename_template: init.nc
restart:
output_interval: 0030_00:00:00
output:
type: output
filename_template: output.nc
output_interval: 0024_00:00:00
clobber_mode: truncate
reference_time: 0001-01-01_00:00:00
contents:
- tracers
- mesh
- xtime
- normalVelocity
- layerThickness
- refZMid
- refLayerThickness
- kineticEnergyCell
- relativeVorticityCell
Unlike for namelist files (see below), we require that config options be placed in appropriate sections both for clarity and because there is no guarantee that config options must have unique names.
A typical yaml file is added by passing a package where the yaml file
is located and the name of the input yaml file within that package
as arguments to polaris.ModelStep.add_yaml_file()
:
self.add_yaml_file('polaris.ocean.tasks.global_convergence.cosine_bell',
'forward.yaml')
Model config values are replaced by the files (or options, see below) in the sequence they are given. This way, you can add the model config substitutions common to related tasks first, and then override those with the replacements specific to the task or step.
Adding a namelist file
Typically, a step that runs the E3SM component will include one or more calls
to polaris.ModelStep.add_namelist_file()
or
polaris.ModelStep.add_yaml_file()
within the constructor
or setup() method. Calling this method simply adds the file to
a list that will be parsed if and when the step gets set up. (This way, it is
safe to add namelist files to a step in init even if that task will never
get set up or run.)
The format of the namelist file is simply a list of namelist options and the replacement values:
config_write_output_on_startup = .false.
config_run_duration = '0000_00:15:00'
config_use_mom_del2 = .true.
config_implicit_bottom_drag_coeff = 1.0e-2
config_use_cvmix_background = .true.
config_cvmix_background_diffusion = 0.0
config_cvmix_background_viscosity = 1.0e-4
Since all MPAS namelist options must have unique names, we do not worry about which specific namelist within the file each belongs to.
A typical namelist file is added by passing a package where the namelist file
is located and the name of the input namelist file within that package
as arguments to polaris.ModelStep.add_namelist_file()
:
self.add_namelist_file('polaris.ocean.tasks.baroclinic_channel',
'namelist.forward')
Namelist values are replaced by the files (or options, see below) in the sequence they are given. This way, you can add the namelist substitutions for that are common to related tasks first, and then override those with the replacements that are specific to the task or step.
Adding model config options
Sometimes, it is easier to replace yaml or namelist options (together referred
to as model config options) using a dictionary within the code, rather than
a yaml or namelist file. This is appropriate when there are only 1 or 2
options to replace (so creating a file seems like overkill) or when the
model config options rely on values that are determined by the code (e.g.
different values for different resolutions). Simply create a dictionary
replacements and call polaris.ModelStep.add_model_config_options()
either at init or in the setup()
method of the step. These replacements are
parsed, along with replacements from files, in the order they are added.
Thus, you could add replacements from a model config file common to multiple
tasks, specific to a task, and/or specific to step. Then, you could override
them with namelist options in a dictionary for the task or step, as in this
example:
if nu is not None:
# update the viscosity to the requested value
self.add_model_config_options(options=dict(config_mom_del2=nu))
# make sure output is double precision
self.add_yaml_file('polaris.ocean.config', 'output.yaml')
self.add_yaml_file('polaris.ocean.tasks.baroclinic_channel',
'forward.yaml')
Here, we set the viscosity nu
, which is an option passed in when creating
this step. Then, we get default model config options for ocean model output
(output.yaml
) and for baroclinic channel forward steps (forward.yaml
).
Note
Model config options can have values of type bool
, int
, float
or str
,
and are automatically converted to the appropriate type in the yaml or namelist
file.
Dynamic model config options
It is sometimes useful to have model config options that are based on Polaris
config options and/or algorithms. In such cases, the model config options
need to be computed once at setup and again (possibly based on updated
config options) at runtime. A step needs to override the
polaris.ModelStep.dynamic_model_config()
method, e.g.:
def dynamic_model_config(self, at_setup):
"""
Add model config options, namelist, streams and yaml files using config
options or template replacements that need to be set both during step
setup and at runtime
Parameters
----------
at_setup : bool
Whether this method is being run during setup of the step, as
opposed to at runtime
"""
super().dynamic_model_config(at_setup)
config = self.config
options = dict()
# dt is proportional to resolution: default 30 seconds per km
dt_per_km = config.getfloat('baroclinic_channel', 'dt_per_km')
dt = dt_per_km * self.resolution
# https://stackoverflow.com/a/1384565/7728169
options['config_dt'] = \
time.strftime('%H:%M:%S', time.gmtime(dt))
if self.run_time_steps is not None:
# default run duration is a few time steps
run_seconds = self.run_time_steps * dt
options['config_run_duration'] = \
time.strftime('%H:%M:%S', time.gmtime(run_seconds))
# btr_dt is also proportional to resolution: default 1.5 seconds per km
btr_dt_per_km = config.getfloat('baroclinic_channel', 'btr_dt_per_km')
btr_dt = btr_dt_per_km * self.resolution
options['config_btr_dt'] = \
time.strftime('%H:%M:%S', time.gmtime(btr_dt))
self.dt = dt
self.btr_dt = btr_dt
self.add_model_config_options(options=options)
Adding a streams file
Streams files are a bit more complicated than namelist files because streams files are XML documents, requiring some slightly more sophisticated parsing.
Typically, a step that runs the E3SM component will include one or more calls
to polaris.ModelStep.add_streams_file()
within the
constructor or setup() method. Calling this function
simply adds the file to a list within the step
dictionary that will be parsed
if an when the step gets set up. (This way, it is safe to add streams files to
a step at init even if that task will never get set up or run.)
The format of the streams file is essentially the same as the default and generated streams file, e.g.:
<streams>
<immutable_stream name="mesh"
filename_template="init.nc"/>
<immutable_stream name="input"
filename_template="init.nc"/>
<immutable_stream name="restart"/>
<stream name="output"
type="output"
filename_template="output.nc"
output_interval="0000_00:00:01"
clobber_mode="truncate">
<var_struct name="tracers"/>
<var name="xtime"/>
<var name="normalVelocity"/>
<var name="layerThickness"/>
</stream>
</streams>
These are all streams that are already defined in the default forward streams
for MPAS-Ocean, so the defaults will be updated. If only the attributes of
a stream are given, the contents of the stream (the var
, var_struct
and var_array
tags within the stream) are taken from the defaults. If
any contents are given, as for the output
stream in the example above, they
replace the default contents. Polaris does not include a way to add or
remove contents from the defaults, just keep the default contents or replace
them all. (Past experience has shown that such a feature would be
confusing and difficult to keep synchronized with the E3SM code.)
A typical streams file is added by calling
polaris.ModelStep.add_streams_file()
with a package where the streams
file is located and the name of the input streams file within that package:
self.add_streams_file('polaris.ocean.tasks.baroclinic_channel',
'streams.forward')
If the streams file should have a different name than the default
(streams.<component>
), the name can be given via the out_name
keyword
argument. If init
mode is desired, rather than the default, forward
mode, this can also be specified.
Adding a template streams file
The main difference between namelists and streams files is that there is no
direct equivalent for streams of polaris.ModelStep.add_model_config_options()
.
It is simply too confusing to try to define streams within the code.
Instead, polaris.ModelStep.add_streams_file()
includes a keyword
argument template_replacements
. If you provide a dictionary of
replacements to this argument, the input streams file will be treated as a
Jinja2 template that is rendered
using the provided replacements. Here is an example of such a template streams
file:
<streams>
<stream name="output"
output_interval="{{ output_interval }}"/>
<immutable_stream name="restart"
filename_template="../restarts/rst.$Y-$M-$D_$h.$m.$s.nc"
output_interval="{{ restart_interval }}"/>
</streams>
And here is how it would be added, along with replacements:
stream_replacements = {
'output_interval': '00-00-01_00:00:00',
'restart_interval': '00-00-01_00:00:00'}
add_streams_file(step, module, 'streams.template',
template_replacements=stream_replacements)
...
stream_replacements = {
'output_interval': '00-00-01_00:00:00',
'restart_interval': '00-00-01_00:00:00'}
add_streams_file(step, module, 'streams.template',
template_replacements=stream_replacements)
In this example, taken from
polaris.ocean.tasks.global_ocean.mesh.qu240.dynamic_adjustement.QU240DynamicAdjustment
,
we are creating a series of steps that will be used to perform dynamic
adjustment of the ocean model, each of which might have different durations and
restart intervals. Rather than creating a streams file for each step of the
spin up, we reuse the same template with just a few appropriate replacements.
Thus, calls to polaris.ModelStep.add_streams_file()
with
template_replacements
are qualitatively similar to namelist calls to
polaris.ModelStep.add_model_config_options()
.
Adding E3SM component as an input
If a step involves running the E3SM component, it should descend from
:py:classpolaris.ModelStep
. The model executable will automatically be
linked and added as an input to the step. This way, if the user has forgotten
to compile the model, this will be obvious by the broken symlink and the step
will immediately fail because of the missing input. The path to the executable
is automatically detected based on the work directory for the step and the
config options.
Partitioning the mesh
The method polaris.ModelStep.partition()
calls the graph
partitioning executable (gpmetis by
default) to divide up the MPAS mesh across MPI tasks. If you create a
ModelStep
with partition_graph=True
(the default), this method is called
automatically.
In some circumstances, a step may need to partition the mesh separately from
running the model. Typically, this applies to cases where the model is run
multiple times with the same partition and we don’t want to waste time
creating the same partition over and over. For such cases, you can
provide partition_graph=False
and then call
polaris.ModelStep.partition()
manually where appropriate.
Updating PIO namelist options
You can use polaris.ModelStep.update_namelist_pio()
to
automatically set the MPAS namelist options config_pio_num_iotasks
and
config_pio_stride
such that there is 1 PIO task per node of the MPAS run.
This is particularly useful for PIO v1, which we have found performs much
better in this configuration than when there is 1 PIO task per core, the MPAS
default. When running with PIO v2, we have found little performance difference
between the MPAS default and the polaris default of one task per node, so we
feel this is a safe default.
By default, this function is called in
polaris.ModelStep.runtime_setup()
. If the same namelist
file is used for multiple model runs, it may be useful to update the number of
PIO tasks only once. In this case, set the attribute update_pio = False
and
polaris.ModelStep.update_namelist_pio()
yourself.
If you wish to use the MPAS default behavior of 1 PIO task per core, or wish to
set config_pio_num_iotasks
and config_pio_stride
yourself, simply
set update_pio = False
.
Making a graph file
Some polaris tasks take advantage of the fact that the
MPAS-Tools cell culler
can produce a graph file as part of the process of culling cells from an
MPAS mesh. In tasks that do not require cells to be culled, you can
call polaris.model_step.make_graph_file()
to produce a graph file
from an MPAS mesh file. Optionally, you can provide the name of an MPAS field
on cells in the mesh file that gives different weight to different cells
(weight_field
) in the partitioning process.