What happens
cut_along_lines refuses a fault whose tip is correctly placed:
ValueError: 1 triangle(s) are entered but not left, which means a line ends
inside the mesh. A line must cross from boundary to boundary; a terminating
tip needs a vertex placed at the tip and is not supported.
even though add_fault has already called pull_vertex_onto on every control point of the trace, and every control point does have a vertex exactly on it.
Why
pull_vertex_onto moves the globally nearest vertex onto each target. That vertex need not be a corner of the cell that contains the target. When it is not, the containing cell is entered by the line and terminates there with no on-line corner, which is exactly the state cut_along_lines refuses.
Measured on the listric pair at DX=0.30 (~/+Simulations/listric_extension/selfselect.py), fault F2, trace start (0.65, 0.88) inside an embedded ribbon zone of width 0.02:
| vertex |
position |
distance to the start point |
corner of the containing cell? |
| 2213 |
(0.65361, 0.87071) |
9.963e-03 — chosen |
no |
| 1953 |
(0.64068, 0.87638) |
1.000e-02 |
yes |
| 1889 |
(0.65932, 0.88362) |
1.000e-02 |
yes |
The margin is 0.4%. Both corners of the containing cell sit at exactly width/2 from the trace, because they are the ribbon's own edge vertices; the winning vertex is an interior one that happens to fall a hair closer. All 22 control points ended up with a vertex exactly on them, and the cut still refused.
Why it looks intermittent
- On a plain unstructured box the globally nearest vertex is usually a corner of the containing cell. A tip swept across a cell at 7 positions succeeded every time.
- Inside an embedded zone the vertices sit in rows at exactly
±width/2 from the trace, which manufactures near-ties. The same sweep fails at DX=0.30 and passes at DX=0.50 and DX=0.70 — luck, not geometry.
This is what limited the hybrid self-selection sweep to a subset of separations.
The fix we think is right
Choose the nearest vertex of the cell containing the target, falling back to the globally nearest when the target lies outside the mesh. This keeps the existing principle — move the mesh, not the tip, so the tip stays exactly where it was asked for — and makes the choice depend on the containment relation that cut_along_lines actually tests, rather than on a distance comparison that can be decided by 0.4%.
Note that snapping the tip to the nearest vertex, the other obvious fix, is both rejected by pull_vertex_onto's own docstring (measured: tip error 0.0000 against 0.0306, worst angle 8.79 deg against 5.29) and would not address this: the tip does not lack a vertex, the vertex was taken from the wrong cell.
Reproducer
~/+Simulations/listric_extension/selfselect.py 0.30 — fails; 0.50 and 0.70 pass.
Underworld development team with AI support from Claude Code
What happens
cut_along_linesrefuses a fault whose tip is correctly placed:even though
add_faulthas already calledpull_vertex_ontoon every control point of the trace, and every control point does have a vertex exactly on it.Why
pull_vertex_ontomoves the globally nearest vertex onto each target. That vertex need not be a corner of the cell that contains the target. When it is not, the containing cell is entered by the line and terminates there with no on-line corner, which is exactly the statecut_along_linesrefuses.Measured on the listric pair at DX=0.30 (
~/+Simulations/listric_extension/selfselect.py), fault F2, trace start(0.65, 0.88)inside an embedded ribbon zone of width 0.02:The margin is 0.4%. Both corners of the containing cell sit at exactly
width/2from the trace, because they are the ribbon's own edge vertices; the winning vertex is an interior one that happens to fall a hair closer. All 22 control points ended up with a vertex exactly on them, and the cut still refused.Why it looks intermittent
±width/2from the trace, which manufactures near-ties. The same sweep fails at DX=0.30 and passes at DX=0.50 and DX=0.70 — luck, not geometry.This is what limited the hybrid self-selection sweep to a subset of separations.
The fix we think is right
Choose the nearest vertex of the cell containing the target, falling back to the globally nearest when the target lies outside the mesh. This keeps the existing principle — move the mesh, not the tip, so the tip stays exactly where it was asked for — and makes the choice depend on the containment relation that
cut_along_linesactually tests, rather than on a distance comparison that can be decided by 0.4%.Note that snapping the tip to the nearest vertex, the other obvious fix, is both rejected by
pull_vertex_onto's own docstring (measured: tip error 0.0000 against 0.0306, worst angle 8.79 deg against 5.29) and would not address this: the tip does not lack a vertex, the vertex was taken from the wrong cell.Reproducer
~/+Simulations/listric_extension/selfselect.py 0.30— fails;0.50and0.70pass.Underworld development team with AI support from Claude Code