Files
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

81 lines
2.9 KiB
Python

"""Skin-effect corrections: frequency-dependent effective sheet
resistance of a copper foil and via-barrel wall.
1D diffusion through the foil thickness (exact): with tau = (1+j)/delta,
the internal impedance per square of a foil of thickness t is
one-sided field (plane over a return plane): Zs = tau*rho * coth(tau*t)
two-sided field (isolated foil): Zs = tau*rho/2 * coth(tau*t/2)
Both reduce to rho/t at DC and to rho/delta (resp. rho/(2*delta)) at
high frequency. R_AC = Re(Zs) is used as the effective sheet resistance.
HONESTY NOTE (also in the README): only the through-thickness current
crowding is modeled. Lateral redistribution (proximity effect - AC
current following the minimum-inductance path) needs a magneto-
quasistatic solve and is NOT captured; since the resistance-driven
distribution is the minimum-dissipation one, the reported AC resistance
is a rigorous LOWER BOUND at the given frequency.
"""
from __future__ import annotations
import cmath
import math
MU0 = 4e-7 * math.pi
def skin_depth_m(freq_hz: float, rho_ohm_m: float) -> float:
return math.sqrt(2.0 * rho_ohm_m / (2.0 * math.pi * freq_hz * MU0))
def _coth(x: complex) -> complex:
return 1.0 / cmath.tanh(x)
def sheet_resistance_ac(thickness_m: float, freq_hz: float,
rho_ohm_m: float, sides: int = 1) -> float:
"""Effective sheet resistance [ohm/sq] of a foil at freq_hz.
sides=1: field on one side (plane facing a return plane, conservative);
sides=2: symmetric field on both sides (isolated foil)."""
if freq_hz <= 0.0:
return rho_ohm_m / thickness_m
delta = skin_depth_m(freq_hz, rho_ohm_m)
tau = (1.0 + 1.0j) / delta
if sides == 2:
zs = tau * rho_ohm_m / 2.0 * _coth(tau * thickness_m / 2.0)
else:
zs = tau * rho_ohm_m * _coth(tau * thickness_m)
return zs.real
def resistance_factor(thickness_m: float, freq_hz: float,
rho_ohm_m: float, sides: int = 1) -> float:
"""R_AC / R_DC of a foil (or barrel wall) of the given thickness."""
if freq_hz <= 0.0:
return 1.0
return (sheet_resistance_ac(thickness_m, freq_hz, rho_ohm_m, sides)
/ (rho_ohm_m / thickness_m))
def parse_frequency(text: str) -> float:
"""'0', '100k', '1.5M', '142500' -> Hz; empty -> 0 (DC).
Raises ValueError on unparseable or negative input (a typo silently
becoming DC would mislabel the result)."""
t = text.strip().lower().replace(",", ".").removesuffix("hz").strip()
if not t:
return 0.0
mult = 1.0
if t.endswith("meg"):
mult, t = 1e6, t[:-3]
elif t.endswith("m"):
mult, t = 1e6, t[:-1]
elif t.endswith("k"):
mult, t = 1e3, t[:-1]
elif t.endswith("g"):
mult, t = 1e9, t[:-1]
value = float(t) * mult # ValueError on garbage
if value < 0:
raise ValueError(f"negative frequency: {text!r}")
return value