mesh: faster tet improvement (#84)

Profile-driven (thin-ribs: 76% of the improvement in the topological
operations, most of it in face removal walking whole edge rings to find
one neighbour; pinhole-block: 44% in the smoothing passes).

Same results, less work:
- face removal finds the sandwiched face across a ring edge through the
  tetrahedron it already holds (O(1) instead of an edge ring), keeps the
  candidate tetrahedra of ring edges between rounds and updates the
  removed worst incrementally;
- Klincsek's table for edge removal starts at the quality to beat, so
  triangles that cannot help are never evaluated;
- a change clock per vertex: a topological operation (per tetrahedron,
  per edge, per face and side) or a smoothing that failed is not tried
  again until a tetrahedron it reads changes (exact: the operations are
  functions of that region; checked by re-running them);
- the quality of a live tetrahedron is cached per slot until one of its
  vertices is touched;
- smoothing line searches stop at the first tetrahedron that does not
  beat the worst, and gradients are computed only near the worst;
- the journal saves the free list's tail instead of the whole list.

For the clock to be exact, the quality is the same bits for every
listing of a tetrahedron with the same orientation (corners in a
canonical even order), and a closed edge ring starts at its smallest
vertex.

Changed results:
- smoothing uses the analytic gradient of the smallest dihedral sine
  (Klingner and Shewchuk 2007 §3.1; Freitag and Ollivier-Gooch 1997)
  instead of central differences (kept for flat or inverted
  tetrahedra);
- the composite insertion runs its topological passes on the new
  vertex's tetrahedra below the threshold, and smooths the neighbours
  whose star has one, as the passes do.

Tests: analytic gradient against central differences, quality the same
for every even reordering, cached qualities equal fresh ones after
improvement, rollbacks restore every slot; the washer fingerprint moves
(the same on arm64 and x86-64).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 committed 2026-10-16 22:11:53 -04:00
1 parent 4329630943
commit e4290c7366
10 files changed
+587 -106

No files matched your search

+7 -2
View File
@@ -398,7 +398,8 @@ Delaunay methods with Voronoi/ODT improvement, then (2) all-hex and hex-dominant
- **Implemented** in `fieldcraft-mesh::tet::{ops, improve}` from the two papers: Klincsek's dynamic programming for edge
removal (interior edges; boundary edges of one flat B-rep face end in a 2-2 flip), multi-face removal grown greedily
over the sandwiched faces, nonsmooth optimization smoothing (active set, smallest vector of the convex hull of the
active gradients, line search; gradients by central differences), and the composite vertex insertion with rollback
active gradients, line search; analytic gradients of the active measure, as §3.1 and Freitag–Ollivier-Gooch 1997
use), and the composite vertex insertion with rollback
(barycentres of boundary faces, of the tetrahedron, midpoints of boundary edges, and a point inside off a flat
boundary edge). Deviations: the inserted vertex takes the Delaunay cavity rather than the optimal cut of §3.4; each pass
smooths and flips the tetrahedra below sin 40° and inserts only when the worst did not improve; passes and the
@@ -407,7 +408,11 @@ Delaunay methods with Voronoi/ODT improvement, then (2) all-hex and hex-dominant
angle fine, one corner nearly flat; Shewchuk 2002); the scaled Jacobian analysts check (Knupp 2001, B6.6) catches
them, and with the weight it decides only near that limit. Every operation is a checked replacement of a region
(positive by exact `orient3d`, faces matched with opposite orientation, removed vertices fully inside), so the mesh is
valid after each step.
valid after each step. Speed (#84): an operation (topological, per tetrahedron, edge and face; or a smoothing) that
failed is not tried again until a tetrahedron it reads changes (a change clock per vertex; exact, because the
operations are functions of that region and the quality is computed on the corners in a canonical order), live
tetrahedra's qualities are cached, Klincsek's table starts at the quality to beat, and the composite insertion runs
its flips and neighbour smoothing only where a tetrahedron is below the threshold.
**A3.7 Cheng, Dey, Edelsbrunner, Facello, Teng 2000, Sliver exudation.** **Verdict: reference.**
- **Citation.** J. ACM 47(5):883–904, 2000. DOI [10.1145/355483.355487](https://doi.org/10.1145/355483.355487).
+2 -2
View File
@@ -31,7 +31,7 @@ Times are wall clock on the machine that ran it.
| bolted-assembly | plan (sweep), mesh.hex.generate (auto) | – | 6272 (6272 hex8) | 0.583 | 0.939 | 3.6 | 2.0e-4 | 0 | 0 (not relaxed) | 8 coincident, 0 cracks | – | 3.7 | pass | [x=50.000](skill/bolted-assembly.png) |
| cantilever-i-beam | plan (sweep) | – | 61600 (61600 hex8) | 1.000 | 1.000 | 1.0 | 0.0e0 | 0 | 0 (not relaxed) | none | – | 8.5 | pass | [x=100.000](skill/cantilever-i-beam.png) |
| cross-cylinders | none | – | – | – | – | – | – | – | – | – | – | 27.2 | no mesh | – |
| filleted-bracket | tet10 | – | 19991 (19991 tet10) | 0.318 | 0.564 | 4.1 | -6.8e-6 | 0 | 0 (not relaxed) | none | – | 15.1 | pass (fallback) | [x=30.000](skill/filleted-bracket.png) |
| filleted-bracket | tet10 | – | 20020 (20020 tet10) | 0.320 | 0.562 | 4.1 | -6.8e-6 | 0 | 0 (not relaxed) | none | – | 12.7 | pass (fallback) | [x=30.000](skill/filleted-bracket.png) |
| filleted-bracket-blocks | plan (sweep) | – | 75410 (75410 hex8) | 0.704 | 0.954 | 3.5 | 3.7e-5 | 0 | 0 (not relaxed) | none (after merging 2348 nodes) | – | 6.9 | pass | [x=30.000](skill/filleted-bracket-blocks.png) |
| i-beam | plan (sweep) | – | 61600 (61600 hex8) | 1.000 | 1.000 | 1.0 | 0.0e0 | 0 | 0 (not relaxed) | none | – | 4.5 | pass | [z=100.000](skill/i-beam.png) |
| loft-transition | plan (sweep) | – | 2873 (2873 hex8) | 0.189 | 0.959 | 1.7 | -2.1e-3 | 0 | 4 → 4 (4 relaxed, 0 undone; min SJ 0.189 → 0.189, max ratio 1.7 → 1.7) | none | – | 0.9 | fail (SJ) | [z=25.000](skill/loft-transition.png) |
@@ -40,7 +40,7 @@ Times are wall clock on the machine that ran it.
| rounded-box | plan (project) | 2 plane(s) | 11520 (11520 hex8) | 0.717 | 0.973 | 4.0 | -4.1e-3 | 0 | 0 (not relaxed) | none | – | 8.7 | pass | [x=20.000](skill/rounded-box.png) |
| shallow-groove | plan (sweep) | – | 1960 (1960 hex8) | 0.991 | 0.999 | 1.1 | 2.7e-4 | 0 | 0 (not relaxed) | none | – | 0.2 | pass | [x=30.000](skill/shallow-groove.png) |
| sharp-wedge | plan (sweep) | – | 1674 (1674 hex8) | 0.014 | 0.301 | 149.6 | 2.7e-15 | 0 | 1524 (not relaxed) | none | – | 0.2 | fail (SJ, ratio) | [x=40.000](skill/sharp-wedge.png) |
| sphere-on-plate | tet10 | – | 25613 (25613 tet10) | 0.335 | 0.607 | 4.4 | -8.8e-5 | 0 | 0 (not relaxed) | none | – | 7.7 | pass (fallback) | [y=0.000](skill/sphere-on-plate.png) |
| sphere-on-plate | tet10 | – | 24175 (24175 tet10) | 0.341 | 0.604 | 4.2 | -9.6e-5 | 0 | 0 (not relaxed) | none | – | 6.5 | pass (fallback) | [y=0.000](skill/sphere-on-plate.png) |
| spline-vase | plan (sweep) | – | 4536 (4536 hex8) | 0.665 | 0.945 | 9.9 | -6.6e-3 | 0 | 0 (not relaxed) | none | – | 2.3 | pass | [z=-35.000](skill/spline-vase.png) |
| stepped-block | plan (cells) | – | 1416 (1416 hex8) | 1.000 | 1.000 | 1.3 | -4.8e-15 | 0 | 0 (not relaxed) | none | – | 1.6 | pass | [x=30.000](skill/stepped-block.png) |
| thin-ribs | plan (sweep) | – | 301308 (301308 hex8) | 0.999 | 1.000 | 1.3 | -3.4e-12 | 0 | 0 (not relaxed) | none | – | 34.7 | pass | [x=50.000](skill/thin-ribs.png) |
Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 105 KiB