Refined-submesh (coarse/fine companion) investigation#192
Open
lmoresi wants to merge 2 commits into
Open
Conversation
Second submesh flavour alongside the existing rock/air subdomain (extract_region): pull any level out of a mesh's nested refinement hierarchy as a standalone solver-ready uw.Mesh with full parent lineage, and transfer fields back and forth. Contract: refine-DM mode only. Available only when a genuine nested refinement hierarchy exists; transfer uses PETSc's nested interpolator/injector (exact, parallel-local, no point location). No geometric/General fallback, no KDTree -- a non-refined mesh raises rather than silently degrading. Key construction: build the transfer pair by refining a single-field clone of the coarse level (dm_f = dm_c.refine()), so refine() itself establishes the setCoarseDM linkage and regular-refinement flag and createInterpolation/createInjection take the nested exact branch. Independently cloning two hierarchy levels breaks this and petsc4py exposes no setter to restore it. Includes: - refined_pair_prototype.py: coarsened_companion + prolongate/restrict/ inject/sample, refine-DM contract guard - example_refined_companion.py: sibling of test_region_ds_submesh.py, same annulus+radial-buoyancy Stokes so the two submesh flavours are directly comparable - test_refined_pair_solver.py: gating test (extract -> labels -> solve -> map back), round-trip recovers coarse to ~6e-16 - test_refined_pair_contract.py: enforces no-refinement -> raises - probe_hierarchy_labels.py: boundary labels survive every hierarchy level (box, annulus-with-internal-boundary, spherical shell) - submesh-solver-architecture.md: "Two Submesh Flavours" section No source changes; investigation lives under docs/examples/. Underworld development team with AI support from Claude Code
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds an investigation bundle under docs/examples/submesh_investigation/ for a second “submesh flavour”: extracting a coarser refinement-hierarchy level as a solver-ready uw.Mesh companion to a refined parent mesh, with nested PETSc FE transfer operators for mapping fields between levels.
Changes:
- Adds a prototype implementation (
coarsened_companion+ prolongate/restrict/inject/sample) that enforces a refine-DM-only contract (no geometric/KDTree fallback). - Adds runnable gating/contract scripts plus an annulus Stokes example to validate solver viability and round-trip transfer accuracy.
- Updates the developer design doc to describe “subdomain vs resolution-level” submesh approaches together.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/examples/submesh_investigation/refined_pair_prototype.py | Prototype companion-mesh extraction and nested PETSc interpolation/injection transfer utilities. |
| docs/examples/submesh_investigation/test_refined_pair_solver.py | Gating script: solve Stokes on a coarse companion and prolongate/sample back to fine. |
| docs/examples/submesh_investigation/test_refined_pair_contract.py | Contract script: non-refined meshes must raise (no fallback), excessive levels must raise. |
| docs/examples/submesh_investigation/example_refined_companion.py | Annulus Stokes example mirroring the subdomain submesh example for direct comparison. |
| docs/examples/submesh_investigation/probe_hierarchy_labels.py | Probe script to confirm boundary labels persist across hierarchy levels. |
| docs/developer/design/submesh-solver-architecture.md | New section documenting “Two Submesh Flavours” and the refine-DM-only contract. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+253
to
+259
| def prolongate(companion, coarse_var, fine_var): | ||
| """coarse -> fine, FE prolongation (fills all fine DOFs).""" | ||
| matInterp, _, dm_c, dm_f = _get_interpolation(companion, coarse_var, fine_var) | ||
| gc = _var_global_vec(coarse_var, dm_c) | ||
| gf = dm_f.createGlobalVector() | ||
| matInterp.mult(gc, gf) | ||
| _write_global_vec_to_var(fine_var, dm_f, gf) |
Comment on lines
+195
to
+200
| var._set_vec(available=True) | ||
| var.vec.array[...] = loc.array | ||
| var._lvec.array[...] = var.vec.array | ||
| sfdm.restoreLocalVec(loc) | ||
| if hasattr(var, "_canonical_data"): | ||
| var._canonical_data = None |
Comment on lines
+231
to
+234
| key = (id(coarse_var), id(fine_var)) | ||
| if key in companion._interp_cache: | ||
| return companion._interp_cache[key] | ||
|
|
Comment on lines
+127
to
+128
| companion._interp_cache = {} # (id(cv), id(fv)) -> (Mat, Vec) | ||
| companion._inject_cache = {} # (id(cv), id(fv)) -> Mat (MATSCATTER) |
|
|
||
| from enum import Enum | ||
|
|
||
| import numpy as np |
- Drop unused numpy import - Fix stale cache-value-shape comments to match the stored tuples - Key interp/inject caches by variable objects via nested WeakKeyDictionary instead of id() (id reuse after GC could alias a stale entry with a mismatched FE layout) - Invalidate _canonical_data AND _cached_data_array on direct PETSc writes (matches the core _re_extract_from_parent invalidation; .array /.data are copies that otherwise go stale) - Destroy scratch global Vecs after each transfer (try/finally) so repeated per-timestep transfers don't accumulate PETSc objects; cached Mat/DM are built once and kept Verified: contract, gating (round-trip 5.9e-16) and annulus example all still pass unchanged. Underworld development team with AI support from Claude Code
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the second submesh flavour alongside the existing rock/air subdomain (
extract_region): pull any level out of a mesh's nested refinement hierarchy as a standalone, solver-readyuw.Meshwith full parent lineage, then transfer fields back and forth — same usage pattern as a subdomain submesh (get a mesh → build a solver → map back).This is an investigation deliverable under
docs/examples/submesh_investigation/. No source changes; zero regression risk to the existing rock/air path or core code. Promotion ofcoarsened_companioninto theMeshAPI proper is a follow-up.Design contract: refine-DM mode only
refinement >= 1).DMRefinepreserves the coarse partition), no geometric point location.Key construction detail: the transfer pair is built by refining a single-field clone of the coarse level (
dm_f = dm_c.refine()), sorefine()itself establishes thesetCoarseDMlinkage and regular-refinement flag andcreateInterpolation/createInjectiontake the exact nested branch (plex.c:10328). Independently cloning two hierarchy levels breaks this and petsc4py exposes no setter to restore it.What's included
refined_pair_prototype.py—coarsened_companion+prolongate/restrict/inject/sample, refine-DM contract guardexample_refined_companion.py— sibling oftest_region_ds_submesh.py, same annulus + radial-buoyancy Stokes so the two submesh flavours are directly comparabletest_refined_pair_solver.py— gating test: extract → labels intact → Stokes solves on the companion → map back; round-trip recovers the coarse solution to ~6e-16test_refined_pair_contract.py— enforces no-refinement → raises (no fallback)probe_hierarchy_labels.py— boundary labels survive every hierarchy level (box, annulus-with-internal-boundary, spherical shell)docs/developer/design/submesh-solver-architecture.md— new "Two Submesh Flavours" section presenting subdomain vs resolution level togetherEvidence
sample ∘ prolongate) recovers the coarse solution to ~6e-16 — with the geometric escape hatch deliberately removed, proving the transfer is genuinely the nested operatorInternal) confirmed present on every coarse hierarchy level for three mesh familiesTest plan
test_refined_pair_solver.py— all stages passtest_refined_pair_contract.py— contract enforcedexample_refined_companion.py— runs clean, parallel to rock/air exampleprobe_hierarchy_labels.py— labels survive coarseningUnderworld development team with AI support from Claude Code