diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4896421f..030da31b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -42,7 +42,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.10', '3.11', '3.12'] + python-version: ['3.10', '3.11', '3.12', '3.13'] steps: - uses: actions/checkout@v6 @@ -58,11 +58,28 @@ jobs: path: dist - name: Install package wheel with extras run: | + pip install --upgrade pip pip install setuptools - bin/install_wheel_extras.sh dist --extra qasm3 --extra cirq --extra test --extra squin - - name: Run tests with coverage + pip install -r requirements-test.txt + bin/install_wheel_extras.sh dist --extra qasm3 --extra cirq --extra squin + - name: Run tests with pyqir 0.11.x (typed pointers) run: | - pytest --cov=qbraid_qir tests/ --cov-report=html --cov-report=xml --cov-report=term + pip install "pyqir>=0.10.0,<0.12" + pytest tests/ --cov=qbraid_qir --cov-report=term --cov-report= --cov-config=pyproject.toml -q + pip install coverage + mkdir -p cov_runs + python -m coverage combine --data-file=cov_runs/.coverage.run1 .coverage + - name: Run tests with pyqir 0.12+ (opaque pointers) + run: | + pip install "pyqir>=0.12.0,<0.13.0" + pytest tests/ --cov=qbraid_qir --cov-report=term --cov-report= --cov-config=pyproject.toml -q + python -m coverage combine --data-file=cov_runs/.coverage.run2 .coverage + - name: Combine coverage and generate report + run: | + python -m coverage combine cov_runs/.coverage.run1 cov_runs/.coverage.run2 + python -m coverage report + mkdir -p build/coverage + python -m coverage xml -o build/coverage/coverage.xml - name: Upload coverage to Codecov if: matrix.python-version == '3.11' uses: codecov/codecov-action@v5.5.1 @@ -70,32 +87,4 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: false files: ./build/coverage/coverage.xml - verbose: true - - test-cudaq-to-squin: - if: github.event.pull_request.draft == false - needs: build - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.11'] - - steps: - - uses: actions/checkout@v5 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python-version }} - cache: pip - - name: Download built package - uses: actions/download-artifact@v5 - with: - name: built-package - path: dist - - name: Install package wheel with squin and test extras - run: | - pip install setuptools - bin/install_wheel_extras.sh dist --extra squin --extra test - - name: Run CUDAQ to Squin tests - run: | - pytest tests/squin_qir/test_cudaq_to_squin.py -v \ No newline at end of file + verbose: true \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c1a9d193..57881e1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,64 +14,34 @@ Types of changes: ## [Unreleased] -### ➕ New Features -- Added a QIR to `squin` conversion method which allows users to execute QIR programs on `squin` enabled backends. This feature also opens up the Squin representation to many formats such as QASM, and Cirq using the built in qbraid-qir converters. Eg. - ([#252](https://github.com/qBraid/qbraid-qir/pull/252)) - -```python -# Converting a PyQIR module to squin -from qbraid_qir.squin import load -from pyqir import BasicQisBuilder, SimpleModule - -mod = SimpleModule("ghz", num_qubits=3, num_results=3) -qis = BasicQisBuilder(mod.builder) - -qis.h(mod.qubits[0]) -qis.cx(mod.qubits[0], mod.qubits[1]) -qis.cx(mod.qubits[1], mod.qubits[2]) - -squin_kernel = load(mod._module) -squin_kernel.print() -``` - -Output: -```stdout -func.func @main() -> !py.NoneType { - ^0(%main_self): - │ %0 = func.invoke new() : !py.Qubit maybe_pure=False - │ %1 = func.invoke new() : !py.Qubit maybe_pure=False - │ %2 = func.invoke h(%0) : !py.NoneType maybe_pure=False - │ %3 = func.invoke x(%0) : !py.NoneType maybe_pure=False - │ %4 = func.invoke x(%1) : !py.NoneType maybe_pure=False - │ %5 = py.constant.constant 1.5707963267948966 : !py.float - │ %6 = func.invoke rx(%5, %0) : !py.NoneType maybe_pure=False - │ %7 = func.const.none() : !py.NoneType - │ func.return %7 -} // func.func main - -``` +### ➕ New Features +- **PyQIR 0.12+ support**: Add compatibility for pyqir 0.12+ (opaque pointers / QIR 2.0) alongside existing 0.10.x support. New `_pyqir_compat` module provides `pointer_id`, `qubit_pointer_type`, and `pyqir_uses_opaque_pointers()` for version-agnostic code. ([#265](https://github.com/qBraid/qbraid-qir/pull/265)) ### 🌟 Improvements -- Split `qbraid_qir.profile` into `qbraid_qir.module` and `qbraid_qir.visitor` ([#237](https://github.com/qBraid/qbraid-qir/pull/237)) -- Improved circuit pre-processing for Cirq converter using Cirq's built-in `optimize_for_target_gateset` ([#248](https://github.com/qBraid/qbraid-qir/pull/248)) - -- Fix types in the `cirq` converter ([#255](https://github.com/qBraid/qbraid-qir/pull/255)) +- **Backward-compatible PyQIR usage**: Cirq, QASM3, and Squin visitors use the compat layer so the codebase works with both pyqir <0.12 (typed pointers) and ≥0.12 (opaque pointers). ([#265](https://github.com/qBraid/qbraid-qir/pull/265)) +- **Version-aware tests**: Test helpers and fixtures choose typed vs opaque pointer expectations based on the installed pyqir version; added version-specific LL fixtures (e.g. `*_typed.ll`, `*_opaque.ll`) for Cirq and QASM3 tests. ([#265](https://github.com/qBraid/qbraid-qir/pull/265)) +- **CI: test both PyQIR versions**: GitHub Actions test job runs the full suite with pyqir 0.10.x and 0.12+, then combines coverage and uploads to Codecov. ([#265](https://github.com/qBraid/qbraid-qir/pull/265)) +- **Tox: dual PyQIR envs**: New `unit-tests-pyqir10` and `unit-tests-pyqir12` tox environments so `tox` runs unit tests against both pyqir version ranges. +- **Optional test deps**: Skip cudaq-to-Squin tests when cudaq is not installed; skip autoqasm converter tests when autoqasm is not installed. ([#265](https://github.com/qBraid/qbraid-qir/pull/265)) ### 📜 Documentation -- Factor out docs deps into its own requirements file ([#237](https://github.com/qBraid/qbraid-qir/pull/237)) + +- **CHANGELOG**: Clarified "Past releases" section with a short description and link to the Releases page. ([#265](https://github.com/qBraid/qbraid-qir/pull/265)) ### 🐛 Bug Fixes -### ⬇️ Dependency Updates -- Update `pyqasm` requirement from >=0.4.0,<0.5.0 to >=0.4.0,<0.6.0 ([#236](https://github.com/qBraid/qbraid-qir/pull/236)) -- Update docutils requirement from <0.22 to <0.23 ([#238](https://github.com/qBraid/qbraid-qir/pull/238)) -- Bump actions/upload-pages-artifact from 3 to 4 ([#241](https://github.com/qBraid/qbraid-qir/pull/241)) -- Update pyqasm requirement from <0.6.0,>=0.4.0 to >=0.4.0,<1.1.0 ([#245](https://github.com/qBraid/qbraid-qir/pull/245)) -- Update qbraid requirement from <0.10.0,>=0.9.0 to >=0.9.0,<0.11.0 ([#247](https://github.com/qBraid/qbraid-qir/pull/247)) -- Bump actions/download-artifact from 5 to 6 ([#249](https://github.com/qBraid/qbraid-qir/pull/249)) -- Bump actions/upload-artifact from 4 to 5 ([#250](https://github.com/qBraid/qbraid-qir/pull/250)) -- Bump actions/checkout from v5 to v6 ([#254](https://github.com/qBraid/qbraid-qir/pull/254)) +- _(none this release)_ + +### ⬇️ Dependency Updates +- **pyqir**: Relax requirement from `>=0.10.0,<0.11.0` to `>=0.10.0,<0.13.0` so both 0.10.x and 0.12.x are supported. ([#265](https://github.com/qBraid/qbraid-qir/pull/265)) ### 👋 Deprecations + +- _(none)_ + +## Past releases + +Release notes for earlier versions are published on the [Releases](https://github.com/qBraid/qbraid-qir/releases) page. \ No newline at end of file diff --git a/CITATION.cff b/CITATION.cff index 9a6e3863..fbb29f9a 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -19,6 +19,7 @@ keywords: - llvm - cirq - openasm +- squin license: Apache-2.0 version: 0.5.0 date-released: '2025-12-15' diff --git a/README.md b/README.md index 319cf226..7ed44bd8 100644 --- a/README.md +++ b/README.md @@ -29,13 +29,13 @@ qBraid-SDK extension providing support for QIR conversions. -[](https://account.qbraid.com?gitHubUrl=https://github.com/qBraid/qbraid-qir.git) +[](https://account.qbraid.com/explore/projects/qbraid-qir-n792k0) ## Motivation qir -This project aims to make [QIR](https://www.qir-alliance.org/) representations accessible via the qBraid-SDK [transpiler](#architecture-diagram), and by doing so, open the door to language-specific conversions from any and all high-level quantum languages [supported](https://docs.qbraid.com/sdk/user-guide/overview#supported-frontends) by `qbraid`. See QIR Alliance: [why do we need it?](https://www.qir-alliance.org/qir-book/concepts/why-do-we-need.html). +This project aims to make [QIR](https://www.qir-alliance.org/) representations accessible via the qBraid-SDK [transpiler](#architecture-diagram), and by doing so, open the door to language-specific conversions from any and all high-level quantum languages [supported](https://docs.qbraid.com/v2/sdk/user-guide/overview#supported-frontends) by `qbraid`. See QIR Alliance: [why do we need it?](https://www.qir-alliance.org/qir-book/concepts/why-do-we-need.html). ## Installation @@ -61,6 +61,12 @@ For Cirq to QIR conversions, install the `cirq` extra: pip install 'qbraid-qir[cirq]' ``` +For QIR to SQUIN conversions, install the `squin` extra: + +```shell +pip install 'qbraid-qir[squin]' +``` + ### Install from source You can also install from source by cloning this repository and running a pip install command @@ -75,7 +81,7 @@ pip install . To include optional dependencies when installing from source, use the same "extras_require" format, e.g. ```shell -pip install '.[qasm3,cirq]' +pip install '.[qasm3,cirq,squin]' ``` ## Check version @@ -90,7 +96,7 @@ qbraid_qir.__version__ ## Resources -- [User Guide](https://docs.qbraid.com/qir/user-guide) +- [User Guide](https://docs.qbraid.com/v2/qir/user-guide) - [API Reference](https://sdk.qbraid.com/qbraid-qir/api/qbraid_qir.html) - [Example Notebooks](https://github.com/qBraid/qbraid-lab-demo/tree/main/qbraid_qir) - [Docker Containers](docker) @@ -140,9 +146,26 @@ module = cirq_to_qir(circuit, name="my-circuit") ir = str(module) ``` +### SQUIN conversions + +```python +from pyqir import BasicQisBuilder, SimpleModule +from qbraid_qir.squin import load + +module = SimpleModule("ghz_n", num_qubits=2, num_results=2) +qis = BasicQisBuilder(module.builder) + +qis.h(module.qubits[0]) +qis.cx(module.qubits[0], module.qubits[1]) + +squin_module = load(module.ir()) + +squin_module.print() +``` + ## Architecture diagram -qBraid-SDK transpiler hub-and-spokes [architecture](https://docs.qbraid.com/qir/user-guide/overview#architecture-diagram) with qbraid-qir integration (left) mapped to language specific conversion step in QIR abstraction [layers](https://www.qir-alliance.org/qir-book/concepts/why-do-we-need.html) (right). +qBraid-SDK transpiler hub-and-spokes [architecture](https://docs.qbraid.com/v2/qir/user-guide/overview#architecture-diagram) with qbraid-qir integration (left) mapped to language specific conversion step in QIR abstraction [layers](https://www.qir-alliance.org/qir-book/concepts/why-do-we-need.html) (right). architecture @@ -169,10 +192,10 @@ citation details, please refer to [CITATION.cff](CITATION.cff). @software{Gupta_qBraid-QIR_Python_package_2025, author = {Gupta, Harshit and Hill, Ryan James}, license = {Apache-2.0}, - month = jun, + month = dec, title = {{qBraid-QIR: Python package for QIR conversions, integrations, and utilities.}}, url = {https://github.com/qBraid/qbraid-qir}, - version = {0.4.0}, + version = {0.5.0}, year = {2025} } ``` diff --git a/bin/bump_version.py b/bin/bump_version.py index 097c5eb7..7fbe0f9b 100755 --- a/bin/bump_version.py +++ b/bin/bump_version.py @@ -16,6 +16,7 @@ Script to bump the major, minor, or patch version in _version.py """ + import pathlib import sys diff --git a/docs/api/qbraid_qir.squin.rst b/docs/api/qbraid_qir.squin.rst new file mode 100644 index 00000000..9bcb604d --- /dev/null +++ b/docs/api/qbraid_qir.squin.rst @@ -0,0 +1,8 @@ +:orphan: + +qbraid_qir.squin +================= + +.. automodule:: qbraid_qir.squin + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index b5c387f9..185680bd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,7 +15,7 @@ # -- Project information ----------------------------------------------------- project = "qBraid" -copyright = "2025, qBraid Development Team" +copyright = "2026, qBraid Development Team" author = "qBraid Development Team" # The full version, including alpha/beta/rc tags @@ -41,7 +41,7 @@ # set_type_checking_flag = True autodoc_member_order = "bysource" autoclass_content = "both" -autodoc_mock_imports = ["cirq", "openqasm3", "pyqasm", "numpy", "numpy.typing"] +autodoc_mock_imports = ["cirq", "openqasm3", "pyqasm", "numpy", "numpy.typing", "kirin", "bloqade"] napoleon_numpy_docstring = False todo_include_todos = True mathjax_path = "https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js?config=TeX-AMS-MML_HTMLorMML" diff --git a/docs/index.rst b/docs/index.rst index e3c78ff1..977cd951 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -91,7 +91,7 @@ To enable specific conversions such as OpenQASM 3 to QIR or Cirq to QIR, you can Resources ---------- -- `User Guide `_ +- `User Guide `_ - `Example Notebooks `_ - `API Reference `_ - `Source Code `_ @@ -117,6 +117,7 @@ Resources api/qbraid_qir api/qbraid_qir.cirq api/qbraid_qir.qasm3 + api/qbraid_qir.squin .. toctree:: :caption: CORE API Reference @@ -130,3 +131,9 @@ Resources :hidden: pyqasm + +.. toctree:: + :caption: ALGOS API Reference + :hidden: + + qbraid_algorithms diff --git a/examples b/examples index c0c58c9c..a38f897e 160000 --- a/examples +++ b/examples @@ -1 +1 @@ -Subproject commit c0c58c9cb8ddaf2a0f31253cb95d42e21fb7fd28 +Subproject commit a38f897eb5a6e917ee227ffb3afac8f1406021f6 diff --git a/pyproject.toml b/pyproject.toml index 71136e61..68e0a876 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ description = "qBraid-SDK extension providing support for QIR conversions." readme = "README.md" authors = [{name = "qBraid Development Team"}, {email = "contact@qbraid.com"}] license = "Apache-2.0" -keywords = ["qbraid", "quantum", "qir", "llvm", "cirq", "openqasm"] +keywords = ["qbraid", "quantum", "qir", "llvm", "cirq", "openqasm", "squin"] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", @@ -21,17 +21,18 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3 :: Only", "Typing :: Typed", "Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering :: Physics" ] -dependencies = ["pyqir>=0.10.0,<0.11.0"] +dependencies = ["pyqir>=0.10.0,<0.13.0"] requires-python = ">=3.10" [project.urls] Homepage = "https://github.com/qBraid/qbraid-qir" -Documentation = "https://docs.qbraid.com/qir" +Documentation = "https://docs.qbraid.com/v2/qir" "Bug Tracker" = "https://github.com/qBraid/qbraid-qir/issues" Discord = "https://discord.gg/TPBU2sa8Et" "Launch on Lab" = "https://account.qbraid.com/?gitHubUrl=https://github.com/qBraid/qbraid-qir.git" @@ -40,7 +41,6 @@ Discord = "https://discord.gg/TPBU2sa8Et" cirq = ["cirq-core>=1.3.0,<1.6.0"] qasm3 = ["pyqasm>=0.4.0,<1.1.0", "numpy"] squin = ["kirin-toolchain>=0.17.33", "bloqade-circuit>=0.9.1"] -test = ["qbraid>=0.9.0,<0.11.0", "pytest", "pytest-cov", "autoqasm>=0.1.0", "cudaq"] [tool.setuptools] packages = ["qbraid_qir", "qbraid_qir.cirq", "qbraid_qir.qasm3", "qbraid_qir.squin"] diff --git a/qbraid_qir/__init__.py b/qbraid_qir/__init__.py index 122c0fbe..31c670ad 100644 --- a/qbraid_qir/__init__.py +++ b/qbraid_qir/__init__.py @@ -36,6 +36,7 @@ QirConversionError """ + import importlib from typing import TYPE_CHECKING diff --git a/qbraid_qir/_pyqir_compat.py b/qbraid_qir/_pyqir_compat.py new file mode 100644 index 00000000..c2d4e600 --- /dev/null +++ b/qbraid_qir/_pyqir_compat.py @@ -0,0 +1,73 @@ +# Copyright 2026 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. + +""" +Compatibility layer for different pyqir versions. + +PyQIR 0.12+ uses opaque pointers (QIR 2.0) and removed qubit_id/result_id/qubit_type/result_type, +replacing them with ptr_id and PointerType. This module provides a single API that works +with both pyqir 0.10.x (typed pointers) and 0.12+ (opaque pointers). +""" + +from typing import Any, Optional + +import pyqir + + +def _uses_opaque_pointers() -> bool: + """True if this pyqir build uses opaque pointers (0.12+).""" + return getattr(pyqir, "ptr_id", None) is not None + + +def pointer_id(value: Any) -> Optional[int]: + """ + Extract a static pointer id from a value (qubit or result). + + Uses ptr_id on pyqir 0.12+, or qubit_id/result_id on 0.10.x (for qubit/result constants). + """ + if _uses_opaque_pointers(): + return pyqir.ptr_id(value) + # 0.10.x: qubit_id for qubit constants, result_id for result constants + qubit_id_fn = getattr(pyqir, "qubit_id", None) + result_id_fn = getattr(pyqir, "result_id", None) + if qubit_id_fn is not None: + out = qubit_id_fn(value) + if out is not None: + return out + if result_id_fn is not None: + return result_id_fn(value) + return None + + +def qubit_pointer_type(context: Any) -> Any: + """ + Return the LLVM type for a qubit pointer in this pyqir version. + + Uses PointerType(Type.void(context)) on 0.12+, or qubit_type(context) on 0.10.x. + """ + if _uses_opaque_pointers(): + return pyqir.PointerType(pyqir.Type.void(context)) + qubit_type_fn = getattr(pyqir, "qubit_type", None) + if qubit_type_fn is not None: + return qubit_type_fn(context) + return pyqir.PointerType(pyqir.Type.void(context)) + + +def pyqir_uses_opaque_pointers() -> bool: + """ + Return True if the installed pyqir uses opaque pointers (QIR 2.0 / pyqir 0.12+). + + Useful in tests to choose expected IR format (ptr vs %Qubit* / i8*). + """ + return _uses_opaque_pointers() diff --git a/qbraid_qir/_version.py b/qbraid_qir/_version.py index dba69d61..3221e239 100644 --- a/qbraid_qir/_version.py +++ b/qbraid_qir/_version.py @@ -18,4 +18,5 @@ Version number (major.minor.patch[-label]) """ + __version__ = "0.5.0" diff --git a/qbraid_qir/cirq/__init__.py b/qbraid_qir/cirq/__init__.py index 0028c613..fd8a9250 100644 --- a/qbraid_qir/cirq/__init__.py +++ b/qbraid_qir/cirq/__init__.py @@ -44,6 +44,7 @@ CirqConversionError """ + from .convert import cirq_to_qir from .elements import CirqModule from .exceptions import CirqConversionError diff --git a/qbraid_qir/cirq/convert.py b/qbraid_qir/cirq/convert.py index e19824b9..9362d350 100644 --- a/qbraid_qir/cirq/convert.py +++ b/qbraid_qir/cirq/convert.py @@ -16,6 +16,7 @@ Module containing Cirq to qBraid QIR conversion functions """ + from typing import Optional import cirq diff --git a/qbraid_qir/cirq/exceptions.py b/qbraid_qir/cirq/exceptions.py index bd319657..6249240b 100644 --- a/qbraid_qir/cirq/exceptions.py +++ b/qbraid_qir/cirq/exceptions.py @@ -16,6 +16,7 @@ Module defining exceptions for errors raised during Cirq conversions. """ + from qbraid_qir.exceptions import QirConversionError diff --git a/qbraid_qir/cirq/opsets.py b/qbraid_qir/cirq/opsets.py index 0cb69458..de79301e 100644 --- a/qbraid_qir/cirq/opsets.py +++ b/qbraid_qir/cirq/opsets.py @@ -16,6 +16,7 @@ Module mapping supported Cirq gates/operations to pyqir functions. """ + from typing import Callable import cirq diff --git a/qbraid_qir/cirq/visitor.py b/qbraid_qir/cirq/visitor.py index 55d869aa..df6ef5de 100644 --- a/qbraid_qir/cirq/visitor.py +++ b/qbraid_qir/cirq/visitor.py @@ -16,6 +16,7 @@ Module defining CirqVisitor. """ + import logging from abc import ABCMeta, abstractmethod @@ -25,6 +26,8 @@ import pyqir.rt from pyqir import BasicBlock, Builder, Constant, IntType, PointerType +from qbraid_qir._pyqir_compat import pointer_id + from .elements import CirqModule from .opsets import map_cirq_op_to_pyqir_callable @@ -110,7 +113,7 @@ def visit_operation(self, operation: cirq.Operation) -> None: def handle_measurement(pyqir_func): logger.debug("Visiting measurement operation '%s'", str(operation)) for qubit, result in zip(qubits, results): - self._measured_qubits[pyqir.qubit_id(qubit)] = True + self._measured_qubits[pointer_id(qubit)] = True pyqir_func(self._builder, qubit, result) # dealing with conditional gates diff --git a/qbraid_qir/profiles/core.py b/qbraid_qir/profiles/core.py index f8c1aa2a..323f8ca3 100644 --- a/qbraid_qir/profiles/core.py +++ b/qbraid_qir/profiles/core.py @@ -16,6 +16,7 @@ Module containing core profile classes for QIR generation. """ + from abc import ABC, abstractmethod from dataclasses import dataclass from typing import Callable diff --git a/qbraid_qir/qasm3/__init__.py b/qbraid_qir/qasm3/__init__.py index 99c8bcca..a525d8ca 100644 --- a/qbraid_qir/qasm3/__init__.py +++ b/qbraid_qir/qasm3/__init__.py @@ -43,6 +43,7 @@ Qasm3ConversionError """ + from .convert import qasm3_to_qir from .elements import QasmQIRModule from .exceptions import Qasm3ConversionError diff --git a/qbraid_qir/qasm3/convert.py b/qbraid_qir/qasm3/convert.py index c1053c8b..d7b59e55 100644 --- a/qbraid_qir/qasm3/convert.py +++ b/qbraid_qir/qasm3/convert.py @@ -16,6 +16,7 @@ Module containing OpenQASM to QIR conversion functions """ + from enum import Enum from typing import Optional, Union diff --git a/qbraid_qir/qasm3/elements.py b/qbraid_qir/qasm3/elements.py index 87f0a34c..9ad34be1 100644 --- a/qbraid_qir/qasm3/elements.py +++ b/qbraid_qir/qasm3/elements.py @@ -18,6 +18,7 @@ Module defining Qasm3 Converter elements. """ + from __future__ import annotations import uuid diff --git a/qbraid_qir/qasm3/exceptions.py b/qbraid_qir/qasm3/exceptions.py index 0205de37..c544337e 100644 --- a/qbraid_qir/qasm3/exceptions.py +++ b/qbraid_qir/qasm3/exceptions.py @@ -16,6 +16,7 @@ Module defining exceptions for errors raised during QASM3 conversions. """ + import logging from typing import Optional, Type diff --git a/qbraid_qir/qasm3/maps.py b/qbraid_qir/qasm3/maps.py index 2b9188a9..09eaf3ea 100644 --- a/qbraid_qir/qasm3/maps.py +++ b/qbraid_qir/qasm3/maps.py @@ -15,6 +15,7 @@ """ Module mapping supported QASM gates/operations to pyqir functions. """ + from typing import Callable import pyqir diff --git a/qbraid_qir/qasm3/visitor.py b/qbraid_qir/qasm3/visitor.py index c5420f53..65c333c6 100644 --- a/qbraid_qir/qasm3/visitor.py +++ b/qbraid_qir/qasm3/visitor.py @@ -21,6 +21,7 @@ without code duplication, based on JSON profile specifications. """ + import logging from typing import Any, Callable, List, Optional, Union @@ -30,6 +31,7 @@ from openqasm3.ast import UnaryOperator from pyqir import qis +from qbraid_qir._pyqir_compat import pointer_id, qubit_pointer_type from qbraid_qir.profiles import Profile, ProfileRegistry from qbraid_qir.visitor import QIRVisitor @@ -254,7 +256,7 @@ def _check_qubit_use_after_measurement(self, qubit_ids: List[pyqir.Constant]) -> if not self._profile.allow_qubit_use_after_measurement(): # For profiles that don't allow it, we could add validation here for qubit_id in qubit_ids: - qubit_id_result = pyqir.qubit_id(qubit_id) + qubit_id_result = pointer_id(qubit_id) if qubit_id_result is not None and self._measured_qubits.get( qubit_id_result, False ): @@ -283,7 +285,7 @@ def _visit_measurement(self, statement: qasm3_ast.QuantumMeasurementStatement) - for src_id, tgt_id in zip(source_ids, target_ids): # Track measurement if profile supports it if self._profile.should_track_qubit_measurement(): - qubit_id_result = pyqir.qubit_id(src_id) + qubit_id_result = pointer_id(src_id) if qubit_id_result is not None: self._measured_qubits[qubit_id_result] = True @@ -305,7 +307,7 @@ def _visit_reset(self, statement: qasm3_ast.QuantumReset) -> None: for qid in qubit_ids: # Clear measurement tracking if profile supports it if self._profile.should_track_qubit_measurement(): - qubit_id_result = pyqir.qubit_id(qid) + qubit_id_result = pointer_id(qid) if qubit_id_result is not None: self._measured_qubits[qubit_id_result] = False @@ -519,7 +521,7 @@ def _visit_external_gate_operation(self, operation: qasm3_ast.QuantumGate) -> No if qir_function is None: # First time seeing this external gate -> define new function qir_function_arguments = [pyqir.Type.double(context)] * len(operation.arguments) - qir_function_arguments += [pyqir.qubit_type(context)] * op_qubit_count + qir_function_arguments += [qubit_pointer_type(context)] * op_qubit_count qir_function = pyqir.Function( pyqir.FunctionType(pyqir.Type.void(context), qir_function_arguments), diff --git a/qbraid_qir/serialization.py b/qbraid_qir/serialization.py index 67a0eb80..54e3d8af 100644 --- a/qbraid_qir/serialization.py +++ b/qbraid_qir/serialization.py @@ -16,6 +16,7 @@ Module for exporting QIR to bitcode and LLVM IR files. """ + from __future__ import annotations import logging diff --git a/qbraid_qir/squin/__init__.py b/qbraid_qir/squin/__init__.py index 80bc4aae..148d6213 100644 --- a/qbraid_qir/squin/__init__.py +++ b/qbraid_qir/squin/__init__.py @@ -17,6 +17,7 @@ """ This module contains the functionality to convert a PyQIR module into a squin kernel. """ + from .visitor import load __all__ = ["load"] diff --git a/qbraid_qir/squin/maps.py b/qbraid_qir/squin/maps.py index 681bff49..607e173d 100644 --- a/qbraid_qir/squin/maps.py +++ b/qbraid_qir/squin/maps.py @@ -15,6 +15,7 @@ """ Module mapping PyQIR gate strings to squin gates. """ + from bloqade import squin # Mapping from full PyQIR gate strings to squin gates diff --git a/qbraid_qir/squin/visitor.py b/qbraid_qir/squin/visitor.py index bd3748bd..646e7524 100644 --- a/qbraid_qir/squin/visitor.py +++ b/qbraid_qir/squin/visitor.py @@ -15,6 +15,7 @@ """ This module contains the functionality to convert a PyQIR module into a squin kernel. """ + from __future__ import annotations import os @@ -28,6 +29,8 @@ from kirin.dialects import func, ilist, py from kirin.rewrite import CFGCompactify, Walk +from qbraid_qir._pyqir_compat import pointer_id + from .exceptions import InvalidSquinInput from .maps import PYQIR_TO_SQUIN_GATES_MAP, QIR_TO_SQUIN_UNSUPPORTED_STATEMENTS_MAP @@ -428,7 +431,7 @@ def visit_constant( Returns: ir.SSAValue: The SSA value of the constant. """ - qubit_id = pyqir.qubit_id(value) + qubit_id = pointer_id(value) if qubit_id is not None and qubit_id in self.qubit_ssa_map: return self.qubit_ssa_map[qubit_id] diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 00000000..ca205973 --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,5 @@ +qbraid>=0.11.0,<0.12.0 +pytest +pytest-cov +autoqasm>=0.1.0 +cudaq; platform_system == "Linux" and python_version < '3.14' \ No newline at end of file diff --git a/tests/cirq_qir/conftest.py b/tests/cirq_qir/conftest.py index 07565704..820e6222 100644 --- a/tests/cirq_qir/conftest.py +++ b/tests/cirq_qir/conftest.py @@ -17,6 +17,7 @@ without needing to import them (pytest will automatically discover them). """ + # pylint: disable=unused-import,wildcard-import,unused-wildcard-import from .fixtures.basic_gates import * diff --git a/tests/cirq_qir/fixtures/basic_gates.py b/tests/cirq_qir/fixtures/basic_gates.py index 2b860e00..d71f09ec 100644 --- a/tests/cirq_qir/fixtures/basic_gates.py +++ b/tests/cirq_qir/fixtures/basic_gates.py @@ -16,6 +16,7 @@ Module defining Cirq basic gate fixtures for use in tests. """ + import cirq import pytest diff --git a/tests/cirq_qir/fixtures/cirq_circuits.py b/tests/cirq_qir/fixtures/cirq_circuits.py index 1d36893f..a2b6799e 100644 --- a/tests/cirq_qir/fixtures/cirq_circuits.py +++ b/tests/cirq_qir/fixtures/cirq_circuits.py @@ -16,6 +16,7 @@ Module containing Cirq circuit fixtures for unit tests. """ + import cirq import pytest diff --git a/tests/cirq_qir/fixtures/pyqir_circuits.py b/tests/cirq_qir/fixtures/pyqir_circuits.py index 01f5e887..cd6fb4d0 100644 --- a/tests/cirq_qir/fixtures/pyqir_circuits.py +++ b/tests/cirq_qir/fixtures/pyqir_circuits.py @@ -16,6 +16,7 @@ Module containing PyQIR circuit fixtures for unit tests. """ + import pytest from pyqir import BasicQisBuilder, SimpleModule diff --git a/tests/cirq_qir/resources/test_conditional_gates_opaque.ll b/tests/cirq_qir/resources/test_conditional_gates_opaque.ll new file mode 100644 index 00000000..6aacc043 --- /dev/null +++ b/tests/cirq_qir/resources/test_conditional_gates_opaque.ll @@ -0,0 +1,89 @@ +; ModuleID = 'test_conditional_gates' +source_filename = "circuit-778bcda" + +define void @main() #0 { +entry: + call void @__quantum__rt__initialize(ptr null) + call void @__quantum__qis__h__body(ptr null) + call void @__quantum__qis__h__body(ptr inttoptr (i64 1 to ptr)) + %0 = call i1 @__quantum__qis__read_result__body(ptr inttoptr (i64 1 to ptr)) + br i1 %0, label %then, label %else + +then: ; preds = %entry + call void @__quantum__qis__z__body(ptr inttoptr (i64 2 to ptr)) + br label %continue + +else: ; preds = %entry + call void @__quantum__qis__x__body(ptr inttoptr (i64 2 to ptr)) + call void @__quantum__qis__x__body(ptr inttoptr (i64 2 to ptr)) + br label %continue + +continue: ; preds = %else, %then + %1 = call i1 @__quantum__qis__read_result__body(ptr null) + br i1 %1, label %then1, label %else2 + +then1: ; preds = %continue + call void @__quantum__qis__z__body(ptr inttoptr (i64 2 to ptr)) + br label %continue3 + +else2: ; preds = %continue + br label %continue3 + +continue3: ; preds = %else2, %then1 + call void @__quantum__qis__mz__body(ptr null, ptr null) + call void @__quantum__qis__mz__body(ptr inttoptr (i64 1 to ptr), ptr inttoptr (i64 1 to ptr)) + %2 = call i1 @__quantum__qis__read_result__body(ptr inttoptr (i64 1 to ptr)) + br i1 %2, label %then4, label %else5 + +then4: ; preds = %continue3 + call void @__quantum__qis__rz__body(double 5.000000e-01, ptr inttoptr (i64 2 to ptr)) + br label %continue6 + +else5: ; preds = %continue3 + call void @__quantum__qis__x__body(ptr inttoptr (i64 2 to ptr)) + call void @__quantum__qis__x__body(ptr inttoptr (i64 2 to ptr)) + br label %continue6 + +continue6: ; preds = %else5, %then4 + %3 = call i1 @__quantum__qis__read_result__body(ptr null) + br i1 %3, label %then7, label %else8 + +then7: ; preds = %continue6 + call void @__quantum__qis__rz__body(double 5.000000e-01, ptr inttoptr (i64 2 to ptr)) + br label %continue9 + +else8: ; preds = %continue6 + br label %continue9 + +continue9: ; preds = %else8, %then7 + call void @__quantum__rt__result_record_output(ptr null, ptr null) + call void @__quantum__rt__result_record_output(ptr inttoptr (i64 1 to ptr), ptr null) + call void @__quantum__rt__result_record_output(ptr inttoptr (i64 2 to ptr), ptr null) + ret void +} + +declare void @__quantum__rt__initialize(ptr) + +declare void @__quantum__qis__h__body(ptr) + +declare i1 @__quantum__qis__read_result__body(ptr) + +declare void @__quantum__qis__z__body(ptr) + +declare void @__quantum__qis__x__body(ptr) + +declare void @__quantum__qis__mz__body(ptr, ptr writeonly) #1 + +declare void @__quantum__qis__rz__body(double, ptr) + +declare void @__quantum__rt__result_record_output(ptr, ptr) + +attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="3" "required_num_results"="3" } +attributes #1 = { "irreversible" } + +!llvm.module.flags = !{!0, !1, !2, !3} + +!0 = !{i32 1, !"qir_major_version", i32 1} +!1 = !{i32 7, !"qir_minor_version", i32 0} +!2 = !{i32 1, !"dynamic_qubit_management", i1 false} +!3 = !{i32 1, !"dynamic_result_management", i1 false} diff --git a/tests/cirq_qir/resources/test_conditional_gates.ll b/tests/cirq_qir/resources/test_conditional_gates_typed.ll similarity index 100% rename from tests/cirq_qir/resources/test_conditional_gates.ll rename to tests/cirq_qir/resources/test_conditional_gates_typed.ll diff --git a/tests/cirq_qir/resources/test_qir_bell_cirq_opaque.ll b/tests/cirq_qir/resources/test_qir_bell_cirq_opaque.ll new file mode 100644 index 00000000..422b18b6 --- /dev/null +++ b/tests/cirq_qir/resources/test_qir_bell_cirq_opaque.ll @@ -0,0 +1,27 @@ +; ModuleID = 'test_qir_bell' +source_filename = "test_qir_bell" + +define void @main() #0 { +entry: + call void @__quantum__qis__h__body(ptr null) + call void @__quantum__qis__cnot__body(ptr null, ptr inttoptr (i64 1 to ptr)) + call void @__quantum__qis__mz__body(ptr null, ptr null) + call void @__quantum__qis__mz__body(ptr inttoptr (i64 1 to ptr), ptr inttoptr (i64 1 to ptr)) + ret void +} + +declare void @__quantum__qis__h__body(ptr) + +declare void @__quantum__qis__cnot__body(ptr, ptr) + +declare void @__quantum__qis__mz__body(ptr, ptr writeonly) #1 + +attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="2" "required_num_results"="2" } +attributes #1 = { "irreversible" } + +!llvm.module.flags = !{!0, !1, !2, !3} + +!0 = !{i32 1, !"qir_major_version", i32 1} +!1 = !{i32 7, !"qir_minor_version", i32 0} +!2 = !{i32 1, !"dynamic_qubit_management", i1 false} +!3 = !{i32 1, !"dynamic_result_management", i1 false} diff --git a/tests/cirq_qir/resources/test_qir_bell.ll b/tests/cirq_qir/resources/test_qir_bell_cirq_typed.ll similarity index 94% rename from tests/cirq_qir/resources/test_qir_bell.ll rename to tests/cirq_qir/resources/test_qir_bell_cirq_typed.ll index befbc736..894818ff 100644 --- a/tests/cirq_qir/resources/test_qir_bell.ll +++ b/tests/cirq_qir/resources/test_qir_bell_cirq_typed.ll @@ -27,4 +27,4 @@ attributes #1 = { "irreversible" } !0 = !{i32 1, !"qir_major_version", i32 1} !1 = !{i32 7, !"qir_minor_version", i32 0} !2 = !{i32 1, !"dynamic_qubit_management", i1 false} -!3 = !{i32 1, !"dynamic_result_management", i1 false} \ No newline at end of file +!3 = !{i32 1, !"dynamic_result_management", i1 false} diff --git a/tests/cirq_qir/resources/test_qir_bell_opaque.ll b/tests/cirq_qir/resources/test_qir_bell_opaque.ll new file mode 100644 index 00000000..5dd8174f --- /dev/null +++ b/tests/cirq_qir/resources/test_qir_bell_opaque.ll @@ -0,0 +1,27 @@ +; ModuleID = 'test_qir_bell' +source_filename = "test_qir_bell" + +define void @main() #0 { +entry: + call void @__quantum__qis__h__body(ptr null) + call void @__quantum__qis__cnot__body(ptr null, ptr inttoptr (i64 1 to ptr)) + call void @__quantum__qis__mz__body(ptr null, ptr null) + call void @__quantum__qis__mz__body(ptr inttoptr (i64 1 to ptr), ptr inttoptr (i64 1 to ptr)) + ret void +} + +declare void @__quantum__qis__h__body(ptr) + +declare void @__quantum__qis__cnot__body(ptr, ptr) + +declare void @__quantum__qis__mz__body(ptr, ptr writeonly) #1 + +attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="2" "required_num_results"="2" } +attributes #1 = { "irreversible" } + +!llvm.module.flags = !{!0, !1, !2, !3} + +!0 = !{i32 1, !"qir_major_version", i32 2} +!1 = !{i32 7, !"qir_minor_version", i32 0} +!2 = !{i32 1, !"dynamic_qubit_management", i1 false} +!3 = !{i32 1, !"dynamic_result_management", i1 false} diff --git a/tests/cirq_qir/resources/test_qir_bell_typed.ll b/tests/cirq_qir/resources/test_qir_bell_typed.ll new file mode 100644 index 00000000..894818ff --- /dev/null +++ b/tests/cirq_qir/resources/test_qir_bell_typed.ll @@ -0,0 +1,30 @@ +; ModuleID = 'test_qir_bell' +source_filename = "test_qir_bell" + +%Qubit = type opaque +%Result = type opaque + +define void @main() #0 { +entry: + call void @__quantum__qis__h__body(%Qubit* null) + call void @__quantum__qis__cnot__body(%Qubit* null, %Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) + call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) + ret void +} + +declare void @__quantum__qis__h__body(%Qubit*) + +declare void @__quantum__qis__cnot__body(%Qubit*, %Qubit*) + +declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 + +attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="2" "required_num_results"="2" } +attributes #1 = { "irreversible" } + +!llvm.module.flags = !{!0, !1, !2, !3} + +!0 = !{i32 1, !"qir_major_version", i32 1} +!1 = !{i32 7, !"qir_minor_version", i32 0} +!2 = !{i32 1, !"dynamic_qubit_management", i1 false} +!3 = !{i32 1, !"dynamic_result_management", i1 false} diff --git a/tests/cirq_qir/test_cirq_module.py b/tests/cirq_qir/test_cirq_module.py index f99d830c..644f6436 100644 --- a/tests/cirq_qir/test_cirq_module.py +++ b/tests/cirq_qir/test_cirq_module.py @@ -16,6 +16,7 @@ Module containing unit tests for CirqModule and Module elements. """ + import hashlib import cirq diff --git a/tests/cirq_qir/test_cirq_preprocess.py b/tests/cirq_qir/test_cirq_preprocess.py index 75aaade5..07a3e38c 100644 --- a/tests/cirq_qir/test_cirq_preprocess.py +++ b/tests/cirq_qir/test_cirq_preprocess.py @@ -16,6 +16,7 @@ Test functions that preprocess Cirq circuits before conversion to QIR. """ + import cirq import numpy as np import pytest diff --git a/tests/cirq_qir/test_cirq_to_qir.py b/tests/cirq_qir/test_cirq_to_qir.py index 657fb9ba..7f8d4692 100644 --- a/tests/cirq_qir/test_cirq_to_qir.py +++ b/tests/cirq_qir/test_cirq_to_qir.py @@ -16,6 +16,7 @@ Module containing unit tests for Cirq to QIR conversion functions. """ + import os from pathlib import Path @@ -23,6 +24,7 @@ import pyqir import pytest +from qbraid_qir._pyqir_compat import pyqir_uses_opaque_pointers from qbraid_qir.cirq import cirq_to_qir from tests.cirq_qir.fixtures.basic_gates import ( double_op_tests, @@ -54,10 +56,16 @@ def resources_file(filename: str) -> str: return os.path.join(RESOURCES_DIR, f"{filename}.ll") +def version_specific_ll_path(base_name: str) -> str: + """Path to typed or opaque fixture based on installed pyqir version.""" + suffix = "opaque" if pyqir_uses_opaque_pointers() else "typed" + return os.path.join(RESOURCES_DIR, f"{base_name}_{suffix}.ll") + + def compare_reference_ir(generated_bitcode: bytes, name: str) -> None: module = pyqir.Module.from_bitcode(pyqir.Context(), generated_bitcode, f"{name}") ir = str(module) - file = os.path.join(os.path.dirname(__file__), f"resources/{name}.ll") + file = version_specific_ll_path(name) expected = Path(file).read_text(encoding="utf-8") assert ir == expected @@ -195,8 +203,7 @@ def test_pauli_term_measurements(): def test_verify_qir_bell_fixture(pyqir_bell): """Test that pyqir fixture generates code equal to test_qir_bell.ll file.""" - test_name = "test_qir_bell" - filepath = resources_file(test_name) + filepath = version_specific_ll_path("test_qir_bell") assert_equal_qir(pyqir_bell.ir(), filepath) @@ -210,7 +217,7 @@ def test_entry_point_name(cirq_bell): def test_convert_bell_compare_file(cirq_bell): """Test converting Cirq bell circuit to QIR.""" test_name = "test_qir_bell" - filepath = resources_file(test_name) + filepath = version_specific_ll_path("test_qir_bell_cirq") module = cirq_to_qir(cirq_bell, name=test_name, initialize_runtime=False, record_output=False) assert_equal_qir(str(module), filepath) diff --git a/tests/cirq_qir/test_serialization.py b/tests/cirq_qir/test_serialization.py index 69bba3d5..a632acf8 100644 --- a/tests/cirq_qir/test_serialization.py +++ b/tests/cirq_qir/test_serialization.py @@ -16,6 +16,7 @@ Unit tests for the export module """ + import os import pathlib import shutil diff --git a/tests/qasm3_qir/autoqasm/test_convert.py b/tests/qasm3_qir/autoqasm/test_convert.py index aca5e08c..45e441f8 100644 --- a/tests/qasm3_qir/autoqasm/test_convert.py +++ b/tests/qasm3_qir/autoqasm/test_convert.py @@ -16,17 +16,25 @@ Tests the convert module of autoqasm to qir """ + import re from typing import TYPE_CHECKING -import autoqasm as aq -import autoqasm.instructions as ins -import numpy as np import pytest -from pyqir import Module -from qbraid.passes.qasm.compat import add_stdgates_include, insert_gate_def -from qbraid_qir.qasm3 import qasm3_to_qir +pytest.importorskip("autoqasm") + +# Imports after importorskip so optional dependency is not required at import time. +import autoqasm as aq # pylint: disable=wrong-import-position +import autoqasm.instructions as ins # pylint: disable=wrong-import-position +import numpy as np # pylint: disable=wrong-import-position +from pyqir import Module # pylint: disable=wrong-import-position +from qbraid.passes.qasm.compat import ( # pylint: disable=wrong-import-position + add_stdgates_include, + insert_gate_def, +) + +from qbraid_qir.qasm3 import qasm3_to_qir # pylint: disable=wrong-import-position if TYPE_CHECKING: from autoqasm.program import MainProgram diff --git a/tests/qasm3_qir/conftest.py b/tests/qasm3_qir/conftest.py index ccb3b8fa..5be19f05 100644 --- a/tests/qasm3_qir/conftest.py +++ b/tests/qasm3_qir/conftest.py @@ -17,6 +17,7 @@ without needing to import them (pytest will automatically discover them). """ + # pylint: disable=unused-import,wildcard-import,unused-wildcard-import from .fixtures.gates import * diff --git a/tests/qasm3_qir/converter/test_alias.py b/tests/qasm3_qir/converter/test_alias.py index 33c167ff..cc174ead 100644 --- a/tests/qasm3_qir/converter/test_alias.py +++ b/tests/qasm3_qir/converter/test_alias.py @@ -28,7 +28,7 @@ check_two_qubit_gate_op, ) -from .test_if import compare_reference_ir, resources_file +from .test_if import compare_reference_ir, version_specific_ll_file def test_alias(): @@ -152,7 +152,7 @@ def test_alias_in_scope_1(): generated_qir = str(result).splitlines() check_attributes(generated_qir, 4, 4) - simple_file = resources_file("simple_if.ll") + simple_file = version_specific_ll_file("simple_if") compare_reference_ir(result.bitcode, simple_file) @@ -187,5 +187,5 @@ def test_alias_in_scope_2(): generated_qir = str(result).splitlines() check_attributes(generated_qir, 4, 4) - simple_file = resources_file("simple_if.ll") + simple_file = version_specific_ll_file("simple_if") compare_reference_ir(result.bitcode, simple_file) diff --git a/tests/qasm3_qir/converter/test_barrier.py b/tests/qasm3_qir/converter/test_barrier.py index 28ae8425..107ea9ff 100644 --- a/tests/qasm3_qir/converter/test_barrier.py +++ b/tests/qasm3_qir/converter/test_barrier.py @@ -16,6 +16,7 @@ Module containing unit tests for QASM3 to QIR conversion functions. """ + import pytest from qbraid_qir.qasm3 import qasm3_to_qir diff --git a/tests/qasm3_qir/converter/test_gates.py b/tests/qasm3_qir/converter/test_gates.py index fe893ea5..cfab5079 100644 --- a/tests/qasm3_qir/converter/test_gates.py +++ b/tests/qasm3_qir/converter/test_gates.py @@ -16,6 +16,7 @@ Module containing unit tests for QASM3 to QIR conversion functions. """ + import pytest from qbraid_qir.qasm3 import qasm3_to_qir @@ -248,8 +249,7 @@ def test_inv_gate_modifier(): def test_nested_gate_modifiers(): - complex_qir = qasm3_to_qir( - """ + complex_qir = qasm3_to_qir(""" OPENQASM 3; include "stdgates.inc"; qubit[2] q; @@ -262,8 +262,7 @@ def test_nested_gate_modifiers(): } pow(1) @ inv @ pow(2) @ custom q; pow(-1) @ custom q; - """ - ) + """) generated_qir = str(complex_qir).splitlines() check_attributes(generated_qir, 2, 0) check_single_qubit_gate_op(generated_qir, 2, [0, 0, 0], "y") @@ -271,14 +270,12 @@ def test_nested_gate_modifiers(): def test_ctrl_modifiers(): - ctrl_modifiers = qasm3_to_qir( - """ + ctrl_modifiers = qasm3_to_qir(""" OPENQASM 3; include "stdgates.inc"; qubit[2] q; ctrl @ x q[0], q[1]; - """ - ) + """) generated_qir = str(ctrl_modifiers).splitlines() check_attributes(generated_qir, 2, 0) check_two_qubit_gate_op(generated_qir, 1, [[0, 1]], "cx") diff --git a/tests/qasm3_qir/converter/test_if.py b/tests/qasm3_qir/converter/test_if.py index b4469e36..4176d7f1 100644 --- a/tests/qasm3_qir/converter/test_if.py +++ b/tests/qasm3_qir/converter/test_if.py @@ -22,6 +22,7 @@ import pyqir +from qbraid_qir._pyqir_compat import pyqir_uses_opaque_pointers from qbraid_qir.qasm3 import qasm3_to_qir from tests.qir_utils import check_attributes, get_entry_point_body @@ -34,6 +35,12 @@ def resources_file(filename: str) -> str: return str(os.path.join(RESOURCES_DIR, f"{filename}")) +def version_specific_ll_file(base: str) -> str: + """Path to typed or opaque .ll fixture based on installed pyqir version.""" + suffix = "opaque" if pyqir_uses_opaque_pointers() else "typed" + return resources_file(f"{base}_{suffix}.ll") + + def compare_reference_ir(generated_bitcode: bytes, file_path: str) -> None: module = pyqir.Module.from_bitcode(pyqir.Context(), generated_bitcode, f"{file_path}") ir = str(module) @@ -74,7 +81,7 @@ def test_simple_if(): generated_qir = str(result).splitlines() check_attributes(generated_qir, 4, 4) - simple_file = resources_file("simple_if.ll") + simple_file = version_specific_ll_file("simple_if") compare_reference_ir(result.bitcode, simple_file) @@ -113,5 +120,5 @@ def test_complex_if(): generated_qir = str(result).splitlines() check_attributes(generated_qir, 4, 8) - complex_if = resources_file("complex_if.ll") + complex_if = version_specific_ll_file("complex_if") compare_reference_ir(result.bitcode, complex_if) diff --git a/tests/qasm3_qir/converter/test_loop.py b/tests/qasm3_qir/converter/test_loop.py index 8c882518..ce8a2467 100644 --- a/tests/qasm3_qir/converter/test_loop.py +++ b/tests/qasm3_qir/converter/test_loop.py @@ -20,6 +20,7 @@ import pytest +from qbraid_qir._pyqir_compat import pyqir_uses_opaque_pointers from qbraid_qir.qasm3 import qasm3_to_qir from tests.qir_utils import ( check_attributes, @@ -47,6 +48,52 @@ EXAMPLE_QIR_OUTPUT = """; ModuleID = 'test' source_filename = "test" +define void @test() #0 { +entry: + call void @__quantum__rt__initialize(ptr null) + call void @__quantum__qis__h__body(ptr null) + call void @__quantum__qis__h__body(ptr inttoptr (i64 1 to ptr)) + call void @__quantum__qis__h__body(ptr inttoptr (i64 2 to ptr)) + call void @__quantum__qis__h__body(ptr inttoptr (i64 3 to ptr)) + call void @__quantum__qis__cnot__body(ptr null, ptr inttoptr (i64 1 to ptr)) + call void @__quantum__qis__cnot__body(ptr inttoptr (i64 1 to ptr), ptr inttoptr (i64 2 to ptr)) + call void @__quantum__qis__cnot__body(ptr inttoptr (i64 2 to ptr), ptr inttoptr (i64 3 to ptr)) + call void @__quantum__qis__mz__body(ptr null, ptr null) + call void @__quantum__qis__mz__body(ptr inttoptr (i64 1 to ptr), ptr inttoptr (i64 1 to ptr)) + call void @__quantum__qis__mz__body(ptr inttoptr (i64 2 to ptr), ptr inttoptr (i64 2 to ptr)) + call void @__quantum__qis__mz__body(ptr inttoptr (i64 3 to ptr), ptr inttoptr (i64 3 to ptr)) + call void @__quantum__rt__result_record_output(ptr null, ptr null) + call void @__quantum__rt__result_record_output(ptr inttoptr (i64 1 to ptr), ptr null) + call void @__quantum__rt__result_record_output(ptr inttoptr (i64 2 to ptr), ptr null) + call void @__quantum__rt__result_record_output(ptr inttoptr (i64 3 to ptr), ptr null) + ret void +} + +declare void @__quantum__rt__initialize(ptr) + +declare void @__quantum__qis__h__body(ptr) + +declare void @__quantum__qis__cnot__body(ptr, ptr) + +declare void @__quantum__qis__mz__body(ptr, ptr writeonly) #1 + +declare void @__quantum__rt__result_record_output(ptr, ptr) + +attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base" "required_num_qubits"="4" "required_num_results"="4" } +attributes #1 = { "irreversible" } + +!llvm.module.flags = !{!0, !1, !2, !3} + +!0 = !{i32 1, !"qir_major_version", i32 1} +!1 = !{i32 7, !"qir_minor_version", i32 0} +!2 = !{i32 1, !"dynamic_qubit_management", i1 false} +!3 = !{i32 1, !"dynamic_result_management", i1 false} +""" + + +EXAMPLE_QIR_OUTPUT_TYPED = """; ModuleID = 'test' +source_filename = "test" + %Qubit = type opaque %Result = type opaque @@ -93,6 +140,10 @@ """ +def _example_qir_expected(): + return EXAMPLE_QIR_OUTPUT if pyqir_uses_opaque_pointers() else EXAMPLE_QIR_OUTPUT_TYPED + + def test_convert_qasm3_for_loop(): """Test converting a QASM3 program that contains a for loop.""" qir_expected = qasm3_to_qir(EXAMPLE_WITHOUT_LOOP, name="test") @@ -113,7 +164,7 @@ def test_convert_qasm3_for_loop(): name="test", ) assert str(qir_expected) == str(qir_from_loop) - assert str(qir_from_loop) == EXAMPLE_QIR_OUTPUT + assert str(qir_from_loop) == _example_qir_expected() def test_convert_qasm3_for_loop_shadow(): @@ -276,7 +327,7 @@ def test_convert_qasm3_for_loop_discrete_set(): name="test", ) assert str(qir_expected) == str(qir_from_loop) - assert str(qir_from_loop) == EXAMPLE_QIR_OUTPUT + assert str(qir_from_loop) == _example_qir_expected() def test_function_executed_in_loop(): diff --git a/tests/qasm3_qir/converter/test_switch.py b/tests/qasm3_qir/converter/test_switch.py index 83278378..d7aa6dd8 100644 --- a/tests/qasm3_qir/converter/test_switch.py +++ b/tests/qasm3_qir/converter/test_switch.py @@ -18,7 +18,6 @@ """ - from qbraid_qir.qasm3 import qasm3_to_qir from tests.qir_utils import ( check_attributes, diff --git a/tests/qasm3_qir/fixtures/gates.py b/tests/qasm3_qir/fixtures/gates.py index 4cfe1016..3183819f 100644 --- a/tests/qasm3_qir/fixtures/gates.py +++ b/tests/qasm3_qir/fixtures/gates.py @@ -16,6 +16,7 @@ Module defining Cirq basic gate fixtures for use in tests. """ + import os import pytest diff --git a/tests/qasm3_qir/fixtures/resources/complex_if.ll b/tests/qasm3_qir/fixtures/resources/complex_if.ll index 7930253e..26e6a544 100644 --- a/tests/qasm3_qir/fixtures/resources/complex_if.ll +++ b/tests/qasm3_qir/fixtures/resources/complex_if.ll @@ -1,42 +1,39 @@ ; ModuleID = 'program-b7eef4a0-4573-11f0-be85-773714bb7840' source_filename = "program-b7eef4a0-4573-11f0-be85-773714bb7840" -%Qubit = type opaque -%Result = type opaque - define void @main() #0 { entry: - call void @__quantum__rt__initialize(i8* null) - call void @__quantum__qis__h__body(%Qubit* null) - call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) - call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*)) - call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 3 to %Qubit*)) - call void @__quantum__qis__mz__body(%Qubit* null, %Result* inttoptr (i64 4 to %Result*)) - call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 5 to %Result*)) - call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 6 to %Result*)) - call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 3 to %Qubit*), %Result* inttoptr (i64 7 to %Result*)) - call void @__quantum__qis__reset__body(%Qubit* null) - call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 1 to %Qubit*)) - call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 2 to %Qubit*)) - call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 3 to %Qubit*)) - %0 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 4 to %Result*)) + call void @__quantum__rt__initialize(ptr null) + call void @__quantum__qis__h__body(ptr null) + call void @__quantum__qis__h__body(ptr inttoptr (i64 1 to ptr)) + call void @__quantum__qis__h__body(ptr inttoptr (i64 2 to ptr)) + call void @__quantum__qis__h__body(ptr inttoptr (i64 3 to ptr)) + call void @__quantum__qis__mz__body(ptr null, ptr inttoptr (i64 4 to ptr)) + call void @__quantum__qis__mz__body(ptr inttoptr (i64 1 to ptr), ptr inttoptr (i64 5 to ptr)) + call void @__quantum__qis__mz__body(ptr inttoptr (i64 2 to ptr), ptr inttoptr (i64 6 to ptr)) + call void @__quantum__qis__mz__body(ptr inttoptr (i64 3 to ptr), ptr inttoptr (i64 7 to ptr)) + call void @__quantum__qis__reset__body(ptr null) + call void @__quantum__qis__reset__body(ptr inttoptr (i64 1 to ptr)) + call void @__quantum__qis__reset__body(ptr inttoptr (i64 2 to ptr)) + call void @__quantum__qis__reset__body(ptr inttoptr (i64 3 to ptr)) + %0 = call i1 @__quantum__qis__read_result__body(ptr inttoptr (i64 4 to ptr)) br i1 %0, label %then, label %else then: ; preds = %entry - call void @__quantum__qis__x__body(%Qubit* null) - call void @__quantum__qis__cnot__body(%Qubit* null, %Qubit* inttoptr (i64 1 to %Qubit*)) - %1 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 5 to %Result*)) + call void @__quantum__qis__x__body(ptr null) + call void @__quantum__qis__cnot__body(ptr null, ptr inttoptr (i64 1 to ptr)) + %1 = call i1 @__quantum__qis__read_result__body(ptr inttoptr (i64 5 to ptr)) br i1 %1, label %then1, label %else2 else: ; preds = %entry br label %continue continue: ; preds = %else, %continue3 - %2 = call i1 @__quantum__qis__read_result__body(%Result* null) + %2 = call i1 @__quantum__qis__read_result__body(ptr null) br i1 %2, label %then4, label %else5 then1: ; preds = %then - call void @__quantum__qis__cnot__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Qubit* inttoptr (i64 2 to %Qubit*)) + call void @__quantum__qis__cnot__body(ptr inttoptr (i64 1 to ptr), ptr inttoptr (i64 2 to ptr)) br label %continue3 else2: ; preds = %then @@ -46,43 +43,43 @@ continue3: ; preds = %else2, %then1 br label %continue then4: ; preds = %continue - call void @__quantum__qis__cnot__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Qubit* inttoptr (i64 3 to %Qubit*)) - call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*)) + call void @__quantum__qis__cnot__body(ptr inttoptr (i64 2 to ptr), ptr inttoptr (i64 3 to ptr)) + call void @__quantum__qis__h__body(ptr inttoptr (i64 2 to ptr)) br label %continue6 else5: ; preds = %continue br label %continue6 continue6: ; preds = %else5, %then4 - call void @__quantum__rt__result_record_output(%Result* null, i8* null) - call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) - call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 2 to %Result*), i8* null) - call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 3 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(ptr null, ptr null) + call void @__quantum__rt__result_record_output(ptr inttoptr (i64 1 to ptr), ptr null) + call void @__quantum__rt__result_record_output(ptr inttoptr (i64 2 to ptr), ptr null) + call void @__quantum__rt__result_record_output(ptr inttoptr (i64 3 to ptr), ptr null) ret void } -declare void @__quantum__rt__initialize(i8*) +declare void @__quantum__rt__initialize(ptr) -declare void @__quantum__qis__h__body(%Qubit*) +declare void @__quantum__qis__h__body(ptr) -declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 +declare void @__quantum__qis__mz__body(ptr, ptr writeonly) #1 -declare void @__quantum__qis__reset__body(%Qubit*) +declare void @__quantum__qis__reset__body(ptr) -declare i1 @__quantum__qis__read_result__body(%Result*) +declare i1 @__quantum__qis__read_result__body(ptr) -declare void @__quantum__qis__x__body(%Qubit*) +declare void @__quantum__qis__x__body(ptr) -declare void @__quantum__qis__cnot__body(%Qubit*, %Qubit*) +declare void @__quantum__qis__cnot__body(ptr, ptr) -declare void @__quantum__rt__result_record_output(%Result*, i8*) +declare void @__quantum__rt__result_record_output(ptr, ptr) attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base" "required_num_qubits"="4" "required_num_results"="8" } attributes #1 = { "irreversible" } !llvm.module.flags = !{!0, !1, !2, !3} -!0 = !{i32 1, !"qir_major_version", i32 1} +!0 = !{i32 1, !"qir_major_version", i32 2} !1 = !{i32 7, !"qir_minor_version", i32 0} !2 = !{i32 1, !"dynamic_qubit_management", i1 false} -!3 = !{i32 1, !"dynamic_result_management", i1 false} \ No newline at end of file +!3 = !{i32 1, !"dynamic_result_management", i1 false} diff --git a/tests/qasm3_qir/fixtures/resources/complex_if_opaque.ll b/tests/qasm3_qir/fixtures/resources/complex_if_opaque.ll new file mode 100644 index 00000000..26e6a544 --- /dev/null +++ b/tests/qasm3_qir/fixtures/resources/complex_if_opaque.ll @@ -0,0 +1,85 @@ +; ModuleID = 'program-b7eef4a0-4573-11f0-be85-773714bb7840' +source_filename = "program-b7eef4a0-4573-11f0-be85-773714bb7840" + +define void @main() #0 { +entry: + call void @__quantum__rt__initialize(ptr null) + call void @__quantum__qis__h__body(ptr null) + call void @__quantum__qis__h__body(ptr inttoptr (i64 1 to ptr)) + call void @__quantum__qis__h__body(ptr inttoptr (i64 2 to ptr)) + call void @__quantum__qis__h__body(ptr inttoptr (i64 3 to ptr)) + call void @__quantum__qis__mz__body(ptr null, ptr inttoptr (i64 4 to ptr)) + call void @__quantum__qis__mz__body(ptr inttoptr (i64 1 to ptr), ptr inttoptr (i64 5 to ptr)) + call void @__quantum__qis__mz__body(ptr inttoptr (i64 2 to ptr), ptr inttoptr (i64 6 to ptr)) + call void @__quantum__qis__mz__body(ptr inttoptr (i64 3 to ptr), ptr inttoptr (i64 7 to ptr)) + call void @__quantum__qis__reset__body(ptr null) + call void @__quantum__qis__reset__body(ptr inttoptr (i64 1 to ptr)) + call void @__quantum__qis__reset__body(ptr inttoptr (i64 2 to ptr)) + call void @__quantum__qis__reset__body(ptr inttoptr (i64 3 to ptr)) + %0 = call i1 @__quantum__qis__read_result__body(ptr inttoptr (i64 4 to ptr)) + br i1 %0, label %then, label %else + +then: ; preds = %entry + call void @__quantum__qis__x__body(ptr null) + call void @__quantum__qis__cnot__body(ptr null, ptr inttoptr (i64 1 to ptr)) + %1 = call i1 @__quantum__qis__read_result__body(ptr inttoptr (i64 5 to ptr)) + br i1 %1, label %then1, label %else2 + +else: ; preds = %entry + br label %continue + +continue: ; preds = %else, %continue3 + %2 = call i1 @__quantum__qis__read_result__body(ptr null) + br i1 %2, label %then4, label %else5 + +then1: ; preds = %then + call void @__quantum__qis__cnot__body(ptr inttoptr (i64 1 to ptr), ptr inttoptr (i64 2 to ptr)) + br label %continue3 + +else2: ; preds = %then + br label %continue3 + +continue3: ; preds = %else2, %then1 + br label %continue + +then4: ; preds = %continue + call void @__quantum__qis__cnot__body(ptr inttoptr (i64 2 to ptr), ptr inttoptr (i64 3 to ptr)) + call void @__quantum__qis__h__body(ptr inttoptr (i64 2 to ptr)) + br label %continue6 + +else5: ; preds = %continue + br label %continue6 + +continue6: ; preds = %else5, %then4 + call void @__quantum__rt__result_record_output(ptr null, ptr null) + call void @__quantum__rt__result_record_output(ptr inttoptr (i64 1 to ptr), ptr null) + call void @__quantum__rt__result_record_output(ptr inttoptr (i64 2 to ptr), ptr null) + call void @__quantum__rt__result_record_output(ptr inttoptr (i64 3 to ptr), ptr null) + ret void +} + +declare void @__quantum__rt__initialize(ptr) + +declare void @__quantum__qis__h__body(ptr) + +declare void @__quantum__qis__mz__body(ptr, ptr writeonly) #1 + +declare void @__quantum__qis__reset__body(ptr) + +declare i1 @__quantum__qis__read_result__body(ptr) + +declare void @__quantum__qis__x__body(ptr) + +declare void @__quantum__qis__cnot__body(ptr, ptr) + +declare void @__quantum__rt__result_record_output(ptr, ptr) + +attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base" "required_num_qubits"="4" "required_num_results"="8" } +attributes #1 = { "irreversible" } + +!llvm.module.flags = !{!0, !1, !2, !3} + +!0 = !{i32 1, !"qir_major_version", i32 2} +!1 = !{i32 7, !"qir_minor_version", i32 0} +!2 = !{i32 1, !"dynamic_qubit_management", i1 false} +!3 = !{i32 1, !"dynamic_result_management", i1 false} diff --git a/tests/qasm3_qir/fixtures/resources/complex_if_typed.ll b/tests/qasm3_qir/fixtures/resources/complex_if_typed.ll new file mode 100644 index 00000000..2c8ac57a --- /dev/null +++ b/tests/qasm3_qir/fixtures/resources/complex_if_typed.ll @@ -0,0 +1,88 @@ +; ModuleID = 'program-b7eef4a0-4573-11f0-be85-773714bb7840' +source_filename = "program-b7eef4a0-4573-11f0-be85-773714bb7840" + +%Qubit = type opaque +%Result = type opaque + +define void @main() #0 { +entry: + call void @__quantum__rt__initialize(i8* null) + call void @__quantum__qis__h__body(%Qubit* null) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 3 to %Qubit*)) + call void @__quantum__qis__mz__body(%Qubit* null, %Result* inttoptr (i64 4 to %Result*)) + call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 5 to %Result*)) + call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 6 to %Result*)) + call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 3 to %Qubit*), %Result* inttoptr (i64 7 to %Result*)) + call void @__quantum__qis__reset__body(%Qubit* null) + call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 2 to %Qubit*)) + call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 3 to %Qubit*)) + %0 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 4 to %Result*)) + br i1 %0, label %then, label %else + +then: ; preds = %entry + call void @__quantum__qis__x__body(%Qubit* null) + call void @__quantum__qis__cnot__body(%Qubit* null, %Qubit* inttoptr (i64 1 to %Qubit*)) + %1 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 5 to %Result*)) + br i1 %1, label %then1, label %else2 + +else: ; preds = %entry + br label %continue + +continue: ; preds = %else, %continue3 + %2 = call i1 @__quantum__qis__read_result__body(%Result* null) + br i1 %2, label %then4, label %else5 + +then1: ; preds = %then + call void @__quantum__qis__cnot__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Qubit* inttoptr (i64 2 to %Qubit*)) + br label %continue3 + +else2: ; preds = %then + br label %continue3 + +continue3: ; preds = %else2, %then1 + br label %continue + +then4: ; preds = %continue + call void @__quantum__qis__cnot__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Qubit* inttoptr (i64 3 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*)) + br label %continue6 + +else5: ; preds = %continue + br label %continue6 + +continue6: ; preds = %else5, %then4 + call void @__quantum__rt__result_record_output(%Result* null, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 2 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 3 to %Result*), i8* null) + ret void +} + +declare void @__quantum__rt__initialize(i8*) + +declare void @__quantum__qis__h__body(%Qubit*) + +declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 + +declare void @__quantum__qis__reset__body(%Qubit*) + +declare i1 @__quantum__qis__read_result__body(%Result*) + +declare void @__quantum__qis__x__body(%Qubit*) + +declare void @__quantum__qis__cnot__body(%Qubit*, %Qubit*) + +declare void @__quantum__rt__result_record_output(%Result*, i8*) + +attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base" "required_num_qubits"="4" "required_num_results"="8" } +attributes #1 = { "irreversible" } + +!llvm.module.flags = !{!0, !1, !2, !3} + +!0 = !{i32 1, !"qir_major_version", i32 1} +!1 = !{i32 7, !"qir_minor_version", i32 0} +!2 = !{i32 1, !"dynamic_qubit_management", i1 false} +!3 = !{i32 1, !"dynamic_result_management", i1 false} diff --git a/tests/qasm3_qir/fixtures/resources/simple_if.ll b/tests/qasm3_qir/fixtures/resources/simple_if.ll index 037359a5..ab90d3d9 100644 --- a/tests/qasm3_qir/fixtures/resources/simple_if.ll +++ b/tests/qasm3_qir/fixtures/resources/simple_if.ll @@ -1,86 +1,83 @@ ; ModuleID = 'program-fe043bae-4572-11f0-be85-773714bb7840' source_filename = "program-fe043bae-4572-11f0-be85-773714bb7840" -%Qubit = type opaque -%Result = type opaque - define void @main() #0 { entry: - call void @__quantum__rt__initialize(i8* null) - call void @__quantum__qis__h__body(%Qubit* null) - call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) - call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*)) - call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 3 to %Qubit*)) - call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) - call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) - call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*)) - call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 3 to %Qubit*), %Result* inttoptr (i64 3 to %Result*)) - call void @__quantum__qis__reset__body(%Qubit* null) - call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 1 to %Qubit*)) - call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 2 to %Qubit*)) - %0 = call i1 @__quantum__qis__read_result__body(%Result* null) + call void @__quantum__rt__initialize(ptr null) + call void @__quantum__qis__h__body(ptr null) + call void @__quantum__qis__h__body(ptr inttoptr (i64 1 to ptr)) + call void @__quantum__qis__h__body(ptr inttoptr (i64 2 to ptr)) + call void @__quantum__qis__h__body(ptr inttoptr (i64 3 to ptr)) + call void @__quantum__qis__mz__body(ptr null, ptr null) + call void @__quantum__qis__mz__body(ptr inttoptr (i64 1 to ptr), ptr inttoptr (i64 1 to ptr)) + call void @__quantum__qis__mz__body(ptr inttoptr (i64 2 to ptr), ptr inttoptr (i64 2 to ptr)) + call void @__quantum__qis__mz__body(ptr inttoptr (i64 3 to ptr), ptr inttoptr (i64 3 to ptr)) + call void @__quantum__qis__reset__body(ptr null) + call void @__quantum__qis__reset__body(ptr inttoptr (i64 1 to ptr)) + call void @__quantum__qis__reset__body(ptr inttoptr (i64 2 to ptr)) + %0 = call i1 @__quantum__qis__read_result__body(ptr null) br i1 %0, label %then, label %else then: ; preds = %entry - call void @__quantum__qis__x__body(%Qubit* null) - call void @__quantum__qis__cnot__body(%Qubit* null, %Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__x__body(ptr null) + call void @__quantum__qis__cnot__body(ptr null, ptr inttoptr (i64 1 to ptr)) br label %continue else: ; preds = %entry br label %continue continue: ; preds = %else, %then - %1 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*)) + %1 = call i1 @__quantum__qis__read_result__body(ptr inttoptr (i64 1 to ptr)) br i1 %1, label %then1, label %else2 then1: ; preds = %continue - call void @__quantum__qis__cnot__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Qubit* inttoptr (i64 2 to %Qubit*)) + call void @__quantum__qis__cnot__body(ptr inttoptr (i64 1 to ptr), ptr inttoptr (i64 2 to ptr)) br label %continue3 else2: ; preds = %continue br label %continue3 continue3: ; preds = %else2, %then1 - %2 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 2 to %Result*)) + %2 = call i1 @__quantum__qis__read_result__body(ptr inttoptr (i64 2 to ptr)) br i1 %2, label %then4, label %else5 then4: ; preds = %continue3 br label %continue6 else5: ; preds = %continue3 - call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*)) + call void @__quantum__qis__h__body(ptr inttoptr (i64 2 to ptr)) br label %continue6 continue6: ; preds = %else5, %then4 - call void @__quantum__rt__result_record_output(%Result* null, i8* null) - call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) - call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 2 to %Result*), i8* null) - call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 3 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(ptr null, ptr null) + call void @__quantum__rt__result_record_output(ptr inttoptr (i64 1 to ptr), ptr null) + call void @__quantum__rt__result_record_output(ptr inttoptr (i64 2 to ptr), ptr null) + call void @__quantum__rt__result_record_output(ptr inttoptr (i64 3 to ptr), ptr null) ret void } -declare void @__quantum__rt__initialize(i8*) +declare void @__quantum__rt__initialize(ptr) -declare void @__quantum__qis__h__body(%Qubit*) +declare void @__quantum__qis__h__body(ptr) -declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 +declare void @__quantum__qis__mz__body(ptr, ptr writeonly) #1 -declare void @__quantum__qis__reset__body(%Qubit*) +declare void @__quantum__qis__reset__body(ptr) -declare i1 @__quantum__qis__read_result__body(%Result*) +declare i1 @__quantum__qis__read_result__body(ptr) -declare void @__quantum__qis__x__body(%Qubit*) +declare void @__quantum__qis__x__body(ptr) -declare void @__quantum__qis__cnot__body(%Qubit*, %Qubit*) +declare void @__quantum__qis__cnot__body(ptr, ptr) -declare void @__quantum__rt__result_record_output(%Result*, i8*) +declare void @__quantum__rt__result_record_output(ptr, ptr) attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base" "required_num_qubits"="4" "required_num_results"="4" } attributes #1 = { "irreversible" } !llvm.module.flags = !{!0, !1, !2, !3} -!0 = !{i32 1, !"qir_major_version", i32 1} +!0 = !{i32 1, !"qir_major_version", i32 2} !1 = !{i32 7, !"qir_minor_version", i32 0} !2 = !{i32 1, !"dynamic_qubit_management", i1 false} -!3 = !{i32 1, !"dynamic_result_management", i1 false} \ No newline at end of file +!3 = !{i32 1, !"dynamic_result_management", i1 false} diff --git a/tests/qasm3_qir/fixtures/resources/simple_if_opaque.ll b/tests/qasm3_qir/fixtures/resources/simple_if_opaque.ll new file mode 100644 index 00000000..ab90d3d9 --- /dev/null +++ b/tests/qasm3_qir/fixtures/resources/simple_if_opaque.ll @@ -0,0 +1,83 @@ +; ModuleID = 'program-fe043bae-4572-11f0-be85-773714bb7840' +source_filename = "program-fe043bae-4572-11f0-be85-773714bb7840" + +define void @main() #0 { +entry: + call void @__quantum__rt__initialize(ptr null) + call void @__quantum__qis__h__body(ptr null) + call void @__quantum__qis__h__body(ptr inttoptr (i64 1 to ptr)) + call void @__quantum__qis__h__body(ptr inttoptr (i64 2 to ptr)) + call void @__quantum__qis__h__body(ptr inttoptr (i64 3 to ptr)) + call void @__quantum__qis__mz__body(ptr null, ptr null) + call void @__quantum__qis__mz__body(ptr inttoptr (i64 1 to ptr), ptr inttoptr (i64 1 to ptr)) + call void @__quantum__qis__mz__body(ptr inttoptr (i64 2 to ptr), ptr inttoptr (i64 2 to ptr)) + call void @__quantum__qis__mz__body(ptr inttoptr (i64 3 to ptr), ptr inttoptr (i64 3 to ptr)) + call void @__quantum__qis__reset__body(ptr null) + call void @__quantum__qis__reset__body(ptr inttoptr (i64 1 to ptr)) + call void @__quantum__qis__reset__body(ptr inttoptr (i64 2 to ptr)) + %0 = call i1 @__quantum__qis__read_result__body(ptr null) + br i1 %0, label %then, label %else + +then: ; preds = %entry + call void @__quantum__qis__x__body(ptr null) + call void @__quantum__qis__cnot__body(ptr null, ptr inttoptr (i64 1 to ptr)) + br label %continue + +else: ; preds = %entry + br label %continue + +continue: ; preds = %else, %then + %1 = call i1 @__quantum__qis__read_result__body(ptr inttoptr (i64 1 to ptr)) + br i1 %1, label %then1, label %else2 + +then1: ; preds = %continue + call void @__quantum__qis__cnot__body(ptr inttoptr (i64 1 to ptr), ptr inttoptr (i64 2 to ptr)) + br label %continue3 + +else2: ; preds = %continue + br label %continue3 + +continue3: ; preds = %else2, %then1 + %2 = call i1 @__quantum__qis__read_result__body(ptr inttoptr (i64 2 to ptr)) + br i1 %2, label %then4, label %else5 + +then4: ; preds = %continue3 + br label %continue6 + +else5: ; preds = %continue3 + call void @__quantum__qis__h__body(ptr inttoptr (i64 2 to ptr)) + br label %continue6 + +continue6: ; preds = %else5, %then4 + call void @__quantum__rt__result_record_output(ptr null, ptr null) + call void @__quantum__rt__result_record_output(ptr inttoptr (i64 1 to ptr), ptr null) + call void @__quantum__rt__result_record_output(ptr inttoptr (i64 2 to ptr), ptr null) + call void @__quantum__rt__result_record_output(ptr inttoptr (i64 3 to ptr), ptr null) + ret void +} + +declare void @__quantum__rt__initialize(ptr) + +declare void @__quantum__qis__h__body(ptr) + +declare void @__quantum__qis__mz__body(ptr, ptr writeonly) #1 + +declare void @__quantum__qis__reset__body(ptr) + +declare i1 @__quantum__qis__read_result__body(ptr) + +declare void @__quantum__qis__x__body(ptr) + +declare void @__quantum__qis__cnot__body(ptr, ptr) + +declare void @__quantum__rt__result_record_output(ptr, ptr) + +attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base" "required_num_qubits"="4" "required_num_results"="4" } +attributes #1 = { "irreversible" } + +!llvm.module.flags = !{!0, !1, !2, !3} + +!0 = !{i32 1, !"qir_major_version", i32 2} +!1 = !{i32 7, !"qir_minor_version", i32 0} +!2 = !{i32 1, !"dynamic_qubit_management", i1 false} +!3 = !{i32 1, !"dynamic_result_management", i1 false} diff --git a/tests/qasm3_qir/fixtures/resources/simple_if_typed.ll b/tests/qasm3_qir/fixtures/resources/simple_if_typed.ll new file mode 100644 index 00000000..2813135a --- /dev/null +++ b/tests/qasm3_qir/fixtures/resources/simple_if_typed.ll @@ -0,0 +1,86 @@ +; ModuleID = 'program-fe043bae-4572-11f0-be85-773714bb7840' +source_filename = "program-fe043bae-4572-11f0-be85-773714bb7840" + +%Qubit = type opaque +%Result = type opaque + +define void @main() #0 { +entry: + call void @__quantum__rt__initialize(i8* null) + call void @__quantum__qis__h__body(%Qubit* null) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 3 to %Qubit*)) + call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) + call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) + call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*)) + call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 3 to %Qubit*), %Result* inttoptr (i64 3 to %Result*)) + call void @__quantum__qis__reset__body(%Qubit* null) + call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 2 to %Qubit*)) + %0 = call i1 @__quantum__qis__read_result__body(%Result* null) + br i1 %0, label %then, label %else + +then: ; preds = %entry + call void @__quantum__qis__x__body(%Qubit* null) + call void @__quantum__qis__cnot__body(%Qubit* null, %Qubit* inttoptr (i64 1 to %Qubit*)) + br label %continue + +else: ; preds = %entry + br label %continue + +continue: ; preds = %else, %then + %1 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*)) + br i1 %1, label %then1, label %else2 + +then1: ; preds = %continue + call void @__quantum__qis__cnot__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Qubit* inttoptr (i64 2 to %Qubit*)) + br label %continue3 + +else2: ; preds = %continue + br label %continue3 + +continue3: ; preds = %else2, %then1 + %2 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 2 to %Result*)) + br i1 %2, label %then4, label %else5 + +then4: ; preds = %continue3 + br label %continue6 + +else5: ; preds = %continue3 + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*)) + br label %continue6 + +continue6: ; preds = %else5, %then4 + call void @__quantum__rt__result_record_output(%Result* null, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 2 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 3 to %Result*), i8* null) + ret void +} + +declare void @__quantum__rt__initialize(i8*) + +declare void @__quantum__qis__h__body(%Qubit*) + +declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 + +declare void @__quantum__qis__reset__body(%Qubit*) + +declare i1 @__quantum__qis__read_result__body(%Result*) + +declare void @__quantum__qis__x__body(%Qubit*) + +declare void @__quantum__qis__cnot__body(%Qubit*, %Qubit*) + +declare void @__quantum__rt__result_record_output(%Result*, i8*) + +attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base" "required_num_qubits"="4" "required_num_results"="4" } +attributes #1 = { "irreversible" } + +!llvm.module.flags = !{!0, !1, !2, !3} + +!0 = !{i32 1, !"qir_major_version", i32 1} +!1 = !{i32 7, !"qir_minor_version", i32 0} +!2 = !{i32 1, !"dynamic_qubit_management", i1 false} +!3 = !{i32 1, !"dynamic_result_management", i1 false} diff --git a/tests/qir_utils.py b/tests/qir_utils.py index dabf7b19..6802eb67 100644 --- a/tests/qir_utils.py +++ b/tests/qir_utils.py @@ -30,6 +30,7 @@ required_num_results, ) +from qbraid_qir._pyqir_compat import pyqir_uses_opaque_pointers from qbraid_qir.qasm3.maps import CONSTANTS_MAP @@ -53,6 +54,10 @@ def assert_equal_qir(given_qir: str, filepath: str) -> None: def _qubit_string(qubit: int) -> str: + if pyqir_uses_opaque_pointers(): + if qubit == 0: + return "ptr null" + return f"ptr inttoptr (i64 {qubit} to ptr)" if qubit == 0: return "%Qubit* null" return f"%Qubit* inttoptr (i64 {qubit} to %Qubit*)" @@ -63,12 +68,18 @@ def _barrier_string() -> str: def _result_string(res: int) -> str: + if pyqir_uses_opaque_pointers(): + if res == 0: + return "ptr null" + return f"ptr inttoptr (i64 {res} to ptr)" if res == 0: return "%Result* null" return f"%Result* inttoptr (i64 {res} to %Result*)" def initialize_call_string() -> str: + if pyqir_uses_opaque_pointers(): + return "call void @__quantum__rt__initialize(ptr null)" return "call void @__quantum__rt__initialize(i8* null)" @@ -97,11 +108,13 @@ def measure_call_string(name: str, res: str, qb: int) -> str: def array_record_output_string(num_elements: int) -> str: - return f"call void @__quantum__rt__array_record_output(i64 {num_elements}, i8* null)" + null_arg = "ptr null" if pyqir_uses_opaque_pointers() else "i8* null" + return f"call void @__quantum__rt__array_record_output(i64 {num_elements}, {null_arg})" def result_record_output_string(res: str) -> str: - return f"call void @__quantum__rt__result_record_output({_result_string(res)}, i8* null)" + null_arg = "ptr null" if pyqir_uses_opaque_pointers() else "i8* null" + return f"call void @__quantum__rt__result_record_output({_result_string(res)}, {null_arg})" def reset_call_string(qb: int) -> str: diff --git a/tests/squin_qir/test_cudaq_to_squin.py b/tests/squin_qir/test_cudaq_to_squin.py index 3a4fd8a6..28f9a042 100644 --- a/tests/squin_qir/test_cudaq_to_squin.py +++ b/tests/squin_qir/test_cudaq_to_squin.py @@ -14,11 +14,14 @@ """Unit tests for CUDAQ to Squin conversion functions.""" -import cudaq +import pytest -from qbraid_qir.squin import load +cudaq = pytest.importorskip("cudaq") -from .test_qir_to_squin import _compare_output +# Imports after importorskip so optional dependency is not required at import time. +from qbraid_qir.squin import load # pylint: disable=wrong-import-position + +from .test_qir_to_squin import _compare_output # pylint: disable=wrong-import-position def test_bell_state(): diff --git a/tests/squin_qir/test_qir_to_squin.py b/tests/squin_qir/test_qir_to_squin.py index 7b4bf03b..73374ce6 100644 --- a/tests/squin_qir/test_qir_to_squin.py +++ b/tests/squin_qir/test_qir_to_squin.py @@ -13,6 +13,7 @@ # limitations under the License. """Unit tests for QIR to Squin conversion functions.""" + import os import re diff --git a/tox.ini b/tox.ini index e62cabfa..fbd683b2 100644 --- a/tox.ini +++ b/tox.ini @@ -2,6 +2,8 @@ minversion = 4.2.0 envlist = unit-tests + unit-tests-pyqir11 + unit-tests-pyqir12 docs linters format-check @@ -12,11 +14,38 @@ commands_pre = python -m pip install . basepython = python3 [testenv:unit-tests] -description = Run pytests and generate coverage report. +description = Run pytests and generate coverage report (default pyqir). +deps = -r {toxinidir}/requirements-test.txt extras = cirq qasm3 + squin +commands = + pytest tests --cov=qbraid_qir --cov-config=pyproject.toml --cov-report=term --cov-report=xml {posargs} + +[testenv:unit-tests-pyqir11] +description = Run unit tests with pyqir 0.11.x (typed pointers). +deps = -r {toxinidir}/requirements-test.txt +extras = + cirq + qasm3 + squin + test +commands_pre = + python -m pip install "pyqir>=0.10.0,<0.12" +commands = + pytest tests --cov=qbraid_qir --cov-config=pyproject.toml --cov-report=term --cov-report=xml {posargs} + +[testenv:unit-tests-pyqir12] +description = Run unit tests with pyqir 0.12+ (opaque pointers). +deps = -r {toxinidir}/requirements-test.txt +extras = + cirq + qasm3 + squin test +commands_pre = + python -m pip install "pyqir>=0.12.0,<0.13.0" commands = pytest tests --cov=qbraid_qir --cov-config=pyproject.toml --cov-report=term --cov-report=xml {posargs} @@ -57,7 +86,7 @@ commands = [testenv:headers] envdir = .tox/linters skip_install = true -deps = qbraid-cli>=0.10.1 +deps = qbraid-cli>=0.12.0 commands = qbraid admin headers tests bin qbraid_qir docker --type=apache {posargs}