Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

UXarray logo

MPAS Atmosphere

This recipe demonstrates how to create visualizations using 30km MPAS Atmosphere model output. We’ll explore techniques for visualizing atmospheric variables on both the primal and dual MPAS grids, focusing on relative humidity and vorticity at the 200hPa pressure level.

Visualization Objectives

This recipe will guide you through:

  • Creating polygon plots using the MPAS primal grid to visualize relative humidity at 200hPa

  • Developing polygon plots using the MPAS dual grid to visualize vorticity at 200hPa

  • Understanding the differences between primal and dual grid visualizations in MPAS


import uxarray as ux
import cartopy.crs as ccrs
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...

Relative Humidity

For visualizing relative humidity, we use the Primal MPAS grid, which is composed of hexagons.

grid_path = "../../meshfiles/x1.655362.grid.nc"
data_path = "../../meshfiles/x1.655362.data.nc"

uxds_primal = ux.open_dataset(grid_path, data_path)
uxds_primal
Loading...
uxds_primal['relhum_200hPa'][0].plot(projection=ccrs.Robinson(), backend='matplotlib', pixel_ratio=4.0, features=['coastline'], width=1000, height=500, cmap='viridis', title="30km Relative Humidity (MPAS Primal Grid)")
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...

Vorticity

For visualizing relative humidity, we use the Dual MPAS grid, which is composed of triangles.

grid_path = "../../meshfiles/x1.655362.grid.nc"
data_path = "../../meshfiles/x1.655362.data.nc"

uxds_dual = ux.open_dataset(grid_path, data_path, use_dual=True)
uxds_dual['vorticity_200hPa']
Loading...
uxds_dual['vorticity_200hPa'][0].plot(projection=ccrs.Robinson(), rasterize=True, backend='matplotlib', pixel_ratio=4.0, features=['coastline'], width=1000, height=500, cmap='coolwarm', title="30km Vorticity (MPAS Dual Grid)", clim=(-0.0001,0.0001))
/home/runner/micromamba/envs/unstructured-grid-viz-cookbook-dev/lib/python3.14/site-packages/antimeridian/_implementation.py:734: FixWindingWarning: The exterior ring of this shape is wound clockwise. Since this is a common error in real-world geometries, this package is reversing the exterior coordinates of the input shape before running its algorithm. If you know that your input shape is correct (i.e. if your data encompasses both poles), pass `fix_winding=False`. To silence this warning while keeping the fix, pass `fix_winding=True`.
  FixWindingWarning.warn()
Loading...

MPAS Dual & Primal Grids

The Model for Prediction Across Scales (MPAS) utilizes two complementary grid structures for atmospheric modeling: the primal grid and the dual grid. The primal grid consists of hexagonal cells that form the primary computational mesh, while the dual grid is composed of triangular cells that connect the centers of the primary hexagons.

In the primal grid structure, scalar quantities like relative humidity are naturally represented at the centers of the hexagonal cells. The dual grid, with its triangular elements, is particularly well-suited for vector quantities and derived fields such as vorticity.

Below, we visualize both grid structures to illustrate their complementary nature.

(uxds_primal.uxgrid.subset.bounding_box(lon_bounds = (-1, 1), lat_bounds=(-0.5, 0.5)).plot(title="MPAS Primal Grid Structure", ) + 
 uxds_dual.uxgrid.subset.bounding_box(lon_bounds = (-1, 1), lat_bounds=(-0.5, 0.5)).plot(title="MPAS Dual Grid Structure")).cols(1).opts(fig_size=200)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/micromamba/envs/unstructured-grid-viz-cookbook-dev/lib/python3.14/site-packages/holoviews/core/dimension.py:1491, in Dimensioned._repr_mimebundle_(self, include, exclude)
   1484 def _repr_mimebundle_(self, include=None, exclude=None):
   1485     """Resolves the class hierarchy for the class rendering the
   1486     object using any display hooks registered on Store.display
   1487     hooks.  The output of all registered display_hooks is then
   1488     combined and returned.
   1489 
   1490     """
-> 1491     return Store.render(self)

File ~/micromamba/envs/unstructured-grid-viz-cookbook-dev/lib/python3.14/site-packages/holoviews/core/options.py:1517, in Store.render(cls, obj)
   1515 data, metadata = {}, {}
   1516 for hook in hooks:
-> 1517     ret = hook(obj)
   1518     if ret is None:
   1519         continue

File ~/micromamba/envs/unstructured-grid-viz-cookbook-dev/lib/python3.14/site-packages/holoviews/ipython/display_hooks.py:330, in pprint_display(obj)
    328 if not ip.display_formatter.formatters["text/plain"].pprint:
    329     return None
--> 330 return display(obj, raw_output=True)

File ~/micromamba/envs/unstructured-grid-viz-cookbook-dev/lib/python3.14/site-packages/holoviews/ipython/display_hooks.py:301, in display(obj, raw_output, **kwargs)
    299 elif isinstance(obj, (Layout, NdLayout, AdjointLayout)):
    300     with option_state(obj):
--> 301         output = layout_display(obj)
    302 elif isinstance(obj, (HoloMap, DynamicMap)):
    303     with option_state(obj):

File ~/micromamba/envs/unstructured-grid-viz-cookbook-dev/lib/python3.14/site-packages/holoviews/ipython/display_hooks.py:193, in display_hook.<locals>.wrapped(element)
    191 try:
    192     max_frames = OutputSettings.options["max_frames"]
--> 193     mimebundle = fn(element, max_frames=max_frames)
    194     if mimebundle is None:
    195         return {}, {}

File ~/micromamba/envs/unstructured-grid-viz-cookbook-dev/lib/python3.14/site-packages/holoviews/ipython/display_hooks.py:261, in layout_display(layout, max_frames)
    258     max_frame_warning(max_frames)
    259     return None
--> 261 return render(layout)

File ~/micromamba/envs/unstructured-grid-viz-cookbook-dev/lib/python3.14/site-packages/holoviews/ipython/display_hooks.py:62, in render(obj, **kwargs)
     59 if renderer.fig == "pdf":
     60     renderer = renderer.instance(fig="png")
---> 62 return renderer.components(obj, **kwargs)

File ~/micromamba/envs/unstructured-grid-viz-cookbook-dev/lib/python3.14/site-packages/holoviews/plotting/renderer.py:430, in Renderer.components(self, obj, fmt, comm, **kwargs)
    428     plot = obj
    429 else:
--> 430     plot, fmt = self._validate(obj, fmt)
    432 if not isinstance(plot, Viewable):
    433     html = self._figure_data(plot, fmt, as_script=True, **kwargs)

File ~/micromamba/envs/unstructured-grid-viz-cookbook-dev/lib/python3.14/site-packages/holoviews/plotting/renderer.py:361, in Renderer._validate(self, obj, fmt, **kwargs)
    359     plot = HoloViewsPane(obj, center=self.center, backend=self.backend, renderer=self)
    360 else:
--> 361     plot = self.get_plot(obj, renderer=self, **kwargs)
    363 all_formats = set(fig_formats + holomap_formats)
    364 if fmt not in all_formats:

File ~/micromamba/envs/unstructured-grid-viz-cookbook-dev/lib/python3.14/site-packages/holoviews/plotting/renderer.py:291, in Renderer.get_plot(self_or_cls, obj, doc, renderer, comm, **kwargs)
    287     defaults = [kd.default for kd in plot.dimensions]
    288     init_key = tuple(
    289         v if d is None else d for v, d in zip(plot.keys[0], defaults, strict=None)
    290     )
--> 291     plot.update(init_key)
    292 else:
    293     plot = obj

File ~/micromamba/envs/unstructured-grid-viz-cookbook-dev/lib/python3.14/site-packages/holoviews/plotting/mpl/plot.py:335, in MPLPlot.update(self, key)
    333 def update(self, key):
    334     if len(self) == 1 and key in (0, self.keys[0]) and not self.drawn:
--> 335         return self.initialize_plot()
    336     return self.__getitem__(key)

File ~/micromamba/envs/unstructured-grid-viz-cookbook-dev/lib/python3.14/site-packages/holoviews/plotting/mpl/plot.py:64, in mpl_rc_context.<locals>.wrapper(self, *args, **kwargs)
     62 def wrapper(self, *args, **kwargs):
     63     with _rc_context(self.fig_rcparams):
---> 64         return f(self, *args, **kwargs)

File ~/micromamba/envs/unstructured-grid-viz-cookbook-dev/lib/python3.14/site-packages/holoviews/plotting/mpl/plot.py:1283, in LayoutPlot.initialize_plot(self)
   1277 traverse_fn = lambda x: x.handles.get("bbox_extra_artists", None)
   1278 extra_artists = list(
   1279     chain.from_iterable(
   1280         artists for artists in self.traverse(traverse_fn) if artists is not None
   1281     )
   1282 )
-> 1283 fix_aspect(
   1284     fig,
   1285     self.rows,
   1286     self.cols,
   1287     title_obj,
   1288     extra_artists,
   1289     vspace=self.vspace * self.fig_scale,
   1290     hspace=self.hspace * self.fig_scale,
   1291 )
   1292 colorbars = self.traverse(specs=[lambda x: hasattr(x, "colorbar")])
   1293 for cbar_plot in colorbars:

File ~/micromamba/envs/unstructured-grid-viz-cookbook-dev/lib/python3.14/site-packages/holoviews/plotting/mpl/util.py:288, in fix_aspect(fig, nrows, ncols, title, extra_artists, vspace, hspace)
    286 if title and title.get_text():
    287     offset = title.get_window_extent().height / fig.dpi
--> 288 fig.set_size_inches(w, (w * aspect) + offset)
    290 # Redraw and adjust title position if defined
    291 fig.canvas.draw()

File ~/micromamba/envs/unstructured-grid-viz-cookbook-dev/lib/python3.14/site-packages/matplotlib/figure.py:3167, in Figure.set_size_inches(self, w, h, forward)
   3165 size = np.array([w, h])
   3166 if not np.isfinite(size).all() or (size < 0).any():
-> 3167     raise ValueError(f'figure size must be positive finite not {size}')
   3168 self.bbox_inches.p1 = size
   3169 if forward:

ValueError: figure size must be positive finite not [ 8. nan]
:Layout .Path.I :Path [Longitude,Latitude] .Path.II :Path [Longitude,Latitude]

The visualization below demonstrates the intricate geometric relationship between MPAS primal and dual grids. By overlaying both grid structures, we can observe how the vertices of each hexagonal cell in the primal grid serve as the cell centers for the triangular elements of the dual grid. Conversely, the vertices of the triangular cells in the dual grid correspond to the centers of the hexagonal cells in the primal grid.

(uxds_primal.uxgrid.subset.bounding_box(lon_bounds = (-1, 1), lat_bounds=(-0.5, 0.5)).plot() * 
 uxds_dual.uxgrid.subset.bounding_box(lon_bounds = (-1, 1), lat_bounds=(-0.5, 0.5)).plot()).opts(fig_size=200, title="Primal & Dual Grid Together")

3.75km Visualization

For another example of MPAS grid visualization, readers can refer to the UXarray documentation showcasing a 3.75km resolution grid

This example demonstrates MPAS visualization at a higher resolution compared to the 30km grid shown in this recipe.