Unified Base-Mesh Steps and Tasks

This section of the Developer’s guide covers the code for creating the final MPAS base mesh in the unified-mesh workflow. The mesh is generated by JIGSAW using a cell-width (sizing) field and retained river polylines as geometric constraints, so that river channels are aligned with mesh edges.

Available tasks

The helper polaris.tasks.mesh.spherical.unified.base_mesh.add_unified_base_mesh_tasks() registers one standalone task for each mesh name in polaris.mesh.spherical.unified.UNIFIED_MESH_NAMES:

Task structure and shared steps

BaseMeshTask calls polaris.tasks.mesh.spherical.unified.base_mesh.get_unified_base_mesh_steps() with include_viz=True, which calls the sizing-field step factory (see Sizing-Field Steps and Tasks) to obtain all upstream steps and then creates the base-mesh build step.

The task workdir contains symlinks for all upstream coastline, river, and sizing-field steps plus:

  • base_mesh — the shared polaris.mesh.spherical.unified.UnifiedBaseMeshStep

  • base_mesh_viz — the shared VizBaseMeshStep (when include_viz=True)

Implementation map

polaris.mesh.spherical.unified.UnifiedBaseMeshStep

The core mesh-generation step lives in the reusable polaris.mesh layer (not under polaris.tasks) so it can be imported by downstream component tasks without depending on the task-tree structure.

UnifiedBaseMeshStep.setup() links the sizing field and clipped river network from the upstream steps, and sets self.cell_width to the finest configured cell width (used by the parent class for JIGSAW parameter setup).

UnifiedBaseMeshStep.build_cell_width_lat_lon() reads lon, lat, and cell_width directly from sizing_field.nc rather than computing a uniform value, so the full variable-resolution field drives the mesh generation.

UnifiedBaseMeshStep.make_jigsaw_mesh() overrides the parent class to additionally pass clipped_river_network.geojson to JIGSAW as polyline geometric constraints, ensuring that mesh edges align with retained river centerlines.

The snap tolerance used to pre-process these constraints is read directly from the base_mesh_simplify_tolerance_km option in the [river_network] config section (no fallback). Using the full simplify tolerance (rather than a fraction of it) ensures that constraint vertices closer than the mesh’s spatial resolution are merged before reaching JIGSAW, preventing thin-sliver Delaunay triangles that would otherwise produce degenerate MPAS cell polygons.

steps.py and viz.py (task layer)

get_unified_base_mesh_steps() in polaris.tasks.mesh.spherical.unified.base_mesh calls the sizing-field step factory, creates or retrieves the UnifiedBaseMeshStep via component.get_or_create_shared_step, and optionally creates VizBaseMeshStep.

VizBaseMeshStep reads the MPAS mesh, the sizing field, and the clipped river network, and writes:

  • a global resolution map on the MPAS mesh;

  • a dcEdge map on the MPAS mesh;

  • a sizing-field map on the lat-lon source grid; and

  • a river-alignment figure showing the retained river geometry overlaid on the mesh resolution.

A plain-text debug_summary.txt records key scalar diagnostics such as cell count and min/max resolution.

Vertex-snapping and degenerate-polygon prevention

_read_geojson_line_mesh() applies a three-layer strategy to prevent JIGSAW from receiving constraint geometry that would produce degenerate MPAS Voronoi cells (circumcenters of Delaunay triangles that coincide exactly):

  1. First-pass union-find snap (via _union_find): raw constraint vertices from all polylines within snap_tolerance_km of each other are merged into a single cluster; the cluster centroid becomes the representative vertex. snap_tolerance_km equals base_mesh_simplify_tolerance_km.

  2. Second-pass centroid snap (via _merge_close_centroids): after the first pass, cluster centroids are re-checked at the same tolerance. A multi-vertex cluster whose centroid drifted toward a neighbouring cluster will be merged in this pass.

  3. Edge deduplication: after both snapping passes, constraint edges where the start and end vertex map to the same cluster are removed (consecutive same-cluster entries), and any pair of edges that connect the same two clusters regardless of direction are deduplicated.

The earth radius used for the radian snap tolerance always comes from get_constant('mean_radius') in the Physical Constants Dictionary; it is never hard-coded.

Configuration plumbing

get_unified_base_mesh_steps() loads the mesh config with get_unified_base_mesh_config(mesh_name), which extends the standard unified mesh config with base_mesh.cfg defaults and sets the [spherical_mesh] prefix, min_cell_width, and max_cell_width fields derived from the sizing-field settings.

UnifiedBaseMeshStep inherits all JIGSAW options from polaris.mesh.spherical.QuasiUniformSphericalMeshStep and the [spherical_mesh] section of the config.

Extension points

  • Adding a new named mesh. Create a .cfg file under polaris.mesh.spherical.unified. The mesh name is inferred from the filename; UNIFIED_MESH_NAMES is populated automatically.

  • Customizing river alignment. Override make_jigsaw_mesh() in a subclass of UnifiedBaseMeshStep to add additional JIGSAW geometry constraints.

  • Changing the sizing field. Modify the [sizing_field] options in the mesh-specific config or override BuildSizingFieldStep.run() in the sizing field package.

Test coverage

Unit tests in tests/mesh/spherical/unified/test_base_mesh.py validate the UnifiedBaseMeshStep setup and JIGSAW geometry construction.

Unit tests in tests/mesh/spherical/unified/test_base_mesh_tasks.py validate task registration for all named unified meshes and that get_unified_base_mesh_steps creates the expected shared steps.