Skip to content

Add asv benchmarking support - #258

Open
vinayswamik wants to merge 14 commits into
mainfrom
feature/pyqasm-benchmarking
Open

Add asv benchmarking support#258
vinayswamik wants to merge 14 commits into
mainfrom
feature/pyqasm-benchmarking

Conversation

@vinayswamik

@vinayswamik vinayswamik commented Aug 28, 2025

Copy link
Copy Markdown
Contributor

Summary of changes

  • Added asv benchmarking support in PyQASM.
  • Supported benchmarking functions are import, load, draw, unroll, validate, dump and ,dumps.
  • Also added benchmarks for Qubit Register Consolidation and Openpulse programs.

Verification Instructions:

From the project root, you can verify the benchmarks with the following steps:

Creates a benchmarking environment under .asv/.

asv run

Generates html output files containing benchmark results in tests/benchmarking/.

asv publish

Launches a local server to view benchmarking results in your browser.

asv preview

Notes

  • Currently, the benchmark results are based on the latest commit only (HEAD). So, only a single data point is shown until historical runs are added.

Profiling

Profiling data and visualizations for PyQASM functions

  • Added SVG flamegraphs for openpulse, qubit register consolidation, unroll, and validate profiling.
  • Included .speedscope.json files for detailed and interactive profiling data analysis.

Closes #67

Summary by CodeRabbit

  • New Features

    • Added automated performance benchmarking across Python 3.10–3.14.
    • Added benchmarks for importing, loading, validating, unrolling, serializing, drawing, and processing OpenPulse programs.
    • Added benchmark datasets, metadata, download caching, and a neutral-atom calibration example.
  • Documentation

    • Added an Unreleased changelog entry documenting benchmarking support.
  • Chores

    • Added repository rules for benchmark files and downloaded benchmark assets.

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@TheGupta2012

Copy link
Copy Markdown
Member

I ran the benchmarks locally, but they are failing in the pulse test -


· Creating environments.........
· Discovering benchmarks
·· Uninstalling from virtualenv-py3.11
·· Building d5bf684d <main> for virtualenv-py3.11.....
·· Installing d5bf684d <main> into virtualenv-py3.11...
· Running 27 total benchmarks (1 commits * 3 environments * 9 benchmarks)

[ 0.00%] · For pyqasm commit d5bf684d <main>:

[ 0.00%] ·· Benchmarking virtualenv-py3.11
[ 1.85%] ··· Running (import.PyqasmImport.time_pyqasm_import--)....
[ 9.26%] ··· Running (pyqasm_functions.PyqasmFunctions.time_dumps--)..
[12.96%] ··· Running (unroll.Unrolling.time_qubit_reg_consolidation--)..
[16.67%] ··· Running (validate.Validate.time_validate--).
[18.52%] ··· import.PyqasmImport.time_pyqasm_import                                                                           98.6±3ms
[20.37%] ··· openpulse.Openpulse.time_openpulse                                                                                 failed
[22.22%] ··· pyqasm_functions.PyqasmFunctions.time_draw                                                                      402±0.8ms
[24.07%] ··· pyqasm_functions.PyqasmFunctions.time_dump                                                                     72.9±0.4ms
[25.93%] ··· pyqasm_functions.PyqasmFunctions.time_dumps                                                                    71.5±0.5ms
[27.78%] ··· pyqasm_functions.PyqasmFunctions.time_load                                                                     2.52±0.01s
[29.63%] ··· unroll.Unrolling.time_qubit_reg_consolidation                                                                     676±2ms
[31.48%] ··· unroll.Unrolling.time_unroll                                                                                      640±6ms
[33.33%] ··· validate.Validate.time_validate                                                                                   626±2ms

[33.33%] ·· Building for virtualenv-py3.12......
[33.33%] ·· Benchmarking virtualenv-py3.12
[35.19%] ··· Running (import.PyqasmImport.time_pyqasm_import--)....
[42.59%] ··· Running (pyqasm_functions.PyqasmFunctions.time_dumps--)...
[48.15%] ··· Running (unroll.Unrolling.time_unroll--)..
[51.85%] ··· import.PyqasmImport.time_pyqasm_import                                                                          102±0.3ms
[53.70%] ··· openpulse.Openpulse.time_openpulse                                                                                 failed
[55.56%] ··· pyqasm_functions.PyqasmFunctions.time_draw                                                                      388±0.5ms
[57.41%] ··· pyqasm_functions.PyqasmFunctions.time_dump                                                                     64.3±0.8ms
[59.26%] ··· pyqasm_functions.PyqasmFunctions.time_dumps                                                                    62.8±0.4ms
[61.11%] ··· pyqasm_functions.PyqasmFunctions.time_load                                                                     2.35±0.01s
[62.96%] ··· unroll.Unrolling.time_qubit_reg_consolidation                                                                     661±3ms
[64.81%] ··· unroll.Unrolling.time_unroll                                                                                      638±2ms
[66.67%] ··· validate.Validate.time_validate                                                                                   623±1ms

[66.67%] ·· Building for virtualenv-py3.13......
[66.67%] ·· Benchmarking virtualenv-py3.13
[68.52%] ··· Running (import.PyqasmImport.time_pyqasm_import--)....
[75.93%] ··· Running (pyqasm_functions.PyqasmFunctions.time_dumps--)..
[79.63%] ··· Running (unroll.Unrolling.time_qubit_reg_consolidation--)..
[83.33%] ··· Running (validate.Validate.time_validate--).
[85.19%] ··· import.PyqasmImport.time_pyqasm_import                                                                          112±0.1ms
[87.04%] ··· openpulse.Openpulse.time_openpulse                                                                                 failed
[88.89%] ··· pyqasm_functions.PyqasmFunctions.time_draw                                                                      421±0.6ms
[90.74%] ··· pyqasm_functions.PyqasmFunctions.time_dump                                                                       74.2±1ms
[92.59%] ··· pyqasm_functions.PyqasmFunctions.time_dumps                                                                      73.1±1ms
[94.44%] ··· pyqasm_functions.PyqasmFunctions.time_load                                                                     2.78±0.02s
[96.30%] ··· unroll.Unrolling.time_qubit_reg_consolidation                                                                     734±2ms
[98.15%] ··· unroll.Unrolling.time_unroll                                                                                      694±3ms
[100.00%] ··· validate.Validate.time_validate                                                                                 691±0.9ms

@TheGupta2012

Copy link
Copy Markdown
Member

Also a couple of things about benchmarking -

  1. Benchmark trigger : We want benchmarks to be run via some "trigger" ideally through a Github action when a commit is merged to main. This is to detect any regression in performance caused by git commits. I believe we can take inspiration from qiskit.github.io and their workflow on how they publish these benchmarks. It doesn't need to be as extensive at first, we can target only a few machines and gradually build up from there.

  2. Test files : Current diff is 70k+ lines just from tests! While this is okay if we want version control for the tests, but, practically speaking, these tests will not change over time. It is better to keep a metadata file for the tests and either -

    • Generate the files on the fly
    • Pull the files from a public repository / Amazon s3 bucket.

    I think the second method would work better in our case as the benchpress repo is already public

@vinayswamik vinayswamik self-assigned this Aug 28, 2025
Comment thread tests/benchmarks/qasm/benchmark_metadata.json
Comment thread asv.conf.json
Comment thread tests/benchmarks/qasm/benchmark_downloader.py Outdated
Comment thread tests/benchmarks/pyqasm_functions.py Outdated
Comment thread tests/benchmarks/openpulse.py Outdated
Comment thread tests/benchmarks/import.py Outdated
Comment thread tests/benchmarks/qasm/benchmark_downloader.py Outdated
Comment thread tests/benchmarks/qasm/benchmark_downloader.py Outdated
@vinayswamik vinayswamik changed the title Feature/pyqasm benchmarking Feature/pyqasm benchmarking and Profiling Sep 2, 2025
TheGupta2012 and others added 3 commits November 6, 2025 10:29
…of the tree

- benchmark_downloader: remove the CERT_NONE SSL context and global
  opener; fetch with default certificate verification. Downloads now
  cache to tests/benchmarks/qasm/<name> (gitignored) instead of a fresh
  NamedTemporaryFile per call, so repeated asv setups stop re-fetching.
- asv.conf.json: move html_dir under .asv/ so 'asv publish' does not
  dirty the tree; add Python 3.14 to match the support matrix.
@argus-eye

argus-eye Bot commented Aug 7, 2026

Copy link
Copy Markdown

Argus review

Auto-review is off for this repo. Tick the box below to run a review on this PR.

  • Trigger Argus review

Estimated cost

  • Files changed: 22
  • Diff lines (±): 2532
  • Historical avg: ~318.9k tokens · ~$1.35 · across last 6 review(s)

Tip: you can also comment @argus-eye review at any time.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bb14a73c-ac08-418a-ad86-9267edaae556

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ryanhill1

ryanhill1 commented Aug 7, 2026

Copy link
Copy Markdown
Member

Refreshed this branch to get it review-ready again (pushed three commits — main advanced mid-refresh, so there are two merge commits):

  • Merged main — only CHANGELOG.md conflicted; kept the Add asv benchmarking support #258 entry under the current Unreleased → Added section and dropped the stale Fixes: qBraid logo error in docs #257 line that came along from last year's changelog state.
  • benchmark_downloader: removed the CERT_NONE SSL context + global install_opener (downloads now verify certificates normally — confirmed all three benchpress files fetch fine without it), and made the caching the module docstring promised real: files cache to tests/benchmarks/qasm/<name> (gitignored) instead of a fresh temp file per call, so repeated asv setups stop re-downloading ~600KB.
  • asv.conf.json: html_dir.asv/html so asv publish doesn't dirty the tree; added Python 3.14.

Verified against current main: all benchmark code paths run against today's API (unroll(consolidate_qubits=True) repeated calls are stable now — the old #296 concern no longer applies), asv check passes, and a real asv run --quick of validate reports ok across all three file sizes. pylint 10.00/10 on tests/benchmarks, black/isort clean, full suite 648 passed.

The earlier review asks (benchpress + pinned commit hash, size-parameterized benchmarks, FileNotFoundError/ValueError, lint-disable cleanup, .asv/ in .gitignore) all look addressed in the September commits. One open question for reviewers: the committed profile artifacts under tests/profiles/ (~2,000 of the PR's ~2,500 lines) are py-spy snapshots from September — worth deciding whether to keep them in-repo or regenerate/drop them before merge.

The SVG flamegraphs and speedscope JSONs under tests/profiles/ snapshot
a specific commit's performance and go stale on any code change, so
they would need continuous regeneration to stay truthful. Benchmarks
stay reproducible via asv; profiles can be regenerated locally with
py-spy when needed.
@ryanhill1

ryanhill1 commented Aug 7, 2026

Copy link
Copy Markdown
Member

Follow-up: removed the committed py-spy artifacts under tests/profiles/ (SVG flamegraphs + speedscope JSONs, ~2,000 lines) — they snapshot a specific commit's performance and go stale on any code change, so keeping them truthful would mean regenerating continuously. The asv benchmarks remain the reproducible record; profiles can be regenerated locally with py-spy when a specific investigation calls for one. Also trimmed the CHANGELOG entry to match ("benchmarking support", since the committed profiling data is gone). PR is now +540/−0.

@ryanhill1 ryanhill1 changed the title Feature/pyqasm benchmarking and Profiling Add asv benchmarking support Aug 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/benchmarks/__init__.py (1)

1-14: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add module docstrings to the new benchmark packages.

Both package modules omit the required purpose documentation.

  • tests/benchmarks/__init__.py#L1-L14: Add a module docstring that describes the benchmark package.
  • tests/benchmarks/qasm/__init__.py#L1-L14: Add a module docstring that describes the QASM benchmark package.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/benchmarks/__init__.py` around lines 1 - 14, Add a top-level module
docstring to tests/benchmarks/__init__.py describing the benchmark package, and
add a corresponding top-level module docstring to
tests/benchmarks/qasm/__init__.py describing the QASM benchmark package; keep
the existing license headers intact.

Source: Coding guidelines

🧹 Nitpick comments (1)
tests/benchmarks/qasm/benchmark_downloader.py (1)

28-77: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Complete the type and documentation contracts.

__init__ has no -> None annotation. file_info: Dict has no value type. The methods do not document their parameters and return values. Add annotations for self.cache_dir and self.metadata.

As per coding guidelines, “All functions, methods, and class attributes must have type annotations” and docstrings must explain purpose, parameters, and return values.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/benchmarks/qasm/benchmark_downloader.py` around lines 28 - 77, The
BenchmarkDownloader API lacks complete type and documentation contracts. Update
__init__, get_file_path, _fetch_remote_file, and get_benchmark_file with
complete parameter and return annotations, including -> None, a value type for
file_info, and explicit annotations for self.cache_dir and self.metadata; expand
each method docstring to describe its purpose, parameters, and return value.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/benchmarks/import.py`:
- Around line 23-27: Complete the benchmark annotations and docstrings across
tests/benchmarks/import.py:23-27 (document and annotate
PyqasmImport.time_pyqasm_import), tests/benchmarks/openpulse.py:24-33 (annotate
qasm_file, setup, and time_openpulse and document each method),
tests/benchmarks/pyqasm_functions.py:29-85, tests/benchmarks/unroll.py:26-62,
and tests/benchmarks/validate.py:26-58 (annotate ASV metadata, setup-created
attributes, parameters, and return values, and document every method with its
purpose, parameters, and return value). Ensure all affected classes, methods,
functions, and attributes comply with the project’s typing and docstring
requirements.
- Around line 26-27: Update time_pyqasm_import to use subprocess.check_call
instead of subprocess.call when executing the pyqasm import, so nonzero exit
statuses raise CalledProcessError and fail the ASV benchmark.

In `@tests/benchmarks/qasm/benchmark_downloader.py`:
- Around line 64-68: Update the download logic around urllib.request.urlopen in
the benchmark downloader to pass an explicit timeout and prevent stalled
requests from blocking indefinitely. Catch urllib.error.URLError and
timeout-related exceptions explicitly, convert them to the existing
connection-specific exception with the filename and URL context, and allow
unrelated programming errors to propagate.

In `@tests/benchmarks/qasm/neutral_atom_gate.qasm`:
- Around line 27-29: Update the eom_a_port, eom_b_port, and aod_port
declarations to use extern port instead of port, preserving their names and
order.

In `@tests/benchmarks/unroll.py`:
- Around line 56-62: The benchmark methods unroll.py:56-62 and
pyqasm_functions.py:83-85 reuse modules that benchmark operations mutate. Update
time_unroll and time_qubit_reg_consolidation to load a fresh module from
self.qasm_file for every sample, and update mpl_draw to avoid reusing the loaded
module by restoring or recreating an unmodified module between draws;
alternatively, configure the unroll benchmarks with number = 1 and warmup_time =
0.

---

Outside diff comments:
In `@tests/benchmarks/__init__.py`:
- Around line 1-14: Add a top-level module docstring to
tests/benchmarks/__init__.py describing the benchmark package, and add a
corresponding top-level module docstring to tests/benchmarks/qasm/__init__.py
describing the QASM benchmark package; keep the existing license headers intact.

---

Nitpick comments:
In `@tests/benchmarks/qasm/benchmark_downloader.py`:
- Around line 28-77: The BenchmarkDownloader API lacks complete type and
documentation contracts. Update __init__, get_file_path, _fetch_remote_file, and
get_benchmark_file with complete parameter and return annotations, including ->
None, a value type for file_info, and explicit annotations for self.cache_dir
and self.metadata; expand each method docstring to describe its purpose,
parameters, and return value.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a672e7bf-c0dd-4984-99b6-b1ca4fa41fb2

📥 Commits

Reviewing files that changed from the base of the PR and between 7e05f4c and 9106f12.

📒 Files selected for processing (13)
  • .gitignore
  • CHANGELOG.md
  • asv.conf.json
  • tests/benchmarks/__init__.py
  • tests/benchmarks/import.py
  • tests/benchmarks/openpulse.py
  • tests/benchmarks/pyqasm_functions.py
  • tests/benchmarks/qasm/__init__.py
  • tests/benchmarks/qasm/benchmark_downloader.py
  • tests/benchmarks/qasm/benchmark_metadata.json
  • tests/benchmarks/qasm/neutral_atom_gate.qasm
  • tests/benchmarks/unroll.py
  • tests/benchmarks/validate.py

Comment on lines +23 to +27
class PyqasmImport:
"""Test the import time of pyqasm."""

def time_pyqasm_import(self):
call((executable, "-c", "import pyqasm"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add required type annotations and complete docstrings.

Annotate every benchmark method, parameter, return value, and class or instance attribute. Add docstrings that state each method purpose, parameters, and return value.

  • tests/benchmarks/import.py#L23-L27: annotate and document time_pyqasm_import.
  • tests/benchmarks/openpulse.py#L24-L33: annotate qasm_file, setup, and time_openpulse; add complete method docstrings.
  • tests/benchmarks/pyqasm_functions.py#L29-L85: annotate ASV metadata and setup-created attributes; document and annotate all methods.
  • tests/benchmarks/unroll.py#L26-L62: annotate ASV metadata and setup-created attributes; document and annotate all methods.
  • tests/benchmarks/validate.py#L26-L58: annotate ASV metadata and setup-created attributes; document and annotate all methods.

As per coding guidelines, "**/*.py: Every module, class, method, and function must have a docstring explaining its purpose, parameters, and return values" and "All functions, methods, and class attributes must have type annotations."

📍 Affects 5 files
  • tests/benchmarks/import.py#L23-L27 (this comment)
  • tests/benchmarks/openpulse.py#L24-L33
  • tests/benchmarks/pyqasm_functions.py#L29-L85
  • tests/benchmarks/unroll.py#L26-L62
  • tests/benchmarks/validate.py#L26-L58
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/benchmarks/import.py` around lines 23 - 27, Complete the benchmark
annotations and docstrings across tests/benchmarks/import.py:23-27 (document and
annotate PyqasmImport.time_pyqasm_import), tests/benchmarks/openpulse.py:24-33
(annotate qasm_file, setup, and time_openpulse and document each method),
tests/benchmarks/pyqasm_functions.py:29-85, tests/benchmarks/unroll.py:26-62,
and tests/benchmarks/validate.py:26-58 (annotate ASV metadata, setup-created
attributes, parameters, and return values, and document every method with its
purpose, parameters, and return value). Ensure all affected classes, methods,
functions, and attributes comply with the project’s typing and docstring
requirements.

Source: Coding guidelines

Comment on lines +26 to +27
def time_pyqasm_import(self):
call((executable, "-c", "import pyqasm"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files | rg '(^|/)tests/benchmarks/import\.py$|benchmark|asv' || true

echo
echo "Import benchmark file:"
if [ -f tests/benchmarks/import.py ]; then
  cat -n tests/benchmarks/import.py
fi

echo
echo "Search subprocess usage in benchmark test files:"
rg -n "from subprocess|subprocess\.|check_call|call\\(|Executable|pyqasm" tests/benchmarks benchmarks -g '*.py' 2>/dev/null || true

Repository: qBraid/pyqasm

Length of output: 3643


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import subprocess
import tempfile
import os

executable = getattr(subprocess, '_executable', None)
if executable is None:
    executable = os.environ.get('ASV_PYTHON', None)
if executable is None:
    executable = "python3"

cmd = (executable, "-E", "-c", "import sys; sys.exit(1)")
print(f"executable={executable!r}")
ret = subprocess.call(cmd)
print(f"call_exit_status={ret}")

try:
    subprocess.check_call(cmd)
except subprocess.CalledProcessError as exc:
    print(f"check_call_reraised={exc.__class__.__name__}, returncode={exc.returncode}")
PY

Repository: qBraid/pyqasm

Length of output: 244


Fail the benchmark when the import fails.

subprocess.call() returns the nonzero exit status instead of raising, so a failed import pyqasm can still report a successful ASV result. Use check_call() so ASV receives CalledProcessError.

Proposed fix
-from subprocess import call
+from subprocess import check_call
...
-        call((executable, "-c", "import pyqasm"))
+        check_call((executable, "-c", "import pyqasm"))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def time_pyqasm_import(self):
call((executable, "-c", "import pyqasm"))
def time_pyqasm_import(self):
check_call((executable, "-c", "import pyqasm"))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/benchmarks/import.py` around lines 26 - 27, Update time_pyqasm_import
to use subprocess.check_call instead of subprocess.call when executing the
pyqasm import, so nonzero exit statuses raise CalledProcessError and fail the
ASV benchmark.

Comment on lines +64 to +68
try:
with urllib.request.urlopen(url) as response:
content = response.read()
except Exception as e:
raise RuntimeError(f"Failed to fetch {filename} from {url}: {e}") from e

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files | rg '(^|/)benchmark_downloader\.py$|qasm' || true

echo
echo "Target file outline:"
ast-grep outline tests/benchmarks/qasm/benchmark_downloader.py || true

echo
echo "Target lines 1-140:"
cat -n tests/benchmarks/qasm/benchmark_downloader.py | sed -n '1,140p'

echo
echo "Imports/usages of benchmark_downloader:"
rg -n "benchmark_downloader|download.*qasm|fetch .*qasm|urlopen|URLError|timeout" tests/benchmarks || true

echo
echo "Python urllib.timeout behavior probe:"
python3 - <<'PY'
import inspect, urllib.request
sig = inspect.signature(urllib.request.urlopen)
print("urlopen signature:", sig)
PY

Repository: qBraid/pyqasm

Length of output: 9437


Bound the download and preserve connection failures.

urllib.request.urlopen(url) uses no timeout, so a stalled download can block ASV indefinitely. Add an explicit timeout. Catch urllib.error.URLError and timeout-related errors explicitly, then raise a connection-specific exception; let unrelated programming errors propagate.

🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 64-64: Request-controlled URL passed to urlopen; validate against an allowlist to prevent SSRF.
Context: urllib.request.urlopen(url)
Note: [CWE-918] Server-Side Request Forgery (SSRF).

(urlopen-unsanitized-data)

🪛 Ruff (0.16.1)

[error] 65-65: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/benchmarks/qasm/benchmark_downloader.py` around lines 64 - 68, Update
the download logic around urllib.request.urlopen in the benchmark downloader to
pass an explicit timeout and prevent stalled requests from blocking
indefinitely. Catch urllib.error.URLError and timeout-related exceptions
explicitly, convert them to the existing connection-specific exception with the
filename and URL context, and allow unrelated programming errors to propagate.

Source: Coding guidelines

Comment on lines +27 to +29
port eom_a_port;
port eom_b_port;
port aod_port;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file context =="
if [ -f tests/benchmarks/qasm/neutral_atom_gate.qasm ]; then
  cat -n tests/benchmarks/qasm/neutral_atom_gate.qasm | sed -n '1,80p'
else
  echo "missing tests/benchmarks/qasm/neutral_atom_gate.qasm"
fi

echo "== benchmark references =="
rg -n "neutral_atom_gate|BenchmarkDownloader|openpulse|OpenPulse|qasm" tests benchmarks -S 2>/dev/null | head -200 || true

echo "== pyqasm/openpulse parser presence =="
rg -n "openpulse|OpenPulse|pyqasm|qasm" . -g '*.py' -g '*.toml' -g '*.lock' -g '*.txt' -S 2>/dev/null | head -300 || true

Repository: qBraid/pyqasm

Length of output: 44242


🌐 Web query:

OpenQASM 3.0 OpenPulse port extern port declaration bare port

💡 Result:

In OpenQASM 3.0 and OpenPulse, a port is a software abstraction used to represent physical input or output resources for qubit control [1][2]. The declaration extern port is the standard mechanism for accessing these hardware-defined ports [1][2]. This syntax specifies an external linkage that is resolved at compile-time using vendor-supplied translation units [1][2]. For example, a port can be declared as follows: extern port drive_port0; Because these ports represent external physical resources, they are typically defined by the hardware vendor within an include file (often named something like backend.inc), which the user includes in their OpenQASM program [3][4]. There is no standard concept of a "bare port" declaration in the OpenQASM 3.0 specification; all ports must be explicitly declared, typically using the extern keyword to link them to the target system's specific hardware map [1][3]. The term "bare port" appears in other technical contexts (such as networking or RTL design), but it is not a part of the OpenQASM or OpenPulse language grammar [5][6][7][8][9].

Citations:


🌐 Web query:

OpenPulse "extern port" "port" declaration

💡 Result:

In OpenPulse, a port represents an abstract I/O channel used for physical resources such as playing pulses or capturing data [1][2][3]. The extern keyword is used to declare a port that is defined externally to the immediate OpenQASM code block, allowing it to be resolved at compile-time via vendor-supplied translation units or mapping files [1][2]. A typical declaration takes the following form: extern port <port_name>; For example: extern port drive_port0; Key points regarding the extern port declaration: 1. External Linkage: By declaring a port as extern, you specify that its physical implementation (e.g., mapping to a specific XY control line on a qubit) will be handled by the hardware provider [1][2]. 2. Scope: These declarations are typically used within cal (calibration) or defcal blocks [1][4][5]. 3. Compilation: The identifier is resolved during the compilation or transpilation process [1][2]. For instance, tools like LabOne Q use an externs dictionary or map to link these program-defined port names to the actual hardware signals during the experiment setup [6]. 4. Purpose: Once declared, these ports are used to construct frames, which are stateful carriers with frequency, phase, and timing information, essential for playing waveforms to or capturing data from the quantum processor [1][2][3].

Citations:


🌐 Web query:

site:openqasm.com "Port" "extern port" OpenPulse

💡 Result:

In OpenPulse, an OpenQASM specification for quantum control, the "extern port" syntax is used to declare a hardware port that is linked to an external resource [1][2]. A port serves as a software abstraction representing an input or output component (such as a digital-to-analog converter, amplifier, or local oscillator) used to manipulate and observe qubits [1][2][3]. Because these ports represent specific physical hardware, they are vendor- and device-specific [2][3]. When you declare a port using the "extern" keyword, you are specifying that it has an external linkage [1][3]. This linkage is resolved at compile-time by translation units provided by the hardware vendor [1][2]. Essentially, the vendor provides the specific port names and mappings as configuration information, which the user then accesses within their OpenQASM code using the "extern port" declaration [2][3]. Example syntax: extern port drive_port0; frame driveframe0 = newframe(drive_port0, 5e9, 0.0); [1]

Citations:


Declare the OpenPulse ports as extern.

Replace each port declaration at lines 27-29 with extern port. OpenPulse ports are external hardware abstractions declared with extern port; bare port declarations are not useable here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/benchmarks/qasm/neutral_atom_gate.qasm` around lines 27 - 29, Update
the eom_a_port, eom_b_port, and aod_port declarations to use extern port instead
of port, preserving their names and order.

Comment on lines +56 to +62
def time_unroll(self, _):
"""Unroll QASM file of specified size."""
_ = self.pyqasm_obj.unroll()

def time_qubit_reg_consolidation(self, _):
"""Unroll QASM file of specified size with qubit consolidation."""
_ = self.pyqasm_obj.unroll(consolidate_qubits=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Repository files matching benchmarks:\n'
git ls-files 'tests/benchmarks/*.py'

printf '\nRelevant benchmark files:\n'
for f in tests/benchmarks/unroll.py tests/benchmarks/pyqasm_functions.py; do
  if [ -f "$f" ]; then
    echo "===== $f ====="
    wc -l "$f"
    sed -n '1,140p' "$f"
  fi
done

printf '\nSearch for mpl_draw definition/usages and unroll methods:\n'
rg -n "def mpl_draw|mpl_draw|def unroll|class .*ASV|number|warmup_time" -S --glob '*.py' .

Repository: qBraid/pyqasm

Length of output: 13702


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Relevant pyqasm module print/dot unroll methods:\n'
sed -n '370,450p' src/pyqasm/printer.py
printf '\n'
sed -n '320,400p' src/pyqasm/modules/base.py

printf '\nASV benchmark runner behavior/source (installed, if available):\n'
python3 - <<'PY'
try:
    import asv_runner.runner as runner
    import inspect
    print(inspect.getfile(runner))
    src = inspect.getsource(runner)
    for needle in ["class BenchmarkRunner", "number_times_run", "run_benchmarks", "setup"]:
        idx = src.find(needle)
        if idx != -1:
            start = max(0, src.rfind("\n", 0, idx-1000))
            end = min(len(src), src.find("\n", idx+2000))
            print(f"\n--- {needle} slice ---\n{src[start:end]}")
except Exception as exc:
    print(f"ASV runner import not available or error: {type(exc).__name__}: {exc}")
PY

Repository: qBraid/pyqasm

Length of output: 6088


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Top-level unroll implementation:\n'
sed -n '600,720p' src/pyqasm/modules/base.py

printf '\nSearch for unrolled_ast mutation in module code:\n'
rg -n "_unrolled_ast|unrolled_ast|def unroll\\(" src/pyqasm/modules -S

printf '\nPython structural probe for unroll signature and mutation pattern:\n'
python3 - <<'PY'
import ast
from pathlib import Path

path = Path("src/pyqasm/modules/base.py")
tree = ast.parse(path.read_text())
for node in ast.walk(tree):
    if isinstance(node, ast.FunctionDef) and node.name == "unroll":
        print(ast.get_docstring(node))
        print("location:", node.lineno, node.col_offset)
        print("args:", [arg.arg for arg in node.args.args])
        for child in ast.walk(node):
            if isinstance(child, ast.Assign):
                names = []
                for t in child.targets:
                    if isinstance(t, ast.Attribute):
                        names.append(".".join(filter(None, [ast.unparse(n) for n in ([t.value, t.attr] if isinstance(t.value, ast.Attribute) else [t, t.attr])])))
                    else:
                        names.append(ast.unparse(t))
                print("assign_target:", names)
PY

Repository: qBraid/pyqasm

Length of output: 11043


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Printer top-level section and unroll/dot references:\n'
sed -n '1,240p' src/pyqasm/printer.py

printf '\nRelevant Qasm3 unroll implementation:\n'
sed -n '1,120p' src/pyqasm/modules/qasm3.py

printf '\nRead-only benchmark state probe:\n'
python3 - <<'PY'
import ast
from pathlib import Path

for path in [Path("tests/benchmarks/unroll.py"), Path("tests/benchmarks/pyqasm_functions.py")]:
    tree = ast.parse(path.read_text())
    for cls in [n for n in tree.body if isinstance(n, ast.ClassDef) and n.name in {"Unrolling", "PyqasmFunctions"}]:
        print(f"--- {cls.name} ---")
        for node in cls.body:
            if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
                print(f"annot_assign {node.target.id}")
            elif isinstance(node, ast.Assign):
                targets = [t.id if isinstance(t, ast.Name) else ast.unparse(t) for t in node.targets]
                print(f"assign {targets}: {ast.unparse(node.value)[:80]}")
            elif isinstance(node, ast.FunctionDef):
                print(f"def {node.name}")
PY

Repository: qBraid/pyqasm

Length of output: 11133


🌐 Web query:

asv benchmarks module setup timed method number warmup_time setup teardown

💡 Result:

In Airspeed Velocity (asv), setup, teardown, warmup_time, and number are key attributes for controlling benchmark execution and timing accuracy. Setup and Teardown - setup: A method or function called before the benchmark runs to perform necessary initialization [1][2][3]. You can define module-level setup functions (run for every benchmark in the module) or class-based setup methods [1][4]. - teardown: A method or function called after the benchmark finishes, useful for cleaning up resources like filesystem changes [1][3]. - Execution flow: For timing benchmarks, the setup runs first, then the benchmark function is called number times, and finally the teardown runs [2][4][5]. Notably, setup and teardown are not executed between iterations of the number parameter [2][5]. - setup_cache: If setup is computationally expensive, you can use setup_cache instead. It runs only once per parameter set and caches the result, avoiding repeated execution across repeats and profiling runs [1][3]. Timing Attributes - number: Specifies the number of iterations to run the benchmarked function within a single sample [2][5]. If this is not manually set, asv adaptively estimates an optimal number of executions [6]. - warmup_time: Defines the duration (in seconds) that asv spends calling the benchmarked function repeatedly before starting official measurements [2][6][5]. This helps account for transient effects like OS scheduling or JIT compiler optimization (defaulting to 0.1s for CPython and 1.0s for PyPy) [2][6][5]. - Dynamic Adjustment: Attributes like number, repeat, and sample_time can be adjusted dynamically within a setup routine, which is particularly useful for parameterized benchmarks [2][5]. The overall execution sequence for a timing benchmark follows this pattern: calibrate (if number is not set) -> setup -> [repeat loops] -> benchmark (called number times) -> teardown [4].

Citations:


Benchmark each operation from an unmodified module.

ASV runs setup once and then calls the timed method multiple times. unroll() mutates self.pyqasm_obj by replacing _statements with the unrolled AST, so later calls do not measure the operation on the loaded program. mpl_draw() also calls program.unroll() before drawing.

  • tests/benchmarks/unroll.py#L56-L62: run each sample from a fresh load(self.qasm_file) object, or set number = 1 and warmup_time = 0 for these methods.
  • tests/benchmarks/pyqasm_functions.py#L83-L85: do not reuse the same loaded module for mpl_draw() unless you restore an unmodified module between draws.
📍 Affects 2 files
  • tests/benchmarks/unroll.py#L56-L62 (this comment)
  • tests/benchmarks/pyqasm_functions.py#L83-L85
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/benchmarks/unroll.py` around lines 56 - 62, The benchmark methods
unroll.py:56-62 and pyqasm_functions.py:83-85 reuse modules that benchmark
operations mutate. Update time_unroll and time_qubit_reg_consolidation to load a
fresh module from self.qasm_file for every sample, and update mpl_draw to avoid
reusing the loaded module by restoring or recreating an unmodified module
between draws; alternatively, configure the unroll benchmarks with number = 1
and warmup_time = 0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Create Benchmarks for pyqasm

4 participants