Uh oh!
There was an error while loading. Please reload this page.
Thin-volume fault zones + the 2-D fill delegated to gmsh (walk retired) - #519
Conversation
The 2-D placed surface now fills its cavity the way the 3-D sheet always has: the cavity ring goes to gmsh as a discrete curve carrying its exact segmentation, the surface's chain as an embedded discrete curve whose end nodes are shared with the ring where the surface meets the cavity boundary (a crossing, or an end at the wall) and free at a tip. The delegation is gated, not trusted — zero moved nodes, every input segment an edge of the fill, anticlockwise triangles — and refused rather than accepted degraded. Measured basis: gmsh_2d_fill_spike.py (boundary + sub-h hole + free-end embed in one fill) and gmsh_2d_ends_spike.py (crossing and mixed end-on-boundary), both in ~/+Simulations/mesh_reconnection_study/. Retired with it: the arc-length traverse parameter, the angle-fan tip, the zip walk with its ear-clipping third move, and the flank parameter — some 420 lines whose hardest cases (the tip, the wall-collinear sliver) are gmsh's ordinary free-end embed and boundary recovery. One fill mechanism for both dimensions. A new precondition check refuses, with the cause, a surface whose points fall outside the cavity cleared for it (cells held for an earlier surface): previously the walk wedged and said so; gmsh would have said "moved 37 nodes". The repair test now presses a tight clearance for its material and skips loudly when the fill leaves nothing under 15 degrees — the gmsh fill is well-shaped natively, and asserting an improvement of nothing is the vacuous pass the suite's own docstring warns against. The 3-D refusal message now points at place_sheet rather than explaining the walk's limits. Contract: tests/test_0853 (20 pass, 1 principled skip), test_0854 and test_0844 green. n_fill_points joins the info dict for the interior points a fill may insert; the chain identity n_surface_facets == n_placed + n_on_surface - 1 is unchanged. Underworld development team with AI support from Claude Code
The finite-width fault representation, Louis's design ruling made a mesh primitive: each patch is thickened by +-width/2, the thickened volumes of a network are resolved against one another with OCC fragment — the only junction resolver, and CAD-only — the assembly is meshed standalone at layer scale (sub-h widths measured and supported; V = 2 edot w makes the width constitutive), and the meshed assembly is embedded WHOLE into the existing mesh: a cavity is carved around it and gmsh fills the annular gap with the assembly's boundary skin as an interior HOLE, both constraint surfaces discrete and verbatim. Junctions get no geometric treatment: they are ordinary cells of the union and the rheology decides. In the result the layer's CELLS carry (label, value) — the zone exists to hand cells to the rheology — and the skin's faces carry (label_skin, value), deliberately separate: a stratum containing cells is a volume label, invisible to the interface machinery, and it is the skin label that makes a later placement hold its cavity clear of the zone (asserted: a sheet through the zone refuses naming the cause; one placed clear embeds with the zone intact). 3-D is serial AND parallel through place_sheet's gather-first skeleton: the assembly is meshed once (rank 0) and broadcast, the region gathers to one rank, every rank rebuilds collectively; results are bit-identical np=1..4. 2-D is the ribbon — one MITRE-JOINED outline polygon per polyline, after measuring that per-segment quads fragmented together sliver at every kink (the inner-side overlap lens meshes at 2.3 degrees; one outline has no internal seam to lens — kinked-trace min angle 27 degrees). Serial, sharing place_along_lines' scope. New carve machinery for FAT objects, both dimensions: the reach is a length max(clearance*h, 0.6*width) so sub-h layers are covered, and the cavity around a volume can PINCH — the drop set grows at every non-manifold shell edge (3-D) or pinch vertex (2-D) until the boundary is simple, the thin_volume_spike lesson. _gmsh_fill_2d gains hole loops; the annular 3-D fill is new. Gates per call, all collective: assembly meshed volume/area exact against CAD, zero moved constraint nodes, skin not remeshed, every zone cell and skin facet recovered by join, domain volume/area conserved, global Euler 1, held surfaces intact. tests/test_0855 (10 serial) and ptest_0855 (np=2..4); measured basis thin_volume_spike.py in ~/+Simulations/mesh_reconnection_study/. Underworld development team with AI support from Claude Code
…l and the thin volume The placement half described the retired walk and a serial-only, 2-D-only capability. It now describes what exists: the gated gmsh fill (one mechanism, every dimension, with the walk's retirement recorded in one paragraph), the parallel 3-D forms (gather-first, partition-independent, collective refusals), and place_thin_volume — the finite-width zone with junctions in the volume, which is also the promised lifting of the close-pair limit. Underworld development team with AI support from Claude Code
lmoresi
commented
Aug 10, 2026
Adversarial review (self, posted per project practice). What we attacked and what survived: The kink sliver, and a wrong fix caught by its own numbers. First diagnosis blamed the outer-notch wedge at polyline kinks; the wedge-triangle patch changed NOTHING (min angle 2.263°, min area 8.67e-07 — bit-identical before/after, so the probe never fired). Re-measured on the assembly alone: the sliver is the INNER overlap lens that The label split is load-bearing, not taste. A single (label, value) stratum holding cells AND skin faces would be skipped whole by Refusals name causes. A surface whose points fall outside its cleared cavity (cells held for an earlier surface) previously wedged the walk with a message saying so; raw gmsh says "moved 37 constrained node(s)". A precondition check now refuses with the cause before gmsh sees the input; the T-junction contract test enforces the wording. A vacuous pass converted to a loud skip. The repair test's fixture existed to give flips walk-era thin cells to fix; the delegated fill leaves none (min angle 38° on the old fixture). It now presses clearance=0.35 and SKIPS with a reason when there is nothing repair may legally act on — the surface-preservation half still runs. Residual risks we accept, stated: (1) cell orientation of gmsh output — 2-D is re-oriented explicitly and gated by the inverted-cell check; 3-D relies on gmsh's positive-tet convention exactly as the landed place_sheet does, behind the same volume-conservation gate. (2) The assembly is meshed on rank 0 and broadcast, so cross-rank identity is by construction; np=1..4 measured bit-identical. (3) 3-D patches must be planar — curved manifolds are the fault-prep lifecycle item, refused with the reason meanwhile. (4) Underworld development team with AI support from Claude Code |
There was a problem hiding this comment.
Pull request overview
Adds first-class support for embedding finite-width “thin-volume” fault zones into existing meshes, while simplifying 2‑D surface cavity filling by delegating it to gmsh (retiring the bespoke walk).
Changes:
- Introduces
place_surface.place_thin_volume()for 2‑D ribbons and 3‑D thin volumes (OCCfragmentassembly + mesh-then-embed workflow, with separate zone-cell vs skin-face labels). - Replaces the 2‑D cavity “walk” fill in
place_along_lines()with a gated gmsh constrained triangulation (verbatim constraints, no moved nodes). - Adds/updates tests and developer documentation to reflect the new contracts, refusal messages, and parallel behavior.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_0855_place_thin_volume.py | New serial test coverage for thin-volume embedding behavior, refusals, label strata split, and quality/area-volume gates. |
| tests/parallel/ptest_0855_place_thin_volume_parallel.py | New MPI tests asserting collective refusals and rank-consistent deterministic results. |
| tests/test_0854_place_sheet.py | Updates refusal wording expectation to match new messaging. |
| tests/test_0853_place_surface.py | Updates repair test expectations/fixture and refusal wording for 3‑D dispatch guidance. |
| src/underworld3/utilities/place_surface.py | Implements gmsh-gated 2‑D fill, removes retired walk components, and adds place_thin_volume() (2‑D + 3‑D). |
| docs/developer/subsystems/conforming-surfaces-and-fault-zones.md | Updates subsystem documentation to describe gmsh fill delegation and the new thin-volume capability/limits. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| label, label_value : str, int | ||
| Label carried by the layer's cells AND the skin's faces. | ||
| clearance : float |
| def _occ_assembly_2d(polylines, width, size): | ||
| """Thicken each polyline segment into a quad, fragment together, mesh. | ||
| The 2-D thin volume: a ribbon is the union of one quad per polyline | ||
| segment, kinks and crossings resolved by ``fragment`` exactly as the 3-D | ||
| junctions are. Returns ``(points, triangles, cad_area)``. |
| info : dict | ||
| Global counts: ``n_zone_cells``, ``n_skin_faces``, ``n_placed`` | ||
| (vertices added), ``n_removed`` (vertices deleted), ``min_volume``. |
CI caught the routine's own Euler gate firing on linux (global Euler 2, not 1) where macOS passed: the cavity growth — and, in the sheet's carve, the straddle rule — can swallow the WHOLE star of a vertex that is not itself a victim. Such a vertex sits on no shell face (a shell face keeps a surviving cell), so it rides through the rebuild referenced by nothing: an isolated point, chi = 1 + 1. Which vertices get orphaned depends on the drop pattern, which follows the gmsh assembly mesh — hence version-dependent, hence the platform split. The fix is the unconditional invariant, applied in all three carves (thin volume 3-D and 2-D, and the sheet, whose hazard was latent): after the drop set is final, any surviving vertex with no surviving cell is promoted to victim; a wall vertex in that position is a collective refusal. A direct no-isolated-vertex probe joins test_0855. Underworld development team with AI support from Claude Code
lmoresi
commented
Aug 10, 2026
Review addendum — CI round 1 found a real defect and the gates did their job. The linux run failed the routine's OWN collective gate: "the sewn mesh has global Euler number 2, not 1" (macOS passed — the drop pattern follows the gmsh assembly mesh, so the trigger is version-dependent). Mechanism: the cavity growth (and, latently, the sheet carve's straddle rule) can swallow the whole star of a vertex that is not itself a victim; that vertex is on no shell face, so it rides through the rebuild as an ISOLATED point — chi = 1 + 1 = 2. Fix (2528d0f): the unconditional invariant — after the drop set is final, any surviving vertex with no surviving cell is promoted to victim; a wall vertex in that position is a collective refusal. Applied in all three carves including the landed sheet path, whose hazard was latent behind geometric luck. A direct no-isolated-vertex probe joins test_0855. Underworld development team with AI support from Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
…n test The interface-cone fix (#521, closing #520) verifies independently on this branch: CheckFaces green at np=2 on the rebuilt mesh, placed-mesh plain Stokes 24 s (was 3594 s), and — the acceptance that motivated the whole ladder — the COMPOSED chain place_sheet -> split_fault -> frictionless contact at np=2 converges in 18 s with the serial answer (peak slip 0.1461 vs 0.1462) and machine-zero no-opening leak. ptest_0852 locks that chain as a regression gate: the solve IS the gate, because both #518-era defects passed every topological check. Underworld development team with AI support from Claude Code
One development sweep, per the design ruling: land the finite-width fault representation and remove the dead end it exposes.
place_thin_volume — the embedded thin-volume fault mesh (new)
Each fault patch is thickened by ±width/2; the network's volumes are resolved against one another with OCC
fragment(the only junction resolver, and CAD-only); the assembly is meshed standalone at layer scale; and the meshed assembly is embedded whole into the existing mesh — cavity carved, annular gap filled by gmsh with the assembly's boundary skin as an interior HOLE, both constraint surfaces discrete and verbatim. Junctions get no geometric treatment: they are ordinary cells of the union, and the rheology decides.(label, value); the skin's faces carry(label_skin, value)— deliberately separate, because a cell-bearing stratum is a volume label and invisible to the interface machinery; the skin label is what makes later surgery hold clear of the zone (asserted both ways in the tests).max(clearance*h, 0.6*width), and the cavity around a volume can PINCH — the drop set grows at non-manifold shell edges (3-D) / pinch vertices (2-D) until the boundary is simple.Measured basis:
thin_volume_spike.pyin the mesh_reconnection_study (widths h, h/2, h/4; junction angles 90/45/20/10 degrees, zero slivers at every angle — glancing junctions cost cells, not quality).The 2-D fill delegated to gmsh; the walk retired (dead-end removal)
The 2-D placed surface now fills its cavity the way the 3-D sheet always has: cavity ring as a discrete curve (verbatim), the surface's chain embedded, chain ends sharing the ring's own node tags where the surface meets the cavity boundary. Gated per call: zero moved nodes, every input segment an edge of the fill, orientation checked. Measured basis:
gmsh_2d_fill_spike.py(boundary + sub-h hole + free-end embed in one fill) andgmsh_2d_ends_spike.py(crossing and mixed end-on-boundary).Retired: the traverse parameter, the angle-fan tip, the zip walk with its ear-clipping third move — ~420 lines whose hardest cases are gmsh's ordinary free-end embed and boundary recovery. A new precondition check refuses, with the cause, a surface whose points fall outside the cavity cleared for it (previously the walk wedged and said so; gmsh would have said "moved 37 nodes").
Tests and contract changes
tests/test_0855_place_thin_volume.py(10 serial: 3-D crossing pair, sub-h, zone-vs-sheet both ways, wall/planarity/kink/width refusals, 2-D X junction, kink quality, second-zone safety) andtests/parallel/ptest_0855_place_thin_volume_parallel.py(np = 2..4: serial contract + collective refusals). Test number 0855 claimed in the coordination ledger.test_0853(the 2-D contract, 20 pass): two legitimate updates — the 3-D refusal now points atplace_sheetinstead of explaining the walk's limits, and the repair test presses a tight clearance for its material and SKIPS loudly when the fill leaves nothing under 15 degrees (the gmsh fill is well-shaped natively; asserting an improvement of nothing is the vacuous pass that test's own docstring warns against). One test intest_0854matched the old refusal wording.docs/developer/subsystems/conforming-surfaces-and-fault-zones.mdbrought up to date (it still described a serial, 2-D-only, walk-filled capability).Net: +1.6k/−0.5k lines, of which the deletions are the walk and the stale doc text. Shared file
place_surface.pychanges only via this PR to development, per the coordination ledger rules; the ledger carries a note for the contact session (walk internals no longer exist as patch targets).Underworld development team with AI support from Claude Code