Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 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
456e91c
fix(review): address Argus findings in sv_sim kernel dispatch and ben…
ryanhill1 Aug 5, 2026
3fc9c01
bench: regenerate bench_qft.png for the corrected QFT circuit
ryanhill1 Aug 5, 2026
ef6df2b
Merge branch 'main' into kenny/evolved-sv-preprocess-no-cache
ryanhill1 Aug 14, 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
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,27 @@ 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 already-unrolled `QasmModule` and returns a `SimulatorResult` carrying the final statevector, outcome probabilities, and sampled measurement counts, all indexed little-endian to match qiskit. 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))

```python
from pyqasm.simulator import Simulator

program = """
OPENQASM 3.0;
include "stdgates.inc";
qubit[2] q;
bit[2] c;
h q[0];
cx q[0], q[1];
c = measure q;
"""

result = Simulator(seed=42).run(program, shots=1000)

print(result.measurement_counts) # Counter({'00': 503, '11': 497})
print(result.final_statevector) # [0.70710678+0.j 0.+0.j 0.+0.j 0.70710678+0.j]
```

- Added support for `#pragma` statements, which previously raised `ValidationError: Unsupported statement of type <class 'openqasm3.ast.Pragma'>` and blocked any program carrying one. Pragmas are now passed through `loads`/`validate`/`unroll`/`dumps` unchanged, and are printed in their `#pragma` form (the upstream printer emits the bare `pragma` keyword). A `#pragma braket verbatim` additionally marks the `box` that immediately follows it: gates inside a verbatim box are emitted as written instead of being decomposed, so verbatim submissions to Braket QPUs keep the native gates they were built with. ([#341](https://github.com/qBraid/pyqasm/pull/341))
- 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))

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/*
84 changes: 84 additions & 0 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# 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. |
Comment thread
argus-eye[bot] marked this conversation as resolved.
| `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).

The two committed plots were measured on different machines, so compare
backends within a plot, never timings across the two: `bench_qft.png` is from
an Apple M1 Pro (serial build — macOS disables OpenMP by default) and
`bench_random.png` predates it. Each plot is internally consistent: every
backend in it was measured in one run on one host.

## 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