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 ccrsRelative 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_primaluxds_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)")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']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()
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.