Speed up rasterization ~40x and large solves ~2x

- Hybrid rasterizer: PIL scanline fill for the bulk, with cells in a
  ~2 px band around each ring edge re-tested exactly against the
  polygon - cell-for-cell identical to the old center-in-polygon pass
  (equivalence test added) but O(vertices + cells) instead of
  O(vertices x cells). Measured 4.5 s -> 0.11 s at 1.45M cells with
  8.8k polygon vertices.
- AMG-preconditioned CG (pyamg, new requirement) above 500k unknowns:
  measured 7.0 s vs 15.3 s spsolve at 1.4M unknowns at a fraction of
  the memory, R identical to 1e-6; the old Jacobi-CG (kept as fallback
  when pyamg is missing) needed tens of minutes there. spsolve stays
  the default below 500k where it is exact and fastest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
janik
2026-07-15 15:24:21 +07:00
parent 6e989fc5f8
commit b62e45a9b4
9 changed files with 124 additions and 23 deletions
+12 -1
View File
@@ -79,10 +79,21 @@ def test_hole_increases_resistance_and_converges():
assert abs(r_a.R_ohm - r_b.R_ohm) < 0.01 * r_b.R_ohm
def test_cg_path_matches_direct(monkeypatch):
def test_iterative_paths_match_direct(monkeypatch):
"""AMG-CG (default iterative) and Jacobi-CG (pyamg-missing fallback)
both reproduce the direct solve."""
p = strip_problem(length=50, width=10, e_len=5)
r_direct, _ = _solve(p, 0.25)
monkeypatch.setattr(config, "SPSOLVE_MAX_UNKNOWNS", 0)
r_amg, _ = _solve(p, 0.25)
assert r_amg.solve_info.method == "amg+cg"
assert r_amg.R_ohm == pytest.approx(r_direct.R_ohm, rel=1e-6)
assert r_amg.mismatch_rel < 1e-5
def no_pyamg(A, b):
raise ImportError("pyamg unavailable")
monkeypatch.setattr(solver, "_solve_amg", no_pyamg)
r_cg, _ = _solve(p, 0.25)
assert r_cg.solve_info.method == "cg+jacobi"
assert r_cg.R_ohm == pytest.approx(r_direct.R_ohm, rel=1e-6)