spice_sim.py — explained

circuitgenome/sizer/shared/spice_sim.py — a 25-line shim that keeps an old import path working after the real code moved house.

Part 1 · Why this file even exists

The one job: let old code keep writing from …spice_sim import simulate_metrics even though the actual ngspice code was moved out into a spice/ sub-package.

There is almost no code here to "run." The whole file is a signpost. So the lesson isn't a line of logic — it's a design pattern: the facade.

Picture a company that outgrew a single office. The ngspice verification used to live in one big file, spice_sim.py. It grew — deck building, a testbench rig, a dozen per-metric measurements, DC bias soundness — until it deserved to be split into a proper spice/ package with one file per job. But dozens of callers all over the codebase already say:

from circuitgenome.sizer.shared.spice_sim import simulate_metrics

Move the code and every one of those imports breaks. The facade is the fix: a thin module that forwards the old names to their new home. Callers never notice the furniture moved.

The switchboard analogy

Think of a company receptionist. You call one well-known number and ask for "simulate metrics." The receptionist doesn't do the work — she just connects you to whichever department now handles it. Tomorrow that department could move to a different floor; you still dial the same number. spice_sim.py is that receptionist for the spice/ package.

callers the rest of the codebase spice_sim .py the facade import forward spice/ sub-package deckdecks, netlists, runners rigshared testbench rig measureper-metric tests opop-point + bias soundness simulatesimulate_metrics()
The receptionist in the middle does no work — it just routes each old name onward to the file that now owns it.

Looking ahead: Part 2 opens the receptionist's desk and reads the two things she actually contains — an import that pulls names in, and an __all__ that hands them back out.

Part 2 · The re-export lines, in full

The one job: pull six names in from the sub-package, then declare those same six as this module's public surface so from spice_sim import X resolves for every one of them.

Here is the entire body of the file. Two statements, nothing else.

from .spice import (
    check_bias_soundness,
    ngspice_available,
    pdk_netlist,
    read_op_operating_point,
    simulate_metrics,
    sized_netlist,
)

__all__ = [
    "check_bias_soundness",
    "ngspice_available",
    "pdk_netlist",
    "read_op_operating_point",
    "simulate_metrics",
    "sized_netlist",
]

Line 1 — the forwarding import

The mailbox analogy is exact. You moved apartments but filed a change-of-address so mail to the old address still reaches you. from .spice import (…) is that forwarding card: the . means "the package next to me," and each name listed is a piece of mail redirected from spice/ into this module's namespace. After this line runs, simulate_metrics is a real, callable name living inside spice_sim — pointing at the exact same function object defined over in spice/simulate.py.

Why is it called __all__?

I like this name. __all__ is Python's answer to "when someone does from spice_sim import *, what counts as all of me?" Without it, a wildcard import would also drag along anything else that happened to be in scope. Listing the six names makes the public API explicit and intentional — this is the storefront window, and only these six items are on display.

So the two statements are a matched pair: the import brings the goods into the shop, and __all__ puts them in the window.

public name defined in facade simulate_metrics simulate.py ngspice_available deck.py pdk_netlist deck.py sized_netlist deck.py check_bias_soundness op.py read_op_operating_point op.py
One flat public list, three different home files — the facade hides that split entirely from callers.

Notice what the picture reveals: the six names come from three different files (deck, op, simulate), yet callers see one flat menu. That collapsing of internal structure into a single friendly surface is the facade's value.

Public nameWhat it doesReal home
simulate_metricsEntry point: re-simulate a sized circuit, measure every metricspice/simulate.py
ngspice_availableIs the ngspice binary usable at all?spice/deck.py
pdk_netlistEmit the base PDK/model netlistspice/deck.py
sized_netlistStamp chosen W/L into the netlistspice/deck.py
check_bias_soundnessDC bias-soundness verdict from the op pointspice/op.py
read_op_operating_pointParse ngspice's operating-point dumpspice/op.py

Part 3 · The hidden idea, the limits, and one sentence

The file never states its purpose as a principle, but this is the idea it quietly implements — the public API is decoupled from the internal package layout:

what callers import  ≠  where the code lives

Because those two are held apart by the facade, the team is free to reshape spice/ — rename files, split deck in two, move check_bias_soundness from op.py to some new bias.py — and not one caller has to change. Only this one 25-line file gets edited. That is the whole payoff, drawn below.

from …spice_sim import simulate_metrics ↑ identical in both worlds — never changes ↑ before spice_sim.py deck + rig + measure + op + simulate all in one big file split after spice_sim.py facade · ~25 lines spice/ package deck · rig · measure · op · simulate
The big file became a package; the import line on top didn't move a character. That gap is exactly what the facade buys.

What it honestly does not do

  • It adds no behavior — no logic, no wrapping, no validation. Call simulate_metrics here and you run the identical code as calling it from spice.simulate.
  • It forwards only these six names. Anything else in spice/ is not reachable through this shim — by design, so the public surface stays small.
  • It does not shield you from signature changes. If simulate_metrics gains a new required argument, the facade forwards that change straight through — it hides location, not contract.
  • It is a compatibility bridge. New code can import from …shared.spice directly; this path exists chiefly to avoid breaking what already works.

Why it's elegant

The cleverness is in the restraint. A whole subsystem got reorganized, and the blast radius was contained to one tiny file that carries no logic to break. Six names in, six names out, and every existing caller sails on unaware. Cheap to write, nearly impossible to get wrong, and it turns a scary cross-cutting refactor into a one-file edit.


In one sentence: spice_sim.py is a facade that re-exports six names from the spice/ sub-package so the historical import path from …spice_sim import simulate_metrics keeps working — decoupling what callers import from where the code actually lives, so the internals can be refactored without breaking a single caller.