Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ venv/
ENV/
env.bak/
venv.bak/
.asv/

# Spyder project settings
.spyderproject
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,11 @@ Types of changes:
saturation_pulse $0;
}
```

- Added `asv` benchmarking and profiling support in `pyqasm`. ([#258](https://github.com/qBraid/pyqasm/pull/258))
- Added a workflow to track changes in the `docs/_static/logo.png` file to prevent unnecessary modifications. ([#257](https://github.com/qBraid/pyqasm/pull/257))


### 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",
"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.
27 changes: 27 additions & 0 deletions tests/benchmarks/import.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 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.

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

from subprocess import call
from sys import executable


class PyqasmImport:
"""Test the import time of pyqasm."""

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.

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

from pyqasm import load

from .qasm.benchmark_downloader import get_benchmark_file


class Openpulse:
"""Test the pyqasm openpulse functionality."""

def setup(self):
# Get benchmark file, downloading if necessary
# pylint: disable-next=attribute-defined-outside-init
self.qasm_file = get_benchmark_file("neutral_atom_gate.qasm")

def time_openpulse(self):
_ = load(self.qasm_file).unroll()
85 changes: 85 additions & 0 deletions tests/benchmarks/pyqasm_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# 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=attribute-defined-outside-init

"""
This module is used to test the pyqasm functions.
"""

import os
from pathlib import Path

from pyqasm import dump, dumps, load, printer

from .qasm.benchmark_downloader import get_benchmark_file


class PyqasmFunctions:
"""Test the pyqasm functions."""

# Define parameters for asv
params = [["small (224 lines)", "mid (2335 lines)", "large (17460 lines)"]]
param_names = ["qasm_file"]
timeout = 600

def setup(self, file_size):
# Extract the original file size name from the parameter value
if "(224 lines)" in file_size:
file_size_key = "small"
elif "(2335 lines)" in file_size:
file_size_key = "mid"
elif "(17460 lines)" in file_size:
file_size_key = "large"
else:
file_size_key = file_size

# Define files for each size category
self.files = {
"small": "vqe_uccsd_n4.qasm", # 224 lines
"mid": "dnn_n16.qasm", # 2335 lines
"large": "qv_N029_12345.qasm", # 17460 lines
}

# Get benchmark file for the specified size
self.qasm_file = get_benchmark_file(self.files[file_size_key])
self.pyqasm_obj = load(self.qasm_file)

# Create output file path for dump operations
input_path = Path(self.qasm_file)
self.output_file = str(input_path.parent / f"{file_size_key}_unrolled.qasm")

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 QASM file of specified size."""
_ = load(self.qasm_file)

def time_dumps(self, _):
"""Serialize QASM object of specified size to string."""
_ = dumps(self.pyqasm_obj)

def time_dump(self, _):
"""Dump QASM object of specified size to file."""
dump(self.pyqasm_obj, self.output_file)

def time_draw(self, _):
"""Draw QASM object of specified size."""
_ = printer.mpl_draw(self.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.
93 changes: 93 additions & 0 deletions tests/benchmarks/qasm/benchmark_downloader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# 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.
"""

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."""
# Check local_files first
if filename in self.metadata["local_files"]:
file_path = self.cache_dir / filename
if not file_path.exists():
raise FileNotFoundError(f"Local file {filename} not found in {self.cache_dir}")
return file_path

# Check benchmark_files
if filename in self.metadata["benchmark_files"]:
file_info = self.metadata["benchmark_files"][filename]
# Fetch remote files
return self._fetch_remote_file(filename, file_info)

raise ValueError(f"Unknown benchmark file: {filename}")

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/CI)
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
with tempfile.NamedTemporaryFile(
mode="wb", suffix=".qasm", prefix=f"benchmark_{filename}_", delete=False
) as temp_file:
temp_file.write(content)
temp_file_path = Path(temp_file.name)

return temp_file_path

except Exception as e:
# pylint: disable-next=raise-missing-from
raise RuntimeError(f"Failed to fetch {filename} from {url}: {e}")


def get_benchmark_file(filename: str) -> str:
"""Get the path to a benchmark file, downloading if necessary."""
downloader = BenchmarkDownloader()
return str(downloader.get_file_path(filename))
37 changes: 37 additions & 0 deletions tests/benchmarks/qasm/benchmark_metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"benchmark_files": {
"vqe_uccsd_n4.qasm": {
"source": "Qiskit-benchpress",
"url": "https://raw.githubusercontent.com/Qiskit/benchpress/573c9ce7a1ffebd4ca6d1b0971eba7aca2c9c9a4/benchpress/qasm/qasmbench-small/vqe_uccsd_n4/vqe_uccsd_n4.qasm",
"description": "VQE UCCSD benchmark with 4 qubits (small)",
"size_bytes": 15000
},
"dnn_n16.qasm": {
"source": "Qiskit-benchpress",
"url": "https://raw.githubusercontent.com/Qiskit/benchpress/573c9ce7a1ffebd4ca6d1b0971eba7aca2c9c9a4/benchpress/qasm/qasmbench-medium/dnn_n16/dnn_n16.qasm",
"description": "Deep Neural Network benchmark with 16 qubits (medium)",
"size_bytes": 120000
},
"qv_N029_12345.qasm": {
"source": "Qiskit-benchpress",
"url": "https://raw.githubusercontent.com/Qiskit/benchpress/573c9ce7a1ffebd4ca6d1b0971eba7aca2c9c9a4/benchpress/qasm/qv/qv_N029_12345.qasm",
"description": "Quantum Volume benchmark with 29 qubits (large, 17,460 lines)",
"size_bytes": 470000
}
},
"repository_info": {
"name": "Qiskit-benchpress",
"url": "https://github.com/Qiskit/benchpress",
"description": "QASM benchmark files from Qiskit benchpress repository",
"license": "Apache-2.0",
"commit_hash": "573c9ce7a1ffebd4ca6d1b0971eba7aca2c9c9a4"
},
"local_files": {
"neutral_atom_gate.qasm": {
"source": "local",
"description": "Neutral atom gate benchmark (local file)",
"size_bytes": 3584,
"local_only": true
}
}
}
Loading
Loading