Source code for reasitic.exports.fasthenry

"""FastHenry input-file (``.inp``) writer.

FastHenry (Kamon, Tsuk, White, MIT 1994) is the de-facto open-source
inductance-extraction tool, taking a polygonal-conductor description
and producing the full 3-D inductance matrix at user-supplied
frequencies. Its ``.inp`` format is line-oriented::

    .units um
    .default sigma=...

    N1 x=... y=... z=...
    N2 x=... y=... z=...

    E1 N1 N2 w=... h=... sigma=...

    .external N1 N2

    .freq fmin=1e9 fmax=10e9 ndec=1

    .end

Lets users feed reASITIC's geometry into FastHenry for cross-
validation against an independent solver.
"""

from __future__ import annotations

from io import StringIO
from pathlib import Path

from reasitic.geometry import Shape
from reasitic.tech import Tech


[docs] def write_fasthenry( shape: Shape, tech: Tech, *, freqs_ghz: list[float] | None = None, ) -> str: """Render ``shape`` as a FastHenry ``.inp`` string. Each segment becomes one ``E`` element with two named nodes; consecutive segments share end nodes so the spiral forms a proper conductor chain. """ out = StringIO() out.write("* reASITIC FastHenry input\n") out.write(".units um\n") out.write(".default x=0 y=0 z=0\n") segments = shape.segments() if not segments: out.write(".end\n") return out.getvalue() # Emit nodes: one per unique endpoint nodes: dict[tuple[float, float, float], str] = {} for s in segments: for v in (s.a, s.b): key = (v.x, v.y, v.z) if key not in nodes: nodes[key] = f"N{len(nodes) + 1}" out.write( f"{nodes[key]} x={v.x:g} y={v.y:g} z={v.z:g}\n" ) # Emit segments for i, s in enumerate(segments): n_a = nodes[(s.a.x, s.a.y, s.a.z)] n_b = nodes[(s.b.x, s.b.y, s.b.z)] if 0 <= s.metal < len(tech.metals): rsh = tech.metals[s.metal].rsh t_um = tech.metals[s.metal].t # Conductivity from rsh (Ω/sq) and t (μm): σ = 1/(rsh·t) sigma_si = ( 1.0 / (rsh * t_um * 1.0e-6) if rsh > 0 and t_um > 0 else 4e7 ) else: sigma_si = 4e7 out.write( f"E{i + 1} {n_a} {n_b}" f" w={s.width:g} h={s.thickness:g} sigma={sigma_si:g}\n" ) # External ports = first and last node first_node = nodes[(segments[0].a.x, segments[0].a.y, segments[0].a.z)] last_node = nodes[(segments[-1].b.x, segments[-1].b.y, segments[-1].b.z)] out.write(f".external {first_node} {last_node}\n") # Frequency directive if freqs_ghz: f_min = min(freqs_ghz) * 1.0e9 f_max = max(freqs_ghz) * 1.0e9 n_pts = max(1, len(freqs_ghz) - 1) out.write(f".freq fmin={f_min:g} fmax={f_max:g} ndec={n_pts}\n") out.write(".end\n") return out.getvalue()
[docs] def write_fasthenry_file( path: str | Path, shape: Shape, tech: Tech, *, freqs_ghz: list[float] | None = None, ) -> None: """Write the FastHenry rendering of ``shape`` to ``path``.""" Path(path).write_text(write_fasthenry(shape, tech, freqs_ghz=freqs_ghz))