Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4f481f4
dev
ryanhill1 Jan 17, 2025
798d194
1 & 2 qubit gate handling
ryanhill1 Jan 17, 2025
2683290
remove class attributes
ryanhill1 Feb 7, 2025
7bc59c2
Merge branch 'main' into sv
ryanhill1 Feb 7, 2025
ea3b1ff
add result class
ryanhill1 Feb 8, 2025
13e76ae
check-in
ryanhill1 Feb 8, 2025
1135970
check-in updates2
ryanhill1 Feb 8, 2025
a274785
add test cases
ryanhill1 Feb 8, 2025
5881c97
get swap gate a little closer - still not right tho
ryanhill1 Feb 8, 2025
064b922
Merge branch 'main' into sv
ryanhill1 Mar 7, 2026
e71fe3d
Optimize statevector simulator: gate fusion, native gate preprocessin…
ryanhill1 Mar 7, 2026
b7bbe46
Optimize statevector preprocessing with evolved no-cache path
Kenny-Heitritter May 26, 2026
b84a23e
Merge branch 'main' into kenny/evolved-sv-preprocess-no-cache
Kenny-Heitritter May 26, 2026
0cfd83a
Add evolved no-cache statevector benchmark artifacts
Kenny-Heitritter May 26, 2026
5afa323
Regenerate statevector benchmark plots
Kenny-Heitritter May 26, 2026
9af158f
Rename bench_random_evolved_no_cache.png to bench_random_evolved.png
Kenny-Heitritter May 26, 2026
e7879ce
Rename bench_qft_evolved_no_cache.png to bench_qft_evolved.png
Kenny-Heitritter May 26, 2026
f07ebcd
Rename bench_evolved_no_cache.json to bench_evolved.json
Kenny-Heitritter May 26, 2026
1d56076
Fix CI for statevector simulator PR
Kenny-Heitritter May 26, 2026
734ec88
fix: repair CI for statevector simulator PR
ryanhill1 Jun 7, 2026
bc49a3d
build: use portable, compiler-aware compile flags
ryanhill1 Jun 7, 2026
b78c0e2
fix: make macOS wheels portable by disabling OpenMP there
ryanhill1 Jun 7, 2026
2b03019
fix: make OpenMP a compile-time option so macOS builds serial
ryanhill1 Jun 7, 2026
762dd67
Fix crz fast path in statevector simulator; add qiskit-free tests
ryanhill1 Jun 8, 2026
1caac11
Merge branch 'main' into kenny/evolved-sv-preprocess-no-cache
ryanhill1 Jun 19, 2026
9b6d3ef
Merge remote-tracking branch 'origin/main' into kenny/evolved-sv-prep…
ryanhill1 Jul 22, 2026
14e2b28
fix(simulator): resolve qubits by register, stop silently dropping st…
ryanhill1 Jul 22, 2026
5cf1f32
fix(build): validate kernel inputs, make OpenMP flag-only, stop shipp…
ryanhill1 Jul 22, 2026
c21394b
chore(ci,docs,benchmarks): coverage path, tox benchmark filter, runna…
ryanhill1 Jul 22, 2026
3c3a2ba
fix(review): phase-invariant statevector comparisons (CodeRabbit)
ryanhill1 Jul 22, 2026
6f06324
fix(lint): satisfy pylint in CI's installed-package environment
ryanhill1 Jul 22, 2026
dfb2b03
fix(review): validate --repeats, quote pytest path, setup.py doc nitp…
ryanhill1 Jul 22, 2026
a39d9a4
docs(benchmarks): annotate and docstring bench_simulator helpers (Cod…
ryanhill1 Jul 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ jobs:
- name: Install and Test wheel with extras
run: |
pip install setuptools
bash bin/install_wheel_extras.sh dist --type wheel --extra test --extra cli --extra pulse
python -m pytest --cov=pyqasm tests/ --cov-report=html --cov-report=xml --cov-report=term
bash bin/install_wheel_extras.sh dist --type wheel --extra test --extra test-sim --extra cli --extra pulse
python -m pytest --cov=pyqasm tests/ -m "not benchmark" --cov-report=html:build/coverage/html --cov-report=xml:build/coverage/coverage.xml --cov-report=term

- name: Upload coverage to Codecov
if: ${{ matrix.python == 311 }}
Expand All @@ -190,4 +190,4 @@ jobs:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
files: ./build/coverage/coverage.xml
verbose: true
verbose: true
2 changes: 1 addition & 1 deletion .github/workflows/pre-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -176,4 +176,4 @@ jobs:
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
password: ${{ secrets.PYPI_API_TOKEN }}
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,4 @@ jobs:
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
password: ${{ secrets.PYPI_API_TOKEN }}
2 changes: 1 addition & 1 deletion .github/workflows/test-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,4 @@ jobs:
with:
repository-url: https://test.pypi.org/legacy/
user: __token__
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ __pycache__/

# Cython build files
src/pyqasm/accelerate/linalg.c
src/pyqasm/accelerate/sv_sim.c

# Distribution / packaging
.Python
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Types of changes:
## Unreleased

### Added
- Added a statevector simulator as a new `pyqasm.simulator` subpackage, backed by a Cython/OpenMP-capable kernel (`pyqasm.accelerate.sv_sim`). `Simulator.run()` accepts an OpenQASM 3 string or an unrolled `QasmModule` and returns a `SimulatorResult` with the final statevector, probabilities, and sampled measurement counts. Ships with cross-validation tests against qiskit (`test-sim` extra), an optional `simulation` extra (numba-accelerated preprocessing helpers), and a `benchmarks/` suite. ([#316](https://github.com/qBraid/pyqasm/pull/316))
- Added support for the `c3x` (3-controlled X) and `rc3x`/`rcccx` (relative-phase 3-controlled X) gates, decomposed into basis gates following qiskit's `C3XGate`/`RC3XGate` definitions. Also extended the `ctrl @` modifier chain so that 3- and 4-control stacks on `x` (e.g. `ctrl @ ctrl @ ctrl @ x`, `ctrl(4) @ x`) resolve to `c3x`/`c4x`. ([#320](https://github.com/qBraid/pyqasm/pull/320))

### Improved / Modified
Expand All @@ -27,6 +28,7 @@ Types of changes:
### Fixed
- Fixed `reset` on a physical qubit rewriting the operand to the internal pulse register, e.g. `reset $2;` unrolled to `reset __PYQASM_QUBITS__[2];`. That names a register the program never declares, so the unrolled output did not round-trip through `dumps()`/`loads()`, and the qubit was never registered (a program whose only operation was `reset $3;` reported `num_qubits == 0`). Physical qubits are now kept as-is in plain QASM programs, matching how gate and measurement operands already treat them; the rename is still applied for OpenPulse programs, where the pulse visitor expects it. ([#325](https://github.com/qBraid/pyqasm/pull/325))
- Fixed `measure` and `reset` on a user register whose name merely starts with the reserved internal register name (e.g. `__PYQASM_QUBITS__foo`) being mistaken for the internal register itself. Such statements were short-circuited out of unrolling and emitted verbatim, so `c = measure __PYQASM_QUBITS__foo;` was never expanded per qubit and `reset __PYQASM_QUBITS__foo;` silently reset nothing. The register is now matched on its exact name, or on the name followed by an index or slice. ([#325](https://github.com/qBraid/pyqasm/pull/325))
- Fixed the statevector simulator's `crz` fast path, which implemented a controlled-phase gate (`diag(1, 1, 1, e^{i*theta/2})`) instead of a true controlled-Rz (`diag(1, 1, e^{-i*theta/2}, e^{i*theta/2})`); it dropped the phase on the `|control=1, target=0>` amplitude. It now applies the full `Rz` to the target via the controlled-gate kernel. ([#316](https://github.com/qBraid/pyqasm/pull/316))
- Fixed the `c4x` (4-controlled X) gate, which previously raised `TypeError: c4x_gate() takes 4 positional arguments but 5 were given` because it was declared with four parameters for a five-qubit gate. It is now implemented via qiskit's structured `rc3x`/`c3sx`/`cphaseshift` decomposition. ([#320](https://github.com/qBraid/pyqasm/pull/320))
- Added `inv @` (inverse modifier) support for the multi-controlled-X family: `inv @ c3x` / `inv @ c4x` resolve to the (self-inverse) gate, and `inv @ rc3x` / `inv @ rcccx` resolve to the correct relative-phase dagger. These previously raised `Unsupported / undeclared QASM operation`. ([#320](https://github.com/qBraid/pyqasm/pull/320))
- Fixed the `ctrl @` modifier not resolving gate aliases: `ctrl @ toffoli` / `ctrl @ ccnot` (aliases of `ccx`) and `ctrl @ cnot` / `ctrl @ CX` (aliases of `cx`) now escalate controls identically to their canonical gate instead of raising `Unsupported controlled QASM operation`. ([#320](https://github.com/qBraid/pyqasm/pull/320))
Expand Down
6 changes: 5 additions & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@ include README.md
recursive-include src/pyqasm *.py *.pyx
include src/pyqasm/py.typed

exclude tests/*
# Never ship Cython-generated C: sdists compile from .pyx (Cython is a build
# requirement), and stale generated C must not mask the .pyx sources.
global-exclude *.c

exclude tests/*
78 changes: 78 additions & 0 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Benchmarks

Scripts and reference results for the PyQASM statevector simulator
(`pyqasm.simulator.Simulator`).

## Contents

| File | What it is |
| --- | --- |
| `bench_simulator.py` | Benchmarks PyQASM against Qiskit Aer, Cirq, and PennyLane Lightning on shared random and QFT circuits, with cross-simulator correctness checks. |
| `plot_benchmarks.py` | Runs the same benchmarks and renders log-scale comparison plots (`bench_random.png`, `bench_qft.png`). |
| `profile_simulator.py` | Profiles the PyQASM simulator itself: cProfile, section timing (preprocess vs simulation vs post-processing), or per-kernel timing. |
| `bench_random.png`, `bench_qft.png` | Committed comparison plots produced by `plot_benchmarks.py`. |
| `bench_evolved.json`, `bench_qft_evolved.png`, `bench_random_evolved.png` | Historical before/after results for the evolved preprocessing change (see provenance note below). |

## Requirements

All scripts need only `pyqasm` (installed from this repo) for the PyQASM
paths. Additional dependencies:

- `bench_simulator.py`: optionally `qiskit` + `qiskit-aer`, `cirq`, and
`pennylane` (Lightning) for the comparison columns, and `tabulate` for the
summary table. Every one of these is optional — missing simulators are
reported as `skipped: <name> not installed` and the script runs the
remaining backends (worst case, PyQASM only).
- `plot_benchmarks.py`: `matplotlib`, plus the same optional simulators as
`bench_simulator.py`. Plots include only the installed backends.
- `profile_simulator.py`: no extra dependencies.

None of these are declared as a package extra; install them ad hoc, e.g.
`pip install qiskit qiskit-aer cirq pennylane tabulate matplotlib`.

## Running

From the repository root:

```bash
# Full benchmark table (up to 22 qubits; minutes of runtime)
python benchmarks/bench_simulator.py

# Smoke test: small circuits, 2 repeats
python benchmarks/bench_simulator.py --quick

# Regenerate bench_random.png / bench_qft.png in benchmarks/
python benchmarks/plot_benchmarks.py

# Quick plot run, writing PNGs somewhere else
python benchmarks/plot_benchmarks.py --quick --out-dir /tmp/bench-plots

# Profile the simulator (modes: section | cprofile | kernel)
python benchmarks/profile_simulator.py --mode section --qubits 16 --depth 200
python benchmarks/profile_simulator.py --mode cprofile --qubits 12 --depth 100
python benchmarks/profile_simulator.py --mode kernel --qubits 12 --depth 100
```

Timing covers simulation only; circuit construction/transpilation is done in
each backend's `prepare_*` step and excluded from the measured time. Reported
numbers are the median of `N_REPEATS` runs (default 5; `--quick` uses 2,
`--repeats N` overrides).

## Provenance of the `*_evolved` files

`bench_evolved.json`, `bench_qft_evolved.png`, and `bench_random_evolved.png`
were produced during development of the evolved no-cache preprocessing change
(PR #316). They compare two states of this branch:

- **baseline**: the branch state *before* the evolved-preprocessing commit
(the previous statevector preprocessing implementation);
- **candidate**: the branch with the evolved no-cache preprocessing.

They are retained for historical reference only. They are **not** regenerated
by any script in this tree: the baseline implementation no longer exists in
the working tree, so the before/after comparison cannot be reproduced from
the current sources alone. To reproduce something comparable, check out the
commit preceding the evolved-preprocessing commit and benchmark both
revisions with `bench_simulator.py`. Current-code performance can always be
re-measured with `bench_simulator.py` / `plot_benchmarks.py`, which
regenerate `bench_random.png` and `bench_qft.png`.
183 changes: 183 additions & 0 deletions benchmarks/bench_evolved.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
{
"benchmark": "statevector preprocessing no-cache comparison",
"baseline": "PyQASM sv branch before evolved preprocessing commit",
"candidate": "PyQASM sv branch with evolved no-cache preprocessing",
"n_repeats": 5,
"pyqasm_num_threads": 1,
"notes": [
"No QASM string memoization is included in the candidate.",
"Both series use pre-unrolled QasmModule inputs, matching the branch benchmark convention."
],
"series": {
"random": [
{
"num_qubits": 4,
"depth": 20,
"pyqasm_sv_ms": 0.06092991679906845,
"evolved_no_cache_ms": 0.06546999793499708,
"speedup": 0.9306540204807044,
"correct": true
},
{
"num_qubits": 6,
"depth": 40,
"pyqasm_sv_ms": 0.1580890966579318,
"evolved_no_cache_ms": 0.1151899341493845,
"speedup": 1.372421104546456,
"correct": true
},
{
"num_qubits": 8,
"depth": 60,
"pyqasm_sv_ms": 0.16204908024519682,
"evolved_no_cache_ms": 0.14194007962942123,
"speedup": 1.1416724625509327,
"correct": true
},
{
"num_qubits": 10,
"depth": 80,
"pyqasm_sv_ms": 0.24963996838778257,
"evolved_no_cache_ms": 0.21547905635088682,
"speedup": 1.1585347208002805,
"correct": true
},
{
"num_qubits": 12,
"depth": 100,
"pyqasm_sv_ms": 0.458328053355217,
"evolved_no_cache_ms": 0.41990901809185743,
"speedup": 1.0914937131808757,
"correct": true
},
{
"num_qubits": 14,
"depth": 150,
"pyqasm_sv_ms": 1.5235739992931485,
"evolved_no_cache_ms": 1.3638049131259322,
"speedup": 1.117149516495739,
"correct": true
},
{
"num_qubits": 16,
"depth": 200,
"pyqasm_sv_ms": 7.1852849796414375,
"evolved_no_cache_ms": 5.937180016189814,
"speedup": 1.210218480835721,
"correct": true
},
{
"num_qubits": 18,
"depth": 250,
"pyqasm_sv_ms": 35.15114798210561,
"evolved_no_cache_ms": 28.548280941322446,
"speedup": 1.231287728124666,
"correct": true
},
{
"num_qubits": 20,
"depth": 300,
"pyqasm_sv_ms": 162.53185598179698,
"evolved_no_cache_ms": 126.72401999589056,
"speedup": 1.282565499319447,
"correct": true
},
{
"num_qubits": 22,
"depth": 400,
"pyqasm_sv_ms": 1289.0826460206881,
"evolved_no_cache_ms": 1231.460615992546,
"speedup": 1.0467916141854843,
"correct": true
}
],
"qft": [
{
"num_qubits": 4,
"depth": "QFT",
"pyqasm_sv_ms": 0.20883907563984394,
"evolved_no_cache_ms": 0.16430008690804243,
"speedup": 1.2710831720784765,
"correct": true
},
{
"num_qubits": 6,
"depth": "QFT",
"pyqasm_sv_ms": 0.4983479157090187,
"evolved_no_cache_ms": 0.315018929541111,
"speedup": 1.5819618091997316,
"correct": true
},
{
"num_qubits": 8,
"depth": "QFT",
"pyqasm_sv_ms": 0.9089269442483783,
"evolved_no_cache_ms": 0.5902479169890285,
"speedup": 1.5399070764789728,
"correct": true
},
{
"num_qubits": 10,
"depth": "QFT",
"pyqasm_sv_ms": 1.5004950109869242,
"evolved_no_cache_ms": 1.0477059986442327,
"speedup": 1.4321718238977499,
"correct": true
},
{
"num_qubits": 12,
"depth": "QFT",
"pyqasm_sv_ms": 2.5123610394075513,
"evolved_no_cache_ms": 1.782613922841847,
"speedup": 1.409369133279482,
"correct": true
},
{
"num_qubits": 14,
"depth": "QFT",
"pyqasm_sv_ms": 5.448751035146415,
"evolved_no_cache_ms": 4.14173596072942,
"speedup": 1.3155718005226993,
"correct": true
},
{
"num_qubits": 16,
"depth": "QFT",
"pyqasm_sv_ms": 18.029507948085666,
"evolved_no_cache_ms": 15.524596092291176,
"speedup": 1.1613511772482323,
"correct": true
},
{
"num_qubits": 18,
"depth": "QFT",
"pyqasm_sv_ms": 77.3717820411548,
"evolved_no_cache_ms": 68.92851099837571,
"speedup": 1.1224931587885028,
"correct": true
},
{
"num_qubits": 20,
"depth": "QFT",
"pyqasm_sv_ms": 382.6392919290811,
"evolved_no_cache_ms": 349.38782698009163,
"speedup": 1.0951706452866319,
"correct": true
}
]
},
"summary": {
"random": {
"geomean_speedup": 1.1521318658620598,
"min_speedup": 0.9306540204807044,
"max_speedup": 1.372421104546456,
"all_correct": true
},
"qft": {
"geomean_speedup": 1.3147632402156637,
"min_speedup": 1.0951706452866319,
"max_speedup": 1.5819618091997316,
"all_correct": true
}
}
}
Binary file added benchmarks/bench_qft.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added benchmarks/bench_qft_evolved.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added benchmarks/bench_random.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added benchmarks/bench_random_evolved.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading