A generic, NetworkX-driven Python wrapper for the Sandia Xyce circuit simulation engine.
Project description
xyce-py
xyce-py is a small Python interface for building circuit topologies, compiling
them into Xyce-compatible netlists, running the Sandia Xyce simulator, and reading
simulation output back into Pandas.
The package does not replace Xyce. Xyce remains the simulation engine. xyce-py
handles the Python-side work around it: graph construction, SPICE netlist
generation, process execution, result loading, and node-name translation.
For method-by-method documentation, see the API Reference. For runnable user pipelines, see the examples.
What It Provides
- A
CircuitGraphAPI for building circuits from named nodes, branches, and multi-terminal devices. - Built-in models for common elements: resistors, capacitors, inductors, voltage sources, current sources, diodes, BJTs, MOSFETs, behavioral sources, and subcircuit instances.
- Raw template devices for exact Xyce element lines that still need graph-owned node-name translation.
- A
NetlistCompilerthat converts the graph into a Xyce/SPICE-style netlist. - Simulation helpers for operating point, transient, AC, and DC analyses.
- Typed directive builders for common outer contracts such as
.PARAM,.PRINT, and.MEASURE. - A raw
XyceProjectinterface for exact netlists that use advanced Xyce syntax beyond the typed graph helpers. - Pandas
DataFrameoutput for waveforms, plus helpers to translate generated SPICE node names back to user node names. - Fail-fast validation for invalid Python-side inputs and disconnected topologies before Xyce is launched.
Requirements
- Python 3.10 or newer.
- A separately installed Sandia Xyce executable for simulation runs.
Xyce is not bundled with this package. Install Xyce from Sandia National Laboratories before running simulations. Official entry points:
xyce-py looks for Xyce in common Sandia installer layouts under
/usr/local, Windows Program Files, and then on your PATH as Xyce.
You can always pass an explicit executable path with xyce_path=....
Installation
From PyPI:
pip install xyce-py
For local development from this repository:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e '.[test]'
Configure Xyce
Start here if you have no local setup yet:
- Install Python 3.10 or newer.
- Create and activate a virtual environment.
- Install
xyce-pywithpython -m pip install xyce-py. - Install Xyce from Sandia's executable packages page.
- Confirm the Xyce executable works:
Xyce -v
If that command is not found, either add Xyce's bin directory to your PATH
or pass the full executable path when constructing a graph or project:
graph = CircuitGraph(xyce_path="/path/to/Xyce")
result = project.run(xyce_path="/path/to/Xyce")
Check what executable xyce-py will use:
python -c "from xyce_py import find_xyce_executable; print(find_xyce_executable())"
If the command prints only Xyce, xyce-py will rely on your shell PATH.
If it prints an absolute path, that path was discovered from a known installer
layout.
Quick Start
This example builds a voltage divider and compiles it to a netlist. It does not run Xyce.
from xyce_py import CircuitGraph, Resistor, VoltageSource
graph = CircuitGraph(xyce_path="Xyce")
graph.add_node("gnd", is_ground=True)
graph.add_branch("vin", "gnd", [VoltageSource("supply", 5.0)])
graph.add_branch("vin", "vout", [Resistor("r1", 1000)])
graph.add_branch("vout", "gnd", [Resistor("r2", 1000)])
compiled_body = graph.compile_body()
netlist = "\n".join([*compiled_body.lines, ".END"]) + "\n"
print(netlist)
Generated netlist:
* Generated Circuit
.OPTIONS DEVICE GMIN=1e-8
V_supply N_1 0 DC 5.0
R_r1 N_1 N_2 1000.0
R_r2 N_2 0 1000.0
.END
Run with Xyce
Use CircuitGraph.simulate_op() when Xyce is installed and available.
from tempfile import TemporaryDirectory
from xyce_py import CircuitGraph, Resistor, VoltageSource, find_xyce_executable
with TemporaryDirectory() as tmpdir:
graph = CircuitGraph(xyce_path=find_xyce_executable(), base_out_dir=tmpdir)
graph.add_node("gnd", is_ground=True)
graph.add_branch("vin", "gnd", [VoltageSource("supply", 5.0)])
graph.add_branch("vin", "vout", [Resistor("r1", 1000)])
graph.add_branch("vout", "gnd", [Resistor("r2", 1000)])
result = graph.simulate_op()
print(result.translated_waveforms())
result.waveforms contains Xyce's generated column names, such as V(N_1).
result.translated_waveforms() returns a copy with voltage columns translated
back to the original user node names, such as V(vin) and V(vout).
result.solved_graph(row=0) returns a copy of the input topology annotated with
solved node voltages for the selected waveform row:
solved = result.solved_graph(row=0)
print(solved.nodes["vout"]["solved_voltage"])
The waveform DataFrame remains the canonical numeric result, especially for
time-series, frequency sweeps, and branch/device current columns. The solved
graph is an optional topology projection for node voltage inspection and
visualization.
Extra Xyce output files can be declared when the run directory is kept:
from xyce_py import OutputSpec
graph.add_measurement("TRAN", "max_out", "MAX V(vout)")
result = graph.simulate_transient(
"1n",
"20n",
output_specs=[OutputSpec.text("measurements", "circuit.cir.mt0")],
keep_run_dir=True,
)
print(result.measurements()["MAX_OUT"].value)
Input Graph Contract
The public topology input is CircuitGraph. A CircuitGraph owns an internal
networkx.MultiDiGraph, exposed as graph.G for inspection and low-level
compiler use.
xyce-py does not accept arbitrary external nx.Graph, nx.DiGraph, or
nx.MultiGraph instances as simulation input. Use CircuitGraph.add_node(),
add_branch(), and add_device() so topology data has the attributes required
by the compiler. MultiDiGraph is used because circuits need parallel branches,
directed terminal polarity, and compiler-expanded internal nodes.
If a future NetworkX import interface is added, it should be a strict
CircuitGraph.from_networkx() adapter with an explicit schema and fail-fast
validation. It should not guess circuit meaning from arbitrary graph attributes.
Supported Analysis Helpers
result = graph.simulate_op()
result = graph.simulate_transient("1u", "20u")
result = graph.simulate_ac("DEC", "10", "1", "1e6")
result = graph.simulate_dc("V_supply", "0", "5", "0.5")
You can also call graph.simulate(".OP"), graph.simulate(".TRAN ..."),
graph.simulate(".AC ..."), or graph.simulate(".DC ...") directly.
For advanced Xyce analyses, keep simulate() on its strict helper path and
compile the graph into a project with configurable Xyce feature specs:
from xyce_py import XyceAnalysisSpec, XyceFeatureConfig, XyceOutputSpec
body = graph.compile_body()
vout = body.user_to_spice_node["vout"]
config = XyceFeatureConfig(
analyses=[
XyceAnalysisSpec(".NOISE", [f"V({vout})", "V_supply", "DEC", "10", "1", "1e6"]),
],
outputs=[
XyceOutputSpec("noise", "NOISE", ["ONOISE", "INOISE"], "noise.csv"),
],
)
project = graph.compile_project(
"noise-analysis",
config.directive_lines(),
output_specs=config.output_specs(),
)
result = project.run(xyce_path="Xyce")
compile_project() validates topology, directive-list shape, output specs, and
package-owned .END insertion. It does not parse advanced Xyce semantics.
When configurable lines refer to graph nodes, use compile_body() to get the
generated SPICE node names.
See Configurable Xyce Features for .NOISE,
.HB, .SENS, .FOUR, .STEP, arbitrary devices, arbitrary models, output
reports, XDM, and ADMS workflow examples.
Run Raw Xyce Netlists
Use XyceProject when you already have an exact Xyce netlist, need an advanced
analysis directive, or want Xyce to remain the only parser for a feature.
from xyce_py import OutputSpec, XyceProject
project = XyceProject(
"raw-divider",
"""* raw voltage divider
V1 1 0 DC 10
R1 1 2 1000
R2 2 0 1000
.OP
.PRINT DC FORMAT=CSV FILE=raw.csv V(1) V(2)
.END
""",
output_specs=(OutputSpec.csv("waveforms", "raw.csv"),),
)
result = project.run(xyce_path="Xyce")
print(result.outputs["waveforms"].frame)
For .MEASURE output, declare Xyce's generated measurement file as text and
parse it from the project result:
from xyce_py import MeasureDirective, OutputSpec, XyceProject
project = XyceProject(
"measured-run",
f"""* measured transient
V1 in 0 PULSE(0 1 0 1n 1n 5n 10n)
R1 in out 1k
C1 out 0 1n
.TRAN 1n 20n
.PRINT TRAN FORMAT=CSV FILE=waveforms.csv V(out)
{MeasureDirective("TRAN", "max_out", "MAX V(out)").to_spice()}
.END
""",
output_specs=(
OutputSpec.csv("waveforms", "waveforms.csv"),
OutputSpec.text("measurements", "circuit.cir.mt0"),
),
)
result = project.run(xyce_path="Xyce")
print(result.measurements()["MAX_OUT"].value)
The same raw-netlist path is available from the command line:
xyce-py run raw-divider.cir --csv-output waveforms raw.csv
The command prints a JSON summary containing the run directory, solve time, Xyce stdout/stderr, and declared output metadata.
Run Parameter Sweeps
Use XyceParameterSweep for Python-side sweeps over explicit .PARAM values:
from xyce_py import OutputSpec, SweepParameter, XyceParameterSweep
sweep = XyceParameterSweep(
"divider-sweep",
"""* sweep divider
V1 1 0 DC 10
R1 1 2 {RLOAD}
R2 2 0 1000
.OP
.PRINT DC FORMAT=CSV FILE=out.csv V(2)
.END
""",
parameters=(SweepParameter("RLOAD", [1000, 3000]),),
output_specs=(OutputSpec.csv("waveforms", "out.csv"),),
)
result = sweep.run(xyce_path="Xyce")
print(result.run(0).point.parameters)
print(result.run(0).result.output("waveforms").frame)
Native Xyce .STEP netlists can still be run exactly through XyceProject.
For deterministic Monte Carlo sweeps, provide explicit distributions and a seed:
from xyce_py import MonteCarloParameter, UniformDistribution, XyceMonteCarloSweep
monte_carlo = XyceMonteCarloSweep(
"divider-monte-carlo",
sweep.netlist_content,
parameters=(MonteCarloParameter("RLOAD", UniformDistribution(1000, 3000)),),
samples=10,
seed=7,
output_specs=sweep.output_specs,
)
Run XDM Translation
Use XdmTranslator to invoke an installed XDM translator with explicit XDM
arguments and validate the translated artifact:
from xyce_py import XdmTranslator
translation = XdmTranslator(xdm_path="xdm").run(
["source.sp", "translated.cir"],
working_dir=".",
expected_output="translated.cir",
)
print(translation.translated_netlist_text())
Models, Options, and Subcircuits
Raw Xyce directives can be attached to the graph when needed:
graph.add_model(".MODEL DFAST D(IS=1e-9)")
graph.add_options(".OPTIONS DEVICE GMIN=1e-9")
graph.add_subcircuit(".SUBCKT BUF IN OUT\nR1 OUT IN 1k\n.ENDS")
Subcircuit definitions are passed through as opaque SPICE text. Xyce validates subcircuit internals and arity during simulation.
Use raw template devices when Xyce already owns the exact element syntax but the Python graph should still own topology and node-name translation:
from xyce_py import RawNTerminalDevice, RawTwoTerminalElement
graph.add_branch(
"vin",
"vout",
[RawTwoTerminalElement("load", "R_$name $node_pos $node_neg {RLOAD}")],
)
graph.add_device(
RawNTerminalDevice(
"amp",
"AMP_MODEL",
terminals=2,
template="X_$name $n0 $n1 $model_name",
),
["vin", "vout"],
)
RawTwoTerminalElement requires $node_pos and $node_neg.
RawNTerminalDevice requires one ordered placeholder per terminal: $n0,
$n1, and so on. Both support $name; raw multi-terminal devices also support
$model_name.
Parameters, Solver Options, and Directive Builders
Use add_parameter() for .PARAM values in CircuitGraph netlists:
graph.add_parameter("RLOAD", "1k")
graph.add_branch("vout", "gnd", [Resistor("load", "{RLOAD}")])
Pass solver options as package-scoped .OPTIONS values:
graph = CircuitGraph(
xyce_path="Xyce",
solver_params={"NONLIN": {"RELTOL": "1e-4"}},
)
Directive builders emit exact SPICE directive text while leaving Xyce-specific expressions to Xyce:
from xyce_py import MeasureDirective, OptionsDirective, PrintDirective
options_line = OptionsDirective("NONLIN", {"RELTOL": "1e-4"}).to_spice()
print_line = PrintDirective("TRAN", ["V(out)"], file="tran.csv").to_spice()
measure_line = MeasureDirective(
"TRAN",
"rise_time",
"TRIG V(out) VAL=0.1 RISE=1 TARG V(out) VAL=0.9 RISE=1",
).to_spice()
Use configurable feature specs when the Xyce feature should be data-driven or is outside the small typed helper set:
from xyce_py import XyceDeviceSpec, XyceModelSpec
model_line = XyceModelSpec("DFAST", "D", {"IS": "1e-12"}).to_spice()
device_line = XyceDeviceSpec("D1", ["out", "0"], model_name="DFAST").to_spice()
Error Handling
xyce-py validates Python-side contracts early:
- Node identifiers must be hashable.
- Each graph may have only one ground node.
- Branches must contain at least one
CircuitElement. - Device terminal counts must match the device type.
solver_paramsmust map Xyce option packages to option mappings.- Floating subgraphs are rejected before launching Xyce.
If Xyce exits with a non-zero status, xyce-py raises XyceRunError with the
return code, stdout, stderr, run directory, netlist path, CSV path, and elapsed
solve time.
Troubleshooting Setup
Xyce -vis not found: install Xyce from Sandia, then add the Xycebindirectory toPATH, or passxyce_path="/path/to/Xyce"explicitly.find_xyce_executable()printsXyce: no known installer path was found, so subprocess execution depends on your current shellPATH.- A simulation fails but compile-only examples work: inspect the
XyceRunErrorstdout/stderr fields. Xyce remains responsible for netlist semantics. - Extra output files are missing: declare them with
OutputSpecand usekeep_run_dir=Trueso the files still exist after return. - Real-Xyce tests skip locally: install Xyce or pass a valid executable path in the test fixture environment.
Development and Testing
Install test dependencies:
python -m pip install -e '.[test]'
Run the full test suite:
pytest
Real-Xyce tests are marked with @pytest.mark.xyce and are skipped only when
Xyce is unavailable.
Packaging
Build the source distribution and wheel:
python -m build
Validate the built metadata:
python -m twine check dist/*
See docs/release.md for the release checklist used before publishing.
See docs/capability-matrix.md for the supported and planned Xyce capability
surface.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file xyce_py-1.0.0.tar.gz.
File metadata
- Download URL: xyce_py-1.0.0.tar.gz
- Upload date:
- Size: 107.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
81c2f92adbe2b49eea327fb7df4b0da503deeae35298391f9da58bb98d05c007
|
|
| MD5 |
4100ac0ebb0a634d80a0efa357d4adc3
|
|
| BLAKE2b-256 |
7530bf165a9201b661cb229f2e890ac354200314111ad078900d4c7dfc6283eb
|
Provenance
The following attestation bundles were made for xyce_py-1.0.0.tar.gz:
Publisher:
publish.yml on xuctang/xyce-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
xyce_py-1.0.0.tar.gz -
Subject digest:
81c2f92adbe2b49eea327fb7df4b0da503deeae35298391f9da58bb98d05c007 - Sigstore transparency entry: 1812776344
- Sigstore integration time:
-
Permalink:
xuctang/xyce-py@05a792d213c335016389b9dc5aebe469e3cae076 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/xuctang
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@05a792d213c335016389b9dc5aebe469e3cae076 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file xyce_py-1.0.0-py3-none-any.whl.
File metadata
- Download URL: xyce_py-1.0.0-py3-none-any.whl
- Upload date:
- Size: 36.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
32c33334e0a89dcbee7fc5ed01c8b59080babf4ce6b1007762bf3c4440790a0a
|
|
| MD5 |
4c30123292fb5abeb28c1d92bcabbaaf
|
|
| BLAKE2b-256 |
2c3e826f672cbed489672841983a7dc8d602e9365afe261015e460d77ee0617e
|
Provenance
The following attestation bundles were made for xyce_py-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on xuctang/xyce-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
xyce_py-1.0.0-py3-none-any.whl -
Subject digest:
32c33334e0a89dcbee7fc5ed01c8b59080babf4ce6b1007762bf3c4440790a0a - Sigstore transparency entry: 1812776719
- Sigstore integration time:
-
Permalink:
xuctang/xyce-py@05a792d213c335016389b9dc5aebe469e3cae076 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/xuctang
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@05a792d213c335016389b9dc5aebe469e3cae076 -
Trigger Event:
workflow_dispatch
-
Statement type: