Skip to main content

A fast Rust-backed reader for Cadence binary PSF files, with a clean numpy API

Project description

psfox

A fast Rust-backed reader for Cadence binary PSF files, with a clean numpy API.

The Cadence psf-parser (pure Python) is slow and memory-hungry — it OOM-crashes on large files because it stores every sample as an individual Python object. psfox is a Rust core (PyO3 + maturin) that reads the same files much faster and with low RAM, returning numpy arrays directly.

Performance

Measured against the pinned pure-Python psf-parser (each parser in a fresh subprocess, median of repeated runs):

Dataset Size psfox psf-parser speedup psfox peak RSS
noise (struct-typed) ~634 MB ~1.2 s ~60 s ~49× ~1.3 GB
AC (complex) ~133 MB ~0.34 s ~17 s ~51× ~0.27 GB

Small files (S-parameter, DC) parse in single-digit milliseconds (tens of times faster). The output is bit-identical to psf-parser (exact, NaN/Inf-aware) on every tested file, and the memory ceiling is low enough to parse a 600 MB+ file on a 4 GB machine without OOM.

What it reads

psfbin 1.1, big-endian: swept and non-swept analyses; real and complex vectors; and struct/composite types — swept struct traces (noise) become numpy structured arrays, and non-swept struct values (e.g. dcOpInfo per-device operating points, which mix float64 with an Int8 region field) become Python dicts. Common analysis types (noise, AC, DC, S-parameter, dc-info) are supported. Out of scope (for now): PSF ASCII, PSF-XL/transient, and multi-sweep / family / corner files.

Requirements

  • Python ≥ 3.9
  • numpy ≥ 1.23 — a runtime dependency (the reader returns numpy arrays). It is declared in the package metadata, so installing psfox pulls numpy in automatically.

Install

Install from PyPI — psfox ships abi3 wheels (Linux x86_64, Windows x86_64) plus an sdist, and numpy is pulled in automatically:

uv add psfox      # or: pip install psfox

No matching wheel for your platform/architecture? Build one with maturin (no Rust toolchain is needed to install the resulting wheel), then install it:

maturin build --release
# -> target/wheels/psfox-0.2.0-cp39-abi3-<platform>.whl
pip install target/wheels/psfox-0.2.0-cp39-abi3-<platform>.whl

Notes:

  • The wheel is abi3 (cp39-abi3): one wheel works for Python 3.9, 3.10, … 3.13+ — no rebuild per Python version.
  • A wheel is platform-specific (Linux / macOS / Windows × architecture). For Linux x86_64 and Windows x86_64 the published wheels cover you (uv add psfox); for other platforms (macOS, Linux aarch64) build on the target platform or cross-build a Linux wheel from any host (see below).

To build from source instead (needs the Rust toolchain), see CONTRIBUTING.md in the repository.

Cross-building a Linux wheel

psfox has no system C dependencies — the only native code is the Rust core, built as an abi3 extension — so a manylinux wheel can be cross-compiled from any host (Windows, macOS, Linux) using zig as the linker. No Docker or virtual machine required:

rustup target add x86_64-unknown-linux-gnu   # one-time: add the Linux target
pip install ziglang                           # the zig toolchain, shipped as a Python package
maturin build --release --target x86_64-unknown-linux-gnu --zig --compatibility manylinux2014
# -> target/wheels/psfox-0.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

The manylinux2014 tag links against an old glibc, so the wheel installs on essentially any modern Linux distribution.

On Windows, maturin may not auto-detect the zig that ships inside the ziglang package (it fails with Failed to find zig). If so, put that package's folder on PATH for the build:

$env:PATH = "$PWD\.venv\Lib\site-packages\ziglang;$env:PATH"

Because the wheel is cross-compiled, smoke-test import psfox on an actual Linux host (or WSL) — it cannot run on the machine that built it.

Usage

import psfox

psf = psfox.read_psf("path/to/data.sp")
print(psf)                         # <Psf sweeps=1 traces=16 values=0>
print(psf.header["PSFversion"])    # header is a plain dict

# Sweep axes and traces are numpy arrays
for sw in psf.sweeps:
    print(sw.name, sw.data.dtype, sw.data.shape)   # e.g. freq float64 (700,)

t = psf.traces[0]
print(t.name, t.data.dtype, t.data.shape)          # e.g. s11 complex128 (700,)

# Non-swept DC -> .values (Python scalars), no traces/sweeps
dc = psfox.read_psf("path/to/data.dc")
print(len(dc.values), dc.values[0].name, dc.values[0].data)   # e.g. 5349 'net1' -7.7e-04

# Non-swept struct values (e.g. dcOpInfo) -> .values with dict data, one dict per device
info = psfox.read_psf("path/to/dcOpInfo.info")
v = info.values[0]
print(v.name, type(v.data).__name__)   # e.g. 'Q1'  dict   (one struct per device instance)
print(v.data["region"], v.data["temp"])  # region -> int, other members -> float

# Struct traces -> numpy structured array, one float64 field per contribution
noise = psfox.read_psf("path/to/data.noise")
tr = noise.traces[0]
if tr.data.dtype.names is not None:                # struct vs flat discriminator
    print(tr.data.dtype.names[:3])                 # e.g. ('<dev>.thermal', '<dev>.shot', ...)
    print(tr.data[tr.data.dtype.names[0]])         # float64[N] for that contribution

Run a script with the project environment: uv run python your_script.py (or activate .venv).

API

Object Attributes
read_psf(path) -> Psf parse a psfbin file
Psf .header (dict), .sweeps (list[Sweep]), .traces (list[Trace]), .values (list[Value])
Sweep, Trace .name (str), .data (numpy array)
Value .name (str), .data (Python scalar, or a dict {field: scalar} for a struct-typed value)
psfox.__version__ version string
  • flat vs struct trace: trace.data.dtype.names is None → flat (float64[N] / complex128[N]); not None → struct (one named float64 field per contribution).
  • swept vs non-swept: swept analyses populate .traces (+ .sweeps); non-swept analyses populate .values — each .data is a Python scalar, or a dict of {field: scalar} for a struct-typed value (e.g. dcOpInfo), mixing float/int/complex/str per the struct's members.
  • real vs complex is inferred per trace from the file, not assumed file-wide.

Type stubs and a py.typed marker ship with the package, so editors give autocomplete and type checking; help(psfox.read_psf) shows the inline docstrings.

Distribution

psfox is built with maturin + PyO3 — the same stack as polars and pydantic-core — and is distributed on PyPI as abi3 wheels (Linux x86_64, Windows x86_64) plus an sdist. Thanks to abi3, that is one wheel per platform rather than one per platform × Python version. macOS and Linux aarch64 wheels aren't published yet — build those locally (see Install).

Contributing

Build, test, and benchmark instructions live in CONTRIBUTING.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

psfox-0.2.0.tar.gz (54.7 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

psfox-0.2.0-cp39-abi3-win_amd64.whl (169.6 kB view details)

Uploaded CPython 3.9+Windows x86-64

psfox-0.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (283.0 kB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ x86-64

File details

Details for the file psfox-0.2.0.tar.gz.

File metadata

  • Download URL: psfox-0.2.0.tar.gz
  • Upload date:
  • Size: 54.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for psfox-0.2.0.tar.gz
Algorithm Hash digest
SHA256 984b42766e74825f35c426d1bc85e0c3eef375a7d8b5530589de43eb8d2ee146
MD5 f448a7b1a4ee0f294b3284f60c82061f
BLAKE2b-256 5a743a28b81c4ffe26570f7bf03452d349a010548b85411a90aaecee86e129ce

See more details on using hashes here.

Provenance

The following attestation bundles were made for psfox-0.2.0.tar.gz:

Publisher: publish.yml on Zwelckovich/psfox

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file psfox-0.2.0-cp39-abi3-win_amd64.whl.

File metadata

  • Download URL: psfox-0.2.0-cp39-abi3-win_amd64.whl
  • Upload date:
  • Size: 169.6 kB
  • Tags: CPython 3.9+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for psfox-0.2.0-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 70738cfea347f187a5c12a01e44dea5974b929a507eeb8e4f7dd599c36d4f174
MD5 821cf9f526a1800c84e56f9a84c84da0
BLAKE2b-256 5d6bc02ddedd107faa41ebc39a239fb6a6467663100defc041d4dcfdb930ddac

See more details on using hashes here.

Provenance

The following attestation bundles were made for psfox-0.2.0-cp39-abi3-win_amd64.whl:

Publisher: publish.yml on Zwelckovich/psfox

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file psfox-0.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for psfox-0.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 e75fd029504d9f40d1b4a2e6de3e5f8e8e156c2f8fec450a403d7ec67beeda4c
MD5 98ab22be834db5e129569c2e5d5933c5
BLAKE2b-256 3c8d6f84d3a8797f9559be816716ef5e4d260dfa166bc847dfbeb2a9b98b6e4b

See more details on using hashes here.

Provenance

The following attestation bundles were made for psfox-0.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: publish.yml on Zwelckovich/psfox

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page