Build circuit and control schematics in Python, compile them to netlists, run them, and measure the waveforms that come back.
from gsim_tools.schematic import Schematic
sch = Schematic("rc", "RC low-pass")
vin = sch.add("source.voltage.dc", id="Vin", at=(0, 0), V=5.0)
r1 = sch.add("passive.resistor", id="R1", at=(60, 0), R=1000.0)
c1 = sch.add("passive.capacitor", id="C1", at=(120, 0), C=1e-6)
gnd = sch.add("routing.ground", id="gnd", at=(60, 80))
sch.connect(vin.p, r1.p1)
sch.connect(r1.p2, c1.p1)
sch.connect(c1.p2, gnd.p)
sch.connect(vin.n, gnd.p)
print(sch.compile())* RC low-pass -- generated by gsim-core 0.1.0 from model 'rc'
Vin n_R1_p1 0 5
R1 n_R1_p1 n_C1_p1 1000
C1 n_C1_p1 0 1e-6
A schematic is an object you construct, wire up and validate, not a file you draw. Validation happens as you build: a wire to a port that does not exist, a signal port fed by two sources, a connection across incompatible paradigms or dimensions — each raises its own exception, at the line that caused it, rather than producing a netlist that fails later for a reason you have to work backwards from.
gsim_tools.schematic— build, validate and compile schematics. The main surface, and the reason this package exists.gsim_tools.results— read simulation output back, from either format that exists:general-simulator's own CSV (including its streaming transient output) andkernel-sidecar'sSIMULATE_RESULTmanifest directories.gsim_tools.measure— RMS, THD, average power, peak, mean, frequency and duty cycle, threshold crossings, error norms — over non-uniformly sampled waveforms, which is what a variable-step solver actually produces. A port of thegs-waveform-measurementsRust crate thatgeneral-simulator's ownkind=measureblocks are built on, and cross-validated against it intests/test_measure_parity.py.gsim_tools.logs— structured parsing ofgeneral-simulator'serror:diagnostics and itskind=measurelog files.
pip install gsim-tools # results + measure + logs: stdlib only, no toolchain
pip install 'gsim-tools[sidecar]' # adds numpy/h5py for kernel-sidecar manifestsThe document model, the validation rules and the compile registry live in
gsim-core — one Rust implementation behind
the schematic editor, this library and the engine, instead of three copies that drift apart.
gsim_tools.schematic is the Python layer over it, so it needs the gsim_py extension from
the moment you construct a Schematic, not only to compile.
Until gsim-py publishes wheels, build it once into the same environment:
git clone https://github.com/GLODI-Engineering/gsim-core
cd gsim-core/crates/gsim-py
pip install maturin
maturin build --release
pip install ../../target/wheels/gsim_py-*.whlWithout it, every entry point in schematic raises GsimPyNotAvailable carrying these
instructions. The other three modules are unaffected and need no toolchain at all.
Build a schematic, run it through the real simulator, measure the result:
from gsim_tools import measure, results
from gsim_tools.schematic import Schematic
sch = Schematic("rc", "RC low-pass")
vin = sch.add("source.voltage.dc", id="Vin", at=(0, 0), V=5.0)
r1 = sch.add("passive.resistor", id="R1", at=(60, 0), R=1000.0)
c1 = sch.add("passive.capacitor", id="C1", at=(120, 0), C=1e-6)
gnd = sch.add("routing.ground", id="gnd", at=(60, 80))
sch.connect(vin.p, r1.p1)
sch.connect(r1.p2, c1.p1)
sch.connect(c1.p2, gnd.p)
sch.connect(vin.n, gnd.p)
run = results.run_netlist_text(sch.compile(), mode="transient", tfinal=0.02, dt=1e-5)
print(measure.find_at(run.trace.samples("V(out)"), 1e-3))Coordinates need not be hand-picked. Schematic wraps all three of gsim_py's layout
entry points:
sch.auto_arrange() # layer the whole model over *all* its wires, in place
pos = sch.layered_layout( # the same layering, but it hands the positions back
bboxes={"IM": (80, 88)}, # real sizes where you know them
skip_wires=[feedback.id], # feedback makes cycles; layer the forward path only
column_gap=90, row_gap=70,
)
sch.place_without_overlap("VSI", (900, 200)) # drop one block in near a pointlayered_layout is a longest-path (Sugiyama-style) layering via Kahn's algorithm: columns run
left-to-right by signal-flow depth, blocks stack down within a column. It guarantees reading
order and no overlap — it does not minimise wire crossings, and cycle members stay wherever the
layering stalled, so a feedback loop is usually worth excluding via skip_wires.
Or, with no gsim_py anywhere, just read and measure an existing run:
trace = results.read_csv_file("run.csv")
v = trace.samples("V(a)")
print(measure.rms(v), measure.thd(v, fundamental_freq=50.0, num_harmonics=5))python -m pytest tests -q # pure-Python suite; needs neither gsim_py nor the simulatorTests marked simulator additionally need the general-simulator binary (on PATH or at
$GSIM_SIMULATOR); tests marked gsim_py need the extension built as above. Both are skipped
when unavailable. examples/rc_lowpass_end_to_end.py runs the whole pipeline and asserts a
known-correct numeric answer; see its own header for how to run it.
MIT — see LICENSE.
gsim-core, whose gsim_py extension gsim_tools.schematic is built on, is MIT too.
The schematic editor and the UI-driving library in the same family are AGPL-3.0; this
package and the core are deliberately permissive so they can be embedded.