diff --git a/.github/workflows/test-publish.yml b/.github/workflows/test-publish.yml
deleted file mode 100644
index b6607d2..0000000
--- a/.github/workflows/test-publish.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-name: Publish to TestPyPI
-
-on:
- workflow_dispatch:
-
-jobs:
- pypi-publish:
- name: Build dist & upload to TestPyPI
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- with:
- fetch-depth: 1
-
- - name: Set up Python
- uses: actions/setup-python@v4
- with:
- python-version: '3.10'
-
- - name: Build binary wheel + source tarball
- run: |
- python3 -m pip install --upgrade pip build
- python3 -m build
-
- - name: Publish package to TestPyPI
- uses: pypa/gh-action-pypi-publish@release/v1
- with:
- user: __token__
- password: ${{ secrets.TEST_PYPI_API_TOKEN }}
- repository-url: https://test.pypi.org/legacy/
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 2adbb18..48dd425 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
# Docs
docs/stubs
+.vscode/
# Byte-compiled / optimized / DLL files
__pycache__/
diff --git a/README.md b/README.md
index 5f30d0c..d3dcbf1 100644
--- a/README.md
+++ b/README.md
@@ -1,33 +1,62 @@
-# qbraid-qir
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
*Work in progress*
-qBraid-SDK extension providing support for QIR conversions
+qBraid-SDK extension providing support for QIR conversions.
+
+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/en/latest/sdk/overview.html#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).
-## Planned features
+## Getting started
-This project aims to make [QIR](https://www.qir-alliance.org/) representations accessible via the qBraid-SDK hub and spokes [model](#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/en/latest/sdk/overview.html#supported-frontends) by `qbraid`.
+### Installation
+
+```bash
+pip install qbraid-qir
+```
-- [ ] Cirq $\rightarrow$ QIR
- - [ ] Quantum operations
- - [ ] Classical operations
-- [ ] OpenQASM 3 $\rightarrow$ QIR
+### Example
+
+```python
+import cirq
+from qbraid_qir import cirq_to_qir
+
+q0, q1 = cirq.LineQubit.range(2)
+
+circuit = cirq.Circuit(
+ cirq.H(q0),
+ cirq.CNOT(q0, q1),
+ cirq.measure(q0, q1)
+)
+
+module = cirq_to_qir(circuit, name="my-circuit")
+
+ir = str(module)
+```
-See: https://www.qir-alliance.org/qir-book/concepts/why-do-we-need.html
+## Development
-## Local install
+### Install from source
```bash
git clone https://github.com/qBraid/qbraid-qir.git
@@ -35,7 +64,7 @@ cd qbraid-qir
pip install -e .
```
-## Run tests
+### Run tests
```bash
pip install -r requirements-dev.txt
@@ -48,7 +77,7 @@ with coverage report
pytest --cov=qbraid_qir --cov-report=term tests/
```
-## Build docs
+### Build docs
```bash
cd docs
diff --git a/docs/api/qbraid_qir.cirq.rst b/docs/api/qbraid_qir.cirq.rst
index 9e16800..ce984f1 100644
--- a/docs/api/qbraid_qir.cirq.rst
+++ b/docs/api/qbraid_qir.cirq.rst
@@ -1,7 +1,7 @@
:orphan:
qbraid_qir.cirq
-=================
+================
.. automodule:: qbraid_qir.cirq
:members:
diff --git a/docs/index.rst b/docs/index.rst
index ce1a2f1..a0335c7 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -84,5 +84,4 @@ Indices and Tables
------------------
* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
+* :ref:`modindex`
\ No newline at end of file
diff --git a/docs/userguide/cirq_qir.rst b/docs/userguide/cirq_qir.rst
index 98e7c0b..53511d5 100644
--- a/docs/userguide/cirq_qir.rst
+++ b/docs/userguide/cirq_qir.rst
@@ -20,11 +20,10 @@ Convert a ``Cirq`` circuit to ``QIR`` code:
circuit = cirq.Circuit(
cirq.H(q0),
cirq.CNOT(q0, q1),
- cirq.measure(q0, key='result0'),
- cirq.measure(q1, key='result1')
+ cirq.measure(q0, q1)
)
- qir_code = cirq_to_qir(circuit)
+ qir_code = cirq_to_qir(circuit, name="Bell")
print(qir_code)
diff --git a/qbraid_qir/__init__.py b/qbraid_qir/__init__.py
index ce26d60..abcd10a 100644
--- a/qbraid_qir/__init__.py
+++ b/qbraid_qir/__init__.py
@@ -13,14 +13,6 @@
.. currentmodule:: qbraid_qir
-Functions
------------
-
-.. autosummary::
- :toctree: ../stubs/
-
- cirq_to_qir
-
Exceptions
-----------
diff --git a/qbraid_qir/cirq/__init__.py b/qbraid_qir/cirq/__init__.py
index ab209cc..0d53582 100644
--- a/qbraid_qir/cirq/__init__.py
+++ b/qbraid_qir/cirq/__init__.py
@@ -21,5 +21,17 @@
cirq_to_qir
+
+Classes
+---------
+
+.. autosummary::
+ :toctree: ../stubs/
+
+ CirqModule
+ BasicQisVisitor
+
"""
from .convert import cirq_to_qir
+from .elements import CirqModule
+from .visitor import BasicQisVisitor
diff --git a/qbraid_qir/cirq/convert.py b/qbraid_qir/cirq/convert.py
index 952084c..a0782cb 100644
--- a/qbraid_qir/cirq/convert.py
+++ b/qbraid_qir/cirq/convert.py
@@ -15,32 +15,14 @@
from typing import Optional
import cirq
-import qbraid.programs.cirq
from pyqir import Context, Module, qir_module
from qbraid_qir.cirq.elements import CirqModule, generate_module_id
+from qbraid_qir.cirq.passes import preprocess_circuit
from qbraid_qir.cirq.visitor import BasicQisVisitor
from qbraid_qir.exceptions import QirConversionError
-def _preprocess_circuit(circuit: cirq.Circuit) -> cirq.Circuit:
- """
- Preprocesses a Cirq circuit to ensure that it is compatible with the QIR conversion.
-
- Args:
- circuit (cirq.Circuit): The Cirq circuit to preprocess.
-
- Returns:
- cirq.Circuit: The preprocessed Cirq circuit.
-
- """
- # circuit = cirq.contrib.qasm_import.circuit_from_qasm(circuit.to_qasm()) # decompose?
- qprogram = qbraid.programs.cirq.CirqCircuit(circuit)
- qprogram._convert_to_line_qubits()
- cirq_circuit = qprogram.program
- return cirq_circuit
-
-
def cirq_to_qir(circuit: cirq.Circuit, name: Optional[str] = None, **kwargs) -> Module:
"""
Converts a Cirq circuit to a PyQIR module.
@@ -49,21 +31,32 @@ def cirq_to_qir(circuit: cirq.Circuit, name: Optional[str] = None, **kwargs) ->
circuit (cirq.Circuit): The Cirq circuit to convert.
name (str, optional): Identifier for created QIR module. Auto-generated if not provided.
+ Keyword Args:
+ initialize_runtime (bool): Whether to perform quantum runtime environment initialization,
+ default `True`.
+ record_output (bool): Whether to record output calls for registers, default `True`
+
Returns:
The QIR ``pyqir.Module`` representation of the input Cirq circuit.
Raises:
TypeError: If the input is not a valid Cirq circuit.
+ ValueError: If the input circuit is empty.
QirConversionError: If the conversion fails.
"""
if not isinstance(circuit, cirq.Circuit):
raise TypeError("Input quantum program must be of type cirq.Circuit.")
+ if len(circuit) == 0:
+ raise ValueError(
+ "Input quantum circuit must consist of at least one operation."
+ )
+
if name is None:
name = generate_module_id(circuit)
try:
- circuit = _preprocess_circuit(circuit)
+ circuit = preprocess_circuit(circuit)
except Exception as e: # pylint: disable=broad-exception-caught
raise QirConversionError("Failed to preprocess circuit.") from e
diff --git a/qbraid_qir/cirq/elements.py b/qbraid_qir/cirq/elements.py
index 4f18926..4a30b4e 100644
--- a/qbraid_qir/cirq/elements.py
+++ b/qbraid_qir/cirq/elements.py
@@ -71,6 +71,25 @@ def accept(self, visitor):
class CirqModule:
+ """
+ A module representing a quantum circuit in Cirq using QIR.
+
+ This class encapsulates a quantum circuit from Cirq and translates it into QIR format,
+ maintaining information about quantum operations, qubits, and classical bits. It provides
+ methods to interact with the underlying QIR module and circuit elements.
+
+ Args:
+ name (str): Name of the module.
+ module (Module): QIR Module instance.
+ num_qubits (int): Number of qubits in the circuit.
+ elements (List[_CircuitElement]): List of circuit elements.
+
+ Example:
+ >>> circuit = cirq.Circuit()
+ >>> cirq_module = CirqModule.from_circuit(circuit)
+ >>> print(cirq_module.num_qubits)
+ """
+
def __init__(
self,
name: str,
@@ -86,25 +105,30 @@ def __init__(
@property
def name(self) -> str:
+ """Returns the name of the module."""
return self._name
@property
def module(self) -> Module:
+ """Returns the QIR Module instance."""
return self._module
@property
def num_qubits(self) -> int:
+ """Returns the number of qubits in the circuit."""
return self._num_qubits
@property
def num_clbits(self) -> int:
+ """Returns the number of classical bits in the circuit."""
return self._num_clbits
@classmethod
def from_circuit(
cls, circuit: cirq.Circuit, module: Optional[Module] = None
) -> "CirqModule":
- """Create a new CirqModule from a cirq.Circuit object."""
+ """Class method. Constructs a CirqModule from a given cirq.Circuit object
+ and an optional QIR Module."""
elements: List[_CircuitElement] = []
# Register(s). Tentatively using cirq.Qid as input. Better approaches might exist tbd.
@@ -117,7 +141,7 @@ def from_circuit(
if module is None:
module = Module(Context(), generate_module_id(circuit))
return cls(
- name=module.source_filename,
+ name="main",
module=module,
num_qubits=len(circuit.all_qubits()),
elements=elements,
diff --git a/qbraid_qir/cirq/opsets.py b/qbraid_qir/cirq/opsets.py
index 2b2b345..f1b4282 100644
--- a/qbraid_qir/cirq/opsets.py
+++ b/qbraid_qir/cirq/opsets.py
@@ -41,6 +41,7 @@
# Two-Qubit Gates
"SWAP": pyqir._native.swap,
"CNOT": pyqir._native.cx,
+ "CZ": pyqir._native.cz,
# Three-Qubit Gates
"TOFFOLI": pyqir._native.ccx,
# Classical Gates/Operations
@@ -69,7 +70,7 @@ def map_cirq_op_to_pyqir_callable(op: cirq.Operation) -> Tuple[Callable, str]:
if isinstance(gate, cirq.ops.MeasurementGate):
op_name = "MEASURE"
elif isinstance(gate, (cirq.ops.Rx, cirq.ops.Ry, cirq.ops.Rz)):
- op_name = re.search(r"([A-Za-z]+)\(", str(gate)).group(1)
+ op_name = re.search(r"([Rx-z]+)\(", str(gate)).group(1)
else:
op_name = str(gate)
else:
diff --git a/qbraid_qir/cirq/passes.py b/qbraid_qir/cirq/passes.py
new file mode 100644
index 0000000..52e3512
--- /dev/null
+++ b/qbraid_qir/cirq/passes.py
@@ -0,0 +1,75 @@
+# Copyright (C) 2023 qBraid
+#
+# This file is part of the qBraid-SDK
+#
+# The qBraid-SDK is free software released under the GNU General Public License v3
+# or later. You can redistribute and/or modify it under the terms of the GPL v3.
+# See the LICENSE file in the project root or .
+#
+# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3.
+
+"""
+Module for processing Cirq circuits before conversion to QIR.
+
+"""
+
+from typing import List
+
+import cirq
+import qbraid.programs.cirq
+
+from qbraid_qir.cirq.opsets import map_cirq_op_to_pyqir_callable
+from qbraid_qir.exceptions import QirConversionError
+
+
+def _decompose_gate_op(op: cirq.GateOperation) -> List[cirq.OP_TREE]:
+ try:
+ # Try converting to PyQIR. If successful, keep the operation.
+ _ = map_cirq_op_to_pyqir_callable(op)
+ return [op]
+ except QirConversionError:
+ pass
+
+ return cirq.decompose_once(op, flatten=True, default=[op])
+
+
+def _decompose_unsupported_gates(circuit: cirq.Circuit) -> cirq.Circuit:
+ """
+ Decompose gates in a circuit that are not in the supported set.
+
+ Args:
+ circuit (cirq.Circuit): The quantum circuit to process.
+
+ Returns:
+ cirq.Circuit: A new circuit with unsupported gates decomposed.
+ """
+ new_circuit = cirq.Circuit()
+ for moment in circuit:
+ new_ops = []
+ for op in moment:
+ if isinstance(op, cirq.GateOperation):
+ decomposed_ops = _decompose_gate_op(op)
+ new_ops.extend(decomposed_ops)
+ else:
+ new_ops.append(op)
+
+ new_circuit.append(new_ops)
+ return new_circuit
+
+
+def preprocess_circuit(circuit: cirq.Circuit) -> cirq.Circuit:
+ """
+ Preprocesses a Cirq circuit to ensure that it is compatible with the QIR conversion.
+
+ Args:
+ circuit (cirq.Circuit): The Cirq circuit to preprocess.
+
+ Returns:
+ cirq.Circuit: The preprocessed Cirq circuit.
+
+ """
+ circuit = _decompose_unsupported_gates(circuit)
+ qprogram = qbraid.programs.cirq.CirqCircuit(circuit)
+ qprogram._convert_to_line_qubits()
+ cirq_circuit = qprogram.program
+ return cirq_circuit
diff --git a/qbraid_qir/cirq/visitor.py b/qbraid_qir/cirq/visitor.py
index 1a7a88e..20c859e 100644
--- a/qbraid_qir/cirq/visitor.py
+++ b/qbraid_qir/cirq/visitor.py
@@ -40,16 +40,25 @@ def visit_operation(self, operation):
class BasicQisVisitor(CircuitElementVisitor):
- def __init__(self, profile: str = "AdaptiveExecution", **kwargs):
+ """A visitor for QIS (Quantum Instruction Set) basic elements.
+
+ This class is designed to traverse and interact with elements in a quantum circuit.
+
+ Args:
+ initialize_runtime (bool): If True, quantum runtime will be initialized. Defaults to True.
+ record_output (bool): If True, output of the circuit will be recorded. Defaults to True.
+ """
+
+ def __init__(self, initialize_runtime: bool = True, record_output: bool = True):
self._module = None
self._builder = None
self._entry_point = None
self._qubit_labels = {}
- self._profile = profile
self._measured_qubits = {}
- self._record_output = kwargs.get("record_output", True)
+ self._initialize_runtime = initialize_runtime
+ self._record_output = record_output
- def visit_cirq_module(self, module: CirqModule):
+ def visit_cirq_module(self, module: CirqModule) -> None:
_log.debug("Visiting Cirq module '%s' (%d)", module.name, module.num_qubits)
self._module = module.module
context = self._module.context
@@ -61,9 +70,10 @@ def visit_cirq_module(self, module: CirqModule):
self._builder = Builder(context)
self._builder.insert_at_end(BasicBlock(context, "entry", entry))
- i8p = PointerType(IntType(context, 8))
- nullptr = Constant.null(i8p)
- pyqir.rt.initialize(self._builder, nullptr)
+ if self._initialize_runtime is True:
+ i8p = PointerType(IntType(context, 8))
+ nullptr = Constant.null(i8p)
+ pyqir.rt.initialize(self._builder, nullptr)
@property
def entry_point(self) -> str:
@@ -96,7 +106,7 @@ def visit_register(self, qids: List[cirq.Qid]) -> None:
)
_log.debug("Added labels for qubits %s", str(qids))
- def visit_operation(self, operation: cirq.Operation):
+ def visit_operation(self, operation: cirq.Operation) -> None:
qlabels = [self._qubit_labels.get(bit) for bit in operation.qubits]
qubits = [pyqir.qubit(self._module.context, n) for n in qlabels]
results = [pyqir.result(self._module.context, n) for n in qlabels]
@@ -104,9 +114,10 @@ def visit_operation(self, operation: cirq.Operation):
pyqir_func, op_str = map_cirq_op_to_pyqir_callable(operation)
if op_str == "MEASURE":
- # TODO: naive implementation, revisit and test
_log.debug("Visiting measurement operation '%s'", str(operation))
- pyqir_func(self._builder, *qubits, *results)
+ for qubit, result in zip(qubits, results):
+ self._measured_qubits[pyqir.qubit_id(qubit)] = True
+ pyqir_func(self._builder, qubit, result)
elif op_str in ["Rx", "Ry", "Rz"]:
angle = operation.gate._rads * np.pi
pyqir_func(self._builder, angle, *qubits)
diff --git a/requirements.txt b/requirements.txt
index 26d0f2b..0953bf6 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,3 @@
pyqir~=0.10.0
-qbraid==0.5.0.dev20231213012035
-cirq-core
\ No newline at end of file
+qbraid~=0.5.0.dev20240101201141
+cirq-core~=1.3.0
\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
index facff9f..d43ae30 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -6,10 +6,10 @@ author_email = contact@qbraid.com
description = qBraid-SDK extension providing support for QIR conversions
long_description = file: README.md
long_description_content_type = text/markdown
-keywords = qbraid, quantum
+keywords = qbraid, quantum, qir
url = https://www.qbraid.com/
project_urls =
- Documentation = https://docs.qbraid.com/en/latest/
+ Documentation = https://docs.qbraid.com/projects/qir/en/latest/
Bug Tracker = https://github.com/qBraid/qbraid-qir/issues
Source Code = https://github.com/qBraid/qbraid-qir
Discord = https://discord.gg/TPBU2sa8Et
@@ -19,6 +19,7 @@ classifiers =
Intended Audience :: Science/Research
Natural Language :: English
Programming Language :: Python
+ Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Topic :: Scientific/Engineering
@@ -26,4 +27,4 @@ classifiers =
[options]
packages = find:
-python_requires = >=3.10
+python_requires = >=3.9
diff --git a/test-containers/README.md b/test-containers/README.md
new file mode 100644
index 0000000..c60b57b
--- /dev/null
+++ b/test-containers/README.md
@@ -0,0 +1,76 @@
+# Test containers
+
+Docker containers used for testing.
+
+## QIR Runner
+
+[Docker image](./qir_runner/Dockerfile) providing an environment for testing and executing QIR programs
+with the [qir-runner](https://github.com/qir-alliance/qir-runner/tree/main) package.
+
+### Build & run image
+
+Build the QIR runner image:
+
+```bash
+docker build -t qbraid-test/qir-runner:latest qir_runner
+```
+
+Start the container running a Jupyter Server with the JupyterLab frontend and expose the container's internal port `8888` to port `8888` of the host machine:
+
+```bash
+docker run -p 8888:8888 qbraid-test/qir-runner:latest
+```
+
+Visiting `http://:8888/?token=` in a browser will launch JupyterLab, where:
+
+- The hostname is the name of the computer running Docker (e.g. `localhost`)
+- The token is the secret token printed in the console.
+
+Alternatively, you can open a shell inside the running container directly:
+
+```bash
+docker exec -it /bin/bash
+```
+
+### Testing
+
+Once inside the container, the `qir-runner` executable is accessible via command-line:
+
+```bash
+Usage: qir-runner [OPTIONS] --file
+
+Options:
+ -f, --file (Required) Path to the QIR file to run
+ -e, --entrypoint Name of the entry point function to execute
+ -s, --shots The number of times to repeat the execution of the chosen entry point in the program [default: 1]
+ -r, --rngseed The value to use when seeding the random number generator used for quantum simulation
+ -h, --help Print help
+```
+
+Convert a cirq program and save the output to a file
+
+```python
+import cirq
+from qbraid_qir import cirq_to_qir
+
+# create a test circuit
+q0, q1 = cirq.LineQubit.range(2)
+circuit = cirq.Circuit(cirq.H(q0), cirq.CNOT(q0, q1), cirq.measure(q0, q1))
+
+# convert to QIR
+module = cirq_to_qir(circuit)
+
+# save to file
+file_path = os.path.join(os.path.dirname(__file__), "bell.ll")
+
+with open(file_path, "w") as file:
+ file.write(str(module))
+
+print("Saved to", file_path)
+```
+
+And then execute the QIR program:
+
+```bash
+qir-runner -f bell.ll
+```
diff --git a/test-containers/qir_runner/Dockerfile b/test-containers/qir_runner/Dockerfile
new file mode 100644
index 0000000..8272f1b
--- /dev/null
+++ b/test-containers/qir_runner/Dockerfile
@@ -0,0 +1,82 @@
+# Copyright (C) 2024 qBraid Development Team.
+# Distributed under terms of the GNU General Public License v3.
+FROM jupyter/minimal-notebook:latest
+
+USER root
+
+RUN apt-get update --yes && apt-get install --yes --no-install-recommends \
+ # Basic utilities
+ vim \
+ git \
+ curl \
+ pkg-config \
+ lsb-release \
+ wget \
+ software-properties-common \
+ gnupg \
+ # SSL related dependencies
+ openssl \
+ libssl-dev \
+ # Build dependencies
+ g++ \
+ ninja-build \
+ cmake \
+ gfortran \
+ build-essential \
+ # Mathematical libraries
+ libblas-dev \
+ libopenblas-dev \
+ liblapack-dev \
+ # Compression library
+ libz-dev \
+ # LLVM dependencies
+ clang-format \
+ clang-tidy \
+ clang-tools \
+ clang \
+ clangd \
+ libc++-dev \
+ libc++1 \
+ libc++abi-dev \
+ libc++abi1 \
+ libclang-dev \
+ libclang1 \
+ liblldb-dev \
+ libllvm-ocaml-dev \
+ libomp-dev \
+ libomp5 \
+ lld \
+ lldb \
+ llvm-dev \
+ llvm-runtime \
+ llvm \
+ python3-clang
+
+USER $NB_UID
+
+# Install Rustup: https://www.rust-lang.org/tools/install
+RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
+
+# Set PATH to include Cargo's bin directory
+ENV PATH="$HOME/.cargo/bin:${PATH}"
+
+USER root
+
+# Install LLVM
+RUN bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)"
+
+# Clone and build qir-runner
+RUN git clone https://github.com/qir-alliance/qir-runner.git /opt/qir-runner && \
+ chown -R $NB_UID:$NB_GID /opt/qir-runner
+
+USER $NB_UID
+
+WORKDIR /opt/qir-runner
+
+# Install llvmenv and build qir-runner
+RUN cargo install llvmenv && \
+ cargo build --release
+
+ENV PATH="/opt/qir-runner/target/release:${PATH}"
+
+WORKDIR $HOME
\ No newline at end of file
diff --git a/tests/conftest.py b/tests/conftest.py
index f75e669..8dd504c 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -15,6 +15,6 @@
"""
# pylint: disable=unused-import
-from .fixtures.basic_gates import single_op_tests
-from .fixtures.cirq_circuits import cirq_bell
-from .fixtures.pyqir_circuits import pyqir_bell
+from .fixtures.basic_gates import *
+from .fixtures.cirq_circuits import *
+from .fixtures.pyqir_circuits import *
diff --git a/tests/fixtures/basic_gates.py b/tests/fixtures/basic_gates.py
index c5817b9..c7db4ed 100644
--- a/tests/fixtures/basic_gates.py
+++ b/tests/fixtures/basic_gates.py
@@ -63,7 +63,7 @@ def _generate_one_qubit_fixture(gate_name: str):
@pytest.fixture()
def test_fixture():
circuit = cirq.Circuit()
- q = cirq.NamedQubit("q")
+ q = cirq.NamedQubit("q0")
circuit.append(getattr(cirq, gate_name)(q))
return _map_gate_name(gate_name), circuit
@@ -80,7 +80,7 @@ def _generate_rotation_fixture(gate_name: str):
@pytest.fixture()
def test_fixture():
circuit = cirq.Circuit()
- q = cirq.NamedQubit("q")
+ q = cirq.NamedQubit("q0")
circuit.append(getattr(cirq, gate_name)(rads=0.5)(q))
return _map_gate_name(gate_name), circuit
@@ -157,7 +157,7 @@ def test_qft():
@pytest.mark.parametrize("angle", np.linspace(0, 2 * np.pi, 5))
def test_rx_gate(angle):
- qubit = cirq.NamedQubit("q")
+ qubit = cirq.NamedQubit("q0")
circuit = cirq.Circuit(cirq.rx(angle)(qubit))
# Add assertions or checks for the rotation
diff --git a/tests/test_cirq_decompose.py b/tests/test_cirq_decompose.py
new file mode 100644
index 0000000..f103c57
--- /dev/null
+++ b/tests/test_cirq_decompose.py
@@ -0,0 +1,69 @@
+# Copyright (C) 2023 qBraid
+#
+# This file is part of the qBraid-SDK
+#
+# The qBraid-SDK is free software released under the GNU General Public License v3
+# or later. You can redistribute and/or modify it under the terms of the GPL v3.
+# See the LICENSE file in the project root or .
+#
+# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3.
+
+"""
+Test functions that decompose unsupported Cirq gates before conversion to QIR.
+
+"""
+
+import cirq
+import numpy as np
+from qbraid.programs.testing import circuits_allclose
+
+from qbraid_qir.cirq.passes import _decompose_unsupported_gates
+
+
+def test_only_supported_gates():
+ qubits = cirq.LineQubit.range(2)
+ circuit = cirq.Circuit(cirq.H(qubits[0]), cirq.CNOT(qubits[0], qubits[1]))
+ decomposed_circuit = _decompose_unsupported_gates(circuit)
+ assert decomposed_circuit == circuit
+ assert circuits_allclose(decomposed_circuit, circuit)
+
+
+def test_contains_unsupported_gates():
+ qubits = cirq.LineQubit.range(2)
+ circuit = cirq.Circuit(
+ cirq.ops.ISwapPowGate(exponent=np.pi).on(*qubits),
+ )
+ decomposed_circuit = _decompose_unsupported_gates(circuit)
+ assert decomposed_circuit != circuit
+ assert circuits_allclose(decomposed_circuit, circuit)
+
+
+def test_empty_circuit():
+ circuit = cirq.Circuit()
+ decomposed_circuit = _decompose_unsupported_gates(circuit)
+ assert decomposed_circuit == circuit
+ assert circuits_allclose(decomposed_circuit, circuit)
+
+
+def test_custom_gate():
+ class CustomGate(cirq.Gate): # pylint: disable=abstract-method
+ def _num_qubits_(self):
+ return 1
+
+ def _decompose_(self, qubits):
+ yield cirq.X(qubits[0])
+
+ custom_gate = CustomGate()
+ qubit = cirq.LineQubit(0)
+ circuit = cirq.Circuit(custom_gate.on(qubit))
+ decomposed_circuit = _decompose_unsupported_gates(circuit)
+ assert decomposed_circuit != circuit
+ assert (
+ any(
+ isinstance(op.gate, CustomGate)
+ for moment in decomposed_circuit
+ for op in moment
+ )
+ is False
+ )
+ assert circuits_allclose(decomposed_circuit, circuit)
diff --git a/tests/test_preprocess.py b/tests/test_cirq_preprocess.py
similarity index 87%
rename from tests/test_preprocess.py
rename to tests/test_cirq_preprocess.py
index 1defb8b..9b54f84 100644
--- a/tests/test_preprocess.py
+++ b/tests/test_cirq_preprocess.py
@@ -15,7 +15,7 @@
import numpy as np
import pytest
-from qbraid_qir.cirq.convert import _preprocess_circuit
+from qbraid_qir.cirq.passes import preprocess_circuit
@pytest.fixture
@@ -33,7 +33,7 @@ def namedqubit_circuit():
def test_convert_gridqubits_to_linequbits(gridqubit_circuit):
- linequbit_circuit = _preprocess_circuit(gridqubit_circuit)
+ linequbit_circuit = preprocess_circuit(gridqubit_circuit)
for qubit in linequbit_circuit.all_qubits():
assert isinstance(qubit, cirq.LineQubit), "Qubit is not a LineQubit"
assert np.allclose(
@@ -42,7 +42,7 @@ def test_convert_gridqubits_to_linequbits(gridqubit_circuit):
def test_convert_namedqubits_to_linequbits(namedqubit_circuit):
- linequbit_circuit = _preprocess_circuit(namedqubit_circuit)
+ linequbit_circuit = preprocess_circuit(namedqubit_circuit)
for qubit in linequbit_circuit.all_qubits():
assert isinstance(qubit, cirq.LineQubit), "Qubit is not a LineQubit"
assert np.allclose(
@@ -52,7 +52,7 @@ def test_convert_namedqubits_to_linequbits(namedqubit_circuit):
def test_empty_circuit_conversion():
circuit = cirq.Circuit()
- converted_circuit = _preprocess_circuit(circuit)
+ converted_circuit = preprocess_circuit(circuit)
assert (
len(converted_circuit.all_qubits()) == 0
), "Converted empty circuit should have no qubits"
diff --git a/tests/test_cirq_to_qir.py b/tests/test_cirq_to_qir.py
index b4c2ac6..80f984a 100644
--- a/tests/test_cirq_to_qir.py
+++ b/tests/test_cirq_to_qir.py
@@ -16,8 +16,7 @@
import pytest
import tests.test_utils as test_utils
-from qbraid_qir.cirq.convert import cirq_to_qir, generate_module_id
-from qbraid_qir.exceptions import QirConversionError
+from qbraid_qir.cirq.convert import cirq_to_qir
from tests.fixtures.basic_gates import single_op_tests
from .qir_utils import assert_equal_qir
@@ -29,45 +28,41 @@ def test_cirq_to_qir_type_error():
cirq_to_qir(None)
-@pytest.mark.skip(reason="Not implemented yet")
def test_cirq_to_qir_conversion_error():
"""Test raising exception for conversion error."""
circuit = cirq.Circuit()
- with pytest.raises(QirConversionError):
+ with pytest.raises(ValueError):
cirq_to_qir(circuit)
-@pytest.mark.skip(reason="Not implemented yet")
@pytest.mark.parametrize("circuit_name", single_op_tests)
def test_single_qubit_gates(circuit_name, request):
qir_op, circuit = request.getfixturevalue(circuit_name)
- generated_qir = str(cirq_to_qir(circuit)[0]).splitlines()
- func = test_utils.get_entry_point_body(generated_qir)
+ qir_module = cirq_to_qir(circuit, record_output=False)
+ qir_str = str(qir_module).splitlines()
+ func = test_utils.get_entry_point_body(qir_str)
assert func[0] == test_utils.initialize_call_string()
assert func[1] == test_utils.single_op_call_string(qir_op, 0)
assert func[2] == test_utils.return_string()
assert len(func) == 3
-def test_cirq_workings():
- circuit = cirq.Circuit()
- qubits = cirq.LineQubit.range(3)
- circuit.append(cirq.CX(qubits[0], qubits[1]))
- circuit.append(cirq.measure(qubits[0]))
- circuit.append(cirq.H(qubits[0]))
- circuit.append(cirq.H(qubits[1]))
- circuit.append(cirq.H(qubits[2]))
- print(circuit)
-
-
def test_verify_qir_bell_fixture(pyqir_bell):
"""Test that pyqir fixture generates code equal to test_qir_bell.ll file."""
assert_equal_qir(pyqir_bell.ir(), "test_qir_bell")
-@pytest.mark.skip(reason="Not implemented yet")
+def test_entry_point_name(cirq_bell):
+ """Test that entry point name is consistent with module ID."""
+ name = "quantum_123"
+ module = cirq_to_qir(cirq_bell, name=name)
+ assert module.source_filename == name
+
+
def test_convert_bell_compare_file(cirq_bell):
"""Test converting Cirq bell circuit to QIR."""
test_name = "test_qir_bell"
- generator = cirq_to_qir(cirq_bell, name=test_name)
- assert_equal_qir(generator.ir(), test_name)
+ module = cirq_to_qir(
+ cirq_bell, name=test_name, initialize_runtime=False, record_output=False
+ )
+ assert_equal_qir(str(module), test_name)
diff --git a/tools/create_dev_build.sh b/tools/create_dev_build.sh
index 1918fc5..06d5c10 100755
--- a/tools/create_dev_build.sh
+++ b/tools/create_dev_build.sh
@@ -40,7 +40,7 @@ OUT_DIR="${2}"
# Constants
REPO_DIR=$(git rev-parse --show-toplevel)
-VERSION_FILE="${REPO_DIR}/qbraid/_version.py"
+VERSION_FILE="${REPO_DIR}/qbraid_qir/_version.py"
TMP_BRANCH="tmp_build_branch_$(date "+%Y%m%d%H%M%S")"
# Cleanup function
diff --git a/tools/stamp_dev_version.sh b/tools/stamp_dev_version.sh
index bee6432..9d38d99 100755
--- a/tools/stamp_dev_version.sh
+++ b/tools/stamp_dev_version.sh
@@ -31,7 +31,7 @@
set -e
# Constants
-PROJECT_NAME="qbraid/qir"
+PROJECT_NAME="qbraid_qir"
VERSION_FILE_PATH="_version.py"
TIMESTAMP_FORMAT="+%Y%m%d%H%M%S"
diff --git a/tools/verify_headers.py b/tools/verify_headers.py
index e723b37..feb11bc 100755
--- a/tools/verify_headers.py
+++ b/tools/verify_headers.py
@@ -46,6 +46,16 @@ def should_skip(file_path, content):
if os.path.basename(file_path) == "__init__.py":
return not content.strip()
+ skip_header_tag = "# qbraid: skip-header"
+ line_number = 0
+
+ for line in content.splitlines():
+ line_number += 1
+ if 5 <= line_number <= 30 and skip_header_tag in line:
+ return True
+ if line_number > 30:
+ break
+
return False
@@ -91,9 +101,29 @@ def process_files_in_directory(directory, fix=False):
return count
+def display_help():
+ help_message = """
+ Usage: python verify_headers.py SRC [OPTIONS] ...
+
+ This script checks for copyright headers at the specified path.
+ If no flags are passed, it will indicate which files would be
+ modified without actually making any changes.
+
+ Options:
+ --help Display this help message and exit.
+ --fix Adds/modifies file headers as necessary.
+ """
+ print(help_message)
+ sys.exit(0)
+
+
if __name__ == "__main__":
- if len(sys.argv) < 2:
- print("Please provide at least one directory as a command-line argument.")
+ if "--help" in sys.argv:
+ display_help()
+
+ # Check if at least two arguments are provided and the first argument is not a flag
+ if len(sys.argv) < 2 or sys.argv[1].startswith("--"):
+ print("Usage: python verify_headers.py SRC [OPTIONS] ...")
sys.exit(1)
script_directory = os.path.dirname(os.path.abspath(__file__))