Model sub-resolution traces as exact 1D resistor chains
Tracks are now first-class Problem objects (TrackSeg: centerline + width, dump schema v5), so the wide/narrow decision replays at raster time: traces at least TRACK_1D_FACTOR (3) cells wide rasterize from their outline as before; narrower ones mark the cells their centerline crosses as copper and connect them with explicit conductance links carrying the trace's TRUE arc length per link - no staircase inflation for diagonals or arcs, and no discretization error in the trace R, at any grid size. Links across cells already joined by pour faces are skipped (union, not sum); chain-only cells get no sheet faces (their copper is narrower than a cell). Electrodes, via barrels, connectivity restriction and the skin-effect scaling all work on chain cells unchanged. This removes the need to shrink the cell size for thin traces: a 0.2 mm bridge at 500 um cells now matches its finely-rasterized ground truth within a few percent (tested), including diagonal and arc traces. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -141,6 +141,10 @@ def build_edges(stack: RasterStack, problem: Problem, sigmas: list[float],
|
||||
|
||||
for li in range(L):
|
||||
m = stack.masks[li]
|
||||
if stack.chain is not None:
|
||||
# chain-only cells connect through their explicit 1D links,
|
||||
# never through sheet faces (their copper is narrower than h)
|
||||
m = m & ~stack.chain[li]
|
||||
sig = sigmas[li]
|
||||
scell = _sigma_2d(stack, li, sig, sigma_buildup)
|
||||
base = li * plane
|
||||
@@ -207,6 +211,19 @@ def build_edges(stack: RasterStack, problem: Problem, sigmas: list[float],
|
||||
ww.append(np.array([1.0 / r]))
|
||||
vv.append(np.array([vi], dtype=np.int32))
|
||||
|
||||
if stack.chain_edges is not None and len(stack.chain_edges[0]):
|
||||
ca, cb, cg, cl = stack.chain_edges
|
||||
alive = stack.masks.ravel()[ca] & stack.masks.ravel()[cb]
|
||||
if alive.any():
|
||||
# skin correction: scale like the layer's sheet conductance
|
||||
fac = np.array([sigmas[l] * problem.rho_ohm_m
|
||||
/ (problem.layers[l].thickness_nm * 1e-9)
|
||||
for l in range(L)])
|
||||
aa.append(ca[alive])
|
||||
bb.append(cb[alive])
|
||||
ww.append((cg * fac[cl])[alive])
|
||||
vv.append(np.full(int(alive.sum()), -1, dtype=np.int32))
|
||||
|
||||
if not aa:
|
||||
raise ConnectivityError("No copper found on the selected layers.")
|
||||
return Edges(a=np.concatenate(aa), b=np.concatenate(bb),
|
||||
@@ -540,6 +557,8 @@ def run_solve(problem: Problem, stack: RasterStack, e1: np.ndarray,
|
||||
)
|
||||
if stack.buildup is not None:
|
||||
stack.buildup &= stack.masks
|
||||
if stack.chain is not None:
|
||||
stack.chain &= stack.masks
|
||||
for label, m in (parts1 or []) + (parts2 or []):
|
||||
had = bool(m.any())
|
||||
m &= stack.masks # follow the component restriction
|
||||
|
||||
Reference in New Issue
Block a user