Outcropping zones: the fault zone leaves its band on the surface - #526
Conversation
The thin-volume form of the outcrop, completing "get all the implementation done". The assembly is clipped against the domain box in OCC (specify-long: patches may protrude; clipped faces snap exactly onto the wall plane), and a clipped face left in ONE wall plane becomes the zone's BAND. The gap's boundary is then ONE closed surface of three discrete pieces — the interior shell, the pre-meshed CAP (an annulus in the wall plane: rim ring verbatim, the band outline as its HOLE, via the 2-D fill), and the interior part of the skin, rim-matched to the cap's hole. The band's triangles become boundary faces carrying BOTH the zone's skin label and the wall's own labels; the cap's annulus is relabelled the same way, full closures, counts gated. The volume carve gains open_wall (victims may lie in the open wall's plane; the shell may open there via the shared _closed_shell_3d); box-edge bands and multi-wall contact refuse with the reason. Measured serial and np=2,4: identical zone counts, P2 quadratic FE-exact (5e-11..7e-11) with Dirichlet gripping the remeshed Top, full Top coverage. Interior zones unchanged: suites 0853-0856 (45 serial) and the placement ptests green. Band contract test added to test_0855. Underworld development team with AI support from Claude Code
|
Adversarial review (self, posted per project practice): The three-piece boundary is the risk we attacked. The gap's closed surface is assembled from three independently produced triangulations sharing nodes only by tag identity: shell↔cap at the wall rim, cap↔skin at the band outline. Every sharing is gated — moved-nodes zero across all constrained sets, cap/skin triangle counts refused if remeshed, and every band and cap face recovered by join with the relabel count compared against the expectation. The FE oracle with Dirichlet on the remeshed Top then closes over the whole chain: a single mis-sewn edge or unlabelled patch cannot reproduce the quadratic. A subtlety we verified rather than assumed: OCC's clipped faces land within rounding of the wall plane, not on it; the band logic keys on EXACT plane values, so assembly nodes within 1e-9 snap onto the plane before anything reads them. Without the snap the band detection silently returns empty and the placement refuses at the wall — loud, but wrong. Stated residuals: removal of an outcropping object is the one asymmetry left (the removal carve does not yet pass open_wall — a spent outcropping fault currently refuses removal rather than clearing its band); box-edge bands and curved tops refuse with reasons as before. Underworld development team with AI support from Claude Code |
Underworld development team with AI support from Claude Code
There was a problem hiding this comment.
Pull request overview
Completes the “outcropping zones” path for place_thin_volume() by clipping the OCC-built zone assembly to the domain box and treating the clipped wall-face as a surface “band”, including explicit wall relabelling and a contract test that validates the full chain via a P2 Poisson exactness oracle.
Changes:
- Extend
_occ_assembly_3d()to optionally intersect the assembly solids with the domain box (“specify-long”) and snap clipped faces to exact wall-plane coordinates. - Add band/cap plumbing for outcropping thin volumes: split skin into interior vs band, allow the carve to open onto one wall, pre-mesh a cap annulus with a hole matching the band outline, and explicitly relabel new wall faces with the replaced wall-label closures.
- Add
test_an_outcropping_zone_leaves_a_band_on_the_surface()to assert band existence/labels and FE exactness after outcrop remeshing.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/underworld3/utilities/place_surface.py |
Implements outcropping-zone “band on wall” handling: box-clipped OCC assembly, band extraction, open-wall carve, cap-annulus gap fill, and explicit relabelling gates. |
tests/test_0855_place_thin_volume.py |
Adds a contract test covering the outcropping zone band + relabel + P2 exactness check chain. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if wall_code is not None: | ||
| axis, side = wall_code // 2, wall_code % 2 | ||
| open_wall = (int(axis), | ||
| float(box_hi[axis] if side else box_lo[axis])) | ||
| from collections import Counter as _Counter | ||
| band_edge = _Counter() | ||
| for t in skin_tris[band_idx]: | ||
| a, b, c = sorted(int(v) for v in t) | ||
| for e in ((a, b), (a, c), (b, c)): | ||
| band_edge[e] += 1 | ||
| band_outline = _single_loop( | ||
| [e for e, k in band_edge.items() if k == 1], | ||
| "zone's outcrop band outline") | ||
| skin_tris_fill = (skin_tris[interior_idx] if open_wall is not None | ||
| else skin_tris) |
Some Azure runners advertise an InfiniBand verbs device (mana_0) that UCX cannot open; MPI_Init aborts inside the import smoke test before any underworld3 code runs. Four hits in a week across #466/#526/#527 (issues #461/#528), each fixed by re-rolling the runner lottery. A single-node CI runner needs only shared memory and TCP, so pin the transports at the workflow level - it covers the smoke test and every pytest batch alike. Underworld development team with AI support from Claude Code
adapt-on-top-faults: the rotated free-slip path now picks up an adapt child's mesh-owned MG tail automatically (custom_mg.build_transfers, the #467 fix) — the "FUNDAMENTAL, not a quick fix" gotcha row was describing a bug that is gone. Point the band-sizing section at the interface-alignment primitives that now exist (place_sheet / place_thin_volume / remove_embedded, #517-#526). nonlinear-solver: three stale claims corrected — rotated free-slip is no longer the exception to the automatic tail pickup (#467); the adapt tail is one MG level per DOUBLING of h, not per refinement generation (mg_coarsening_ratio=2.0, #515); refinement=0 still yields a hierarchy that starts at the base, so "no coarse grid" overstated it. Three capabilities that landed since the branch: preconditioner="gamg" is respected on adapt children (#530), single-field FMG (#478/#534), and solver.pc_fallbacks as the observability hook (#534). The supersession note no longer names the removed in-SNES ramp API. Underworld development team with AI support from Claude Code
…current; plasticity-solvers rewritten (#454) (#489) * skills: parallel adapt engines, band pinning, and FMG on adapt children adapt-on-top-faults - engines section: nvb vs edge_split, both parallel in 2-D and 3-D and bit-confluent; edge_split has no conforming closure so refinement cannot escape the marked region, and marks on the DIAMETER (the volume proxy reported the target met while the mesh was 3.2x coarser across the feature). - repair=True: gates on reducing the largest angle, NOT on Delaunay. Delaunay maximises the minimum angle while P1 depends on the maximum, and flipping a gmsh mesh toward Delaunay raised the 99th-percentile max angle. Worth it on a poor base (156 -> 115 degrees, slivers 3.84% -> 0.00%), marginal on a clean one, and it gives up bit-confluence, so it is opt-in. - relax on a mesh refined onto an interface makes things WORSE (+77% leak); pin_bands is the fix. - new section on sizing the band and representing the fault margin: the -2 Cov(eta, edot) leak metric, why a within-cell marking rule loses to the plain distance size field, why the optimal band width depends on which objective you pick, and what a step-edged margin buys and costs. - gotchas: Mesh(dm) takes the DM over (bare SIGSEGV if you keep using the old handle); Mesh(dm) without boundaries= loses the boundary enum; evaluate() "Total components 8 != 6" on a variable-heavy mesh. adaptive-meshing - PIN THE INTERFACE section for relax(pin_bands=...), including the signed-vs-unsigned distance rule and the pinned_labels merge trap. - cross-reference to nonlinear-solver for the FMG setup. nonlinear-solver - new section: FMG on an adapt-on-top child. The child carries its own graded custom-P tail and solvers pick it up automatically; the base must have refinement>=1; a base-only tail triples the V-cycle count; V-cycle counts are insensitive to element quality (a pass, not a failed measurement) so use GAMG as the quality probe; relax can trip #424 into the dense RBF fallback; repair invalidates the any-degree transfer but not the vertex prolongation. - cross-references to adapt-on-top-faults and adaptive-meshing. Underworld development team with AI support from Claude Code * skills: bring the two adapt/solver skills current with development adapt-on-top-faults: the rotated free-slip path now picks up an adapt child's mesh-owned MG tail automatically (custom_mg.build_transfers, the #467 fix) — the "FUNDAMENTAL, not a quick fix" gotcha row was describing a bug that is gone. Point the band-sizing section at the interface-alignment primitives that now exist (place_sheet / place_thin_volume / remove_embedded, #517-#526). nonlinear-solver: three stale claims corrected — rotated free-slip is no longer the exception to the automatic tail pickup (#467); the adapt tail is one MG level per DOUBLING of h, not per refinement generation (mg_coarsening_ratio=2.0, #515); refinement=0 still yields a hierarchy that starts at the base, so "no coarse grid" overstated it. Three capabilities that landed since the branch: preconditioner="gamg" is respected on adapt children (#530), single-field FMG (#478/#534), and solver.pc_fallbacks as the observability hook (#534). The supersession note no longer names the removed in-SNES ramp API. Underworld development team with AI support from Claude Code * skills: rewrite plasticity-solvers around what the yield campaigns measured Fixes #454. The skill taught the retired yield-homotopy doctrine in ~8 places, headlined by an enable method that no longer exists in src/ and an in-SNES delta-ramp that is separately proven to diverge. The doctrine rested on a unit-scaling error: re-measured on the corrected problem, the delta-march never succeeded where a direct hard-Min solve failed, and the ruling was to regularise the problem, not the solver. Rewritten around the evidence: Newton with the automatic Picard entry (Picard is an entry requirement, not an accelerator), rescue on failure OR stagnation, grid sequencing as the validated warm start, the per-model tangent table, and the #475 yield_mode / yield_smoother / yield_anchor substrate presented as a modelling choice with the multi-solve-only discipline for any delta march. yield_continuation is described honestly per open issue #473 (the cold-start guarantee does not hold on a Piecewise yield stress; the step control is effectively one-shot). Floors updated to the post-#475 semantics (viscosity_min_rounding). Kept: the Newton-confirmation check, the VEP-indefinite ruling, the SNESFAS ruling, the Picard-folklore footnote. Underworld development team with AI support from Claude Code * The nonlinear-solver recipe demotes the delta-march to a rescue The recipe's step 2 still presented solve(homotopy=True) as the default entry point ("one call - automatic"), which is the retracted doctrine in its post-API form: the evidence that recommended a delta-march first rested on a unit-scaling error, and the driver's cold-start guarantee is broken (issue #473). The step now escalates honestly - grid sequencing first, the delta-continuation as rescue of last resort with the #473 caveat stated - and the Layer-2 status note carries the same demotion. The delta-discipline itself (constant per solve, never in-SNES) is unchanged; plasticity-solvers holds the ruling and evidence. Underworld development team with AI support from Claude Code
Completes the outcrop implementation (with #525): the thin-volume form. A fault ZONE specified past the box is clipped in OCC; its clipped face becomes the BAND on the surface; the gap's boundary is one closed surface of three discrete pieces (interior shell + cap-annulus with the band outline as its hole, pre-meshed by the 2-D fill + interior skin, rim-matched). Band faces carry BOTH the zone's skin label and the wall's labels; the cap annulus is relabelled explicitly with full closures, counts gated.
Measured serial and np=2,4: identical zone counts, P2 FE-exactness (5e-11–7e-11) with Dirichlet gripping the remeshed Top, full Top-label coverage face-by-face. Interior zones byte-for-byte unaffected — suites 0853–0856 (45 serial) and the placement ptests green. Band contract test in test_0855.
Stated residuals (refuse with reasons): box-edge bands / multi-wall contact; curved or deformed tops (the manifold-prep slot); removal of an OUTCROPPING object (its cavity legitimately touches the wall — the removal carve does not yet take open_wall; next small instalment if needed).
Underworld development team with AI support from Claude Code