From 4e0d37e20ffedce98dedbbe70ff5d96ece866237 Mon Sep 17 00:00:00 2001 From: lmoresi Date: Thu, 10 Sep 2026 09:32:18 -0700 Subject: [PATCH] Say in the library what a 3-D split patch needs from the mesh around it Both requirements follow from the interior-vertex rule, and both were recorded only in a study rig's README, where nobody building a patch of their own would find them. A patch must be about four cells across in each direction, and a vertical fault in a box needs a blind top setback, a fault depth and a base clearance on top of that, so a box shallower than about nine cells cannot hold a splittable vertical fault however finely the fault itself is triangulated. Separately, a structured extrusion triangulates each corner quad on a diagonal that can cut off a triangle with all three vertices on the rim; that face has no interior vertex and the split refuses it, and the fix is to flip that one diagonal, not to refine. The refusal now names both remedies instead of asking only for refinement, which is the wrong answer to the second one. Underworld development team with AI support from Claude Code Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01RzK7JXSxsc2GoG7TGnQgUS --- docs/advanced/fault-networks.md | 27 +++++++++++++++++++++ src/underworld3/utilities/fault_split.py | 30 ++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/docs/advanced/fault-networks.md b/docs/advanced/fault-networks.md index 2424b00f7..ebecb7737 100644 --- a/docs/advanced/fault-networks.md +++ b/docs/advanced/fault-networks.md @@ -295,6 +295,33 @@ field is balanced. On the crossing-patches fixture that is 8012 of serial: parallel is a correctness mode for this path, not a speed-up, until the placed region is rebalanced. +### What the mesh around a 3-D patch has to provide + +A split patch is doubled everywhere strictly inside its rim, so **every +face of it needs a vertex that is not on the rim**. Two consequences, +both cheaper to design for than to discover: + +- **A patch is at least about four cells across in each direction**, and + a vertical fault in a box needs more than the fault itself: a blind top + setback of two cells or more, four cells or more of fault depth, and + three cells or more of base clearance so the carve's cavity clears the + wall. A box shallower than about **nine cells cannot hold a splittable + vertical fault at all**, however finely the fault itself is + triangulated. Refining only the fault does not help; the budget is in + the background mesh. + +- **Triangulate corner quads on the other diagonal.** A structured + extrusion cuts each quad of the patch in two, and at a corner one + choice of diagonal produces a triangle whose three vertices all lie on + the rim. That face has no interior vertex and the split refuses it with + *"a fault face has no interior vertex, so its two copies would carry + the same vertex triple"*. It is a choice of diagonal, not a call for + refinement: flip it for any quad whose triangulation would be all-rim. + +Both are properties of the splitter, not of any particular model, so +they apply to a patch built by hand as much as to one from +`FaultSurface`. + ## Limitations - 3-D: planar convex patches, X crossings only (above). Finite-width diff --git a/src/underworld3/utilities/fault_split.py b/src/underworld3/utilities/fault_split.py index 89d49646c..514f58e23 100644 --- a/src/underworld3/utilities/fault_split.py +++ b/src/underworld3/utilities/fault_split.py @@ -774,6 +774,26 @@ def split_along_label_3d(dm, name, value, plus_name, plus_value, redistributes the star onto one rank so the managed path never sees the refusal, exactly as in 2-D. + **What the patch needs from the mesh around it.** Both consequences + below follow from the interior-vertex rule, and both are cheaper to + design for than to discover: + + * *Every face needs an interior vertex*, so a patch must be at least + about four cells across in each direction. Budgeting a vertical + fault in a box: a blind top setback of two cells or more, four + cells or more of fault depth, and three cells or more of base + clearance for the carve's cavity to clear the wall — so a box + shallower than about **nine cells** cannot hold a splittable + vertical fault at all, whatever the fault's own resolution. + + * *A structured extrusion makes all-rim corner faces.* At a corner + of a rectangular patch the quad's diagonal can cut off a triangle + whose three vertices are all on the rim; that face has no interior + vertex and the split refuses it. It is a choice of DIAGONAL, not a + call for refinement: a patch builder that triangulates its own + quads should pick the other diagonal for any quad whose + triangulation would be all-rim. + Parameters and returns match :func:`split_along_label`, with ``orientation`` a reference NORMAL vector (Plus is the side it points into) rather than a chain direction. @@ -867,8 +887,14 @@ def split_along_label_3d(dm, name, value, plus_name, plus_value, problem = (ValueError, "fault_split: a fault face has no interior vertex, " "so its two copies would carry the same vertex " - "triple. The patch is too coarse to split — refine " - "it until every face reaches inside the rim.") + "triple. Either the patch is too coarse — it needs " + "to be about four cells across in each direction, " + "and a vertical fault also needs a top setback, a " + "depth and a base clearance the box may be too " + "shallow for — or a corner quad was triangulated " + "on the diagonal that cuts off an all-rim triangle, " + "which is fixed by flipping that one diagonal " + "rather than by refining anything.") if problem is None: oriented, problem = _orient_patch(face_verts, edge_faces, X, vS,