Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Types of changes:
saturation_pulse $0;
}
```
- Added `asv` benchmarking support in `pyqasm`. ([#258](https://github.com/qBraid/pyqasm/pull/258))

### Improved / Modified
- Modified if statement validation to now include empty blocks as well. See [Issue #246](https://github.com/qBraid/pyqasm/issues/246) for details. ([#251](https://github.com/qBraid/pyqasm/pull/251))
Expand Down
32 changes: 32 additions & 0 deletions asv.conf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"version": 1,
"project": "pyqasm",
"project_url": "https://sdk.qbraid.com/pyqasm/",
"repo": ".",
"install_command": [
"in-dir={env_dir} python -m pip install {wheel_file}[visualization,pulse]"
],
"uninstall_command": [
"return-code=any python -m pip uninstall -y pyqasm"
],
"build_command": [
"python -m pip install -U build",
"python -m build --outdir {build_cache_dir} --wheel {build_dir}"
],
"branches": [
"main"
],
"dvcs": "git",
"environment_type": "virtualenv",
"show_commit_url": "https://github.com/qBraid/pyqasm/commit/",
"pythons": [
"3.10",
"3.11",
"3.12",
"3.13"
],
"benchmark_dir": "tests/benchmarks",
"env_dir": ".asv/env",
Comment thread
vinayswamik marked this conversation as resolved.
"results_dir": ".asv/results",
"html_dir": "tests/benchmarks/.html"
}
13 changes: 13 additions & 0 deletions tests/benchmarks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2025 qBraid
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
28 changes: 28 additions & 0 deletions tests/benchmarks/import.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copyright 2025 qBraid
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=no-member,invalid-name,missing-docstring,no-name-in-module
# pylint: disable=attribute-defined-outside-init,unsubscriptable-object
Comment thread
ryanhill1 marked this conversation as resolved.
Outdated

"""
This module is used to test the import time of pyqasm.
"""

from subprocess import call
from sys import executable


class PyqasmImport:
def time_pyqasm_import(self):
call((executable, "-c", "import pyqasm"))
33 changes: 33 additions & 0 deletions tests/benchmarks/openpulse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright 2025 qBraid
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=no-member,invalid-name,missing-docstring,no-name-in-module
# pylint: disable=attribute-defined-outside-init,unsubscriptable-object
Comment thread
ryanhill1 marked this conversation as resolved.
Outdated

"""
This module is used to test the openpulse of pyqasm.
"""

from pyqasm import load

from .qasm.benchmark_downloader import get_benchmark_file


class Openpulse:
def setup(self):
# Get benchmark file, downloading if necessary
self.qasm_file = get_benchmark_file("neutral_atom_gate.qasm")

def time_openpulse(self):
_ = load(self.qasm_file).unroll()
61 changes: 61 additions & 0 deletions tests/benchmarks/pyqasm_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Copyright 2025 qBraid
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=no-member,invalid-name,missing-docstring,no-name-in-module
# pylint: disable=attribute-defined-outside-init,unsubscriptable-object

Comment thread
ryanhill1 marked this conversation as resolved.
Outdated
"""
This module is used to test the basic functions of pyqasm.
"""

import os
from pathlib import Path

from pyqasm import dump, dumps, load, printer

from .qasm.benchmark_downloader import get_benchmark_file


class PyqasmFunctions:
def setup(self):
# Get benchmark files, downloading if necessary
self.qasm_file = get_benchmark_file("qft_N100.qasm")

# Create output file path in the same directory as input file
input_path = Path(self.qasm_file)
self.output_file = str(input_path.parent / "qft_N100_unrolled.qasm")

self.pyqasm_obj = load(self.qasm_file)
self.mid_qasm_file = get_benchmark_file("pea_3_pi_8.qasm")
self.mid_pyqasm_obj = load(self.mid_qasm_file)

def teardown(self):
# Clean up the output file if it was created
if hasattr(self, "output_file") and os.path.exists(self.output_file):
try:
os.remove(self.output_file)
except OSError:
pass

def time_load(self):
_ = load(self.qasm_file)

def time_dumps(self):
_ = dumps(self.pyqasm_obj)

def time_dump(self):
dump(self.pyqasm_obj, self.output_file)

def time_draw(self):
_ = printer.mpl_draw(self.mid_pyqasm_obj, idle_wires=True, external_draw=False)
13 changes: 13 additions & 0 deletions tests/benchmarks/qasm/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2025 qBraid
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
102 changes: 102 additions & 0 deletions tests/benchmarks/qasm/benchmark_downloader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Copyright 2025 qBraid
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Benchmark file downloader utility.

This module handles downloading benchmark QASM files from the Qiskit repository
and caching them locally to avoid storing large files in version control.
"""

# pylint: disable-all
Comment thread
ryanhill1 marked this conversation as resolved.
Outdated

import json
import ssl
import tempfile
import urllib.request
from pathlib import Path
from typing import Dict, Optional


class BenchmarkDownloader:
"""Handles downloading and caching of benchmark files."""

def __init__(self, cache_dir: Optional[str] = None):
"""Initialize the downloader."""
self.cache_dir = Path(cache_dir) if cache_dir else Path(__file__).parent
self.cache_dir.mkdir(exist_ok=True)

# Load metadata
with open(self.cache_dir / "benchmark_metadata.json", "r", encoding="utf-8") as f:
self.metadata = json.load(f)

def get_file_path(self, filename: str) -> Path:
"""Get the path for a benchmark file, fetching from remote repository if needed."""
if filename not in self.metadata["benchmark_files"]:
raise KeyError(f"Unknown benchmark file: {filename}")

file_info = self.metadata["benchmark_files"][filename]

# Check if this is a local-only file
if file_info.get("local_only", False):
file_path = self.cache_dir / filename
if not file_path.exists():
raise RuntimeError(
f"Local file {filename} not found. Please ensure it exists in the repository."
)
return file_path

# For remote files, fetch and return a temporary file
return self._fetch_remote_file(filename, file_info)

def _fetch_remote_file(self, filename: str, file_info: Dict) -> Path:
"""Fetch a remote benchmark file and return a temporary file path."""
url = file_info["url"]

try:
# Create SSL context that doesn't verify certificates (for development)
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE

# Create opener with SSL context and fetch content
opener = urllib.request.build_opener(urllib.request.HTTPSHandler(context=ssl_context))
urllib.request.install_opener(opener)

with urllib.request.urlopen(url) as response:
content = response.read()

# Create temporary file and write content
temp_file = tempfile.NamedTemporaryFile(
mode="wb", suffix=".qasm", prefix=f"benchmark_{filename}_", delete=False
)
temp_file.write(content)
temp_file.close()

return Path(temp_file.name)

except Exception as e:
raise RuntimeError(f"Failed to fetch {filename} from {url}: {e}")


# Global instance for convenience
_downloader = None


def get_benchmark_file(filename: str) -> str:
"""Get the path to a benchmark file, downloading if necessary."""
global _downloader
if _downloader is None:
_downloader = BenchmarkDownloader()
return str(_downloader.get_file_path(filename))
Loading
Loading