(dev-mesh-unified-base-mesh)= # 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 {py:func}`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`: - {py:class}`polaris.tasks.mesh.spherical.unified.base_mesh.BaseMeshTask` at `mesh/spherical/unified//base_mesh/task`. ## Task structure and shared steps `BaseMeshTask` calls {py:func}`polaris.tasks.mesh.spherical.unified.base_mesh.get_unified_base_mesh_steps` with `include_viz=True`, which calls the sizing-field step factory (see {ref}`dev-mesh-unified-sizing-field`) 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 {py:class}`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 {py:class}`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.