remove_embedded: the fault-lifecycle removal primitive (no redistribution, ever) - #522
Conversation
…d (lifecycle half 1) The design ruling this serves: a fault is ADDED to the running distributed mesh and a spent one DELETED, with no redistribution ever. Removal is the inverse placement: the object's labels seed the carve (cells = a thin volume, faces = a sheet; a zone's companion _skin label goes with it), the cavity is refilled by a PLAIN gmsh fill at the background scale — erasing the layer-scale grading — and the labels vanish with their points (gated: globally empty afterwards). Every OTHER embedded surface is held and count-gated exactly as a placement holds it; the interface readers gain an exclude= parameter so a removal does not hold its own object against itself. Parallel via the same gather-first surgery; the object's geometry is allgathered from its labels first, so removal works whatever the current distribution. Composing the lifecycle surfaced three robustness fixes, all measured: - The sheet carve gains the volume carve's shell-growth: a sheet placed into a REFILLED region can pinch against the new connectivity where the same sheet in the original mesh did not. The growth is now one shared helper (_closed_shell_3d) used by every 3-D carve — the flagged unification debt, paid. - The sheet carve gains the centroid rule: the plane-straddle test covers triangle interiors, and a refill's locally-small cells can straddle a rim EDGE with every vertex outside reach (gmsh: "a segment and a facet intersect"). Any cell whose centre is within reach of the bounded sheet is dropped. - Surgery-rank catches broadened to Exception: a raw gmsh error is a plain Exception, and an uncaught raise on the surgery rank is a HANG for its peers — every failure must become a collective refusal. - The removal carve does NOT use the centroid rule (nothing is being embedded, there are no straddlers to catch); with it, a sheet removal reached the domain wall that the sheet's own placement cleared. Round trip measured at np=1..4: add zone -> FE-exact (9e-16) -> remove -> FE-exact (4e-11), cell count back within ~8% of base -> add sheet in the SAME region -> remove again -> FE-exact. Cell counts after a refill are partition-sensitive (gmsh fill vs shell node ordering) and are not part of the contract; the invariants are. tests/test_0856 (5 serial, number claimed in the ledger) and ptest_0856 (np=2,4); suites 0853-0855 green against the widened sheet carve. Underworld development team with AI support from Claude Code
|
Adversarial review (self, posted per project practice). What we attacked: The compose case is the honest test, and it failed twice before it passed. Removing then re-placing in the SAME region is the lifecycle's actual workload, and the refilled region is a different mesh than the original: locally smaller cells (fill grades 0.3h–1.2h) and different connectivity. First failure: the sheet carve pinched against the new connectivity — the volume carve had growth, the sheet carve had a raise; unified. Second failure: a rim edge crossed a small refill cell whose every vertex was outside reach — gmsh PLC error, escaping as a plain Exception that would have HUNG a parallel run (the catch was too narrow). Both fixes are in the carve, not the fixture. Asymmetry we defend: the placement carves keep the centroid rule, the removal carve drops it. The rule exists to catch cells straddling an object about to be embedded; a removal embeds nothing, and with the rule a sheet removal reached the domain wall its own placement had cleared. Same reach logic, different purpose, measured both ways. What we deliberately did not promise: refill cell counts across partitions. gmsh's fill depends on shell node ordering, which the gather sets — counts vary by ±3 at np=1..4 while volume, labels, conformity and FE exactness hold identically. The old cut had the same property; pinning counts would manufacture flakiness. Residual risks, stated: (1) Underworld development team with AI support from Claude Code |
There was a problem hiding this comment.
Pull request overview
This PR adds the mesh “fault lifecycle” deletion half by introducing remove_embedded(dm, label, value) to carve out an embedded sheet/zone and refill the cavity at background resolution (without redistribution), while preserving other embedded interfaces via the same gather-first surgery discipline used by placements.
Changes:
- Add
remove_embedded()plus supporting helpers (_gmsh_fill_plain_3d,_labelled_face_soup) to remove labelled embedded objects and plain-refill their cavity. - Unify 3‑D carve shell closure behavior via
_closed_shell_3d()and extend the sheet carve to include the centroid-distance drop rule. - Add serial and MPI tests for removal behavior and collective refusals.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/underworld3/utilities/place_surface.py |
Implements remove_embedded(), adds plain gmsh refill, adds exclude support for interface detection, and refactors 3‑D shell closure into a shared helper. |
tests/test_0856_remove_embedded.py |
Adds serial lifecycle tests (remove zone/sheet, preserve unrelated surface, refusal messaging, add→remove→add composition). |
tests/parallel/ptest_0856_remove_embedded_parallel.py |
Adds MPI tests for distributed removal correctness and collective refusal behavior. |
Suppressed comments (1)
src/underworld3/utilities/place_surface.py:2725
- In remove_embedded(), the surgery-rank selection uses
n_region = (d_skin < reach_v).sum() + seed.sum(). Ifclearanceis 0 (or very small),reach_vcan be 0 andd_skinis 0 on the labelled surface, sod_skin < reach_vis false everywhere; this can select the wrong target rank and make the removal fail even though the object exists. Use the same superset radius as the gather mask (or add an explicit collective guard when owners.sum()==0).
n_region = int((d_skin < reach_v).sum() + seed.sum())
owners = np.asarray(comm.allgather(n_region))
target = int(np.argmax(owners))
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| # The object's global geometry, and which cells are its own. | ||
| soup_pts, soup_tris = _labelled_face_soup(dm, removed_pairs) | ||
| n_soup = int(comm.allreduce(len(soup_tris), op=MPI.MAX)) | ||
| if n_soup == 0: | ||
| raise ValueError( |
| raise RuntimeError( | ||
| "the thin volume's cavity needs a cell that belongs to a surface " | ||
| "already embedded. Zones and surfaces must be separated by at " | ||
| "least a cell.") | ||
| if not drop.any(): |
First half of the lifecycle ruling (2026-08-11): a fault is added to the running distributed mesh and a spent one deleted, with no redistribution. This PR is the delete.
remove_embedded(dm, label, value)— the inverse placement. The object's labels seed the carve (cells = a thin volume, faces = a placed sheet; a zone's_skincompanion goes with it); the cavity refills with a plain gmsh fill at background scale, erasing the layer-scale grading; the labels vanish with their points, gated globally empty. Other embedded surfaces are held and count-gated exactly as placements hold them (the interface readers gainexclude=so a removal doesn't hold its own object against itself). Parallel by the same gather-first surgery; the object's geometry is allgathered from its labels first, so removal works whatever the current distribution.Measured round trip, np=1..4: add zone → FE-exact (9e-16) → remove → FE-exact (4e-11), cells back within ~8% of base → add a sheet in the SAME region → remove → FE-exact. (Refill cell counts are partition-sensitive — gmsh vs shell node ordering — and deliberately not part of the contract; the invariants are.)
Composing the lifecycle surfaced three robustness fixes, each measured before fixing:
_closed_shell_3d) for every 3-D carve — the unification debt from the Thin-volume fault zones + the 2-D fill delegated to gmsh (walk retired) #519 review, paid.Exception: raw gmsh errors are plain Exceptions, and an uncaught raise on the surgery rank is a hang for its peers.test_0856 (5 serial; number claimed in the ledger) + ptest_0856 (np=2,4, collective refusals asserted); suites 0853–0855 green against the widened carve. The 2-D parallel family (which retires the 2-D serial-only refusals and brings 2-D removal) is the next PR on this branch line.
Underworld development team with AI support from Claude Code