Files
T
janik e7627352c1 Fix solver correctness and input-validation issues from code review
- Refuse the uniform contact model when the fills form multiple
  disconnected copper groups that each touch both terminals: the
  prescribed injection split is ill-posed and the grounded system was
  singular, silently returning garbage (e.g. negative gigaohms).
  connected_restrict now reports the component count; a power-balance
  backstop (SolverError) catches any other inconsistent solve.
- Connect via/pad barrels to the nearest fill copper within the pad
  footprint (+1 cell) instead of only the exact center cell, so
  thermal-relief spokes still stitch layers; barrels that reach fill on
  fewer than two layers are warned about. ViaLink gains pad_nm
  (extracted from the padstack, JSON-roundtripped).
- Validate dialog input on OK (layers, current > 0, cell > 0, parseable
  frequency, extra Cu >= 0) with an inline error instead of silently
  substituting defaults; parse_frequency raises on garbage; pipeline
  rejects i_test <= 0; choose_cell_size rejects non-positive overrides.
- Warn when a contact part is dropped by the connectivity restriction;
  floor instead of truncate in cell_of; correct the uniform-model
  summary line; drop an unused variable; refresh plugin.json wording.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 14:54:45 +07:00

55 lines
2.2 KiB
Python

"""Geometry-in -> results-out pipeline shared by the KiCad entrypoint and
the offline standalone runner."""
from __future__ import annotations
from pathlib import Path
from . import config, plots, raster, report, solver
from .errors import UserFacingError
from .geometry import Problem
from .solver import Result
def run(problem: Problem, outdir: Path | None, show: bool = True,
i_test: float | None = None, freq_hz: float = 0.0,
contact_model: str | None = None) -> Result:
if i_test is None:
i_test = config.TEST_CURRENT_A
if i_test <= 0:
raise UserFacingError(f"Test current must be > 0 A (got {i_test:g}).")
h = raster.choose_cell_size(problem.copper_bbox(), len(problem.layers))
print(f"rasterizing {len(problem.layers)} layer(s) at cell size "
f"{h / 1000:.1f} um ...")
stack = raster.rasterize_stack(problem, h)
print(f"grid {stack.shape2d[1]}x{stack.shape2d[0]}x{stack.nlayers}, "
f"{int(stack.masks.sum())} copper cells, {len(problem.vias)} "
f"via/pad barrel(s)")
e1, e2 = raster.electrode_masks(stack, problem)
parts1, parts2 = raster.electrode_partition(stack, problem)
print(f"solving @ {i_test:g} A"
+ (f", {freq_hz:g} Hz" if freq_hz > 0 else " DC") + " ...")
result = solver.run_solve(problem, stack, e1, e2, i_test, freq_hz,
contact_model, parts1, parts2)
for prefix, pcs in (("P", result.part_currents1),
("N", result.part_currents2)):
for i, (label, amps) in enumerate(pcs):
print(f" {prefix}{i + 1} ({label}): {amps:.4g} A "
f"({100 * amps / i_test:.1f}%)")
if outdir is not None:
outdir.mkdir(parents=True, exist_ok=True)
report.write_summary(outdir, problem, stack, result)
print(report.result_line(result, problem, stack))
figs = [
(plots.fig_raster(stack, e1, e2, problem, result), "1_raster_map"),
(plots.fig_potential(result, stack, e1, e2, problem), "2_potential"),
(plots.fig_current(result, stack, e1, e2, problem),
"3_current_density"),
(plots.fig_power(result, stack, e1, e2, problem), "4_power_density"),
]
plots.save_and_show(figs, outdir, show=show)
return result