Skip to content

perf: two-level grid makes point location outpace TriFinder - #5

Merged
ycli0536 merged 1 commit into
mainfrom
two-level-point-location-grid
Aug 30, 2026
Merged

perf: two-level grid makes point location outpace TriFinder#5
ycli0536 merged 1 commit into
mainfrom
two-level-point-location-grid

Conversation

@ycli0536

Copy link
Copy Markdown
Owner

Follow-up to #4, removing the 0.7s regression it accepted — and going past the matplotlib implementation it replaced. Motivated by upcoming models with much larger seed counts.

Diagnosis

Profiling the largest fixture showed the single grid was healthy on average but wrong exactly where it mattered:

triangles per cell:    mean 3.3, median 2        ← grid fine overall
candidates per QUERY:  mean 1701, max 2940       ← seeds land in the worst cells
total pairs tested:    65M

A graded MARE2DEM mesh concentrates its small triangles — and every region seed — in a core that the huge boundary triangles dwarf. Cells sized to the whole domain put thousands of core triangles into single cells.

Fix

Two grid levels:

  • Fine level: cells sized to the median triangle, so the dense core stays at a few candidates per query. Axis count capped at 2048 to bound the offset table.
  • Coarse level: the previous sqrt(n) grid, holding only triangles that would smear across >64 fine cells. They are few, so its cells stay small.

Queries hit the fine level first; the coarse pass skips already-resolved points.

Measured (identical indices to TriFinder in every case)

workload single grid (#4) two-level mpl TriFinder
75k triangles × 38k seeds 1.09s 0.137s 0.402s
75k triangles × 380k seeds 0.392s 0.791s

The pure-numpy locator now beats the C++ trapezoid map 2–3× on these workloads: TriFinder pays an O(n log n) build per instantiation, the two-level grid builds in 72ms.

New test class exercises both levels on a synthetic graded mesh (tiny core inside ±500 boundary giants) with an 800-point cross-check against matplotlib. Suite: 379 passed.

🤖 Generated with Claude Code

The single grid sized cells to the whole domain, but a graded MARE2DEM
mesh concentrates its small triangles -- and every region seed -- in a
core the huge boundary triangles dwarf. Profiling the largest fixture
showed the median cell held 2 triangles while the cells the seeds
actually landed in held up to 2,940: 65M candidate pairs, 1.07s.

Two levels fix the mismatch. The fine level sizes cells to the median
triangle, so the dense core stays at a few candidates per query; the
triangles too large for it (they would smear across >64 fine cells) go
to a coarse sqrt(n) grid, where they are few enough to keep cells small.
Queries check the fine level first and skip resolved points on the
coarse pass.

Largest fixture (75k triangles x 38k seeds), identical indices:
- single grid  1.09s
- two-level    0.137s
- TriFinder    0.402s

380k-seed stress (the target workload), identical indices:
- two-level    0.392s
- TriFinder    0.791s

The 0.7s regression PR #4 accepted is gone; the pure-numpy locator is
now 2-3x faster than matplotlib's C++ trapezoid map on these workloads,
because its per-instantiation build is 72ms against TriFinder's
O(n log n) construction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ycli0536
ycli0536 merged commit 6d2c498 into main Aug 30, 2026
10 checks passed
@ycli0536
ycli0536 deleted the two-level-point-location-grid branch August 30, 2026 22:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant