Skip to content

perf: speed up area preservation with Newton/Steiner buffer search - #8

Merged
wrignj08 merged 1 commit into
mainfrom
perf/area-preservation-newton
Jun 30, 2026
Merged

wrignj08 merged 1 commit into
mainfrom
perf/area-preservation-newton

Conversation

@wrignj08

Copy link
Copy Markdown
Contributor

What

Replaces the bracketed Brent's-method search in _preserve_area_with_buffer with a closed-form Steiner-quadratic seed refined by Newton's method.

The buffered area of a polygon follows the Steiner expansion A(d) ≈ A₀ + L·d + π·d² (L = perimeter), so the initial offset is found by solving that quadratic instead of a linear estimate. Each candidate is then refined with Newton's method using the measured boundary length as the derivative — the rate of area change of an outward offset equals its perimeter (coarea identity), so the derivative is essentially exact and free. Because buffered area is monotonic in distance, no bracketing is needed.

This cuts buffer operations per ring from roughly 5–6 to 1–2.

Why

Profiling the full pipeline on examples/Water.gpkg showed area preservation (brentq + repeated full-polygon buffering) was the single largest cost (~34% at 5 iterations), not the Chaikin smoothing itself.

Results (single-core, examples/Water.gpkg, old vs new)

iters tol old new speedup buffers old→new area err old/new geom Δ med/max
5 0.01 5079 ms 3847 ms 1.32× 2910→801 0.0064% / 0.0064% 0.0003% / 0.0050%
3 0.01 3512 ms 3200 ms 1.10× 2916→869 0.0064% / 0.0064% 0.0002% / 0.0095%
5 0.1 4394 ms 3553 ms 1.24× 2712→781 0.0279% / 0.0279% 0.0011% / 0.0053%

Speedup scales with iteration count and the polygon share of the workload (down to ~1.0× on hole-dominated inputs, where hole-differencing dominates). Verified equivalent across several synthetic datasets (many small, few large, with holes) as well.

Safety

  • Output unchanged within the area tolerance: per-polygon symmetric difference ≤ 0.005%, area-preservation accuracy identical.
  • Shapes where Newton stalls (pinch-offs, topology changes near the root) fall back to the original Brent's-method search, preserved verbatim as _preserve_area_brentq.
  • Pure Python, no new dependencies.

Tests

7 new tests in tests/test_smoothify_core.py (Newton path on concave shapes, the fallback path, a buffer-count regression guard, Newton/brentq agreement, empty input). Full suite: 432 passed. mypy strict + ruff clean.

🤖 Generated with Claude Code

Replace the bracketed Brent's-method search in _preserve_area_with_buffer
with a Steiner-quadratic seed (A(d) ~= A0 + L*d + pi*d^2) refined by
Newton's method, using the measured boundary length as the derivative
(dA/dd equals the offset's perimeter), so no bracketing is needed. This
cuts buffer operations per ring from ~5-6 to ~1-2.

On examples/Water.gpkg the full single-core pipeline is ~1.3x faster at
the default 5 iterations (5.1s -> 3.8s). Output is unchanged within the
area tolerance (per-polygon symmetric difference <= 0.005%, area accuracy
identical). Shapes where Newton stalls (pinch-offs, topology changes near
the root) fall back to the original Brent's-method search, preserved
verbatim as _preserve_area_brentq.

Adds 7 tests covering the Newton path, the fallback, buffer-count
regression, and Newton/brentq agreement.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@wrignj08
wrignj08 merged commit b2e33b7 into main Jun 30, 2026
10 checks passed
@wrignj08
wrignj08 deleted the perf/area-preservation-newton branch July 2, 2026 05:23
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