Skip to content

Commit a8bdd58

Browse files
authored
Merge pull request #333 from xylar/write-cdf5
Switch to writing files in CDF5 (`NETCDF3_64BIT_DATA`) format
2 parents 224a3c6 + e56ec0b commit a8bdd58

File tree

10 files changed

+135
-22
lines changed

10 files changed

+135
-22
lines changed

deploy/default.cfg

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ mpi = nompi
2323
geometric_features = 1.6.1
2424
mache = 1.31.0
2525
conda_moab = 5.5.1
26-
mpas_tools = 1.1.0
26+
mpas_tools = 1.2.0
2727
otps = 2021.10
2828
parallelio = 2.6.6
2929

3030
# versions of conda or spack packages (depending on machine type)
3131
esmf = 8.8.1
3232
metis = 5.1.0
3333
netcdf_c = 4.9.2
34-
netcdf_fortran = 4.6.1
34+
netcdf_fortran = 4.6.2
3535
pnetcdf = 1.14.0
3636

3737
# versions of spack packages

docs/developers_guide/framework/model.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,3 +430,97 @@ call {py:func}`polaris.model_step.make_graph_file()` to produce a graph file
430430
from an MPAS mesh file. Optionally, you can provide the name of an MPAS field
431431
on cells in the mesh file that gives different weight to different cells
432432
(`weight_field`) in the partitioning process.
433+
434+
## Detailed Documentation: polaris.namelist, polaris.streams, and polaris.yaml
435+
436+
### `polaris.namelist`
437+
438+
This module provides utilities for reading, parsing, updating, and writing
439+
MPAS-style namelist files. It is used to manage namelist options for E3SM
440+
components.
441+
442+
**Key Functions:**
443+
- `parse_replacements(package, namelist)`: Reads a namelist file from a
444+
package and returns a dictionary of option replacements.
445+
- `ingest(defaults_filename)`: Reads a full namelist file and returns a nested
446+
dictionary of records and options.
447+
- `replace(namelist, replacements)`: Applies replacements to a namelist
448+
dictionary.
449+
- `write(namelist, filename)`: Writes a nested namelist dictionary to a file.
450+
451+
**Example Usage:**
452+
```python
453+
from polaris import namelist
454+
455+
replacements = namelist.parse_replacements('my_package', 'namelist.forward')
456+
namelist_dict = namelist.ingest('namelist.defaults')
457+
updated = namelist.replace(namelist_dict, replacements)
458+
namelist.write(updated, 'namelist.input')
459+
```
460+
461+
### `polaris.streams`
462+
463+
This module provides tools for reading, updating, and writing MPAS streams XML
464+
files, including support for Jinja2 templating.
465+
466+
**Key Functions:**
467+
- `read(package, streams_filename, tree=None, replacements=None)`: Reads a
468+
streams XML file (optionally as a Jinja2 template) and returns an XML tree.
469+
- `write(streams, out_filename)`: Writes a streams XML tree to a file.
470+
- `update_tree(tree, new_tree)`: Updates an existing streams tree with new
471+
streams.
472+
- `set_default_io_type(tree, io_type='pnetcdf,cdf5')`: Sets the `io_type`
473+
attribute for output streams if not already set.
474+
475+
**Example Usage:**
476+
```python
477+
from polaris import streams
478+
479+
tree = streams.read('my_package', 'streams.forward')
480+
streams.write(tree, 'streams.xml')
481+
```
482+
483+
### `polaris.yaml`
484+
485+
This module provides a class for reading, writing, and updating YAML
486+
configuration files for E3SM components, including support for Jinja2
487+
templating and conversion between YAML and MPAS namelist/streams formats.
488+
489+
**Key Classes and Functions:**
490+
- `PolarisYaml`: Main class for managing YAML configs.
491+
- `PolarisYaml.read(filename, package=None, replacements=None, ...)`: Reads
492+
a YAML file (optionally as a Jinja2 template).
493+
- `PolarisYaml.update(configs=None, options=None, quiet=True)`: Updates the
494+
config with new options.
495+
- `PolarisYaml.write(filename)`: Writes the config to a YAML file.
496+
- `mpas_namelist_and_streams_to_yaml(...)`: Converts MPAS namelist and streams
497+
files to a YAML config.
498+
499+
**Example Usage:**
500+
```python
501+
from polaris.yaml import PolarisYaml
502+
503+
yaml_cfg = PolarisYaml.read('config.yaml')
504+
yaml_cfg.update(options={'config_dt': '00:10:00'})
505+
yaml_cfg.write('updated_config.yaml')
506+
```
507+
508+
**See also:**
509+
Refer to the API documentation for each module for a full list of functions
510+
and classes:
511+
* {py:func}`polaris.namelist.parse_replacements`
512+
* {py:func}`polaris.namelist.ingest`
513+
* {py:func}`polaris.namelist.replace`
514+
* {py:func}`polaris.namelist.write`
515+
* {py:func}`polaris.streams.read`
516+
* {py:func}`polaris.streams.write`
517+
* {py:func}`polaris.streams.update_defaults`
518+
* {py:func}`polaris.streams.update_tree`
519+
* {py:func}`polaris.streams.set_default_io_type`
520+
* {py:class}`polaris.yaml.PolarisYaml`
521+
* {py:meth}`polaris.yaml.PolarisYaml.read`
522+
* {py:meth}`polaris.yaml.PolarisYaml.update`
523+
* {py:meth}`polaris.yaml.PolarisYaml.write`
524+
* {py:func}`polaris.yaml.mpas_namelist_and_streams_to_yaml`
525+
* {py:func}`polaris.yaml.yaml_to_mpas_streams`
526+
* {py:func}`polaris.yaml.main_mpas_to_yaml`

docs/developers_guide/organization/steps.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Some steps are shared between multiple tasks to avoid redundant computation and
2020
to ensure consistency of results. Shared steps are created at the highest
2121
directory level common to all tasks that use them. To facilitate this, the
2222
`Component` class provides the method
23-
{py:method}`polaris.Component.get_or_create_shared_step()`.
23+
{py:meth}`polaris.Component.get_or_create_shared_step()`.
2424

2525
This function checks if a step already exists in a component's `steps`
2626
dictionary under a given subdirectory. If it does, it returns the existing

docs/users_guide/ocean/tasks/overflow.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
# overflow
44

55
The ``ocean/overflow`` test group induces a density current flowing down a
6-
continental slope and includes two test cases.
7-
8-
(ocean-category-of-task-task-name)=
6+
continental slope and includes two test cases.
97

108
## suppported models
119

@@ -31,7 +29,7 @@ The mesh is planar and the resolution is specified by config option
3129
`overflow:resolution`, which defaults to 1 km.
3230

3331
The horizontal dimensions of the domain are set by config options
34-
`overflow:lx` and `overflow:ly`, defaulting to 200 km by 40 km.
32+
`overflow:lx` and `overflow:ly`, defaulting to 200 km by 40 km.
3533

3634
The domain is periodic on the zonal boundaries and solid on the meridional
3735
boundaries.

docs/users_guide/ocean/tasks/template.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
Description of common characteristics of the tasks.
66

7-
(ocean-category-of-task-task-name)=
8-
97
## suppported models
108

119
This section should have one of the following lines, as appropriate, or if
@@ -18,6 +16,8 @@ These tasks support only MPAS-Ocean.
1816

1917
These tasks support only Omega.
2018

19+
(ocean-category-of-task-task-name)=
20+
2121
## task_name
2222

2323
In cases where the test cases within a category share many characteristics,

polaris/default.cfg

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,11 @@ login_cores = 4
3636
[io]
3737

3838
# the NetCDF file format: NETCDF4, NETCDF4_CLASSIC, NETCDF3_64BIT, or
39-
# NETCDF3_CLASSIC
40-
format = NETCDF3_64BIT
41-
42-
# the NetCDF output engine: netcdf4 or scipy
43-
# the netcdf4 engine is not performing well on Chrysalis and Anvil, so we will
44-
# try scipy for now. If we can switch to NETCDF4 format, netcdf4 will be
45-
# required
46-
engine = scipy
39+
# NETCDF3_CLASSIC, NETCDF3_64BIT_DATA
40+
format = NETCDF3_64BIT_DATA
41+
42+
# the NetCDF output engine: netcdf4, h5netcdf or scipy
43+
engine = netcdf4
4744

4845

4946
# Config options related to creating a job script

polaris/mesh/spherical.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
import jigsawpy
44
import matplotlib.pyplot as plt
55
import numpy as np
6-
import xarray
6+
import xarray as xr
77
from jigsawpy.savejig import savejig
88
from mpas_tools.cime.constants import constants
9+
from mpas_tools.io import write_netcdf
910
from mpas_tools.logging import check_call
1011
from mpas_tools.mesh.creation.jigsaw_to_netcdf import jigsaw_to_netcdf
1112
from mpas_tools.ocean.inject_meshDensity import inject_spherical_meshDensity
@@ -70,7 +71,7 @@ def save_and_plot_cell_width(self, lon, lat, cell_width):
7071
m x n array of cell width in km
7172
"""
7273
section = self.config['spherical_mesh']
73-
da = xarray.DataArray(
74+
da = xr.DataArray(
7475
cell_width,
7576
dims=['lat', 'lon'],
7677
coords={'lat': lat, 'lon': lon},
@@ -118,12 +119,17 @@ def run(self):
118119

119120
logger.info('Convert from triangles to MPAS mesh')
120121

121-
args = ['MpasMeshConverter.x', 'mesh_triangles.nc', mpas_mesh_filename]
122+
tmp_mesh_filename = 'mpas_mesh_netcdf4.nc'
123+
args = ['MpasMeshConverter.x', 'mesh_triangles.nc', tmp_mesh_filename]
122124
check_call(args=args, logger=logger)
123125

126+
# open the mesh and rewrite it in the desired NetCDF format
127+
ds_mesh = xr.open_dataset(tmp_mesh_filename)
128+
write_netcdf(ds_mesh, mpas_mesh_filename)
129+
124130
if section.getboolean('add_mesh_density'):
125131
logger.info('Add meshDensity into the mesh file')
126-
ds = xarray.open_dataset('cellWidthVsLatLon.nc')
132+
ds = xr.open_dataset('cellWidthVsLatLon.nc')
127133
inject_spherical_meshDensity(
128134
ds.cellWidth.values,
129135
ds.lon.values,

polaris/model_step.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,9 @@ def _write_model_config(self):
699699
'Trying to write a streams file but no '
700700
'streams XML tree was created.'
701701
)
702+
io_format = self.config.get('io', 'format')
703+
if io_format == 'NETCDF3_64BIT_DATA':
704+
polaris.streams.set_default_io_type(self._streams_tree)
702705
polaris.streams.write(self._streams_tree, streams_filename)
703706
# set these back to None because we don't need to keep them around
704707
# and the streams tree can't be pickled

polaris/streams.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,18 @@ def _update_element(new_child, elements):
197197
if not found:
198198
# add a deep copy of the element
199199
elements.append(deepcopy(new_child))
200+
201+
202+
def set_default_io_type(tree, io_type='pnetcdf,cdf5'):
203+
"""
204+
Set io_type attribute for all <stream> and <immutable_stream>
205+
elements if not already set, except for immutable_stream with name 'mesh'.
206+
"""
207+
streams = next(tree.iter('streams'))
208+
all_streams = streams.findall('stream') + streams.findall(
209+
'immutable_stream'
210+
)
211+
for stream in all_streams:
212+
stream_type = stream.attrib.get('type')
213+
if 'io_type' not in stream.attrib and 'output' in stream_type:
214+
stream.attrib['io_type'] = io_type

polaris/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.8.0-alpha.1'
1+
__version__ = '0.8.0-alpha.2'

0 commit comments

Comments
 (0)