Wire the adaptive quadtree grid into the solve path (phase 2)

config.ADAPTIVE_CELLS (dialog checkbox "adaptive cells", off by
default; standalone --adaptive) routes run_solve through
fill_resistance/adaptive.py: per-layer balanced leaf grids where every
non-uniform fine cell (electrodes, 1D chain cells, buildup, via-mouth
thickness map) is pinned at the fine size, leaf faces via the
series-half-cell rule, chain links and barrels re-attached by node id,
connectivity restriction and both contact models on the leaf graph via
solver cores extracted for reuse (_equipotential_core, _uniform_core,
_conductance_params, _barrel_links). All fields (V, |J|, power density)
are computed per leaf and expanded to the fine grid, so plots, summary
and dumps are unchanged.

Element sizes: minimum = the grid cell size itself (auto / dialog /
CELL_UM_OVERRIDE); maximum = ADAPTIVE_MAX_CELL_UM (2 mm default);
ADAPTIVE_GUARD sets the clearance a block needs to grow.

Measured end-to-end (feature-dense 120x120 plate, h=50um): 25.9 s ->
5.4 s, 5.58M -> 823k unknowns, R -1.1%. Accuracy documented honestly:
coarse-fine interfaces carry a first-order tangential flux error
biasing R low by ~0.5-2% depending on geometry (worst on narrow
strips); the earlier assumption that linear fields solve exactly on the
leaf graph was wrong - offset centers across size transitions leave an
unpaired residue. Gradient-corrected interface fluxes remain as phase 4
if tighter accuracy per leaf is needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
janik
2026-07-15 17:23:41 +07:00
parent 44ffa7b6d1
commit a9233fde32
9 changed files with 690 additions and 104 deletions
+15 -11
View File
@@ -123,7 +123,7 @@ def _emit(S: np.ndarray, mask: np.ndarray, max_block: int) -> LeafGrid:
def _split_unbalanced(grid: LeafGrid, S: np.ndarray) -> bool:
"""Cap S over any leaf more than 2x an edge-adjacent neighbor, so the
next emission splits it. Returns True if anything was capped."""
ia, ib, _ = leaf_faces(grid)
ia, ib, _, _ = leaf_faces(grid)
sa, sb = grid.size[ia], grid.size[ib]
big = np.unique(np.concatenate([ia[sa > 2 * sb], ib[sb > 2 * sa]]))
for lid in big:
@@ -132,32 +132,36 @@ def _split_unbalanced(grid: LeafGrid, S: np.ndarray) -> bool:
return len(big) > 0
def leaf_faces(grid: LeafGrid) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
"""(ia, ib, w) for every edge-adjacent leaf pair, each pair once;
w = shared face length in fine-cell units."""
def leaf_faces(grid: LeafGrid) -> tuple[np.ndarray, np.ndarray, np.ndarray,
np.ndarray]:
"""(ia, ib, w, axis) for every edge-adjacent leaf pair, each pair
once; w = shared face length in fine-cell units. axis 0 = x-faces
(a left of b), axis 1 = y-faces (a above b)."""
n = grid.n
if n == 0:
z = np.zeros(0, dtype=np.int64)
return z, z, z
return z, z, z, z
out = []
for sl_a, sl_b in ((np.s_[:, :-1], np.s_[:, 1:]),
(np.s_[:-1, :], np.s_[1:, :])):
for axis, (sl_a, sl_b) in enumerate(((np.s_[:, :-1], np.s_[:, 1:]),
(np.s_[:-1, :], np.s_[1:, :]))):
a = grid.id_grid[sl_a].ravel().astype(np.int64)
b = grid.id_grid[sl_b].ravel().astype(np.int64)
ok = (a >= 0) & (b >= 0) & (a != b)
key, counts = np.unique(a[ok] * n + b[ok], return_counts=True)
out.append((key // n, key % n, counts))
out.append((key // n, key % n, counts,
np.full(len(key), axis, dtype=np.int8)))
ia = np.concatenate([o[0] for o in out])
ib = np.concatenate([o[1] for o in out])
w = np.concatenate([o[2] for o in out])
return ia, ib, w
ax = np.concatenate([o[3] for o in out])
return ia, ib, w, ax
def leaf_edges(grid: LeafGrid, sigma) -> tuple[np.ndarray, np.ndarray,
np.ndarray]:
"""Face conductances [S]: sigma is a scalar or per-leaf array of
sheet conductance. Uniform limit -> exactly sigma per face."""
ia, ib, w = leaf_faces(grid)
ia, ib, w, _ = leaf_faces(grid)
sig = np.broadcast_to(np.asarray(sigma, dtype=float), (grid.n,))
g = w / (grid.size[ia] / (2.0 * sig[ia])
+ grid.size[ib] / (2.0 * sig[ib]))
@@ -166,6 +170,6 @@ def leaf_edges(grid: LeafGrid, sigma) -> tuple[np.ndarray, np.ndarray,
def balanced(grid: LeafGrid) -> bool:
"""2:1 balance invariant: edge-adjacent leaves differ <= 2x in size."""
ia, ib, _ = leaf_faces(grid)
ia, ib, _, _ = leaf_faces(grid)
sa, sb = grid.size[ia], grid.size[ib]
return bool((np.maximum(sa, sb) <= 2 * np.minimum(sa, sb)).all())