From df31c30c7266bd583af65ff866d1c2e472ec576a Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Thu, 21 Sep 2023 09:43:15 +0200 Subject: [PATCH] Update the docs --- docs/developers_guide/framework/config.md | 16 +- docs/developers_guide/ocean/api.md | 15 +- docs/developers_guide/ocean/framework.md | 213 ++++++++++++++++++ .../ocean/tasks/cosine_bell.md | 14 +- docs/developers_guide/organization/tasks.md | 34 +-- docs/users_guide/ocean/tasks/cosine_bell.md | 69 ++++-- 6 files changed, 313 insertions(+), 48 deletions(-) diff --git a/docs/developers_guide/framework/config.md b/docs/developers_guide/framework/config.md index e54518d35..7cc386126 100644 --- a/docs/developers_guide/framework/config.md +++ b/docs/developers_guide/framework/config.md @@ -33,18 +33,16 @@ the file is in the path `polaris/ocean/tasks/baroclinic_channel` that the config file should always exist, so we would like the code to raise an exception (`exception=True`) if the file is not found. This is the default behavior. In some cases, you would like the code to add the config -options if the config file exists and do nothing if it does not. This can -be useful if a common configure function is being used for all test -cases in a configuration, as in this example from -{py:func}`setup.setup_task()`: +options if the config file exists and do nothing if it does not. In this +example from {py:func}`polaris.setup.setup_task()`, there may not be a config +file for the particular machine we're on, and that's fine: ```python -# add the config options for the task (if defined) -config.add_from_package(task.__module__, - f'{task.name}.cfg', exception=False) +if machine is not None: + config.add_from_package('mache.machines', f'{machine}.cfg', + exception=False) ``` - -If a task doesn't have any config options, nothing will happen. +If there isn't a config file for this machine, nothing will happen. The `MpasConfigParser` class also includes methods for adding a user config file and other config files by file name, but these are largely intended diff --git a/docs/developers_guide/ocean/api.md b/docs/developers_guide/ocean/api.md index fcde46b33..812efda9b 100644 --- a/docs/developers_guide/ocean/api.md +++ b/docs/developers_guide/ocean/api.md @@ -169,7 +169,18 @@ ## Ocean Framework -### OceanModelStep +### Spherical Convergence Tests + +```{eval-rst} +.. currentmodule:: polaris.ocean.convergence.spherical + +.. autosummary:: + :toctree: generated/ + + SphericalConvergenceForward +``` + +### Ocean Model ```{eval-rst} .. currentmodule:: polaris.ocean.model @@ -182,6 +193,8 @@ OceanModelStep.constrain_resources OceanModelStep.compute_cell_count OceanModelStep.map_yaml_to_namelist + + get_time_interval_string ``` ### Spherical Base Mesh Step diff --git a/docs/developers_guide/ocean/framework.md b/docs/developers_guide/ocean/framework.md index 3598a4905..7bdad5610 100644 --- a/docs/developers_guide/ocean/framework.md +++ b/docs/developers_guide/ocean/framework.md @@ -60,6 +60,57 @@ The config options `goal_cells_per_core` and `max_cells_per_core` in the the planar mesh. By default, the number of MPI tasks tries to apportion 200 cells to each core, but it will allow as many as 2000. +### Setting time intervals in model config options + +It is often useful to be able to convert a `float` time interval in days or +seconds to a model config option in the form `DDDD_HH:MM:SS.S`. The +{py:func}`polaris.ocean.model.get_time_interval_string()` function will do this +for you. For example, if you have `resolution` in km and a config `section` +with options `dt_per_km` (in s/km) and `run_duration` (in days), you can use +the function to get appropriate strings for filling in a template model config +file: +```python +from polaris.ocean.model import get_time_interval_string + + +dt_per_km = section.getfloat('dt_per_km') +dt_str = get_time_interval_string(seconds=dt_per_km * resolution) + +run_duration = section.getfloat('run_duration') +run_duration_str = get_time_interval_string(days=run_duration) + +output_interval = section.getfloat('output_interval') +output_interval_str = get_time_interval_string(days=output_interval) + +replacements = dict( + dt=dt_str, + run_duration=run_duration_str, + output_interval=output_interval_str +) + +self.add_yaml_file(package, yaml_filename, + template_replacements=replacements) +``` +where the YAML file might include: +``` +omega: + time_management: + config_run_duration: {{ run_duration }} + time_integration: + config_dt: {{ dt }} + streams: + output: + type: output + filename_template: output.nc + output_interval: {{ output_interval }} + clobber_mode: truncate + reference_time: 0001-01-01_00:00:00 + contents: + - xtime + - normalVelocity + - layerThickness +``` + (dev-ocean-framework-config)= ## Model config options and streams @@ -85,6 +136,168 @@ The function {py:func}`polaris.ocean.mesh.spherical.add_spherical_base_mesh_step returns a step for for a spherical `qu` or `icos` mesh of a given resolution (in km). The step can be shared between tasks. +(dev-ocean-spherical-convergence)= + +## Spherical Convergence Tests + +Several tests that are in Polaris or which we plan to add are convergence +tests on {ref}`dev-ocean-spherical-meshes`. The ocean framework includes +shared config options and a base class for forward steps that are expected +to be useful across these tests. + +The shared config options are: +```cfg +# config options for spherical convergence tests +[spherical_convergence] + +# a list of icosahedral mesh resolutions (km) to test +icos_resolutions = 60, 120, 240, 480 + +# a list of quasi-uniform mesh resolutions (km) to test +qu_resolutions = 60, 90, 120, 150, 180, 210, 240 + +# time integrator: {'split_explicit', 'RK4'} +time_integrator = RK4 + +# RK4 time step per resolution (s/km), since dt is proportional to resolution +rk4_dt_per_km = 3.0 + +# split time step per resolution (s/km), since dt is proportional to resolution +split_dt_per_km = 30.0 + +# the barotropic time step (s/km) for simulations using split time stepping, +# since btr_dt is proportional to resolution +btr_dt_per_km = 1.5 + +# Run duration in days +run_duration = 1.0 + +# Output interval in days +output_interval = ${run_duration} +``` +The first 2 are the default resolutions for icosahedral and quasi-uniform +base meshes, respectively. The `time_integrator` will typically be overridden +by the specific convergence task's config options, and indicates which time +integrator to use for the forward run. Depending on the time integrator, +either `rk4_dt_per_km` or `split_dt_per_km` will be used to determine an +appropriate time step for each mesh resolution (proportional to the cell size). +For split time integrators, `btr_dt_per_km` will be used to compute the +barotropic time step in a similar way. The `run_duration` and +`output_interval` are typically the same, and they are given in days. + +Each convergence test can override these defaults with its own defaults by +defining them in its own config file. Convergence tests should bring in this +config file in their `configure()` methods, then add its own config options +after that to make sure they take precedence, e.g.: + +```python +from polaris import Task +class CosineBell(Task): + def configure(self): + super().configure() + config = self.config + config.add_from_package('polaris.mesh', 'mesh.cfg') + config.add_from_package('polaris.ocean.convergence.spherical', + 'spherical.cfg') + config.add_from_package('polaris.ocean.tasks.cosine_bell', + 'cosine_bell.cfg') +``` + +In addition, the {py:class}`polaris.ocean.convergence.spherical.SphericalConvergenceForward` +step can serve as a parent class for forward steps in convergence tests. This +parent class takes care of setting the time step based on the `dt_per_km` +config option and computes the approximate number of cells in the mesh, used +for determining the computational resources required, using a heuristic +appropriate for approximately uniform spherical meshes. A convergence test's +`Forward` step should descend from this class like in this example: + +```python +from polaris.ocean.convergence.spherical import SphericalConvergenceForward + + +class Forward(SphericalConvergenceForward): + """ + A step for performing forward ocean component runs as part of the cosine + bell test case + """ + + def __init__(self, component, name, subdir, resolution, base_mesh, init): + """ + Create a new step + + Parameters + ---------- + component : polaris.Component + The component the step belongs to + + name : str + The name of the step + + subdir : str + The subdirectory for the step + + resolution : float + The resolution of the (uniform) mesh in km + + base_mesh : polaris.Step + The base mesh step + + init : polaris.Step + The init step + """ + package = 'polaris.ocean.tasks.cosine_bell' + validate_vars = ['normalVelocity', 'tracer1'] + super().__init__(component=component, name=name, subdir=subdir, + resolution=resolution, base_mesh=base_mesh, + init=init, package=package, + yaml_filename='forward.yaml', + output_filename='output.nc', + validate_vars=validate_vars) +``` +Each convergence test must define a YAML file with model config options, called +`forward.yaml` by default. The `package` parameter is the location of this +file within the Polaris code (using python package syntax). The +`output_filename` is an output file that will have fields to validate and +analyze. The `validate_vars` are a list of variables to compare against a +baseline (if one is provided), and can be `None` if baseline validation should +not be performed. + +The `base_mesh` step should be created with the function described in +{ref}`dev-ocean-spherical-meshes`, and the `init` step should produce a file +`initial_state.nc` that will be the initial condition for the forward run. + +The `forward.yaml` file should be a YAML file with Jinja templating for the +time integrator, time step, run duration and output interval, e.g.: +``` +omega: + time_management: + config_run_duration: {{ run_duration }} + time_integration: + config_dt: {{ dt }} + config_time_integrator: {{ time_integrator }} + split_explicit_ts: + config_btr_dt: {{ btr_dt }} + streams: + mesh: + filename_template: init.nc + input: + filename_template: init.nc + restart: {} + output: + type: output + filename_template: output.nc + output_interval: {{ output_interval }} + clobber_mode: truncate + reference_time: 0001-01-01_00:00:00 + contents: + - xtime + - normalVelocity + - layerThickness +``` +`SphericalConvergenceForward` takes care of filling in the template based +on the associated config options (first at setup and again at runtime in case +the config options have changed). + (dev-ocean-framework-vertical)= ## Vertical coordinate diff --git a/docs/developers_guide/ocean/tasks/cosine_bell.md b/docs/developers_guide/ocean/tasks/cosine_bell.md index fe88582d7..180012744 100644 --- a/docs/developers_guide/ocean/tasks/cosine_bell.md +++ b/docs/developers_guide/ocean/tasks/cosine_bell.md @@ -16,7 +16,9 @@ The config options for the `cosine_bell` tests are described in Additionally, the test uses a `forward.yaml` file with a few common model config options related to drag and default horizontal and vertical momentum and tracer diffusion, as well as defining `mesh`, `input`, -`restart`, and `output` streams. +`restart`, and `output` streams. This file has Jinja templating that is +used to update model config options based on Polaris config options, see +{ref}`dev-ocean-spherical-convergence`. ### base_mesh @@ -32,10 +34,12 @@ tracer distributed in a cosine-bell shape. ### forward The class {py:class}`polaris.ocean.tasks.cosine_bell.forward.Forward` -defines a step for running MPAS-Ocean from an initial condition produced in -an `init` step. The time step is determined from the resolution -based on the `dt_per_km` config option. Other namelist options are taken -from the task's `forward.yaml`. +descends from {py:class}`polaris.ocean.convergence.spherical.SphericalConvergenceForward`, +and defines a step for running MPAS-Ocean from an initial condition produced in +an `init` step. See {ref}`dev-ocean-spherical-convergence` for some relevant +discussion of the parent class. The time step is determined from the resolution +based on the `dt_per_km` config option in the `[spherical_convergences]` +section. Other model config options are taken from `forward.yaml`. ### analysis diff --git a/docs/developers_guide/organization/tasks.md b/docs/developers_guide/organization/tasks.md index 61cf9f368..c69775f20 100644 --- a/docs/developers_guide/organization/tasks.md +++ b/docs/developers_guide/organization/tasks.md @@ -459,10 +459,21 @@ config files within the task or its shared framework. The `self.config` attribute that is modified in this function will be written to a config file for the task (see {ref}`config-files`). -If you override this method in a task, you should assume that the -`.cfg` file in its package has already been added to the -config options prior to calling `configure()`. This happens automatically -before running the task. +If you define a `.cfg` file, you will want to override this method +to add those config options, e.g.: + +```python +from polaris import Task + +class InertialGravityWave(Task): + def configure(self): + """ + Add the config file common to inertial gravity wave tests + """ + self.config.add_from_package( + 'polaris.ocean.tasks.inertial_gravity_wave', + 'inertial_gravity_wave.cfg') +``` Since many tasks may need similar behavior in their `configure()` methods, it is sometimes useful to define a parent class that overrides the @@ -472,24 +483,19 @@ the `configure()` method with their own additional changes. A `configure()` method can also be used to perform other operations at the task level when a task is being set up. An example of this would be -creating a symlink to a README file that is shared across the whole task, -as in {py:meth}`polaris.ocean.tasks.global_ocean.files_for_e3sm.FilesForE3SM.configure()`: +creating a symlink to a README file that is shared across the whole task: ```python -from importlib.resources import path - -from polaris.ocean.tasks.global_ocean.configure import configure_global_ocean -from polaris.io import symlink +from polaris.io import imp_res, symlink def configure(self): """ Modify the configuration options for this task """ - configure_global_ocean(task=self, mesh=self.mesh, init=self.init) - with path('polaris.ocean.tasks.global_ocean.files_for_e3sm', - 'README') as target: - symlink(str(target), '{}/README'.format(self.work_dir)) + package = 'compass.ocean.tests.global_ocean.files_for_e3sm' + target = imp_res.files(package).joinpath('README') + symlink(str(target), f'{self.work_dir}/README') ``` The `configure()` method is not the right place for adding or modifying steps diff --git a/docs/users_guide/ocean/tasks/cosine_bell.md b/docs/users_guide/ocean/tasks/cosine_bell.md index 7d44357dd..f08dea90b 100644 --- a/docs/users_guide/ocean/tasks/cosine_bell.md +++ b/docs/users_guide/ocean/tasks/cosine_bell.md @@ -41,24 +41,33 @@ The default resolutions used in the task depends on the mesh type. For the `icos` mesh type, the defaults are: ```cfg -resolutions = 60, 120, 240, 480 +# config options for spherical convergence tests +[spherical_convergence] + +# a list of icosahedral mesh resolutions (km) to test +icos_resolutions = 60, 120, 240, 480 ``` for the `qu` mesh type, they are: ```cfg -resolutions = 60, 90, 120, 150, 180, 210, 240 +# config options for spherical convergence tests +[spherical_convergence] + +# a list of quasi-uniform mesh resolutions (km) to test +qu_resolutions = 60, 90, 120, 150, 180, 210, 240 ``` To alter the resolutions used in this task, you will need to create your own -config file (or add a `cosine_bell` section to a config file if you're -already using one). The resolutions are a comma-separated list of the +config file (or add a `spherical_convergence` section to a config file if +you're already using one). The resolutions are a comma-separated list of the resolution of the mesh in km. If you specify a different list before setting up `cosine_bell`, steps will be generated with the requested -resolutions. (If you alter `resolutions` in the task's config file in -the work directory, nothing will happen.) For `icos` meshes, make sure you -use a resolution close to those listed in {ref}`dev-spherical-meshes`. Each -resolution will be rounded to the nearest allowed icosahedral resolution. +resolutions. (If you alter `icos_resolutions` or `qu_resolutions`) in the +task's config file in the work directory, nothing will happen.) For `icos` +meshes, make sure you use a resolution close to those listed in +{ref}`dev-spherical-meshes`. Each resolution will be rounded to the nearest +allowed icosahedral resolution. The `base_mesh` steps are shared with other tasks so they are not housed in the `cosine_bell` work directory. Instead, they are in work directories like: @@ -146,11 +155,39 @@ field remains at the initial velocity $u_0$. ## time step and run duration -The time step for forward integration is determined by multiplying the -resolution by `dt_per_km`, so that coarser meshes have longer time steps. -You can alter this before setup (in a user config file) or before running the -task (in the config file in the work directory). The run duration is 24 -days. +This task uses the Runge-Kutta 4th-order (RK4) time integrator. The time step +for forward integration is determined by multiplying the resolution by a config +option, `rk4_dt_per_km`, so that coarser meshes have longer time steps. You can +alter this before setup (in a user config file) or before running the task (in +the config file in the work directory). + +```cfg +# config options for spherical convergence tests +[spherical_convergence] + +# time integrator: {'split_explicit', 'RK4'} +time_integrator = RK4 + +# RK4 time step per resolution (s/km), since dt is proportional to resolution +rk4_dt_per_km = 3.0 +``` + +The run duration and output interval are the period for advection to make a +full rotation around the globe, 24 days: + +```cfg +# config options for spherical convergence tests +[spherical_convergence] + +# Run duration in days +run_duration = ${cosine_bell:vel_pd} + +# Output interval in days +output_interval = ${cosine_bell:vel_pd} +``` + +Her, `${cosine_bell:vel_pd}` means that the same value is used as in the +option `vel_pd` in section `[cosine_bell]`, see below. ## config options @@ -160,9 +197,6 @@ The `cosine_bell` config options include: # options for cosine bell convergence test case [cosine_bell] -# time step per resolution (s/km), since dt is proportional to resolution -dt_per_km = 30 - # the constant temperature of the domain temperature = 15.0 @@ -221,9 +255,6 @@ norm_args = {'vmin': 0., 'vmax': 1.} # colorbar_ticks = np.linspace(0., 1., 9) ``` -The `dt_per_km` option `[cosine_bell]` is used to control the time step, as -discussed above in more detail. - The 7 options from `temperature` to `vel_pd` are used to control properties of the cosine bell and the rest of the sphere, as well as the advection.