diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c64f7a9..1172183 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,7 +39,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9', '3.10', '3.11', '3.12'] + python-version: ['3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v4 diff --git a/docs/conf.py b/docs/conf.py index 352e03d..c7d4456 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -35,7 +35,7 @@ "sphinx.ext.mathjax", "sphinx.ext.coverage", "sphinx.ext.viewcode", - "sphinx_copybutton" + "sphinx_copybutton", ] # set_type_checking_flag = True diff --git a/docs/index.rst b/docs/index.rst index 4aa6e90..740353c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -94,7 +94,7 @@ Resources - `User Guide `_ - `Example Notebooks `_ - `API Reference `_ -- `Source Code _` +- `Source Code `_ .. toctree:: diff --git a/pyproject.toml b/pyproject.toml index 40d9389..a92ebda 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,6 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -25,7 +24,7 @@ classifiers = [ "Topic :: Scientific/Engineering :: Physics" ] dependencies = ["pyqir>=0.10.0,<0.11.0", "numpy"] -requires-python = ">=3.9" +requires-python = ">=3.10" [project.urls] Homepage = "https://github.com/qBraid/qbraid-qir" @@ -36,10 +35,10 @@ Discord = "https://discord.gg/TPBU2sa8Et" [project.optional-dependencies] cirq = ["cirq-core>=1.3.0,<1.5.0"] -qasm3 = ["openqasm3[parser]>=0.4.0,<1.1.0"] +qasm3 = ["pyqasm>=0.0.1"] test = ["qbraid>=0.7.1,<0.9.0", "pytest", "pytest-cov", "autoqasm>=0.1.0"] -lint = ["black[jupyter]", "isort", "pylint", "qbraid-cli>=0.8.3"] -docs = ["sphinx>=7.3.7,<8.1.0", "sphinx-autodoc-typehints>=1.24,<2.5", "sphinx-rtd-theme>=2.0,<3.1", "docutils<0.22", "sphinx-copybutton"] +lint = ["black[jupyter]", "isort", "pylint", "qbraid-cli>=0.8.7"] +docs = ["pyqasm>=0.0.1", "sphinx>=7.3.7,<8.1.0", "sphinx-autodoc-typehints>=1.24,<2.5", "sphinx-rtd-theme>=2.0,<3.1", "docutils<0.22", "sphinx-copybutton"] [tool.setuptools.dynamic] version = {attr = "qbraid_qir.__version__"} diff --git a/qbraid_qir/__init__.py b/qbraid_qir/__init__.py index 9d38469..6e0087c 100644 --- a/qbraid_qir/__init__.py +++ b/qbraid_qir/__init__.py @@ -1,12 +1,12 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ This top level module contains the main qBraid QIR functionality. diff --git a/qbraid_qir/_version.py b/qbraid_qir/_version.py index 7b35b1b..8bd0989 100644 --- a/qbraid_qir/_version.py +++ b/qbraid_qir/_version.py @@ -1,12 +1,12 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing version information diff --git a/qbraid_qir/cirq/__init__.py b/qbraid_qir/cirq/__init__.py index c3786b5..68166ac 100644 --- a/qbraid_qir/cirq/__init__.py +++ b/qbraid_qir/cirq/__init__.py @@ -1,12 +1,12 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing Cirq QIR functionality. diff --git a/qbraid_qir/cirq/convert.py b/qbraid_qir/cirq/convert.py index 31c29b5..a6166c2 100644 --- a/qbraid_qir/cirq/convert.py +++ b/qbraid_qir/cirq/convert.py @@ -1,12 +1,12 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing Cirq to qBraid QIR conversion functions diff --git a/qbraid_qir/cirq/elements.py b/qbraid_qir/cirq/elements.py index 8ca45b7..a2405c9 100644 --- a/qbraid_qir/cirq/elements.py +++ b/qbraid_qir/cirq/elements.py @@ -1,12 +1,12 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module defining Cirq LLVM Module elements. diff --git a/qbraid_qir/cirq/exceptions.py b/qbraid_qir/cirq/exceptions.py index ea2884a..70bb7cb 100644 --- a/qbraid_qir/cirq/exceptions.py +++ b/qbraid_qir/cirq/exceptions.py @@ -1,12 +1,12 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module defining exceptions for errors raised during Cirq conversions. diff --git a/qbraid_qir/cirq/opsets.py b/qbraid_qir/cirq/opsets.py index 316c2fd..0d02206 100644 --- a/qbraid_qir/cirq/opsets.py +++ b/qbraid_qir/cirq/opsets.py @@ -1,12 +1,12 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module mapping supported Cirq gates/operations to pyqir functions. diff --git a/qbraid_qir/cirq/passes.py b/qbraid_qir/cirq/passes.py index e7107b9..2592ebf 100644 --- a/qbraid_qir/cirq/passes.py +++ b/qbraid_qir/cirq/passes.py @@ -1,12 +1,12 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module for processing Cirq circuits before conversion to QIR. diff --git a/qbraid_qir/cirq/visitor.py b/qbraid_qir/cirq/visitor.py index 79cf518..37695aa 100644 --- a/qbraid_qir/cirq/visitor.py +++ b/qbraid_qir/cirq/visitor.py @@ -1,12 +1,12 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module defining CirqVisitor. diff --git a/qbraid_qir/exceptions.py b/qbraid_qir/exceptions.py index b3d8ad3..521aa68 100644 --- a/qbraid_qir/exceptions.py +++ b/qbraid_qir/exceptions.py @@ -1,12 +1,12 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module defining exceptions for errors raised by qBraid QIR. diff --git a/qbraid_qir/qasm3/__init__.py b/qbraid_qir/qasm3/__init__.py index 00cc5cb..ae8987b 100644 --- a/qbraid_qir/qasm3/__init__.py +++ b/qbraid_qir/qasm3/__init__.py @@ -1,12 +1,12 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing OpenQASM 3 QIR functionality. @@ -20,7 +20,6 @@ :toctree: ../stubs/ qasm3_to_qir - validate_qasm Classes --------- @@ -28,8 +27,8 @@ .. autosummary:: :toctree: ../stubs/ - Qasm3Module - BasicQasmVisitor + QasmQIRModule + QasmQIRVisitor Exceptions ----------- @@ -40,16 +39,14 @@ Qasm3ConversionError """ -from .checker import validate_qasm from .convert import qasm3_to_qir -from .elements import Qasm3Module +from .elements import QasmQIRModule from .exceptions import Qasm3ConversionError -from .visitor import BasicQasmVisitor +from .visitor import QasmQIRVisitor __all__ = [ - "validate_qasm", "qasm3_to_qir", - "Qasm3Module", + "QasmQIRModule", "Qasm3ConversionError", - "BasicQasmVisitor", + "QasmQIRVisitor", ] diff --git a/qbraid_qir/qasm3/analyzer.py b/qbraid_qir/qasm3/analyzer.py deleted file mode 100644 index 58bfe2a..0000000 --- a/qbraid_qir/qasm3/analyzer.py +++ /dev/null @@ -1,233 +0,0 @@ -# Copyright (C) 2024 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. - -# pylint: disable=import-outside-toplevel,cyclic-import - -""" -Module with analysis functions for QASM3 visitor - -""" -from __future__ import annotations - -from typing import TYPE_CHECKING, Any, Optional, Union - -import numpy as np -from openqasm3.ast import ( - BinaryExpression, - DiscreteSet, - Expression, - Identifier, - IndexExpression, - IntegerLiteral, - IntType, - RangeDefinition, - UnaryExpression, -) - -from .exceptions import Qasm3ConversionError, raise_qasm3_error - -if TYPE_CHECKING: - from qbraid_qir.qasm3.elements import Variable - from qbraid_qir.qasm3.expressions import Qasm3ExprEvaluator - - -class Qasm3Analyzer: - """Class with utility functions for analyzing QASM3 elements""" - - @classmethod - def analyze_classical_indices( - cls, indices: list[Any], var: Variable, expr_evaluator: Qasm3ExprEvaluator - ) -> list: - """Validate the indices for a classical variable. - - Args: - indices (list[list[Any]]): The indices to validate. - var (Variable): The variable to verify - - Raises: - Qasm3ConversionError: If the indices are invalid. - - Returns: - list[list]: The list of indices. Note, we can also have a list of indices within - a list if the variable is a multi-dimensional array. - """ - indices_list = [] - var_dimensions: Optional[list[int]] = var.dims - - if var_dimensions is None or len(var_dimensions) == 0: - raise_qasm3_error( - message=f"Indexing error. Variable {var.name} is not an array", - err_type=Qasm3ConversionError, - span=indices[0].span, - ) - if isinstance(indices, DiscreteSet): - indices = indices.values - - if len(indices) != len(var_dimensions): # type: ignore[arg-type] - raise_qasm3_error( - message=f"Invalid number of indices for variable {var.name}. " - f"Expected {len(var_dimensions)} but got {len(indices)}", # type: ignore[arg-type] - err_type=Qasm3ConversionError, - span=indices[0].span, - ) - - def _validate_index(index, dimension, var_name, span, dim_num): - if index < 0 or index >= dimension: - raise_qasm3_error( - message=f"Index {index} out of bounds for dimension {dim_num} " - f"of variable {var_name}", - err_type=Qasm3ConversionError, - span=span, - ) - - def _validate_step(start_id, end_id, step, span): - if (step < 0 and start_id < end_id) or (step > 0 and start_id > end_id): - direction = "less than" if step < 0 else "greater than" - raise_qasm3_error( - message=f"Index {start_id} is {direction} {end_id} but step" - f" is {'negative' if step < 0 else 'positive'}", - err_type=Qasm3ConversionError, - span=span, - ) - - for i, index in enumerate(indices): - if not isinstance(index, (Identifier, Expression, RangeDefinition, IntegerLiteral)): - raise_qasm3_error( - message=f"Unsupported index type {type(index)} for " - f"classical variable {var.name}", - err_type=Qasm3ConversionError, - span=index.span, - ) - - if isinstance(index, RangeDefinition): - assert var_dimensions is not None - - start_id = 0 - if index.start is not None: - start_id = expr_evaluator.evaluate_expression(index.start, reqd_type=IntType) - - end_id = var_dimensions[i] - 1 - if index.end is not None: - end_id = expr_evaluator.evaluate_expression(index.end, reqd_type=IntType) - - step = 1 - if index.step is not None: - step = expr_evaluator.evaluate_expression(index.step, reqd_type=IntType) - - _validate_index(start_id, var_dimensions[i], var.name, index.span, i) - _validate_index(end_id, var_dimensions[i], var.name, index.span, i) - _validate_step(start_id, end_id, step, index.span) - - indices_list.append((start_id, end_id, step)) - - if isinstance(index, (Identifier, IntegerLiteral, Expression)): - index_value = expr_evaluator.evaluate_expression(index, reqd_type=IntType) - curr_dimension = var_dimensions[i] # type: ignore[index] - _validate_index(index_value, curr_dimension, var.name, index.span, i) - - indices_list.append((index_value, index_value, 1)) - - return indices_list - - @staticmethod - def analyze_index_expression( - index_expr: IndexExpression, - ) -> tuple[str, list[Union[Any, Expression, RangeDefinition]]]: - """Analyze an index expression to get the variable name and indices. - - Args: - index_expr (IndexExpression): The index expression to analyze. - - Returns: - tuple[str, list[Any]]: The variable name and indices in openqasm objects - - """ - indices: list[Any] = [] - var_name = "" - comma_separated = False - - if isinstance(index_expr.collection, IndexExpression): - while isinstance(index_expr, IndexExpression): - if isinstance(index_expr.index, list): - indices.append(index_expr.index[0]) - index_expr = index_expr.collection - else: - comma_separated = True - indices = index_expr.index # type: ignore[assignment] - var_name = ( - index_expr.collection.name # type: ignore[attr-defined] - if comma_separated - else index_expr.name # type: ignore[attr-defined] - ) - if not comma_separated: - indices = indices[::-1] - - return var_name, indices - - @staticmethod - def find_array_element(multi_dim_arr: np.ndarray, indices: list[tuple[int, int, int]]) -> Any: - """Find the value of an array at the specified indices. - - Args: - multi_dim_arr (np.ndarray): The multi-dimensional list to search. - indices (list[tuple[int,int,int]]): The indices to search. - - Returns: - Any: The value at the specified indices. - """ - slicing = tuple( - slice(start, end + 1, step) if start != end else start for start, end, step in indices - ) - return multi_dim_arr[slicing] # type: ignore[index] - - @staticmethod - def analyse_branch_condition(condition: Any) -> bool: - """ - analyze the branching condition to determine the branch to take - - Args: - condition (Any): The condition to analyze - - Returns: - bool: The branch to take - """ - - if isinstance(condition, UnaryExpression): - if condition.op.name != "!": - raise_qasm3_error( - message=f"Unsupported unary expression '{condition.op.name}' in if condition", - err_type=Qasm3ConversionError, - span=condition.span, - ) - return False - if isinstance(condition, BinaryExpression): - if condition.op.name != "==": - raise_qasm3_error( - message=f"Unsupported binary expression '{condition.op.name}' in if condition", - err_type=Qasm3ConversionError, - span=condition.span, - ) - if not isinstance(condition.lhs, IndexExpression): - raise_qasm3_error( - message=f"Unsupported expression type '{type(condition.rhs)}' in if condition", - err_type=Qasm3ConversionError, - span=condition.span, - ) - return condition.rhs.value != 0 # type: ignore[attr-defined] - if not isinstance(condition, IndexExpression): - raise_qasm3_error( - message=( - f"Unsupported expression type '{type(condition)}' in if condition. " - "Can only be a simple comparison" - ), - err_type=Qasm3ConversionError, - span=condition.span, - ) - return True diff --git a/qbraid_qir/qasm3/checker.py b/qbraid_qir/qasm3/checker.py deleted file mode 100644 index c177ff8..0000000 --- a/qbraid_qir/qasm3/checker.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright (C) 2024 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 containing OpenQASM semantic checker function - -""" -from typing import Union - -import openqasm3 -from openqasm3.parser import QASM3ParsingError -from pyqir import Context, qir_module - -from .elements import Qasm3Module, generate_module_id -from .exceptions import Qasm3ConversionError -from .visitor import BasicQasmVisitor - - -class QasmValidationError(Exception): - """Exception raised when a QASM program fails validation.""" - - -def validate_qasm(program: Union[openqasm3.ast.Program, str]) -> None: - """Validates a given OpenQASM 3 program for semantic correctness. - - Args: - program (openqasm3.ast.Program or str): The OpenQASM 3 program to validate. - - Raises: - TypeError: If the input is not a string or an `openqasm3.ast.Program` instance. - QasmValidationError: If the program fails parsing or semantic validation. - """ - if isinstance(program, str): - try: - program = openqasm3.parse(program) - except QASM3ParsingError as err: - raise QasmValidationError(f"Failed to parse OpenQASM string: {err}") from err - elif not isinstance(program, openqasm3.ast.Program): - raise TypeError("Input quantum program must be of type 'str' or 'openqasm3.ast.Program'.") - - name = generate_module_id() - llvm_module = qir_module(Context(), name) - module = Qasm3Module.from_program(program, llvm_module) - - try: - visitor = BasicQasmVisitor(check_only=True) - module.accept(visitor) - except (Qasm3ConversionError, TypeError, ValueError) as err: - raise QasmValidationError(f"Semantic validation failed: {err}") from err diff --git a/qbraid_qir/qasm3/convert.py b/qbraid_qir/qasm3/convert.py index 8531fc9..9a24325 100644 --- a/qbraid_qir/qasm3/convert.py +++ b/qbraid_qir/qasm3/convert.py @@ -1,12 +1,12 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing OpenQASM to QIR conversion functions @@ -15,11 +15,12 @@ from typing import Optional, Union import openqasm3 +import pyqasm from pyqir import Context, Module, qir_module -from .elements import Qasm3Module, generate_module_id +from .elements import QasmQIRModule, generate_module_id from .exceptions import Qasm3ConversionError -from .visitor import BasicQasmVisitor +from .visitor import QasmQIRVisitor def qasm3_to_qir( @@ -45,20 +46,23 @@ def qasm3_to_qir( TypeError: If the input is not a valid OpenQASM 3 program. Qasm3ConversionError: If the conversion fails. """ - if isinstance(program, str): - program = openqasm3.parse(program) + if isinstance(program, openqasm3.ast.Program): + program = openqasm3.dumps(program) - elif not isinstance(program, openqasm3.ast.Program): + elif not isinstance(program, str): raise TypeError("Input quantum program must be of type openqasm3.ast.Program or str.") + qasm3_module = pyqasm.unroll(program) + + print(qasm3_module.unrolled_qasm) if name is None: name = generate_module_id() - llvm_module = qir_module(Context(), name) - module = Qasm3Module.from_program(program, llvm_module) - visitor = BasicQasmVisitor(**kwargs) - module.accept(visitor) + final_module = QasmQIRModule(name, qasm3_module, llvm_module) + + visitor = QasmQIRVisitor(**kwargs) + final_module.accept(visitor) err = llvm_module.verify() if err is not None: diff --git a/qbraid_qir/qasm3/elements.py b/qbraid_qir/qasm3/elements.py index 4fa0e54..a9245cc 100644 --- a/qbraid_qir/qasm3/elements.py +++ b/qbraid_qir/qasm3/elements.py @@ -1,12 +1,12 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. # pylint: disable=too-many-arguments @@ -16,14 +16,9 @@ """ import uuid -from abc import ABCMeta, abstractmethod -from enum import Enum -from typing import Any, Optional, Union -import numpy as np -from openqasm3.ast import BitType, ClassicalDeclaration, Program, QubitDeclaration, Statement -from pyqir import Context as qirContext -from pyqir import Module +from pyqasm.elements import Qasm3Module +from pyqir import Module as qirModule def generate_module_id() -> str: @@ -36,116 +31,25 @@ def generate_module_id() -> str: return f"program-{generated_id}" -class InversionOp(Enum): - NO_OP = 1 - INVERT_ROTATION = 2 - - -class Context(Enum): - """ - Enum for the different contexts in Qasm. - """ - - GLOBAL = "global" - BLOCK = "block" - FUNCTION = "function" - GATE = "gate" - - -class Variable: - """ - Class representing an openqasm variable. - - Args: - name (str): Name of the variable. - base_type (Any): Base type of the variable. - base_size (int): Base size of the variable. - dims (list[int]): Dimensions of the variable. - value (Optional[Union[int, float, list]]): Value of the variable. - is_constant (bool): Flag indicating if the variable is constant. - readonly(bool): Flag indicating if the variable is readonly. - - """ - - def __init__( - self, - name: str, - base_type: Any, - base_size: int, - dims: Optional[list[int]] = None, - value: Optional[Union[int, float, np.ndarray]] = None, - is_constant: bool = False, - readonly: bool = False, - ): - self.name = name - self.base_type = base_type - self.base_size = base_size - self.dims = dims - self.value = value - self.is_constant = is_constant - self.readonly = readonly - - -class _ProgramElement(metaclass=ABCMeta): - - @classmethod - def from_element_list(cls, elements): - return [cls(elem) for elem in elements] - - @abstractmethod - def accept(self, visitor): - pass - - -class _Register(_ProgramElement): - - def __init__(self, register: Union[QubitDeclaration, ClassicalDeclaration]): - self._register: Union[QubitDeclaration, ClassicalDeclaration] = register - - def accept(self, visitor): - visitor.visit_register(self._register) - - def __str__(self) -> str: - return f"Register({self._register})" - - -class _Statement(_ProgramElement): - - def __init__(self, statement: Statement): - self._statement = statement - - def accept(self, visitor): - visitor.visit_statement(self._statement) - - def __str__(self) -> str: - return f"Statement({self._statement})" - - -class Qasm3Module: +class QasmQIRModule: """ A module representing an openqasm3 quantum program using QIR. Args: name (str): Name of the module. - module (Module): QIR Module instance. - num_qubits (int): Number of qubits in the circuit. - num_clbits (int): Number of classical bits in the circuit. - elements (list[Statement]): list of openqasm3 Statements. + qasm_module (pyqasm.elements.Qasm3Module): The pyqasm qasm3 module. + llvm_module (pyqir.Module): The QIR module. """ def __init__( self, name: str, - module: Module, - num_qubits: int, - num_clbits: int, - elements, + qasm_module: Qasm3Module, + llvm_module: qirModule, ): self._name = name - self._module = module - self._num_qubits = num_qubits - self._num_clbits = num_clbits - self._elements = elements + self._llvm_module = llvm_module + self._qasm_program = qasm_module @property def name(self) -> str: @@ -153,68 +57,19 @@ def name(self) -> str: return self._name @property - def module(self) -> Module: + def llvm_module(self) -> qirModule: """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 + return self._llvm_module @property - def num_clbits(self) -> int: - """Returns the number of classical bits in the circuit.""" - return self._num_clbits - - @classmethod - def from_program(cls, program: Program, module: Optional[Module] = None): - """ - Class method. Construct a Qasm3Module from a given openqasm3.ast.Program object - and an optional QIR Module. - """ - elements: list[Union[_Register, _Statement]] = [] - - num_qubits = 0 - num_clbits = 0 - for statement in program.statements: - if isinstance(statement, QubitDeclaration): - size = 1 - if statement.size: - size = statement.size.value # type: ignore[attr-defined] - num_qubits += size - elements.append(_Register(statement)) - - elif isinstance(statement, ClassicalDeclaration) and isinstance( - statement.type, BitType - ): - size = 1 - if statement.type.size: - size = statement.type.size.value # type: ignore[attr-defined] - num_clbits += size - elements.append(_Register(statement)) - # as bit arrays are just 0 / 1 values, we can treat them as - # classical variables too. Thus, need to add them to normal - # statements too. - elements.append(_Statement(statement)) - else: - elements.append(_Statement(statement)) - - if module is None: - # pylint: disable-next=too-many-function-args - module = Module(qirContext(), generate_module_id()) - - return cls( - name="main", - module=module, - num_qubits=num_qubits, - num_clbits=num_clbits, - elements=elements, - ) + def qasm_program(self) -> Qasm3Module: + """Returns the QASM3 program.""" + return self._qasm_program def accept(self, visitor): visitor.visit_qasm3_module(self) - for element in self._elements: - element.accept(visitor) + statements = self.qasm_program.unrolled_ast.statements + for statement in statements: + visitor.visit_statement(statement) visitor.record_output(self) visitor.finalize() diff --git a/qbraid_qir/qasm3/exceptions.py b/qbraid_qir/qasm3/exceptions.py index 2bfe106..b1cea8f 100644 --- a/qbraid_qir/qasm3/exceptions.py +++ b/qbraid_qir/qasm3/exceptions.py @@ -1,12 +1,12 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module defining exceptions for errors raised during QASM3 conversions. diff --git a/qbraid_qir/qasm3/expressions.py b/qbraid_qir/qasm3/expressions.py deleted file mode 100644 index 7f81275..0000000 --- a/qbraid_qir/qasm3/expressions.py +++ /dev/null @@ -1,279 +0,0 @@ -# Copyright (C) 2024 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 containing the class for evaluating QASM3 expressions. - -""" - -from openqasm3.ast import BinaryExpression, BooleanLiteral, BoolType, DurationLiteral, FloatLiteral -from openqasm3.ast import FloatType as Qasm3FloatType -from openqasm3.ast import ( - FunctionCall, - Identifier, - ImaginaryLiteral, - IndexExpression, - IntegerLiteral, -) -from openqasm3.ast import IntType as Qasm3IntType -from openqasm3.ast import SizeOf, UnaryExpression - -from .analyzer import Qasm3Analyzer -from .exceptions import Qasm3ConversionError, raise_qasm3_error -from .maps import CONSTANTS_MAP, qasm3_expression_op_map -from .validator import Qasm3Validator - - -class Qasm3ExprEvaluator: - """Class for evaluating QASM3 expressions.""" - - visitor_obj = None - - @classmethod - def set_visitor_obj(cls, visitor_obj) -> None: - cls.visitor_obj = visitor_obj - - @classmethod - def _check_var_in_scope(cls, var_name, expression): - """Checks if a variable is in scope. - - Args: - var_name: The name of the variable to check. - expression: The expression containing the variable. - Raises: - Qasm3ConversionError: If the variable is undefined in the current scope. - """ - - if not cls.visitor_obj._check_in_scope(var_name): - raise_qasm3_error( - f"Undefined identifier {var_name} in expression", - Qasm3ConversionError, - expression.span, - ) - - @classmethod - def _check_var_constant(cls, var_name, const_expr, expression): - """Checks if a variable is constant. - - Args: - var_name: The name of the variable to check. - const_expr: Whether the expression is a constant. - expression: The expression containing the variable. - - Raises: - Qasm3ConversionError: If the variable is not a constant in the given - expression. - """ - const_var = cls.visitor_obj._get_from_visible_scope(var_name).is_constant - if const_expr and not const_var: - raise_qasm3_error( - f"Variable '{var_name}' is not a constant in given expression", - Qasm3ConversionError, - expression.span, - ) - - @classmethod - def _check_var_type(cls, var_name, reqd_type, expression): - """Check the type of a variable and raise an error if it does not match the - required type. - - Args: - var_name: The name of the variable to check. - reqd_type: The required type of the variable. - expression: The expression where the variable is used. - - Raises: - Qasm3ConversionError: If the variable has an invalid type for the required type. - """ - - if not Qasm3Validator.validate_variable_type( - cls.visitor_obj._get_from_visible_scope(var_name), reqd_type - ): - raise_qasm3_error( - f"Invalid type of variable {var_name} for required type {reqd_type}", - Qasm3ConversionError, - expression.span, - ) - - @staticmethod - def _check_var_initialized(var_name, var_value, expression): - """Checks if a variable is initialized and raises an error if it is not. - - Args: - var_name (str): The name of the variable. - var_value: The value of the variable. - expression: The expression where the variable is used. - Raises: - Qasm3ConversionError: If the variable is uninitialized. - """ - - if var_value is None: - raise_qasm3_error( - f"Uninitialized variable {var_name} in expression", - Qasm3ConversionError, - expression.span, - ) - - @classmethod - def _get_var_value(cls, var_name, indices, expression): - """Retrieves the value of a variable. - - Args: - var_name (str): The name of the variable. - indices (list): The indices of the variable (if it is an array). - expression (Identifier or Expression): The expression representing the variable. - Returns: - var_value: The value of the variable. - """ - - var_value = None - if isinstance(expression, Identifier): - var_value = cls.visitor_obj._get_from_visible_scope(var_name).value - else: - validated_indices = Qasm3Analyzer.analyze_classical_indices( - indices, cls.visitor_obj._get_from_visible_scope(var_name), cls - ) - var_value = Qasm3Analyzer.find_array_element( - cls.visitor_obj._get_from_visible_scope(var_name).value, validated_indices - ) - return var_value - - @classmethod - # pylint: disable-next=too-many-return-statements, too-many-branches - def evaluate_expression(cls, expression, const_expr: bool = False, reqd_type=None): - """Evaluate an expression. Scalar types are assigned by value. - - Args: - expression (Any): The expression to evaluate. - const_expr (bool): Whether the expression is a constant. Defaults to False. - reqd_type (Any): The required type of the expression. Defaults to None. - - Returns: - Any : The result of the evaluation. - - Raises: - Qasm3ConversionError: If the expression is not supported. - """ - if expression is None: - return None - - if isinstance(expression, (ImaginaryLiteral, DurationLiteral)): - raise_qasm3_error( - f"Unsupported expression type {type(expression)}", - Qasm3ConversionError, - expression.span, - ) - - def _process_variable(var_name: str, indices=None): - cls._check_var_in_scope(var_name, expression) - cls._check_var_constant(var_name, const_expr, expression) - cls._check_var_type(var_name, reqd_type, expression) - var_value = cls._get_var_value(var_name, indices, expression) - Qasm3ExprEvaluator._check_var_initialized(var_name, var_value, expression) - return var_value - - if isinstance(expression, Identifier): - var_name = expression.name - if var_name in CONSTANTS_MAP: - if not reqd_type or reqd_type == Qasm3FloatType: - return CONSTANTS_MAP[var_name] - raise_qasm3_error( - f"Constant {var_name} not allowed in non-float expression", - Qasm3ConversionError, - expression.span, - ) - return _process_variable(var_name) - - if isinstance(expression, IndexExpression): - var_name, indices = Qasm3Analyzer.analyze_index_expression(expression) - return _process_variable(var_name, indices) - - if isinstance(expression, SizeOf): - # has 2 elements - target and index - target = expression.target - index = expression.index - - if isinstance(target, Identifier): - var_name = target.name - cls._check_var_in_scope(var_name, expression) - dimensions = cls.visitor_obj._get_from_visible_scope( # type: ignore[union-attr] - var_name - ).dims - else: - raise_qasm3_error( - message=f"Unsupported target type {type(target)} for sizeof expression", - err_type=Qasm3ConversionError, - span=expression.span, - ) - - if dimensions is None or len(dimensions) == 0: - raise_qasm3_error( - message=f"Invalid sizeof usage, variable {var_name} is not an array.", - err_type=Qasm3ConversionError, - span=expression.span, - ) - - if index is None: - # get the first dimension of the array - return dimensions[0] - - index = cls.evaluate_expression(index, const_expr, reqd_type=Qasm3IntType) - assert index is not None and isinstance(index, int) - if index < 0 or index >= len(dimensions): - raise_qasm3_error( - f"Index {index} out of bounds for array {var_name} with " - f"{len(dimensions)} dimensions", - Qasm3ConversionError, - expression.span, - ) - - return dimensions[index] - - if isinstance(expression, (BooleanLiteral, IntegerLiteral, FloatLiteral)): - if reqd_type: - if reqd_type == BoolType and isinstance(expression, BooleanLiteral): - return expression.value - if reqd_type == Qasm3IntType and isinstance(expression, IntegerLiteral): - return expression.value - if reqd_type == Qasm3FloatType and isinstance(expression, FloatLiteral): - return expression.value - raise_qasm3_error( - f"Invalid value {expression.value} with type {type(expression)} " - f"for required type {reqd_type}", - Qasm3ConversionError, - expression.span, - ) - return expression.value - - if isinstance(expression, UnaryExpression): - operand = cls.evaluate_expression(expression.expression, const_expr, reqd_type) - if expression.op.name == "~" and not isinstance(operand, int): - raise_qasm3_error( - f"Unsupported expression type {type(operand)} in ~ operation", - Qasm3ConversionError, - expression.span, - ) - return qasm3_expression_op_map( - "UMINUS" if expression.op.name == "-" else expression.op.name, operand - ) - if isinstance(expression, BinaryExpression): - lhs = cls.evaluate_expression(expression.lhs, const_expr, reqd_type) - rhs = cls.evaluate_expression(expression.rhs, const_expr, reqd_type) - return qasm3_expression_op_map(expression.op.name, lhs, rhs) - - if isinstance(expression, FunctionCall): - # function will not return a reqd / const type - # Reference : https://openqasm.com/language/types.html#compile-time-constants - # para : 5 - return cls.visitor_obj._visit_function_call(expression) # type: ignore[union-attr] - - raise_qasm3_error( - f"Unsupported expression type {type(expression)}", Qasm3ConversionError, expression.span - ) diff --git a/qbraid_qir/qasm3/linalg.py b/qbraid_qir/qasm3/linalg.py index b0a66a6..48bb9e5 100644 --- a/qbraid_qir/qasm3/linalg.py +++ b/qbraid_qir/qasm3/linalg.py @@ -1,12 +1,12 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. # pylint: disable=too-many-locals diff --git a/qbraid_qir/qasm3/maps.py b/qbraid_qir/qasm3/maps.py index a82de4f..cfa3f6e 100644 --- a/qbraid_qir/qasm3/maps.py +++ b/qbraid_qir/qasm3/maps.py @@ -1,89 +1,25 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module mapping supported QASM gates/operations to pyqir functions. """ -from typing import Callable, Union +from typing import Union import numpy as np import pyqir -from openqasm3.ast import ( - AngleType, - BitType, - BoolType, - ClassicalDeclaration, - ComplexType, - FloatType, - IntType, - QuantumGateDefinition, - QubitDeclaration, - SubroutineDefinition, - UintType, -) - -from .elements import InversionOp + from .exceptions import Qasm3ConversionError from .linalg import kak_decomposition_angles -# Define the type for the operator functions -OperatorFunction = Union[ - Callable[[Union[int, float, bool]], Union[int, float, bool]], - Callable[[Union[int, float, bool], Union[int, float, bool]], Union[int, float, bool]], -] - -OPERATOR_MAP: dict[str, OperatorFunction] = { - "+": lambda x, y: x + y, - "-": lambda x, y: x - y, - "*": lambda x, y: x * y, - "/": lambda x, y: x / y, - "%": lambda x, y: x % y, - "==": lambda x, y: x == y, - "!=": lambda x, y: x != y, - "<": lambda x, y: x < y, - ">": lambda x, y: x > y, - "<=": lambda x, y: x <= y, - ">=": lambda x, y: x >= y, - "&&": lambda x, y: x and y, - "||": lambda x, y: x or y, - "^": lambda x, y: x ^ y, - "&": lambda x, y: x & y, - "|": lambda x, y: x | y, - "<<": lambda x, y: x << y, - ">>": lambda x, y: x >> y, - "~": lambda x: ~x, - "!": lambda x: not x, - "UMINUS": lambda x: -x, -} - - -def qasm3_expression_op_map(op_name: str, *args) -> Union[float, int, bool]: - """ - Return the result of applying the given operator to the given operands. - - Args: - op_name (str): The operator name. - *args: The operands of type Union[int, float, bool] - 1. For unary operators, a single operand (e.g., ~3) - 2. For binary operators, two operands (e.g., 3 + 2) - - Returns: - (Union[float, int, bool]): The result of applying the operator to the operands. - """ - try: - operator = OPERATOR_MAP[op_name] - return operator(*args) - except KeyError as exc: - raise Qasm3ConversionError(f"Unsupported / undeclared QASM operator: {op_name}") from exc - def id_gate(builder, qubits): pyqir._native.x(builder, qubits) @@ -460,7 +396,6 @@ def prx_gate(builder, theta, phi, qubit): PYQIR_ONE_QUBIT_OP_MAP = { - "i": id_gate, "id": id_gate, "h": pyqir._native.h, "x": pyqir._native.x, @@ -555,99 +490,6 @@ def map_qasm_op_to_pyqir_callable(op_name: str): raise Qasm3ConversionError(f"Unsupported / undeclared QASM operation: {op_name}") from exc -PYQIR_SELF_INVERTING_ONE_QUBIT_OP_SET = {"id", "h", "x", "y", "z"} -PYQIR_ST_GATE_INV_MAP = { - "s": "sdg", - "t": "tdg", - "sdg": "s", - "tdg": "t", -} -PYQIR_ROTATION_INVERSION_ONE_QUBIT_OP_MAP = {"rx", "ry", "rz"} -PYQIR_U_INV_ROTATION_MAP = { - "U": u3_inv_gate, - "u3": u3_inv_gate, - "U3": u3_inv_gate, - "U2": u2_inv_gate, - "u2": u2_inv_gate, -} - - -def map_qasm_inv_op_to_pyqir_callable(op_name: str): - """ - Map a QASM operation to a PyQIR callable. - - Args: - op_name (str): The QASM operation name. - - Returns: - tuple: A tuple containing the PyQIR callable, the number of qubits the operation acts on, - and what is to be done with the basic gate which we are trying to invert. - """ - if op_name in PYQIR_SELF_INVERTING_ONE_QUBIT_OP_SET: - return PYQIR_ONE_QUBIT_OP_MAP[op_name], 1, InversionOp.NO_OP - if op_name in PYQIR_ST_GATE_INV_MAP: - inv_gate_name = PYQIR_ST_GATE_INV_MAP[op_name] - return PYQIR_ONE_QUBIT_OP_MAP[inv_gate_name], 1, InversionOp.NO_OP - if op_name in PYQIR_TWO_QUBIT_OP_MAP: - return PYQIR_TWO_QUBIT_OP_MAP[op_name], 2, InversionOp.NO_OP - if op_name in PYQIR_THREE_QUBIT_OP_MAP: - return PYQIR_THREE_QUBIT_OP_MAP[op_name], 3, InversionOp.NO_OP - if op_name in PYQIR_U_INV_ROTATION_MAP: - # Special handling for U gate as it is composed of multiple - # basic gates and we need to invert each of them - return PYQIR_U_INV_ROTATION_MAP[op_name], 1, InversionOp.NO_OP - if op_name in PYQIR_ROTATION_INVERSION_ONE_QUBIT_OP_MAP: - return ( - PYQIR_ONE_QUBIT_ROTATION_MAP[op_name], - 1, - InversionOp.INVERT_ROTATION, - ) - raise Qasm3ConversionError(f"Unsupported / undeclared QASM operation: {op_name}") - - -# pylint: disable=inconsistent-return-statements -def qasm_variable_type_cast(openqasm_type, var_name, base_size, rhs_value): - """Cast the variable type to the type to match, if possible. - - Args: - openqasm_type : The type of the variable. - type_of_rhs (type): The type to match. - - Returns: - The casted variable type. - - Raises: - Qasm3ConversionError: If the cast is not possible. - """ - type_of_rhs = type(rhs_value) - - if type_of_rhs not in VARIABLE_TYPE_CAST_MAP[openqasm_type]: - raise Qasm3ConversionError( - f"Cannot cast {type_of_rhs} to {openqasm_type}. " - f"Invalid assignment of type {type_of_rhs} to variable {var_name} " - f"of type {openqasm_type}" - ) - - if openqasm_type == BoolType: - return bool(rhs_value) - if openqasm_type == IntType: - return int(rhs_value) - if openqasm_type == UintType: - return int(rhs_value) % (2**base_size) - if openqasm_type == FloatType: - return float(rhs_value) - # not sure if we wanna hande array bit assignments too. - # For now, we only cater to single bit assignment. - if openqasm_type == BitType: - return bool(rhs_value) - if openqasm_type == AngleType: - return rhs_value # not sure - - -# IEEE 754 Standard for floats -# https://openqasm.com/language/types.html#floating-point-numbers -LIMITS_MAP = {"float_32": 1.70141183 * (10**38), "float_64": 10**308} - CONSTANTS_MAP = { "π": 3.141592653589793, "pi": 3.141592653589793, @@ -656,46 +498,3 @@ def qasm_variable_type_cast(openqasm_type, var_name, base_size, rhs_value): "τ": 6.283185307179586, "tau": 6.283185307179586, } - -VARIABLE_TYPE_MAP = { - BitType: bool, - IntType: int, - UintType: int, - BoolType: bool, - FloatType: float, - ComplexType: complex, - # AngleType: None, # not sure -} - -# Reference: https://openqasm.com/language/types.html#allowed-casts -VARIABLE_TYPE_CAST_MAP = { - BoolType: (int, float, bool, np.int64, np.float64, np.bool_), - IntType: (bool, int, float, np.int64, np.float64, np.bool_), - BitType: (bool, int, np.int64, np.bool_), - UintType: (bool, int, float, np.int64, np.uint64, np.float64, np.bool_), - FloatType: (bool, int, float, np.int64, np.float64, np.bool_), - AngleType: (float, np.float64), -} - -ARRAY_TYPE_MAP = { - BitType: np.bool_, - IntType: np.int64, - UintType: np.uint64, - FloatType: np.float64, - ComplexType: np.complex128, - BoolType: np.bool_, - AngleType: np.float64, -} - - -# Reference : https://openqasm.com/language/types.html#arrays -MAX_ARRAY_DIMENSIONS = 7 - -# Reference : https://openqasm.com/language/classical.html#the-switch-statement -# Paragraph 14 -SWITCH_BLACKLIST_STMTS = { - QubitDeclaration, - ClassicalDeclaration, - SubroutineDefinition, - QuantumGateDefinition, -} diff --git a/qbraid_qir/qasm3/subroutines.py b/qbraid_qir/qasm3/subroutines.py deleted file mode 100644 index f82211a..0000000 --- a/qbraid_qir/qasm3/subroutines.py +++ /dev/null @@ -1,363 +0,0 @@ -# Copyright (C) 2024 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. - -# pylint: disable=too-many-arguments,too-many-locals,too-many-branches - -""" -Module containing the class for validating QASM3 subroutines. - -""" -from typing import Optional, Union - -from openqasm3.ast import ( - AccessControl, - ArrayReferenceType, - Identifier, - IndexExpression, - IntType, - QubitDeclaration, -) - -from .analyzer import Qasm3Analyzer -from .elements import Variable -from .exceptions import raise_qasm3_error -from .expressions import Qasm3ExprEvaluator -from .transformer import Qasm3Transformer -from .validator import Qasm3Validator - - -class Qasm3SubroutineProcessor: - """Class for processing QASM3 subroutines.""" - - visitor_obj = None - - @classmethod - def set_visitor_obj(cls, visitor_obj) -> None: - cls.visitor_obj = visitor_obj - - @staticmethod - def get_fn_actual_arg_name(actual_arg: Union[Identifier, IndexExpression]) -> Optional[str]: - actual_arg_name = None - if isinstance(actual_arg, Identifier): - actual_arg_name = actual_arg.name - elif isinstance(actual_arg, IndexExpression): - if isinstance(actual_arg.collection, Identifier): - actual_arg_name = actual_arg.collection.name - else: - actual_arg_name = ( - actual_arg.collection.collection.name # type: ignore[attr-defined] - ) - return actual_arg_name - - @classmethod - def process_classical_arg(cls, formal_arg, actual_arg, fn_name, span): - actual_arg_name = Qasm3SubroutineProcessor.get_fn_actual_arg_name(actual_arg) - - if isinstance(formal_arg.type, ArrayReferenceType): - return cls._process_classical_arg_by_reference( - formal_arg, actual_arg, actual_arg_name, fn_name, span - ) - return cls._process_classical_arg_by_value( - formal_arg, actual_arg, actual_arg_name, fn_name, span - ) - - @classmethod - def _process_classical_arg_by_value( - cls, formal_arg, actual_arg, actual_arg_name, fn_name, span - ): - """ - Process the classical argument for a function call. - - Args: - formal_arg (FormalArgument): The formal argument of the function. - actual_arg (ActualArgument): The actual argument passed to the function. - actual_arg_name (str): The name of the actual argument. - fn_name (str): The name of the function. - span (Span): The span of the function call. - - Raises: - Qasm3ConversionError: If the actual argument is a qubit register instead - of a classical argument. - Qasm3ConversionError: If the actual argument is an undefined variable. - """ - # 1. variable mapping is equivalent to declaring the variable - # with the formal argument name and doing classical assignment - # in the scope of the function - if actual_arg_name: # actual arg is a variable not literal - if actual_arg_name in cls.visitor_obj._global_qreg_size_map: - raise_qasm3_error( - f"Expecting classical argument for '{formal_arg.name.name}'. " - f"Qubit register '{actual_arg_name}' found for function '{fn_name}'", - span=span, - ) - - # 2. as we have pushed the scope for fn, we need to check in parent - # scope for argument validation - if not cls.visitor_obj._check_in_scope(actual_arg_name): - raise_qasm3_error( - f"Undefined variable '{actual_arg_name}' used" - f" for function call '{fn_name}'", - span=span, - ) - actual_arg_value = Qasm3ExprEvaluator.evaluate_expression(actual_arg) - - # save this value to be updated later in scope - return Variable( - name=formal_arg.name.name, - base_type=formal_arg.type, - base_size=Qasm3ExprEvaluator.evaluate_expression(formal_arg.type.size), - dims=None, - value=actual_arg_value, - is_constant=False, - ) - - @classmethod - def _process_classical_arg_by_reference( - cls, formal_arg, actual_arg, actual_arg_name, fn_name, span - ): - """Process the classical args by reference in the QASM3 visitor. - Currently being used for array references only. - - Args: - formal_arg (Qasm3Expression): The formal argument of the function. - actual_arg (Qasm3Expression): The actual argument passed to the function. - actual_arg_name (str): The name of the actual argument. - fn_name (str): The name of the function. - span (Span): The span of the function call. - - Raises: - Qasm3ConversionError: If the actual argument is - - - not an array. - - an undefined variable. - - a qubit register. - - a literal. - - having type mismatch with the formal argument. - """ - - formal_arg_base_size = Qasm3ExprEvaluator.evaluate_expression( - formal_arg.type.base_type.size - ) - array_expected_type_msg = ( - "Expecting type 'array[" - f"{formal_arg.type.base_type.__class__.__name__.lower().removesuffix('type')}" - f"[{formal_arg_base_size}],...]' for '{formal_arg.name.name}'" - f" in function '{fn_name}'. " - ) - - if actual_arg_name is None: - raise_qasm3_error( - array_expected_type_msg - + f"Literal {Qasm3ExprEvaluator.evaluate_expression(actual_arg)} " - + "found in function call", - span=span, - ) - - if actual_arg_name in cls.visitor_obj._global_qreg_size_map: - raise_qasm3_error( - array_expected_type_msg - + f"Qubit register '{actual_arg_name}' found for function call", - span=span, - ) - - # verify actual argument is defined in the parent scope of function call - if not cls.visitor_obj._check_in_scope(actual_arg_name): - raise_qasm3_error( - f"Undefined variable '{actual_arg_name}' used for function call '{fn_name}'", - span=span, - ) - - array_reference = cls.visitor_obj._get_from_visible_scope(actual_arg_name) - actual_type_string = Qasm3Transformer.get_type_string(array_reference) - - # ensure that actual argument is an array - if not array_reference.dims: - raise_qasm3_error( - array_expected_type_msg - + f"Variable '{actual_arg_name}' has type '{actual_type_string}'.", - span=span, - ) - - # The base types of the elements in array should match - actual_arg_type = array_reference.base_type - actual_arg_size = array_reference.base_size - - if formal_arg.type.base_type != actual_arg_type or formal_arg_base_size != actual_arg_size: - raise_qasm3_error( - array_expected_type_msg - + f"Variable '{actual_arg_name}' has type '{actual_type_string}'.", - span=span, - ) - - # The dimensions passed in the formal arg should be - # within limits of the actual argument - - # need to ensure that we have a positive integer as dimension - actual_dimensions = array_reference.dims - formal_dimensions_raw = formal_arg.type.dimensions - # 1. Either we will have #dim = <> - if not isinstance(formal_dimensions_raw, list): - num_formal_dimensions = Qasm3ExprEvaluator.evaluate_expression( - formal_dimensions_raw, reqd_type=IntType, const_expr=True - ) - # 2. or we will have a list of the dimensions in the formal arg - else: - num_formal_dimensions = len(formal_dimensions_raw) - - if num_formal_dimensions <= 0: - raise_qasm3_error( - f"Invalid number of dimensions {num_formal_dimensions}" - f" for '{formal_arg.name.name}' in function '{fn_name}'", - span=span, - ) - - if num_formal_dimensions > len(actual_dimensions): - raise_qasm3_error( - f"Dimension mismatch for '{formal_arg.name.name}' in function '{fn_name}'. " - f"Expected {num_formal_dimensions} dimensions but" - f" variable '{actual_arg_name}' has {len(actual_dimensions)}", - span=span, - ) - formal_dimensions = [] - - # we need to ensure that the dimensions are within the limits AND valid integers - if not isinstance(formal_dimensions_raw, list): - # the case when we have #dim identifier - formal_dimensions = actual_dimensions[:num_formal_dimensions] - else: - for idx, (formal_dim, actual_dim) in enumerate( - zip(formal_dimensions_raw, actual_dimensions) - ): - formal_dim = Qasm3ExprEvaluator.evaluate_expression( - formal_dim, reqd_type=IntType, const_expr=True - ) - if formal_dim <= 0: - raise_qasm3_error( - f"Invalid dimension size {formal_dim} for '{formal_arg.name.name}'" - f" in function '{fn_name}'", - span=span, - ) - if actual_dim < formal_dim: - raise_qasm3_error( - f"Dimension mismatch for '{formal_arg.name.name}'" - f" in function '{fn_name}'. Expected dimension {idx} with size" - f" >= {formal_dim} but got {actual_dim}", - span=span, - ) - formal_dimensions.append(formal_dim) - - readonly_arr = formal_arg.access == AccessControl.readonly - actual_array_view = array_reference.value - if isinstance(actual_arg, IndexExpression): - _, actual_indices = Qasm3Analyzer.analyze_index_expression(actual_arg) - actual_indices = Qasm3Analyzer.analyze_classical_indices( - actual_indices, array_reference, Qasm3ExprEvaluator - ) - actual_array_view = Qasm3Analyzer.find_array_element( - array_reference.value, actual_indices - ) - - return Variable( - name=formal_arg.name.name, - base_type=formal_arg.type.base_type, - base_size=formal_arg_base_size, - dims=formal_dimensions, - value=actual_array_view, # this is the VIEW of the actual array - readonly=readonly_arr, - ) - - @classmethod - def process_quantum_arg( - cls, - formal_arg, - actual_arg, - formal_qreg_size_map, - duplicate_qubit_map, - qubit_transform_map, - fn_name, - span, - ): - """ - Process a quantum argument in the QASM3 visitor. - - Args: - formal_arg (Qasm3Expression): The formal argument in the function signature. - actual_arg (Qasm3Expression): The actual argument passed to the function. - formal_qreg_size_map (dict): The map of formal quantum register sizes. - duplicate_qubit_map (dict): The map of duplicate qubit registers. - qubit_transform_map (dict): The map of qubit register transformations. - fn_name (str): The name of the function. - span (Span): The span of the function call. - - Returns: - list: The list of actual qubit ids. - - Raises: - Qasm3ConversionError: If there is a mismatch in the quantum register size or - if the actual argument is not a qubit register. - - """ - actual_arg_name = Qasm3SubroutineProcessor.get_fn_actual_arg_name(actual_arg) - formal_reg_name = formal_arg.name.name - formal_qubit_size = Qasm3ExprEvaluator.evaluate_expression( - formal_arg.size, reqd_type=IntType, const_expr=True - ) - if formal_qubit_size is None: - formal_qubit_size = 1 - if formal_qubit_size <= 0: - raise_qasm3_error( - f"Invalid qubit size {formal_qubit_size} for variable '{formal_reg_name}'" - f" in function '{fn_name}'", - span=span, - ) - formal_qreg_size_map[formal_reg_name] = formal_qubit_size - - # we expect that actual arg is qubit type only - # note that we ONLY check in global scope as - # we always map the qubit arguments to the global scope - if actual_arg_name not in cls.visitor_obj._global_qreg_size_map: - raise_qasm3_error( - f"Expecting qubit argument for '{formal_reg_name}'. " - f"Qubit register '{actual_arg_name}' not found for function '{fn_name}'", - span=span, - ) - cls.visitor_obj._label_scope_level[cls.visitor_obj._curr_scope].add(formal_reg_name) - - actual_qids, actual_qubits_size = Qasm3Transformer.get_target_qubits( - actual_arg, cls.visitor_obj._global_qreg_size_map, actual_arg_name - ) - - if formal_qubit_size != actual_qubits_size: - raise_qasm3_error( - f"Qubit register size mismatch for function '{fn_name}'. " - f"Expected {formal_qubit_size} in variable '{formal_reg_name}' " - f"but got {actual_qubits_size}", - span=span, - ) - - if not Qasm3Validator.validate_unique_qubits( - duplicate_qubit_map, actual_arg_name, actual_qids - ): - raise_qasm3_error( - f"Duplicate qubit argument for register '{actual_arg_name}' " - f"in function call for '{fn_name}'", - span=span, - ) - - for idx, qid in enumerate(actual_qids): - qubit_transform_map[(formal_reg_name, idx)] = (actual_arg_name, qid) - - return Variable( - name=formal_reg_name, - base_type=QubitDeclaration, - base_size=formal_qubit_size, - dims=None, - value=None, - is_constant=False, - ) diff --git a/qbraid_qir/qasm3/transformer.py b/qbraid_qir/qasm3/transformer.py deleted file mode 100644 index cb0081f..0000000 --- a/qbraid_qir/qasm3/transformer.py +++ /dev/null @@ -1,347 +0,0 @@ -# Copyright (C) 2024 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 with transformation functions for QASM3 visitor - -""" -from typing import Any, Union - -import numpy as np -from openqasm3.ast import ( - BinaryExpression, - BooleanLiteral, - DiscreteSet, - FloatLiteral, - Identifier, - IndexedIdentifier, - IndexExpression, - IntegerLiteral, - QuantumBarrier, - QuantumGate, - QuantumReset, - RangeDefinition, - UintType, - UnaryExpression, -) - -from .elements import Variable -from .exceptions import raise_qasm3_error -from .expressions import Qasm3ExprEvaluator -from .maps import VARIABLE_TYPE_MAP -from .validator import Qasm3Validator - -# mypy: disable-error-code="attr-defined, union-attr" - - -class Qasm3Transformer: - """Class with utility functions for transforming QASM3 elements""" - - visitor_obj = None - - @classmethod - def set_visitor_obj(cls, visitor_obj) -> None: - cls.visitor_obj = visitor_obj - - @staticmethod - def update_array_element( - multi_dim_arr: np.ndarray, indices: list[tuple[int, int, int]], value: Any - ) -> None: - """Update the value of an array at the specified indices. Single element only. - - Args: - multi_dim_arr (np.ndarray): The multi-dimensional array to update. - indices (list[tuple[int,int,int]]): The indices to update. - value (Any): The value to update. - - Returns: - None - """ - slicing = tuple( - slice(start, stop + 1, step) if start != stop else start - for start, stop, step in indices - ) - multi_dim_arr[slicing] = value - - @staticmethod - def extract_values_from_discrete_set(discrete_set: DiscreteSet) -> list[int]: - """Extract the values from a discrete set. - - Args: - discrete_set (DiscreteSet): The discrete set to extract values from. - - Returns: - list[int]: The extracted values. - """ - values = [] - for value in discrete_set.values: - if not isinstance(value, IntegerLiteral): - raise_qasm3_error( - f"Unsupported discrete set value {value} in discrete set", - span=discrete_set.span, - ) - values.append(value.value) - return values - - @staticmethod - def get_qubits_from_range_definition( - range_def: RangeDefinition, qreg_size: int, is_qubit_reg: bool - ) -> list[int]: - """Get the qubits from a range definition. - Args: - range_def (RangeDefinition): The range definition to get qubits from. - qreg_size (int): The size of the register. - is_qubit_reg (bool): Whether the register is a qubit register. - Returns: - list[int]: The list of qubit identifiers. - """ - start_qid = ( - 0 - if range_def.start is None - else Qasm3ExprEvaluator.evaluate_expression(range_def.start) - ) - end_qid = ( - qreg_size - if range_def.end is None - else Qasm3ExprEvaluator.evaluate_expression(range_def.end) - ) - step = ( - 1 if range_def.step is None else Qasm3ExprEvaluator.evaluate_expression(range_def.step) - ) - Qasm3Validator.validate_register_index(start_qid, qreg_size, qubit=is_qubit_reg) - Qasm3Validator.validate_register_index(end_qid - 1, qreg_size, qubit=is_qubit_reg) - return list(range(start_qid, end_qid, step)) - - @staticmethod - def transform_gate_qubits( - gate_op: QuantumGate, qubit_map: dict[str, IndexedIdentifier] - ) -> None: - """Transform the qubits of a gate operation with a qubit map. - - Args: - gate_op (QuantumGate): The gate operation to transform. - qubit_map (dict[str, IndexedIdentifier]): The qubit map to use for transformation. - - Returns: - None - """ - for i, qubit in enumerate(gate_op.qubits): - if isinstance(qubit, IndexedIdentifier): - raise_qasm3_error( - f"Indexing '{qubit.name.name}' not supported in gate definition", - span=qubit.span, - ) - gate_qubit_name = qubit.name - assert isinstance(gate_qubit_name, str) - gate_op.qubits[i] = qubit_map[gate_qubit_name] - - @staticmethod - def transform_expression(expression, variable_map: dict[str, Union[int, float, bool]]): - """Transform an expression by replacing variables with their values. - - Args: - expression (Any): The expression to transform. - variable_map (dict): The mapping of variables to their values. - - Returns: - expression (Any): The transformed expression. - """ - if expression is None: - return None - - if isinstance(expression, (BooleanLiteral, IntegerLiteral, FloatLiteral)): - return expression - - if isinstance(expression, BinaryExpression): - lhs = Qasm3Transformer.transform_expression(expression.lhs, variable_map) - rhs = Qasm3Transformer.transform_expression(expression.rhs, variable_map) - expression.lhs = lhs - expression.rhs = rhs - - if isinstance(expression, UnaryExpression): - operand = Qasm3Transformer.transform_expression(expression.expression, variable_map) - expression.expression = operand - - if isinstance(expression, Identifier): - if expression.name in variable_map: - value = variable_map[expression.name] - if isinstance(value, int): - return IntegerLiteral(value) - if isinstance(value, float): - return FloatLiteral(value) - if isinstance(value, bool): - return BooleanLiteral(value) - - return expression - - @staticmethod - def transform_gate_params( - gate_op: QuantumGate, param_map: dict[str, Union[int, float, bool]] - ) -> None: - """Transform the parameters of a gate operation with a parameter map. - - Args: - gate_op (QuantumGate): The gate operation to transform. - param_map (dict[str, Union[int, float, bool]]): The parameter map to use - for transformation. - - Returns: - None: arguments are transformed in place - """ - # gate_op.arguments is a list of "actual" arguments used in the gate call inside body - - # param map is a "global dict for this gate" which contains the binding of the params - # to the actual values used in the call - for i, actual_arg in enumerate(gate_op.arguments): - # recursively replace ALL instances of the parameter in the expression - # with the actual value - gate_op.arguments[i] = Qasm3Transformer.transform_expression(actual_arg, param_map) - - @staticmethod - def get_branch_params(condition: Any) -> tuple[int, str]: - """ - Get the branch parameters from the branching condition - - Args: - condition (Any): The condition to analyze - - Returns: - tuple[int, str]: The branch parameters - """ - if isinstance(condition, UnaryExpression): - return ( - condition.expression.index[0].value, - condition.expression.collection.name, - ) - if isinstance(condition, BinaryExpression): - return ( - condition.lhs.index[0].value, - condition.lhs.collection.name, - ) - if isinstance(condition, IndexExpression): - if isinstance(condition.index, DiscreteSet): - raise_qasm3_error( - message="DiscreteSet not supported in branching condition", - span=condition.span, - ) - if isinstance(condition.index, list): - if isinstance(condition.index[0], RangeDefinition): - raise_qasm3_error( - message="RangeDefinition not supported in branching condition", - span=condition.span, - ) - return ( - condition.index[0].value, - condition.collection.name, - ) - # default case - return -1, "" - - @classmethod - def transform_function_qubits( - cls, - q_op: Union[QuantumGate, QuantumBarrier, QuantumReset], - formal_qreg_sizes: dict[str, int], - qubit_map: dict[tuple, tuple], - ) -> list[IndexedIdentifier]: - """Transform the qubits of a function call to the actual qubits. - - Args: - visitor_obj: The visitor object. - gate_op: The quantum operation to transform. - formal_qreg_sizes (dict[str: int]): The formal qubit register sizes. - qubit_map (dict[tuple: tuple]): The mapping of formal qubits to actual qubits. - - Returns: - None - """ - expanded_op_qubits = cls.visitor_obj._get_op_bits(q_op, formal_qreg_sizes, qir_form=False) - - transformed_qubits = [] - for qubit in expanded_op_qubits: - formal_qreg_name = qubit.name.name - formal_qreg_idx = qubit.indices[0][0].value - - # replace the formal qubit with the actual qubit - actual_qreg_name, actual_qreg_idx = qubit_map[(formal_qreg_name, formal_qreg_idx)] - transformed_qubits.append( - IndexedIdentifier( - Identifier(actual_qreg_name), - [[IntegerLiteral(actual_qreg_idx)]], - ) - ) - - return transformed_qubits - - @classmethod - def get_target_qubits( - cls, - target: Union[Identifier, IndexExpression], - qreg_size_map: dict[str, int], - target_name: str, - ) -> tuple: - """Get the target qubits of a statement. - - Args: - target (Any): The target of the statement. - qreg_size_map (dict[str: int]): The quantum register size map. - target_name (str): The name of the register. - - Returns: - tuple: The target qubits. - """ - target_qids = None - target_qubits_size = None - - if isinstance(target, Identifier): # "(q);" - target_qids = list(range(qreg_size_map[target_name])) - target_qubits_size = qreg_size_map[target_name] - - elif isinstance(target, IndexExpression): - if isinstance(target.index, DiscreteSet): # "(q[{0,1}]);" - target_qids = Qasm3Transformer.extract_values_from_discrete_set(target.index) - for qid in target_qids: - Qasm3Validator.validate_register_index( - qid, qreg_size_map[target_name], qubit=True - ) - target_qubits_size = len(target_qids) - elif isinstance(target.index[0], (IntegerLiteral, Identifier)): # "(q[0]); OR (q[i]);" - target_qids = [Qasm3ExprEvaluator.evaluate_expression(target.index[0])] - Qasm3Validator.validate_register_index( - target_qids[0], qreg_size_map[target_name], qubit=True - ) - target_qubits_size = 1 - elif isinstance(target.index[0], RangeDefinition): # "(q[0:1:2]);" - target_qids = Qasm3Transformer.get_qubits_from_range_definition( - target.index[0], - qreg_size_map[target_name], - is_qubit_reg=True, - ) - target_qubits_size = len(target_qids) - return target_qids, target_qubits_size - - @staticmethod - def get_type_string(variable: Variable) -> str: - """Get the type string for a variable.""" - base_type = variable.base_type - base_size = variable.base_size - dims = variable.dims - is_array = dims and len(dims) > 0 - type_str = "" if not is_array else "array[" - - type_str += VARIABLE_TYPE_MAP[base_type.__class__].__name__ - if base_type.__class__ == UintType: - type_str = type_str.replace("int", "uint") - if base_size: - type_str += f"[{base_size}]" - - if is_array: - type_str += f", {', '.join([str(dim) for dim in dims])}]" - return type_str diff --git a/qbraid_qir/qasm3/validator.py b/qbraid_qir/qasm3/validator.py deleted file mode 100644 index 3a0c91a..0000000 --- a/qbraid_qir/qasm3/validator.py +++ /dev/null @@ -1,297 +0,0 @@ -# Copyright (C) 2024 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 with utility functions for QASM3 visitor - -""" -from typing import Any, Optional, Union - -import numpy as np -from openqasm3.ast import ArrayType, ClassicalDeclaration -from openqasm3.ast import IntType as Qasm3IntType -from openqasm3.ast import QuantumGate, QuantumGateDefinition, ReturnStatement, SubroutineDefinition - -from .elements import Variable -from .exceptions import Qasm3ConversionError, raise_qasm3_error -from .maps import LIMITS_MAP, VARIABLE_TYPE_MAP, qasm_variable_type_cast - - -class Qasm3Validator: - """Class with validation functions for QASM3 visitor""" - - @staticmethod - def validate_register_index(index: Optional[int], size: int, qubit: bool = False) -> None: - """Validate the index for a register. - - Args: - index (optional, int): The index to validate. - size (int): The size of the register. - qubit (bool): Whether the register is a qubit register. - - Raises: - Qasm3ConversionError: If the index is out of range. - """ - if index is None or 0 <= index < size: - return None - - raise Qasm3ConversionError( - f"Index {index} out of range for register of size {size} in " - f"{'qubit' if qubit else 'clbit'}" - ) - - @staticmethod - def validate_statement_type(blacklisted_stmts: set, statement: Any, construct: str) -> None: - """Validate the type of a statement. - - Args: - blacklisted_stmts (set): The set of blacklisted statements. - statement (Any): The statement to validate. - construct (str): The construct the statement is in. - - Raises: - Qasm3ConversionError: If the statement is not supported. - """ - stmt_type = statement.__class__ - if stmt_type in blacklisted_stmts: - if stmt_type != ClassicalDeclaration: - raise_qasm3_error( - f"Unsupported statement {stmt_type} in {construct} block", - span=statement.span, - ) - - if statement.type.__class__ == ArrayType: - raise_qasm3_error( - f"Unsupported statement {stmt_type} with {statement.type.__class__}" - f" in {construct} block", - span=statement.span, - ) - - @staticmethod - def validate_variable_type(variable: Optional[Variable], reqd_type: Any) -> bool: - """Validate the type of a variable. - - Args: - variable (Variable): The variable to validate. - reqd_type (Any): The required Qasm3 type of the variable. - - Returns: - bool: True if the variable is of the required type, False otherwise. - """ - if not reqd_type: - return True - if variable is None: - return False - return isinstance(variable.base_type, reqd_type) - - @staticmethod - def validate_variable_assignment_value(variable: Variable, value) -> Any: - """Validate the assignment of a value to a variable. - - Args: - variable (Variable): The variable to assign to. - value (Any): The value to assign. - - Raises: - Qasm3ConversionError: If the value is not of the correct type. - - Returns: - Any: The value casted to the correct type. - """ - # check 1 - type match - qasm_type = variable.base_type.__class__ - base_size = variable.base_size - - try: - type_to_match = VARIABLE_TYPE_MAP[qasm_type] - except KeyError as err: - raise Qasm3ConversionError( - f"Invalid type {qasm_type} for variable {variable.name}" - ) from err - - # For each type we will have a "castable" type set and its corresponding cast operation - type_casted_value = qasm_variable_type_cast(qasm_type, variable.name, base_size, value) - - left: Union[int, float] = 0 - right: Union[int, float] = 0 - # check 2 - range match , if bits mentioned in base size - if type_to_match == int: - base_size = variable.base_size - if qasm_type == Qasm3IntType: - left, right = ( - -1 * (2 ** (base_size - 1)), - 2 ** (base_size - 1) - 1, - ) - else: - # would be uint only so we correctly get this - left, right = 0, 2**base_size - 1 - if type_casted_value < left or type_casted_value > right: - raise_qasm3_error( - f"Value {value} out of limits for variable {variable.name} with " - f"base size {base_size}", - ) - - elif type_to_match == float: - base_size = variable.base_size - - if base_size == 32: - left, right = -1.0 * (LIMITS_MAP["float_32"]), (LIMITS_MAP["float_32"]) - else: - left, right = -1.0 * (LIMITS_MAP["float_64"]), (LIMITS_MAP["float_64"]) - - if type_casted_value < left or type_casted_value > right: - raise_qasm3_error( - f"Value {value} out of limits for variable {variable.name} with " - f"base size {base_size}", - ) - elif type_to_match == bool: - pass - else: - raise_qasm3_error( - f"Invalid type {type_to_match} for variable {variable.name}", TypeError - ) - - return type_casted_value - - @staticmethod - def validate_array_assignment_values( - variable: Variable, dimensions: list[int], values: np.ndarray - ) -> None: - """Validate the assignment of values to an array variable. - - Args: - variable (Variable): The variable to assign to. - dimensions (list[int]): The dimensions of the array. - values (np.ndarray[Any]): The values to assign. - - Raises: - Qasm3ConversionError: If the values are not of the correct type. - """ - # recursively check the array - if values.shape[0] != dimensions[0]: - raise_qasm3_error( - f"Invalid dimensions for array assignment to variable {variable.name}. " - f"Expected {dimensions[0]} but got {values.shape[0]}", - ) - for i, value in enumerate(values): - if isinstance(value, np.ndarray): - Qasm3Validator.validate_array_assignment_values(variable, dimensions[1:], value) - else: - if len(dimensions) != 1: - raise_qasm3_error( - f"Invalid dimensions for array assignment to variable {variable.name}. " - f"Expected {len(dimensions)} but got 1", - ) - values[i] = Qasm3Validator.validate_variable_assignment_value(variable, value) - - @staticmethod - def validate_gate_call( - operation: QuantumGate, - gate_definition: QuantumGateDefinition, - qubits_in_op, - ) -> None: - """Validate the call of a gate operation. - - Args: - operation (QuantumGate): The gate operation to validate. - gate_definition (QuantumGateDefinition): The gate definition to validate against. - qubits_in_op (int): The number of qubits in the operation. - - Raises: - Qasm3ConversionError: If the number of parameters or qubits is invalid. - """ - op_num_args = len(operation.arguments) - gate_def_num_args = len(gate_definition.arguments) - if op_num_args != gate_def_num_args: - s = "" if gate_def_num_args == 1 else "s" - raise_qasm3_error( - f"Parameter count mismatch for gate {operation.name.name}: " - f"expected {gate_def_num_args} argument{s}, but got {op_num_args} instead.", - span=operation.span, - ) - - gate_def_num_qubits = len(gate_definition.qubits) - if qubits_in_op != gate_def_num_qubits: - s = "" if gate_def_num_qubits == 1 else "s" - raise_qasm3_error( - f"Qubit count mismatch for gate {operation.name.name}: " - f"expected {gate_def_num_qubits} qubit{s}, but got {qubits_in_op} instead.", - span=operation.span, - ) - - @staticmethod - def validate_return_statement( # pylint: disable=inconsistent-return-statements - subroutine_def: SubroutineDefinition, - return_statement: ReturnStatement, - return_value: Any, - ): - """Validate the return type of a function. - - Args: - subroutine_def (SubroutineDefinition): The subroutine definition. - return_statement (ReturnStatement): The return statement. - return_value (Any): The return value. - - Raises: - Qasm3ConversionError: If the return type is invalid. - - Returns: - Any: The return value casted to the correct type - """ - - if subroutine_def.return_type is None: - if return_value is not None: - raise_qasm3_error( - f"Return type mismatch for subroutine '{subroutine_def.name.name}'." - f" Expected void but got {type(return_value)}", - span=return_statement.span, - ) - else: - if return_value is None: - raise_qasm3_error( - f"Return type mismatch for subroutine '{subroutine_def.name.name}'." - f" Expected {subroutine_def.return_type} but got void", - span=return_statement.span, - ) - base_size = 1 - if hasattr(subroutine_def.return_type, "size"): - base_size = subroutine_def.return_type.size.value - - return Qasm3Validator.validate_variable_assignment_value( - Variable( - subroutine_def.name.name + "_return", - subroutine_def.return_type, - base_size, - None, - None, - ), - return_value, - ) - - @staticmethod - def validate_unique_qubits(qubit_map: dict, reg_name: str, indices: list) -> bool: - """ - Validates that the qubits in the given register are unique. - - Args: - qubit_map (dict): Dictionary of qubits. - reg_name (str): The name of the register. - indices (list): A list of indices representing the qubits. - - Returns: - bool: True if the qubits are unique, False otherwise. - """ - if reg_name not in qubit_map: - qubit_map[reg_name] = set(indices) - else: - for idx in indices: - if idx in qubit_map[reg_name]: - return False - return True diff --git a/qbraid_qir/qasm3/visitor.py b/qbraid_qir/qasm3/visitor.py index e427043..8a06291 100644 --- a/qbraid_qir/qasm3/visitor.py +++ b/qbraid_qir/qasm3/visitor.py @@ -1,12 +1,12 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. # pylint: disable=too-many-instance-attributes,too-many-lines,too-many-branches @@ -14,51 +14,26 @@ Module defining Qasm3 Visitor. """ -import copy import logging -from abc import ABCMeta, abstractmethod -from collections import deque -from typing import Any, Optional, Union +from typing import Any, Union -import numpy as np import openqasm3.ast as qasm3_ast import pyqir import pyqir._native import pyqir.rt +from openqasm3.ast import UnaryOperator -from .analyzer import Qasm3Analyzer -from .elements import Context, InversionOp, Qasm3Module, Variable -from .exceptions import Qasm3ConversionError, raise_qasm3_error -from .expressions import Qasm3ExprEvaluator -from .maps import ( - ARRAY_TYPE_MAP, - CONSTANTS_MAP, - MAX_ARRAY_DIMENSIONS, - SWITCH_BLACKLIST_STMTS, - map_qasm_inv_op_to_pyqir_callable, - map_qasm_op_to_pyqir_callable, -) -from .subroutines import Qasm3SubroutineProcessor -from .transformer import Qasm3Transformer -from .validator import Qasm3Validator +from .elements import QasmQIRModule +from .exceptions import raise_qasm3_error +from .maps import map_qasm_op_to_pyqir_callable logger = logging.getLogger(__name__) -class ProgramElementVisitor(metaclass=ABCMeta): - @abstractmethod - def visit_register(self, register): - pass +class QasmQIRVisitor: + """A visitor for converting OpenQASM 3 programs to QIR. - @abstractmethod - def visit_statement(self, statement): - pass - - -class BasicQasmVisitor(ProgramElementVisitor): - """A visitor for basic OpenQASM program elements. - - This class is designed to traverse and interact with elements in an OpenQASM program. + This class is designed to traverse and interact with statements in an OpenQASM program. Args: initialize_runtime (bool): If True, quantum runtime will be initialized. Defaults to True. @@ -66,30 +41,23 @@ class BasicQasmVisitor(ProgramElementVisitor): """ def __init__( - self, initialize_runtime: bool = True, record_output: bool = True, check_only: bool = False + self, + initialize_runtime: bool = True, + record_output: bool = True, ): - self._module: pyqir.Module + self._llvm_module: pyqir.Module self._builder: pyqir.Builder self._entry_point: str = "" - self._scope: deque = deque([{}]) - self._context: deque = deque([Context.GLOBAL]) self._qubit_labels: dict[str, int] = {} self._clbit_labels: dict[str, int] = {} self._global_qreg_size_map: dict[str, int] = {} - self._function_qreg_size_map: deque = deque([]) # for nested functions - self._function_qreg_transform_map: deque = deque([]) # for nested functions self._global_creg_size_map: dict[str, int] = {} self._custom_gates: dict[str, qasm3_ast.QuantumGateDefinition] = {} - self._subroutine_defns: dict[str, qasm3_ast.SubroutineDefinition] = {} + self._barrier_qubits: set[pyqir.Constant] = set() self._initialize_runtime: bool = initialize_runtime self._record_output: bool = record_output - self._check_only: bool = check_only - self._curr_scope: int = 0 - self._label_scope_level: dict[int, set] = {self._curr_scope: set()} - self._init_utilities() - - def visit_qasm3_module(self, module: Qasm3Module) -> None: + def visit_qasm3_module(self, module: QasmQIRModule) -> None: """ Visit a Qasm3 module. @@ -99,10 +67,13 @@ def visit_qasm3_module(self, module: Qasm3Module) -> None: Returns: None """ - logger.debug("Visiting Qasm3 module '%s' (%d)", module.name, module.num_qubits) - self._module = module.module - context = self._module.context - entry = pyqir.entry_point(self._module, module.name, module.num_qubits, module.num_clbits) + qasm3_module = module.qasm_program + logger.debug("Visiting Qasm3 module '%s' (%d)", module.name, qasm3_module.num_qubits) + self._llvm_module = module.llvm_module + context = self._llvm_module.context + entry = pyqir.entry_point( + self._llvm_module, module.name, qasm3_module.num_qubits, qasm3_module.num_clbits + ) self._entry_point = entry.name self._builder = pyqir.Builder(context) @@ -113,199 +84,26 @@ def visit_qasm3_module(self, module: Qasm3Module) -> None: nullptr = pyqir.Constant.null(i8p) pyqir.rt.initialize(self._builder, nullptr) - def _init_utilities(self): - """Initialize the utilities for the visitor.""" - for class_obj in [Qasm3Transformer, Qasm3ExprEvaluator, Qasm3SubroutineProcessor]: - class_obj.set_visitor_obj(self) - @property def entry_point(self) -> str: return self._entry_point def finalize(self) -> None: + self._check_and_apply_barrier() # to check if we have an incomplete barrier at program end self._builder.ret(None) - def _push_scope(self, scope: dict) -> None: - if not isinstance(scope, dict): - raise TypeError("Scope must be a dictionary") - self._scope.append(scope) - - def _push_context(self, context: Context) -> None: - if not isinstance(context, Context): - raise TypeError("Context must be an instance of Context") - self._context.append(context) - - def _pop_scope(self) -> None: - if len(self._scope) == 0: - raise IndexError("Scope list is empty, can not pop") - self._scope.pop() - - def _restore_context(self) -> None: - if len(self._context) == 0: - raise IndexError("Context list is empty, can not pop") - self._context.pop() - - def _get_parent_scope(self) -> dict: - if len(self._scope) < 2: - raise IndexError("Parent scope not available") - return self._scope[-2] - - def _get_curr_scope(self) -> dict: - if len(self._scope) == 0: - raise IndexError("No scopes available to get") - return self._scope[-1] - - def _get_curr_context(self) -> Context: - if len(self._context) == 0: - raise IndexError("No context available to get") - return self._context[-1] - - def _get_global_scope(self) -> dict: - if len(self._scope) == 0: - raise IndexError("No scopes available to get") - return self._scope[0] - - def _check_in_scope(self, var_name: str) -> bool: - """ - Checks if a variable is in scope. - - Args: - var_name (str): The name of the variable to check. - - Returns: - bool: True if the variable is in scope, False otherwise. - - NOTE: - - - According to our definition of scope, we have a NEW DICT - for each block scope also - - Since all visible variables of the immediate parent are visible - inside block scope, we have to check till we reach the boundary - contexts - - The "boundary" for a scope is either a FUNCTION / GATE context - OR the GLOBAL context - - Why then do we need a new scope for a block? - - Well, if the block redeclares a variable in its scope, then the - variable in the parent scope is shadowed. We need to remember the - original value of the shadowed variable when we exit the block scope - - """ - global_scope = self._get_global_scope() - curr_scope = self._get_curr_scope() - if self._in_global_scope(): - return var_name in global_scope - if self._in_function_scope() or self._in_gate_scope(): - if var_name in curr_scope: - return True - if var_name in global_scope: - return global_scope[var_name].is_constant - if self._in_block_scope(): - for scope, context in zip(reversed(self._scope), reversed(self._context)): - if context != Context.BLOCK: - return var_name in scope - if var_name in scope: - return True - return False - - def _get_from_visible_scope(self, var_name: str) -> Union[Variable, None]: - """ - Retrieves a variable from the visible scope. - - Args: - var_name (str): The name of the variable to retrieve. - - Returns: - Union[Variable, None]: The variable if found, None otherwise. - """ - global_scope = self._get_global_scope() - curr_scope = self._get_curr_scope() - - if self._in_global_scope(): - return global_scope.get(var_name, None) - if self._in_function_scope() or self._in_gate_scope(): - if var_name in curr_scope: - return curr_scope[var_name] - if var_name in global_scope and global_scope[var_name].is_constant: - return global_scope[var_name] - if self._in_block_scope(): - for scope, context in zip(reversed(self._scope), reversed(self._context)): - if context != Context.BLOCK: - return scope.get(var_name, None) - if var_name in scope: - return scope[var_name] - # keep on checking - return None - - def _add_var_in_scope(self, variable: Variable) -> None: - """Add a variable to the current scope. - - Args: - variable (Variable): The variable to add. - - Raises: - ValueError: If the variable already exists in the current scope. - """ - curr_scope = self._get_curr_scope() - if variable.name in curr_scope: - raise ValueError(f"Variable '{variable.name}' already exists in current scope") - curr_scope[variable.name] = variable - - def _update_var_in_scope(self, variable: Variable) -> None: - """ - Updates the variable in the current scope. - - Args: - variable (Variable): The variable to be updated. - - Raises: - ValueError: If no scope is available to update. - """ - if len(self._scope) == 0: - raise ValueError("No scope available to update") - - global_scope = self._get_global_scope() - curr_scope = self._get_curr_scope() - - if self._in_global_scope(): - global_scope[variable.name] = variable - if self._in_function_scope() or self._in_gate_scope(): - curr_scope[variable.name] = variable - if self._in_block_scope(): - for scope, context in zip(reversed(self._scope), reversed(self._context)): - if context != Context.BLOCK: - scope[variable.name] = variable - break - if variable.name in scope: - scope[variable.name] = variable - break - continue - - def _in_global_scope(self) -> bool: - return len(self._scope) == 1 and self._get_curr_context() == Context.GLOBAL - - def _in_function_scope(self) -> bool: - return len(self._scope) > 1 and self._get_curr_context() == Context.FUNCTION - - def _in_gate_scope(self) -> bool: - return len(self._scope) > 1 and self._get_curr_context() == Context.GATE - - def _in_block_scope(self) -> bool: # block scope is for if/else/for/while constructs - return len(self._scope) > 1 and self._get_curr_context() == Context.BLOCK - - def record_output(self, module: Qasm3Module) -> None: - if self._record_output is False or self._check_only: + def record_output(self, module: QasmQIRModule) -> None: + if self._record_output is False: return - - i8p = pyqir.PointerType(pyqir.IntType(self._module.context, 8)) - - for i in range(module.num_qubits): - result_ref = pyqir.result(self._module.context, i) + i8p = pyqir.PointerType(pyqir.IntType(self._llvm_module.context, 8)) + for i in range(module.qasm_program.num_qubits): + result_ref = pyqir.result(self._llvm_module.context, i) pyqir.rt.result_record_output(self._builder, result_ref, pyqir.Constant.null(i8p)) - def visit_register( + def _visit_register( self, register: Union[qasm3_ast.QubitDeclaration, qasm3_ast.ClassicalDeclaration] ) -> None: - """Visit a register element. + """Visit a register statement. Args: register (QubitDeclaration|ClassicalDeclaration): The register name and size. @@ -336,66 +134,24 @@ def visit_register( size_map = self._global_qreg_size_map if is_qubit else self._global_creg_size_map label_map = self._qubit_labels if is_qubit else self._clbit_labels - if self._check_in_scope(register_name): - raise_qasm3_error( - f"Invalid declaration of register with name '{register_name}'", span=register.span - ) - - if is_qubit: # as bit type vars are added in classical decl handler - self._add_var_in_scope( - Variable( - register_name, - qasm3_ast.QubitDeclaration, - register_size, - None, - None, - False, - ) - ) - for i in range(register_size): - # required if indices are not used while applying a gate or measurement size_map[f"{register_name}"] = register_size label_map[f"{register_name}_{i}"] = current_size + i - self._label_scope_level[self._curr_scope].add(register_name) - logger.debug("Added labels for register '%s'", str(register)) - def _check_if_name_in_scope(self, name: str, operation: Any) -> None: - """Check if a name is in scope to avoid duplicate declarations. - Args: - name (str): The name to check. - operation (Any): The operation to check the name in scope for. - - Returns: - bool: Whether the name is in scope. - """ - for scope_level in range(0, self._curr_scope + 1): - if name in self._label_scope_level[scope_level]: - return - raise_qasm3_error( - f"Variable {name} not in scope for operation {operation}", span=operation.span - ) - - def _get_op_bits( - self, operation: Any, reg_size_map: dict, qubits: bool = True, qir_form: bool = True - ) -> Union[list[pyqir.Constant], list[qasm3_ast.IndexedIdentifier]]: + def _get_op_bits(self, operation: Any, qubits: bool = True) -> list[pyqir.Constant]: """Get the quantum / classical bits for the operation. Args: operation (Any): The operation to get qubits for. reg_size_map (dict): The size map of the registers in scope. qubits (bool): Whether the bits are quantum bits or classical bits. Defaults to True. - qir_form (bool): Whether to return bits in QIR form or not. Defaults to True. Returns: - Union[list[pyqir.Constant], list[qasm3_ast.IndexedIdentifier]] : The bits for the - operation. + Unionlist[pyqir.Constant] : The bits for the operation. """ qir_bits = [] - openqasm_bits = [] - visited_bits = set() bit_list = [] if isinstance(operation, qasm3_ast.QuantumMeasurementStatement): assert operation.target is not None @@ -406,74 +162,31 @@ def _get_op_bits( ) for bit in bit_list: - if isinstance(bit, qasm3_ast.IndexedIdentifier): - reg_name = bit.name.name - else: - reg_name = bit.name - - if reg_name not in reg_size_map: - raise_qasm3_error( - f"Missing register declaration for {reg_name} in operation {operation}", - span=operation.span, - ) - self._check_if_name_in_scope(reg_name, operation) - - if isinstance(bit, qasm3_ast.IndexedIdentifier): - if isinstance(bit.indices[0], qasm3_ast.DiscreteSet): - bit_ids = Qasm3Transformer.extract_values_from_discrete_set(bit.indices[0]) - elif isinstance(bit.indices[0][0], qasm3_ast.RangeDefinition): - bit_ids = Qasm3Transformer.get_qubits_from_range_definition( - bit.indices[0][0], reg_size_map[reg_name], is_qubit_reg=qubits + # as we have unrolled qasm3, we can assume that the bit is an IndexedIdentifier + assert isinstance(bit, qasm3_ast.IndexedIdentifier) + reg_name = bit.name.name + + assert isinstance(bit.indices, list) and len(bit.indices) == 1 + assert isinstance(bit.indices[0], list) and len(bit.indices[0]) == 1 + assert isinstance(bit.indices[0][0], qasm3_ast.IntegerLiteral) + bit_id = bit.indices[0][0].value + bit_ids = [bit_id] + + label_map = self._qubit_labels if qubits else self._clbit_labels + reg_ids = [label_map[f"{reg_name}_{bit_id}"] for bit_id in bit_ids] + + qir_bits.extend( + [ + ( + pyqir.qubit(self._llvm_module.context, bit_id) + if qubits + else pyqir.result(self._llvm_module.context, bit_id) ) - else: - bit_id = Qasm3ExprEvaluator.evaluate_expression(bit.indices[0][0]) - Qasm3Validator.validate_register_index( - bit_id, reg_size_map[reg_name], qubit=qubits - ) - bit_ids = [bit_id] - openqasm_bits.extend( - [ - qasm3_ast.IndexedIdentifier( - qasm3_ast.Identifier(reg_name), [[qasm3_ast.IntegerLiteral(bit_id)]] - ) - for bit_id in bit_ids - ] - ) + for bit_id in reg_ids + ] + ) - else: - bit_ids = list(range(reg_size_map[reg_name])) - openqasm_bits.extend( - [ - qasm3_ast.IndexedIdentifier( - qasm3_ast.Identifier(reg_name), [[qasm3_ast.IntegerLiteral(bit_id)]] - ) - for bit_id in bit_ids - ] - ) - - if qir_form: - label_map = self._qubit_labels if qubits else self._clbit_labels - reg_ids = [label_map[f"{reg_name}_{bit_id}"] for bit_id in bit_ids] - for bit_id in reg_ids: - if bit_id in visited_bits: - raise_qasm3_error( - f"Duplicate {'qubit' if qubits else 'clbit'} " - f"{reg_name}[{bit_id}] argument", - span=operation.span, - ) - visited_bits.add(bit_id) - qir_bits.extend( - [ - ( - pyqir.qubit(self._module.context, bit_id) - if qubits - else pyqir.result(self._module.context, bit_id) - ) - for bit_id in reg_ids - ] - ) - - return qir_bits if qir_form else openqasm_bits + return qir_bits def _visit_measurement(self, statement: qasm3_ast.QuantumMeasurementStatement) -> None: """Visit a measurement statement element. @@ -489,44 +202,10 @@ def _visit_measurement(self, statement: qasm3_ast.QuantumMeasurementStatement) - source = statement.measure.qubit target = statement.target assert source and target - - # # TODO: handle in-function measurements - source_name: str = ( - source.name if isinstance(source, qasm3_ast.Identifier) else source.name.name - ) - if source_name not in self._global_qreg_size_map: - raise_qasm3_error( - f"Missing register declaration for {source_name} in measurement " - f"operation {statement}", - span=statement.span, - ) - - target_name: str = ( - target.name if isinstance(target, qasm3_ast.Identifier) else target.name.name - ) - if target_name not in self._global_creg_size_map: - raise_qasm3_error( - f"Missing register declaration for {target_name} in measurement " - f"operation {statement}", - span=statement.span, - ) - - source_ids = self._get_op_bits( - statement, reg_size_map=self._global_qreg_size_map, qubits=True - ) - target_ids = self._get_op_bits( - statement, reg_size_map=self._global_creg_size_map, qubits=False - ) - - if len(source_ids) != len(target_ids): - raise_qasm3_error( - f"Register sizes of {source_name} and {target_name} do not match " - "for measurement operation", - span=statement.span, - ) - if not self._check_only: - for src_id, tgt_id in zip(source_ids, target_ids): - pyqir._native.mz(self._builder, src_id, tgt_id) # type: ignore[arg-type] + source_ids = self._get_op_bits(statement, qubits=True) + target_ids = self._get_op_bits(statement, qubits=False) + for src_id, tgt_id in zip(source_ids, target_ids): + pyqir._native.mz(self._builder, src_id, tgt_id) # type: ignore[arg-type] def _visit_reset(self, statement: qasm3_ast.QuantumReset) -> None: """Visit a reset statement element. @@ -538,56 +217,59 @@ def _visit_reset(self, statement: qasm3_ast.QuantumReset) -> None: None """ logger.debug("Visiting reset statement '%s'", str(statement)) - if len(self._function_qreg_size_map) > 0: # atleast in SOME function scope - # transform qubits to use the global qreg identifiers - statement.qubits = ( - Qasm3Transformer.transform_function_qubits( # type: ignore[assignment] - statement, - self._function_qreg_size_map[-1], - self._function_qreg_transform_map[-1], - ) - ) - qubit_ids = self._get_op_bits(statement, self._global_qreg_size_map, True) + qubit_ids = self._get_op_bits(statement, True) - if not self._check_only: - for qid in qubit_ids: - # qid is of type Constant which is inherited from Value, so we ignore the type error - pyqir._native.reset(self._builder, qid) # type: ignore[arg-type] + for qid in qubit_ids: + # qid is of type Constant which is inherited from Value, so we ignore the type error + pyqir._native.reset(self._builder, qid) # type: ignore[arg-type] - def _visit_barrier(self, barrier: qasm3_ast.QuantumBarrier) -> None: - """Visit a barrier statement element. + def _barrier_applicable(self) -> bool: + """Check if the barrier operation is applicable. Args: - statement (qasm3_ast.QuantumBarrier): The barrier statement to visit. + None Returns: - None + bool: Whether the barrier operation is applicable. """ - # if barrier is applied to ALL qubits at once, we are fine - if len(self._function_qreg_size_map) > 0: # atleast in SOME function scope - # transform qubits to use the global qreg identifiers - - # since we are changing the qubits to IndexedIdentifiers, we need to supress the - # error for the type checker - barrier.qubits = ( - Qasm3Transformer.transform_function_qubits( # type: ignore [assignment] - barrier, - self._function_qreg_size_map[-1], - self._function_qreg_transform_map[-1], - ) - ) - barrier_qubits = self._get_op_bits(barrier, self._global_qreg_size_map) total_qubit_count = sum(self._global_qreg_size_map.values()) - if len(barrier_qubits) == total_qubit_count: - if not self._check_only: - pyqir._native.barrier(self._builder) + return len(self._barrier_qubits) == total_qubit_count + + def _check_and_apply_barrier(self) -> None: + """Apply the barrier operation. + + Returns: + None + """ + if len(self._barrier_qubits) == 0: + return + + if self._barrier_applicable(): + pyqir._native.barrier(self._builder) + self._barrier_qubits.clear() else: raise_qasm3_error( "Barrier operation on a qubit subset is not supported in pyqir", err_type=NotImplementedError, - span=barrier.span, ) + # pylint: disable=unused-argument + def _visit_barrier(self, barrier: qasm3_ast.QuantumBarrier) -> None: + """Visit a barrier statement element. + + Args: + statement (qasm3_ast.QuantumBarrier): The barrier statement to visit. + Returns: + None + """ + barrier_qubit = self._get_op_bits(barrier, qubits=True) + self._barrier_qubits.update(barrier_qubit) + + # try to apply barrier in case all qubits are covered here itself + if self._barrier_applicable(): + pyqir._native.barrier(self._builder) + self._barrier_qubits.clear() + def _get_op_parameters(self, operation: qasm3_ast.QuantumGate) -> list[float]: """Get the parameters for the operation. @@ -599,42 +281,18 @@ def _get_op_parameters(self, operation: qasm3_ast.QuantumGate) -> list[float]: """ param_list = [] for param in operation.arguments: - param_value = Qasm3ExprEvaluator.evaluate_expression(param) + assert hasattr(param, "value") + param_value = param.value param_list.append(param_value) return param_list - def _visit_gate_definition(self, definition: qasm3_ast.QuantumGateDefinition) -> None: - """Visit a gate definition element. - - Args: - definition (qasm3_ast.QuantumGateDefinition): The gate definition to visit. - - Returns: - None - """ - gate_name = definition.name.name - if gate_name in self._custom_gates: - raise_qasm3_error(f"Duplicate gate definition for {gate_name}", span=definition.span) - self._custom_gates[gate_name] = definition - - def _visit_basic_gate_operation( - self, operation: qasm3_ast.QuantumGate, inverse: bool = False - ) -> None: + def _visit_basic_gate_operation(self, operation: qasm3_ast.QuantumGate) -> None: """Visit a gate operation element. Args: operation (qasm3_ast.QuantumGate): The gate operation to visit. - inverse (bool): Whether the operation is an inverse operation. Defaults to False. - - - if inverse is True, we apply check for different cases in the - map_qasm_inv_op_to_pyqir_callable method. - - Only rotation and S / T gates are affected by this inversion. For S/T - gates we map them to Sdg / Tdg and vice versa. - - - For rotation gates, we map to the same gates but invert the rotation - angles. Returns: None @@ -646,142 +304,20 @@ def _visit_basic_gate_operation( logger.debug("Visiting basic gate operation '%s'", str(operation)) op_name: str = operation.name.name - op_qubits = self._get_op_bits(operation, self._global_qreg_size_map) - inverse_action = None - if not inverse: - qir_func, op_qubit_count = map_qasm_op_to_pyqir_callable(op_name) - else: - # in basic gates, inverse action only affects the rotation gates - qir_func, op_qubit_count, inverse_action = map_qasm_inv_op_to_pyqir_callable(op_name) - + op_qubits = self._get_op_bits(operation) + qir_func, op_qubit_count = map_qasm_op_to_pyqir_callable(op_name) op_parameters = None - if len(op_qubits) % op_qubit_count != 0: - raise_qasm3_error( - f"Invalid number of qubits {len(op_qubits)} for operation {operation.name.name}", - span=operation.span, - ) - if len(operation.arguments) > 0: # parametric gate op_parameters = self._get_op_parameters(operation) - if inverse_action == InversionOp.INVERT_ROTATION: - op_parameters = [-1 * param for param in op_parameters] - - if not self._check_only: - for i in range(0, len(op_qubits), op_qubit_count): - # we apply the gate on the qubit subset linearly - qubit_subset = op_qubits[i : i + op_qubit_count] - if op_parameters is not None: - qir_func(self._builder, *op_parameters, *qubit_subset) - else: - qir_func(self._builder, *qubit_subset) - - def _visit_custom_gate_operation( - self, operation: qasm3_ast.QuantumGate, inverse: bool = False - ) -> None: - """Visit a custom gate operation element recursively. - Args: - operation (qasm3_ast.QuantumGate): The gate operation to visit. - inverse (bool): Whether the operation is an inverse operation. Defaults to False. - - If True, the gate operation is applied in reverse order and the - inverse modifier is appended to each gate call. - See https://openqasm.com/language/gates.html#inverse-modifier - for more clarity. - - Returns: - None - """ - logger.debug("Visiting custom gate operation '%s'", str(operation)) - gate_name: str = operation.name.name - gate_definition: qasm3_ast.QuantumGateDefinition = self._custom_gates[gate_name] - op_qubits: list[qasm3_ast.IndexedIdentifier] = ( - self._get_op_bits( # type: ignore [assignment] - operation, self._global_qreg_size_map, qir_form=False - ) - ) - - Qasm3Validator.validate_gate_call(operation, gate_definition, len(op_qubits)) - # we need this because the gates applied inside a gate definition use the - # VARIABLE names and not the qubits - - # so we need to update the arguments of these gate applications with the actual - # qubit identifiers and then RECURSIVELY call the visit_generic_gate_operation - qubit_map = { - formal_arg.name: actual_arg - for formal_arg, actual_arg in zip(gate_definition.qubits, op_qubits) - } - param_map = { - formal_arg.name: Qasm3ExprEvaluator.evaluate_expression(actual_arg) - for formal_arg, actual_arg in zip(gate_definition.arguments, operation.arguments) - } - - gate_definition_ops = copy.deepcopy(gate_definition.body) - if inverse: - gate_definition_ops.reverse() - - self._push_context(Context.GATE) - - for gate_op in gate_definition_ops: - if isinstance(gate_op, qasm3_ast.QuantumGate): - # necessary to avoid modifying the original gate definition - # in case the gate is reapplied - gate_op_copy = copy.deepcopy(gate_op) - if gate_op.name.name == gate_name: - raise_qasm3_error( - f"Recursive definitions not allowed for gate {gate_name}", span=gate_op.span - ) - Qasm3Transformer.transform_gate_params(gate_op_copy, param_map) - Qasm3Transformer.transform_gate_qubits(gate_op_copy, qubit_map) - # need to trickle the inverse down to the child gates - if inverse: - # span doesn't matter as we don't analyze it - gate_op_copy.modifiers.append( - qasm3_ast.QuantumGateModifier(qasm3_ast.GateModifierName.inv, None) - ) - self._visit_generic_gate_operation(gate_op_copy) + for i in range(0, len(op_qubits), op_qubit_count): + # we apply the gate on the qubit subset linearly + qubit_subset = op_qubits[i : i + op_qubit_count] + if op_parameters is not None: + qir_func(self._builder, *op_parameters, *qubit_subset) else: - # TODO: add control flow support - raise_qasm3_error( - f"Unsupported gate definition statement {gate_op}", span=gate_op.span - ) - - self._restore_context() - - def _collapse_gate_modifiers(self, operation: qasm3_ast.QuantumGate) -> tuple: - """Collapse the gate modifiers of a gate operation. - Some analysis is required to get this result. - The basic idea is that any power operation is multiplied and inversions are toggled. - The placement of the inverse operation does not matter. - - Args: - operation (qasm3_ast.QuantumGate): The gate operation to collapse modifiers for. - - Returns: - tuple[Any, Any]: The power and inverse values of the gate operation. - """ - power_value, inverse_value = 1, False - - for modifier in operation.modifiers: - modifier_name = modifier.modifier - if modifier_name == qasm3_ast.GateModifierName.pow and modifier.argument is not None: - current_power = Qasm3ExprEvaluator.evaluate_expression(modifier.argument) - if current_power < 0: - inverse_value = not inverse_value - power_value = power_value * abs(current_power) - elif modifier_name == qasm3_ast.GateModifierName.inv: - inverse_value = not inverse_value - elif modifier_name in [ - qasm3_ast.GateModifierName.ctrl, - qasm3_ast.GateModifierName.negctrl, - ]: - raise_qasm3_error( - f"Controlled modifier gates not yet supported in gate operation {operation}", - err_type=NotImplementedError, - span=operation.span, - ) - return (power_value, inverse_value) + qir_func(self._builder, *qubit_subset) def _visit_generic_gate_operation(self, operation: qasm3_ast.QuantumGate) -> None: """Visit a gate operation element. @@ -792,262 +328,48 @@ def _visit_generic_gate_operation(self, operation: qasm3_ast.QuantumGate) -> Non Returns: None """ - power_value, inverse_value = self._collapse_gate_modifiers(operation) - operation = copy.deepcopy(operation) - - # only needs to be done once for a gate operation - if not self._in_gate_scope() and len(self._function_qreg_size_map) > 0: - # we are in SOME function scope - # transform qubits to use the global qreg identifiers - operation.qubits = ( - Qasm3Transformer.transform_function_qubits( # type: ignore [assignment] - operation, - self._function_qreg_size_map[-1], - self._function_qreg_transform_map[-1], - ) - ) - # Applying the inverse first and then the power is same as - # apply the power first and then inverting the result - for _ in range(power_value): - if operation.name.name in self._custom_gates: - self._visit_custom_gate_operation(operation, inverse_value) - else: - self._visit_basic_gate_operation(operation, inverse_value) + # TODO: maybe needs to be extended for custom gates + self._visit_basic_gate_operation(operation) - def _visit_constant_declaration(self, statement: qasm3_ast.ConstantDeclaration) -> None: + def _get_branch_params(self, condition: Any) -> tuple[str, int, bool]: """ - Visit a constant declaration element. Const can only be declared for scalar - type variables and not arrays. Assignment is mandatory in constant declaration. + Get the branch parameters from the branching condition Args: - statement (qasm3_ast.ConstantDeclaration): The constant declaration to visit. + condition (Any): The condition to analyze Returns: - None - """ - - var_name = statement.identifier.name - - if var_name in CONSTANTS_MAP: - raise_qasm3_error( - f"Can not declare variable with keyword name {var_name}", span=statement.span - ) - if self._check_in_scope(var_name): - raise_qasm3_error(f"Re-declaration of variable {var_name}", span=statement.span) - init_value = Qasm3ExprEvaluator.evaluate_expression( - statement.init_expression, const_expr=True - ) - - base_type = statement.type - if isinstance(base_type, qasm3_ast.BoolType): - base_size = 1 - elif hasattr(base_type, "size"): - if base_type.size is None: - base_size = 32 # default for now - else: - base_size = Qasm3ExprEvaluator.evaluate_expression(base_type.size, const_expr=True) - if not isinstance(base_size, int) or base_size <= 0: - raise_qasm3_error( - f"Invalid base size {base_size} for variable {var_name}", - span=statement.span, - ) - - variable = Variable(var_name, base_type, base_size, [], init_value, is_constant=True) - - # cast + validation - variable.value = Qasm3Validator.validate_variable_assignment_value(variable, init_value) - - self._add_var_in_scope(variable) - - # pylint: disable=too-many-branches - def _visit_classical_declaration(self, statement: qasm3_ast.ClassicalDeclaration) -> None: - """Visit a classical operation element. - - Args: - statement (ClassicalType): The classical operation to visit. - - Returns: - None - """ - var_name = statement.identifier.name - if var_name in CONSTANTS_MAP: - raise_qasm3_error( - f"Can not declare variable with keyword name {var_name}", span=statement.span - ) - if self._check_in_scope(var_name): - if self._in_block_scope() and var_name not in self._get_curr_scope(): - # we can re-declare variables once in block scope even if they are - # present in the parent scope - # Eg. - # int a = 10; - # { int a = 20; // valid - # } - pass - else: - raise_qasm3_error(f"Re-declaration of variable {var_name}", span=statement.span) - - init_value = None - base_type = statement.type - final_dimensions = [] - - if isinstance(base_type, qasm3_ast.ArrayType): - dimensions = base_type.dimensions - - if len(dimensions) > MAX_ARRAY_DIMENSIONS: - raise_qasm3_error( - f"Invalid dimensions {len(dimensions)} for array declaration for {var_name}. " - f"Max allowed dimensions is {MAX_ARRAY_DIMENSIONS}", - span=statement.span, - ) - - base_type = base_type.base_type - num_elements = 1 - for dim in dimensions: - dim_value = Qasm3ExprEvaluator.evaluate_expression(dim) - if not isinstance(dim_value, int) or dim_value <= 0: - raise_qasm3_error( - f"Invalid dimension size {dim_value} in array declaration for {var_name}", - span=statement.span, - ) - final_dimensions.append(dim_value) - num_elements *= dim_value - - init_value = np.full(final_dimensions, None) - - if statement.init_expression: - if isinstance(statement.init_expression, qasm3_ast.ArrayLiteral): - init_value = self._evaluate_array_initialization( - statement.init_expression, final_dimensions, base_type - ) - else: - init_value = Qasm3ExprEvaluator.evaluate_expression(statement.init_expression) - base_size = 1 - if not isinstance(base_type, qasm3_ast.BoolType): - base_size = ( - 32 - if not hasattr(base_type, "size") or base_type.size is None - else Qasm3ExprEvaluator.evaluate_expression(base_type.size) - ) - - if not isinstance(base_size, int) or base_size <= 0: - raise_qasm3_error( - f"Invalid base size {base_size} for variable {var_name}", span=statement.span - ) - - if isinstance(base_type, qasm3_ast.FloatType) and base_size not in [32, 64]: - raise_qasm3_error( - f"Invalid base size {base_size} for float variable {var_name}", span=statement.span - ) - - variable = Variable(var_name, base_type, base_size, final_dimensions, init_value) - - if statement.init_expression: - if isinstance(init_value, np.ndarray): - assert variable.dims is not None - Qasm3Validator.validate_array_assignment_values(variable, variable.dims, init_value) - else: - variable.value = Qasm3Validator.validate_variable_assignment_value( - variable, init_value - ) - - self._add_var_in_scope(variable) - - def _visit_classical_assignment(self, statement: qasm3_ast.ClassicalAssignment) -> None: - """Visit a classical assignment element. - - Args: - statement (qasm3_ast.ClassicalAssignment): The classical assignment to visit. - - Returns: - None - """ - lvalue = statement.lvalue - lvar_name = lvalue.name - if isinstance(lvar_name, qasm3_ast.Identifier): - lvar_name = lvar_name.name - - lvar = self._get_from_visible_scope(lvar_name) - if lvar is None: # we check for none here, so type errors are irrelevant afterwards - raise_qasm3_error(f"Undefined variable {lvar_name} in assignment", span=statement.span) - if lvar.is_constant: # type: ignore[union-attr] - raise_qasm3_error( - f"Assignment to constant variable {lvar_name} not allowed", span=statement.span - ) - - # rvalue will be an evaluated value (scalar, list) - # if rvalue is a list, we want a copy of it - rvalue = statement.rvalue - rvalue_raw = Qasm3ExprEvaluator.evaluate_expression( - rvalue - ) # consists of scope check and index validation - - # cast + validation - # rhs is a scalar - rvalue_eval = None - if not isinstance(rvalue_raw, np.ndarray): - rvalue_eval = Qasm3Validator.validate_variable_assignment_value( - lvar, rvalue_raw # type: ignore[arg-type] - ) - else: # rhs is a list - rvalue_dimensions = list(rvalue_raw.shape) - - # validate that the values inside rvar are valid for lvar - Qasm3Validator.validate_array_assignment_values( - variable=lvar, # type: ignore[arg-type] - dimensions=rvalue_dimensions, - values=rvalue_raw, # type: ignore[arg-type] - ) - rvalue_eval = rvalue_raw - - if lvar.readonly: # type: ignore[union-attr] - raise_qasm3_error( - f"Assignment to readonly variable '{lvar_name}' not allowed" " in function call", - span=statement.span, - ) - - # lvalue will be the variable which will HOLD this value - if isinstance(lvalue, qasm3_ast.IndexedIdentifier): - # stupid indices structure in openqasm :/ - if len(lvalue.indices[0]) > 1: # type: ignore[arg-type] - l_indices = lvalue.indices[0] - else: - l_indices = [idx[0] for idx in lvalue.indices] # type: ignore[assignment, index] - - validated_l_indices = Qasm3Analyzer.analyze_classical_indices( - l_indices, lvar, Qasm3ExprEvaluator # type: ignore[arg-type] - ) - Qasm3Transformer.update_array_element( - multi_dim_arr=lvar.value, # type: ignore[union-attr, arg-type] - indices=validated_l_indices, - value=rvalue_eval, - ) - else: - lvar.value = rvalue_eval # type: ignore[union-attr] - self._update_var_in_scope(lvar) # type: ignore[arg-type] - - def _evaluate_array_initialization( - self, array_literal: qasm3_ast.ArrayLiteral, dimensions: list[int], base_type: Any - ) -> np.ndarray: - """Evaluate an array initialization. - - Args: - array_literal (qasm3_ast.ArrayLiteral): The array literal to evaluate. - dimensions (list[int]): The dimensions of the array. - base_type (Any): The base type of the array. - - Returns: - np.ndarray: The evaluated array initialization. - """ - init_values = [] - for value in array_literal.values: - if isinstance(value, qasm3_ast.ArrayLiteral): - nested_array = self._evaluate_array_initialization(value, dimensions[1:], base_type) - init_values.append(nested_array) - else: - eval_value = Qasm3ExprEvaluator.evaluate_expression(value) - init_values.append(eval_value) - - return np.array(init_values, dtype=ARRAY_TYPE_MAP[base_type.__class__]) + tuple[str, int, bool]: (register name, register id, positive branch) + """ + + def validate_index_expression(expression): + assert isinstance(expression, qasm3_ast.IndexExpression) + assert isinstance(expression.collection, qasm3_ast.Identifier) + assert isinstance(expression.index, list) and len(expression.index) == 1 + assert isinstance(expression.index[0], qasm3_ast.IntegerLiteral) + + if isinstance(condition, qasm3_ast.UnaryExpression): + validate_index_expression(condition.expression) + return ( + condition.expression.collection.name, # type: ignore + condition.expression.index[0].value, # type: ignore + not condition.op == UnaryOperator["!"], + ) + if isinstance(condition, qasm3_ast.BinaryExpression): + assert isinstance( + condition.rhs, qasm3_ast.BooleanLiteral + ), "Invalid branching condition" + validate_index_expression(condition.lhs) + return ( + condition.lhs.collection.name, # type: ignore + condition.lhs.index[0].value, # type: ignore + condition.rhs.value, + ) + if isinstance(condition, qasm3_ast.IndexExpression): + assert isinstance(condition.index, list) and len(condition.index) == 1 + return (condition.collection.name, condition.index[0].value, True) # type: ignore + # default case + return "", -1, True def _visit_branching_statement(self, statement: qasm3_ast.BranchingStatement) -> None: """Visit a branching statement element. @@ -1058,384 +380,26 @@ def _visit_branching_statement(self, statement: qasm3_ast.BranchingStatement) -> Returns: None """ - self._push_context(Context.BLOCK) - self._push_scope({}) - self._curr_scope += 1 - self._label_scope_level[self._curr_scope] = set() condition = statement.condition - positive_branching = Qasm3Analyzer.analyse_branch_condition(condition) if_block = statement.if_block - if not statement.if_block: - raise_qasm3_error("Missing if block", span=statement.span) else_block = statement.else_block - if not positive_branching: - if_block, else_block = else_block, if_block + reg_name, reg_id, positive_branch = self._get_branch_params(condition) - reg_id, reg_name = Qasm3Transformer.get_branch_params(condition) - - if reg_name not in self._global_creg_size_map: - raise_qasm3_error( - f"Missing register declaration for {reg_name} in {condition}", - span=statement.span, - ) - Qasm3Validator.validate_register_index( - reg_id, self._global_creg_size_map[reg_name], qubit=False - ) + if not positive_branch: + if_block, else_block = else_block, if_block def _visit_statement_block(block): for stmt in block: self.visit_statement(stmt) - if not self._check_only: - # if the condition is true, we visit the if block - pyqir._native.if_result( - self._builder, - pyqir.result(self._module.context, self._clbit_labels[f"{reg_name}_{reg_id}"]), - zero=lambda: _visit_statement_block(else_block), - one=lambda: _visit_statement_block(if_block), - ) - - del self._label_scope_level[self._curr_scope] - self._curr_scope -= 1 - self._pop_scope() - self._restore_context() - - def _visit_forin_loop(self, statement: qasm3_ast.ForInLoop) -> None: - # Compute loop variable values - if isinstance(statement.set_declaration, qasm3_ast.RangeDefinition): - init_exp = statement.set_declaration.start - startval = Qasm3ExprEvaluator.evaluate_expression(init_exp) - range_def = statement.set_declaration - stepval = ( - 1 - if range_def.step is None - else Qasm3ExprEvaluator.evaluate_expression(range_def.step) - ) - endval = Qasm3ExprEvaluator.evaluate_expression(range_def.end) - irange = list(range(startval, endval + stepval, stepval)) - elif isinstance(statement.set_declaration, qasm3_ast.DiscreteSet): - init_exp = statement.set_declaration.values[0] - irange = [ - Qasm3ExprEvaluator.evaluate_expression(exp) - for exp in statement.set_declaration.values - ] - else: - raise Qasm3ConversionError( - f"Unexpected type {type(statement.set_declaration)} of set_declaration in loop." - ) - - i: Optional[Variable] # will store iteration Variable to update to loop scope - - for ival in irange: - self._push_context(Context.BLOCK) - self._push_scope({}) - - # Initialize loop variable in loop scope - # need to re-declare as we discard the block scope in subsequent - # iterations of the loop - self._visit_classical_declaration( - qasm3_ast.ClassicalDeclaration(statement.type, statement.identifier, init_exp) - ) - i = self._get_from_visible_scope(statement.identifier.name) - - # Update scope with current value of loop Variable - if i is not None: - i.value = ival - self._update_var_in_scope(i) - - for stmt in statement.block: - self.visit_statement(stmt) - - # scope not persistent between loop iterations - self._pop_scope() - self._restore_context() - - # as we are only checking compile time errors - # not runtime errors, we can break here - if self._check_only: - break - - def _visit_subroutine_definition(self, statement: qasm3_ast.SubroutineDefinition) -> None: - """Visit a subroutine definition element. - Reference: https://openqasm.com/language/subroutines.html#subroutines - - Args: - statement (qasm3_ast.SubroutineDefinition): The subroutine definition to visit. - - Returns: - None - """ - fn_name = statement.name.name - - if fn_name in CONSTANTS_MAP: - raise_qasm3_error( - f"Subroutine name '{fn_name}' is a reserved keyword", span=statement.span - ) - - if fn_name in self._subroutine_defns: - raise_qasm3_error(f"Redefinition of subroutine '{fn_name}'", span=statement.span) - - if self._check_in_scope(fn_name): - raise_qasm3_error( - f"Can not declare subroutine with name '{fn_name}' as " - "it is already declared as a variable", - span=statement.span, - ) - - self._subroutine_defns[fn_name] = statement - - # pylint: disable=too-many-locals, too-many-statements - def _visit_function_call(self, statement: qasm3_ast.FunctionCall) -> Optional[Any]: - """Visit a function call element. - - Args: - statement (qasm3_ast.FunctionCall): The function call to visit. - Returns: - None - - """ - fn_name = statement.name.name - if fn_name not in self._subroutine_defns: - raise_qasm3_error(f"Undefined subroutine '{fn_name}' was called", span=statement.span) - - subroutine_def = self._subroutine_defns[fn_name] - - if len(statement.arguments) != len(subroutine_def.arguments): - raise_qasm3_error( - f"Parameter count mismatch for subroutine '{fn_name}'. Expected " - f"{len(subroutine_def.arguments)} but got {len(statement.arguments)} in call", - span=statement.span, - ) - - duplicate_qubit_detect_map: dict = {} - qubit_transform_map: dict = {} # {(formal arg, idx) : (actual arg, idx)} - formal_qreg_size_map: dict = {} - - quantum_vars, classical_vars = [], [] - for actual_arg, formal_arg in zip(statement.arguments, subroutine_def.arguments): - if isinstance(formal_arg, qasm3_ast.ClassicalArgument): - classical_vars.append( - Qasm3SubroutineProcessor.process_classical_arg( - formal_arg, actual_arg, fn_name, statement.span - ) - ) - else: - quantum_vars.append( - Qasm3SubroutineProcessor.process_quantum_arg( - formal_arg, - actual_arg, - formal_qreg_size_map, - duplicate_qubit_detect_map, - qubit_transform_map, - fn_name, - statement.span, - ) - ) - - self._push_scope({}) - self._curr_scope += 1 - self._label_scope_level[self._curr_scope] = set() - self._push_context(Context.FUNCTION) - - for var in quantum_vars: - self._add_var_in_scope(var) - - for var in classical_vars: - self._add_var_in_scope(var) - - # push qubit transform maps - self._function_qreg_size_map.append(formal_qreg_size_map) - self._function_qreg_transform_map.append(qubit_transform_map) - - return_statement = None - for function_op in subroutine_def.body: - if isinstance(function_op, qasm3_ast.ReturnStatement): - return_statement = copy.deepcopy(function_op) - break - self.visit_statement(copy.deepcopy(function_op)) - - return_value = None - if return_statement: - return_value = Qasm3ExprEvaluator.evaluate_expression(return_statement.expression) - return_value = Qasm3Validator.validate_return_statement( - subroutine_def, return_statement, return_value - ) - - # remove qubit transformation map - self._function_qreg_transform_map.pop() - self._function_qreg_size_map.pop() - - self._restore_context() - del self._label_scope_level[self._curr_scope] - self._curr_scope -= 1 - self._pop_scope() - - return return_value if subroutine_def.return_type is not None else None - - def _visit_while_loop(self, statement: qasm3_ast.WhileLoop) -> None: - pass - - def _visit_alias_statement(self, statement: qasm3_ast.AliasStatement) -> None: - """Visit an alias statement element. - - Args: - statement (qasm3_ast.AliasStatement): The alias statement to visit. - - Returns: - None - """ - # pylint: disable=too-many-branches - target = statement.target - value = statement.value - - alias_reg_name: str = target.name - alias_reg_size: int = 0 - aliased_reg_name: str = "" - aliased_reg_size: int = 0 - - # Alias should not be redeclared earlier as a variable or a constant - if self._check_in_scope(alias_reg_name): - raise_qasm3_error(f"Re-declaration of variable '{alias_reg_name}'", span=statement.span) - self._label_scope_level[self._curr_scope].add(alias_reg_name) - - if isinstance(value, qasm3_ast.Identifier): - aliased_reg_name = value.name - elif isinstance(value, qasm3_ast.IndexExpression) and isinstance( - value.collection, qasm3_ast.Identifier - ): - aliased_reg_name = value.collection.name - else: - raise_qasm3_error(f"Unsupported aliasing {statement}", span=statement.span) - - if aliased_reg_name not in self._global_qreg_size_map: - raise_qasm3_error( - f"Qubit register {aliased_reg_name} not found for aliasing", span=statement.span - ) - aliased_reg_size = self._global_qreg_size_map[aliased_reg_name] - if isinstance(value, qasm3_ast.Identifier): # "let alias = q;" - for i in range(aliased_reg_size): - self._qubit_labels[f"{alias_reg_name}_{i}"] = self._qubit_labels[ - f"{aliased_reg_name}_{i}" - ] - alias_reg_size = aliased_reg_size - elif isinstance(value, qasm3_ast.IndexExpression): - if isinstance(value.index, qasm3_ast.DiscreteSet): # "let alias = q[{0,1}];" - qids = Qasm3Transformer.extract_values_from_discrete_set(value.index) - for i, qid in enumerate(qids): - Qasm3Validator.validate_register_index( - qid, self._global_qreg_size_map[aliased_reg_name], qubit=True - ) - self._qubit_labels[f"{alias_reg_name}_{i}"] = self._qubit_labels[ - f"{aliased_reg_name}_{qid}" - ] - alias_reg_size = len(qids) - elif len(value.index) != 1: # like "let alias = q[0,1];"? - raise_qasm3_error( - "An index set can be specified by a single integer (signed or unsigned), " - "a comma-separated list of integers contained in braces {a,b,c,…}, " - "or a range", - span=statement.span, - ) - elif isinstance(value.index[0], qasm3_ast.IntegerLiteral): # "let alias = q[0];" - qid = value.index[0].value - Qasm3Validator.validate_register_index( - qid, self._global_qreg_size_map[aliased_reg_name], qubit=True - ) - self._qubit_labels[f"{alias_reg_name}_0"] = value.index[0].value - alias_reg_size = 1 - elif isinstance(value.index[0], qasm3_ast.RangeDefinition): # "let alias = q[0:1:2];" - qids = Qasm3Transformer.get_qubits_from_range_definition( - value.index[0], - aliased_reg_size, - is_qubit_reg=True, - ) - for i, qid in enumerate(qids): - self._qubit_labels[f"{alias_reg_name}_{i}"] = qid - alias_reg_size = len(qids) - - self._global_qreg_size_map[alias_reg_name] = alias_reg_size - - logger.debug("Added labels for aliasing '%s'", target) - - def _visit_switch_statement(self, statement: qasm3_ast.SwitchStatement) -> None: - """Visit a switch statement element. - - Args: - statement (qasm3_ast.SwitchStatement): The switch statement to visit. - - Returns: - None - """ - # 1. analyze the target - it should ONLY be int, not casted - switch_target = statement.target - switch_target_name = "" - # either identifier or indexed expression - if isinstance(switch_target, qasm3_ast.Identifier): - switch_target_name = switch_target.name - elif isinstance(switch_target, qasm3_ast.IndexExpression): - switch_target_name, _ = Qasm3Analyzer.analyze_index_expression(switch_target) - - if not Qasm3Validator.validate_variable_type( - self._get_from_visible_scope(switch_target_name), qasm3_ast.IntType - ): - raise_qasm3_error( - f"Switch target {switch_target_name} must be of type int", span=statement.span - ) - - switch_target_val = Qasm3ExprEvaluator.evaluate_expression(switch_target) - - if len(statement.cases) == 0: - raise_qasm3_error("Switch statement must have at least one case", span=statement.span) - - # 2. handle the cases of the switch stmt - # each element in the list of the values - # should be of const int type and no duplicates should be present - - def _evaluate_case(statements): - # can not put 'context' outside - # BECAUSE the case expression CAN CONTAIN VARS from global scope - self._push_context(Context.BLOCK) - self._push_scope({}) - - for stmt in statements: - Qasm3Validator.validate_statement_type(SWITCH_BLACKLIST_STMTS, stmt, "switch") - self.visit_statement(stmt) - - self._pop_scope() - self._restore_context() - - case_fulfilled = False - for case in statement.cases: - case_list = case[0] - seen_values = set() - for case_expr in case_list: - # 3. evaluate and verify that it is a const_expression - # using vars only within the scope AND each component is either a - # literal OR type int - case_val = Qasm3ExprEvaluator.evaluate_expression( - case_expr, const_expr=True, reqd_type=qasm3_ast.IntType - ) - - if case_val in seen_values: - raise_qasm3_error( - f"Duplicate case value {case_val} in switch statement", span=case_expr.span - ) - - seen_values.add(case_val) - - if case_val == switch_target_val: - case_fulfilled = True - - if case_fulfilled: - case_stmts = case[1].statements - _evaluate_case(case_stmts) - break - - if not case_fulfilled and statement.default: - default_stmts = statement.default.statements - _evaluate_case(default_stmts) + pyqir._native.if_result( + self._builder, + pyqir.result(self._llvm_module.context, self._clbit_labels[f"{reg_name}_{reg_id}"]), + zero=lambda: _visit_statement_block(else_block), + one=lambda: _visit_statement_block(if_block), + ) def visit_statement(self, statement: qasm3_ast.Statement) -> None: """Visit a statement element. @@ -1450,27 +414,20 @@ def visit_statement(self, statement: qasm3_ast.Statement) -> None: visit_map = { qasm3_ast.Include: lambda x: None, # No operation + qasm3_ast.QubitDeclaration: self._visit_register, + qasm3_ast.ClassicalDeclaration: self._visit_register, qasm3_ast.QuantumMeasurementStatement: self._visit_measurement, qasm3_ast.QuantumReset: self._visit_reset, qasm3_ast.QuantumBarrier: self._visit_barrier, - qasm3_ast.QuantumGateDefinition: self._visit_gate_definition, qasm3_ast.QuantumGate: self._visit_generic_gate_operation, - qasm3_ast.ClassicalDeclaration: self._visit_classical_declaration, - qasm3_ast.ClassicalAssignment: self._visit_classical_assignment, - qasm3_ast.ConstantDeclaration: self._visit_constant_declaration, qasm3_ast.BranchingStatement: self._visit_branching_statement, - qasm3_ast.ForInLoop: self._visit_forin_loop, - qasm3_ast.AliasStatement: self._visit_alias_statement, - qasm3_ast.SwitchStatement: self._visit_switch_statement, - qasm3_ast.SubroutineDefinition: self._visit_subroutine_definition, - qasm3_ast.ExpressionStatement: lambda x: self._visit_function_call(x.expression), - qasm3_ast.IODeclaration: lambda x: (_ for _ in ()).throw( - NotImplementedError("OpenQASM 3 IO declarations not yet supported") - ), } visitor_function = visit_map.get(type(statement)) + if not isinstance(statement, qasm3_ast.QuantumBarrier): + self._check_and_apply_barrier() + if visitor_function: visitor_function(statement) # type: ignore[operator] else: @@ -1479,7 +436,7 @@ def visit_statement(self, statement: qasm3_ast.Statement) -> None: ) def ir(self) -> str: - return str(self._module) + return str(self._llvm_module) def bitcode(self) -> bytes: - return self._module.bitcode + return self._llvm_module.bitcode diff --git a/qbraid_qir/serialization.py b/qbraid_qir/serialization.py index 53c4621..04f7dbe 100644 --- a/qbraid_qir/serialization.py +++ b/qbraid_qir/serialization.py @@ -1,12 +1,12 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module for exporting QIR to bitcode and LLVM IR files. diff --git a/tests/autoqasm_qir/test_convert.py b/tests/autoqasm_qir/test_convert.py index fd51a2a..3c4d728 100644 --- a/tests/autoqasm_qir/test_convert.py +++ b/tests/autoqasm_qir/test_convert.py @@ -1,12 +1,12 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Tests the convert module of autoqasm to qir @@ -56,7 +56,7 @@ def _process_qasm(qasm: str) -> str: qasm = add_stdgates_include(qasm) qasm = insert_gate_def(qasm, "iswap") qasm = insert_gate_def(qasm, "sxdg") - + print(qasm) return qasm @@ -81,7 +81,6 @@ def autoqasm_to_qir(program: "MainProgram", **kwargs) -> "Module": @aq.main(num_qubits=1) def one_qubit_gates(): ins.h(0) - ins.i(0) ins.s(0) ins.si(0) ins.t(0) @@ -91,7 +90,7 @@ def one_qubit_gates(): ins.z(0) ins.v(0) ins.vi(0) - return ins.measure(0) + # return ins.measure() # Refernce : https://github.com/qBraid/pyqasm/issues/15 @aq.main(num_qubits=1) diff --git a/tests/cirq_qir/conftest.py b/tests/cirq_qir/conftest.py index b5ad9c5..d7911b5 100644 --- a/tests/cirq_qir/conftest.py +++ b/tests/cirq_qir/conftest.py @@ -1,12 +1,12 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Fixtures imported/defined in this file can be used by any test in this directory diff --git a/tests/cirq_qir/fixtures/basic_gates.py b/tests/cirq_qir/fixtures/basic_gates.py index de07ce3..59e9f9f 100644 --- a/tests/cirq_qir/fixtures/basic_gates.py +++ b/tests/cirq_qir/fixtures/basic_gates.py @@ -1,12 +1,12 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module defining Cirq basic gate fixtures for use in tests. diff --git a/tests/cirq_qir/fixtures/cirq_circuits.py b/tests/cirq_qir/fixtures/cirq_circuits.py index f397d3b..6f9108b 100644 --- a/tests/cirq_qir/fixtures/cirq_circuits.py +++ b/tests/cirq_qir/fixtures/cirq_circuits.py @@ -1,12 +1,12 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing Cirq circuit fixtures for unit tests. diff --git a/tests/cirq_qir/fixtures/pyqir_circuits.py b/tests/cirq_qir/fixtures/pyqir_circuits.py index 6d9785f..4d36f48 100644 --- a/tests/cirq_qir/fixtures/pyqir_circuits.py +++ b/tests/cirq_qir/fixtures/pyqir_circuits.py @@ -1,12 +1,12 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing PyQIR circuit fixtures for unit tests. diff --git a/tests/cirq_qir/test_cirq_decompose.py b/tests/cirq_qir/test_cirq_decompose.py index c285639..878a61c 100644 --- a/tests/cirq_qir/test_cirq_decompose.py +++ b/tests/cirq_qir/test_cirq_decompose.py @@ -1,12 +1,12 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Test functions that decompose unsupported Cirq gates before conversion to QIR. diff --git a/tests/cirq_qir/test_cirq_module.py b/tests/cirq_qir/test_cirq_module.py index 4bb6088..827e07f 100644 --- a/tests/cirq_qir/test_cirq_module.py +++ b/tests/cirq_qir/test_cirq_module.py @@ -1,12 +1,12 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing unit tests for CirqModule and Module elements. diff --git a/tests/cirq_qir/test_cirq_preprocess.py b/tests/cirq_qir/test_cirq_preprocess.py index 1cafa20..4520cc0 100644 --- a/tests/cirq_qir/test_cirq_preprocess.py +++ b/tests/cirq_qir/test_cirq_preprocess.py @@ -1,12 +1,13 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. + """ Test functions that preprocess Cirq circuits before conversion to QIR. diff --git a/tests/cirq_qir/test_cirq_to_qir.py b/tests/cirq_qir/test_cirq_to_qir.py index faab36c..4756c9a 100644 --- a/tests/cirq_qir/test_cirq_to_qir.py +++ b/tests/cirq_qir/test_cirq_to_qir.py @@ -1,12 +1,12 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing unit tests for Cirq to QIR conversion functions. diff --git a/tests/cirq_qir/test_serialization.py b/tests/cirq_qir/test_serialization.py index 0dd810e..e8f4e09 100644 --- a/tests/cirq_qir/test_serialization.py +++ b/tests/cirq_qir/test_serialization.py @@ -1,12 +1,13 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. + """ Unit tests for the export module diff --git a/tests/qasm3_qir/conftest.py b/tests/qasm3_qir/conftest.py index 4f6d215..114dc0c 100644 --- a/tests/qasm3_qir/conftest.py +++ b/tests/qasm3_qir/conftest.py @@ -1,12 +1,12 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Fixtures imported/defined in this file can be used by any test in this directory @@ -16,4 +16,3 @@ # pylint: disable=unused-import,wildcard-import,unused-wildcard-import from .fixtures.gates import * -from .fixtures.variables import * diff --git a/tests/qasm3_qir/converter/declarations/test_classical.py b/tests/qasm3_qir/converter/declarations/test_classical.py deleted file mode 100644 index 9e642f3..0000000 --- a/tests/qasm3_qir/converter/declarations/test_classical.py +++ /dev/null @@ -1,379 +0,0 @@ -# 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 containing unit tests for QASM3 to QIR conversion functions. - -""" -import pytest - -from qbraid_qir.qasm3.convert import qasm3_to_qir -from qbraid_qir.qasm3.exceptions import Qasm3ConversionError -from tests.qasm3_qir.fixtures.variables import ASSIGNMENT_TESTS, DECLARATION_TESTS -from tests.qir_utils import check_attributes, check_single_qubit_rotation_op - - -# 1. Test scalar declarations in different ways -def test_scalar_declarations(): - """Test scalar declarations in different ways""" - qasm3_string = """ - OPENQASM 3; - include "stdgates.inc"; - int a; - uint b; - int[2] c; - uint[3] d; - float[32] f; - float[64] g; - bit h; - bool i; - """ - - result = qasm3_to_qir(qasm3_string) - generated_qir = str(result).splitlines() - check_attributes(generated_qir, 0, 1) - - -# 2. Test const declarations in different ways -def test_const_declarations(): - """Test const declarations in different ways""" - qasm3_string = """ - OPENQASM 3; - include "stdgates.inc"; - const int a = 5; - const uint b = 10; - const int[2*9] c = 1; - const uint[3-1] d = 2; - const bool boolean_var = true; - const float[32] f = 0.00000023; - const float[64] g = 2345623454564564564564545456456546456456456.0; - - const int a1 = 5 + a; - const uint b1 = 10 + b; - const int[2*9] c1 = 1 + 2*c + a; - const uint[6-1] d1 = 2 + d; - const bool boolean_var1 = !boolean_var; - const float[32] f1 = 0.00000023 + f; - """ - - result = qasm3_to_qir(qasm3_string) - generated_qir = str(result).splitlines() - check_attributes(generated_qir, 0, 0) - - -# 3. Test non-constant scalar assignments -def test_scalar_assignments(): - """Test scalar assignments in different ways""" - qasm3_string = """ - OPENQASM 3; - include "stdgates.inc"; - int a = 5; - uint b; - int[2*9] c = 1; - uint[3-1] d = 2; - float r; - float[32] f = 0.00000023; - float[64] g = 23456.023424983573645873465836483645876348564387; - b = 12; - r = 12.2; - """ - - result = qasm3_to_qir(qasm3_string) - generated_qir = str(result).splitlines() - check_attributes(generated_qir, 0, 0) - - -def test_example_qir(): - qasm3_string = """ - OPENQASM 3; - - // static initialization - array[int[32], 3, 2] arr_int = { {1, 2}, {3, 4}, {5, 6} }; - array[float[32], 3, 2] arr_float32 = { {1.0, 2.0}, {3.0, 4.0}, {5.0, 6.0} }; - - // assignment of individual elements - int a = 2; - arr_int[1][1] = a; // arr_int = { {1, 2}, {3, 2}, {5, 6} } - - // array elements in expressions - float[32] b = arr_float32[1][1] * 3.1415; // b = 4.0*3.1415 = 12.566 - - qubit q; - rx(b) q; // rx(12.566) is applied on qubit q - """ - result = qasm3_to_qir(qasm3_string) - generated_qir = str(result).splitlines() - check_attributes(generated_qir, 1, 0) - check_single_qubit_rotation_op(generated_qir, 1, [0], [12.566], "rx") - - -# 4. Scalar value assignment -def test_scalar_value_assignment(): - """Test assigning variable values from other variables""" - qasm3_string = """ - OPENQASM 3; - include "stdgates.inc"; - int a = 5; - float[32] r; - float[32] f = 0.5; - int b = a; - r = 0.23; - qubit q; - rx(b) q; - rx(r + f*4) q; - """ - - b = 5.0 - r = 0.23 - f = 0.5 - result = qasm3_to_qir(qasm3_string) - generated_qir = str(result).splitlines() - check_attributes(generated_qir, 1, 0) - check_single_qubit_rotation_op(generated_qir, 2, [0, 0], [b, r + f * 4], "rx") - - -def test_scalar_type_casts(): - """Test type casts on scalar variables""" - qasm3_string = """ - OPENQASM 3; - include "stdgates.inc"; - int[32] a = 5.1; - float[32] r = 245; - uint[4] b = -4; // -4 % 16 = 12 - bit bit_var = 0; - bool f = 0; - bool g = 1; - - qubit q; - rx(a) q; - rx(r) q; - rx(b) q; - rx(f) q; - rx(g) q; - - """ - a = 5 - r = 245 - b = 12 - f = 0 - g = 1 - - result = qasm3_to_qir(qasm3_string) - generated_qir = str(result).splitlines() - check_attributes(generated_qir, 1, 1) - check_single_qubit_rotation_op(generated_qir, 5, [0, 0, 0, 0, 0], [a, r, b, f, g], "rx") - - -def test_array_type_casts(): - """Test type casts on array variables""" - - qasm3_string = """ - OPENQASM 3; - include "stdgates.inc"; - array[int[32], 3, 2] arr_int = { {1, 2}, {3, 4}, {5, 6.2} }; - array[uint[32], 3, 2] arr_uint = { {1, 2}, {3, 4}, {5, 6.2} }; - array[bool, 3, 2] arr_bool = { {true, false}, {true, false}, {true, 12} }; - array[float[32], 3, 2] arr_float32 = { {1.0, 2.0}, {3.0, 4.0}, {5.0, 6} }; - - qubit q; - rx(arr_int[2][1]) q; // should be 6 - rx(arr_uint[2][1]) q; // should be 6 - rx(arr_bool[2][1]) q; // should be 1 (true) - rx(arr_float32[2][1]) q; // should be 6.0 - - """ - result = qasm3_to_qir(qasm3_string) - generated_qir = str(result).splitlines() - check_attributes(generated_qir, 1, 0) - check_single_qubit_rotation_op(generated_qir, 4, [0, 0, 0, 0], [6, 6, 1, 6.0], "rx") - - -# 5. Array declarations -def test_array_declarations(): - """Test array declarations in different ways""" - qasm3_string = """ - OPENQASM 3; - include "stdgates.inc"; - array[int[32], 3, 2] arr_int; - array[uint[32-9], 3, 2] arr_uint; - array[float[32], 3, 2] arr_float32; - array[float[64], 3, 2] arr_float64; - array[bool, 3, 2] arr_bool; - """ - result = qasm3_to_qir(qasm3_string) - generated_qir = str(result).splitlines() - check_attributes(generated_qir, 0, 0) - - -# 6. Array assignments -def test_array_assignments(): - """Test array assignments""" - - qasm3_string = """ - OPENQASM 3; - include "stdgates.inc"; - - array[int[32], 3, 2] arr_int; - array[uint[32], 3, 2] arr_uint; - array[float[32], 3, 2] arr_float32; - array[float[64], 3, 2] arr_float64; - array[bool, 3, 2] arr_bool; - - int a = 2; - uint b = 3; - float[32] c = 4.5; - float[64] d = 6.7; - bool f = true; - - arr_int[0][1] = a*a; - arr_int[0,1] = a*a; - - arr_uint[0][1] = b*b; - arr_uint[0,1] = b*b; - - arr_float32[0][1] = c*c; - arr_float32[0,1] = c*c; - - arr_float64[0][1] = d*d; - arr_float64[0,1] = d*d; - - arr_bool[0][1] = f; - arr_bool[0,1] = f; - - qubit q; - rx(arr_int[0,1]) q; - rx(arr_uint[0][1]) q; - rx(arr_float32[0,1]) q; - rx(arr_float64[0][1]) q; - rx(arr_bool[0,1]) q; - """ - - a = 2 - b = 3 - c = 4.5 - d = 6.7 - f = True - result = qasm3_to_qir(qasm3_string) - generated_qir = str(result).splitlines() - check_attributes(generated_qir, 1, 0) - check_single_qubit_rotation_op( - generated_qir, 5, [0, 0, 0, 0, 0], [a * a, b * b, c * c, d * d, f], "rx" - ) - - -# 7. Test expressions which involves arrays -def test_array_expressions(): - """Test array expressions""" - qasm3_string = """ - OPENQASM 3; - include "stdgates.inc"; - - array[int[32], 3, 2] arr_int; - array[uint[32], 3, 2] arr_uint; - array[float[32], 3, 2] arr_float32; - array[float[64], 3, 2] arr_float64; - array[bool, 3, 2] arr_bool; - - int a = 2; - uint b = 3; - float[32] c = 4.5; - float[64] d = 6.7; - bool f = true; - - - arr_int[0][1] = a*a; - arr_int[1][0] = arr_int[0][1]; - arr_uint[0][1] = b*b; - arr_float32[0][1] = c*c; - arr_float64[0][1] = d*d; - - qubit q; - rx(arr_int[{0,1}] + arr_uint[0][1]) q; - rx(arr_float32[{0,1}] + arr_float64[0][1]) q; - """ - - a = 2 - b = 3 - c = 4.5 - d = 6.7 - f = True - result = qasm3_to_qir(qasm3_string) - generated_qir = str(result).splitlines() - check_attributes(generated_qir, 1, 0) - check_single_qubit_rotation_op(generated_qir, 2, [0, 0], [a * a + b * b, c * c + d * d], "rx") - - -def test_array_initializations(): - """Test array initializations""" - - qasm3_string = """ - OPENQASM 3; - include "stdgates.inc"; - - array[int[32], 3, 2] arr_int = { {1, 2}, {3, 4}, {5, 6} }; - array[uint[32], 3, 2] arr_uint = { {1, 2}, {3, 4}, {5, 6} }; - array[float[32], 3, 2] arr_float32 = { {1.0, 2.0}, {3.0, 4.0}, {5.0, 6.0} }; - array[float[64], 3, 2] arr_float64 = { {1.0, 2.0}, {3.0, 4.0}, {5.0, 6.0} }; - array[bool, 3, 2] arr_bool = { {true, false}, {true, false}, {true, false} }; - - qubit q; - rx(arr_int[0][1]) q; - rx(arr_uint[0][1]) q; - rx(arr_float32[0][1]) q; - rx(arr_float64[0][1]) q; - rx(arr_bool[0][1]) q; - """ - - result = qasm3_to_qir(qasm3_string) - generated_qir = str(result).splitlines() - check_attributes(generated_qir, 1, 0) - check_single_qubit_rotation_op(generated_qir, 5, [0, 0, 0, 0, 0], [2, 2, 2.0, 2.0, 0], "rx") - - -def test_array_range_assignment(): - """Test array range assignment""" - - qasm3_string = """ - OPENQASM 3; - include "stdgates.inc"; - - array[int[32], 3, 2] arr_int = { {1, 2}, {3, 4}, {5, 6} }; - array[uint[32], 3, 2] arr_uint = { {1, 2}, {3, 4}, {5, 6} }; - array[float[32], 3, 2] arr_float32 = { {1.0, 2.0}, {3.0, 4.0}, {5.0, 6.0} }; - - arr_int[0, 0:1] = arr_int[1, 0:1]; - arr_uint[0:2, 1] = arr_uint[0:2, 0]; - arr_float32[0:2, 1] = arr_float32[0:2, 0]; - - qubit q; - rx(arr_int[0][1]) q; - rx(arr_uint[0][1]) q; - rx(arr_float32[1][1]) q; - - """ - - result = qasm3_to_qir(qasm3_string) - generated_qir = str(result).splitlines() - check_attributes(generated_qir, 1, 0) - check_single_qubit_rotation_op(generated_qir, 3, [0, 0, 0], [4, 1, 3.0], "rx") - - -@pytest.mark.parametrize("test_name", DECLARATION_TESTS.keys()) -def test_incorrect_declarations(test_name): - qasm_input, error_message = DECLARATION_TESTS[test_name] - with pytest.raises(Qasm3ConversionError, match=error_message): - _ = qasm3_to_qir(qasm_input) - - -@pytest.mark.parametrize("test_name", ASSIGNMENT_TESTS.keys()) -def test_incorrect_assignments(test_name): - qasm_input, error_message = ASSIGNMENT_TESTS[test_name] - with pytest.raises(Qasm3ConversionError, match=error_message): - _ = qasm3_to_qir(qasm_input) diff --git a/tests/qasm3_qir/converter/test_alias.py b/tests/qasm3_qir/converter/test_alias.py index d5514eb..c1352c6 100644 --- a/tests/qasm3_qir/converter/test_alias.py +++ b/tests/qasm3_qir/converter/test_alias.py @@ -1,12 +1,12 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing unit tests for converting OpenQASM 3 programs @@ -14,11 +14,9 @@ """ -import re - import pytest -from qbraid_qir.qasm3 import Qasm3ConversionError, qasm3_to_qir +from qbraid_qir.qasm3 import qasm3_to_qir from tests.qir_utils import ( check_attributes, check_single_qubit_gate_op, @@ -115,101 +113,6 @@ def test_valid_alias_redefinition(): check_single_qubit_gate_op(generated_qir, 1, [2], "x") -def test_alias_wrong_indexing(): - """Test converting OpenQASM 3 program with wrong alias indexing.""" - with pytest.raises( - Qasm3ConversionError, - match=re.escape( - r"An index set can be specified by a single integer (signed or unsigned), " - "a comma-separated list of integers contained in braces {a,b,c,…}, or a range" - ), - ): - qasm3_alias_program = """ - OPENQASM 3.0; - include "stdgates.inc"; - - qubit[5] q; - - let myqreg = q[1,2]; - - x myqreg[0]; - """ - _ = qasm3_to_qir(qasm3_alias_program, name="test") - - -def test_alias_invalid_discrete_indexing(): - """Test converting OpenQASM 3 program with invalid alias discrete indexing.""" - with pytest.raises( - Qasm3ConversionError, - match=r"Unsupported discrete set value .*", - ): - qasm3_alias_program = """ - OPENQASM 3.0; - include "stdgates.inc"; - - qubit[5] q; - - let myqreg = q[{0.1}]; - - x myqreg[0]; - """ - _ = qasm3_to_qir(qasm3_alias_program, name="test") - - -def test_invalid_alias_redefinition(): - """Test converting OpenQASM 3 program with redefined alias.""" - with pytest.raises( - Qasm3ConversionError, - match=re.escape(r"Re-declaration of variable 'alias'"), - ): - qasm3_alias_program = """ - OPENQASM 3.0; - include "stdgates.inc"; - - qubit[5] q; - float[32] alias = 4.2; - - let alias = q[2]; - - x alias; - """ - _ = qasm3_to_qir(qasm3_alias_program, name="test") - - -def test_alias_defined_before(): - """Test converting OpenQASM 3 program with alias defined before the qubit register.""" - with pytest.raises( - Qasm3ConversionError, - match=re.escape(r"Qubit register q2 not found for aliasing"), - ): - qasm3_alias_program = """ - OPENQASM 3.0; - include "stdgates.inc"; - - qubit[5] q1; - - let myqreg = q2[1]; - """ - _ = qasm3_to_qir(qasm3_alias_program, name="test") - - -def test_unsupported_alias(): - """Test converting OpenQASM 3 program with unsupported alias.""" - with pytest.raises( - Qasm3ConversionError, - match=r"Unsupported aliasing .*", - ): - qasm3_alias_program = """ - OPENQASM 3.0; - include "stdgates.inc"; - - qubit[5] q; - - let myqreg = q[0] ++ q[1]; - """ - _ = qasm3_to_qir(qasm3_alias_program, name="test") - - def test_alias_in_scope_1(): """Test converting OpenQASM 3 program with alias in scope.""" qasm = """ @@ -242,6 +145,8 @@ def test_alias_in_scope_1(): compare_reference_ir(result.bitcode, simple_file) +# See reference : https://github.com/qBraid/pyqasm/pull/14 +@pytest.mark.skip(reason="Alias parsing bug, enable after fixing") def test_alias_in_scope_2(): """Test converting OpenQASM 3 program with alias in scope.""" qasm = """ @@ -273,34 +178,3 @@ def test_alias_in_scope_2(): check_attributes(generated_qir, 4, 4) simple_file = resources_file("simple_if.ll") compare_reference_ir(result.bitcode, simple_file) - - -def test_alias_out_of_scope(): - """Test converting OpenQASM 3 program with alias out of scope.""" - with pytest.raises( - Qasm3ConversionError, - match=r"Variable alias not in scope for operation .*", - ): - qasm = """ - OPENQASM 3; - include "stdgates.inc"; - qubit[4] q; - bit[4] c; - - h q; - measure q -> c; - if(c[0]){ - let alias = q[0:2]; - x alias[0]; - cx alias[0], alias[1]; - } - - if(c[1] == 1){ - cx alias[1], q[2]; - } - - if(!c[2]){ - h q[2]; - } - """ - _ = qasm3_to_qir(qasm) diff --git a/tests/qasm3_qir/converter/test_barrier.py b/tests/qasm3_qir/converter/test_barrier.py index ac7adfb..20b26f9 100644 --- a/tests/qasm3_qir/converter/test_barrier.py +++ b/tests/qasm3_qir/converter/test_barrier.py @@ -1,12 +1,12 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing unit tests for QASM3 to QIR conversion functions. @@ -14,11 +14,11 @@ """ import pytest -from qbraid_qir.qasm3 import Qasm3ConversionError, qasm3_to_qir -from tests.qir_utils import check_attributes, check_barrier +from qbraid_qir.qasm3 import qasm3_to_qir +from tests.qir_utils import check_attributes, check_barrier, check_single_qubit_gate_op -# 5. Test barrier operations in different ways +# Test barrier operations in different ways def test_barrier(): qasm3_string = """ OPENQASM 3; @@ -30,12 +30,18 @@ def test_barrier(): bit[2] c; bit c2; - + // Only full barrier supported in QIR - barrier q1, q2, q3; + barrier q1, q2, q3; barrier q2, q3, q1; + x q1[0]; barrier q1[0], q1[1], q2[:], q3[0]; + + for int i in [0:1] { + x q1[i]; + } + barrier q1, q2[0:5], q3[:]; """ @@ -43,6 +49,7 @@ def test_barrier(): generated_qir = str(result).splitlines() check_attributes(generated_qir, 8, 3) check_barrier(generated_qir, expected_barriers=4) + check_single_qubit_gate_op(generated_qir, 3, [0, 0, 1], "x") def test_barrier_in_function(): @@ -76,39 +83,4 @@ def test_incorrect_barrier(): with pytest.raises( NotImplementedError, match="Barrier operation on a qubit subset is not supported in pyqir" ): - _ = qasm3_to_qir(subset) - - undeclared = """ - OPENQASM 3; - - qubit[3] q1; - - barrier q2; - """ - - with pytest.raises(Qasm3ConversionError, match=r"Missing register declaration for q2 .*"): - _ = qasm3_to_qir(undeclared) - - out_of_bounds = """ - OPENQASM 3; - - qubit[2] q1; - - barrier q1[:4]; - """ - - with pytest.raises( - Qasm3ConversionError, match="Index 3 out of range for register of size 2 in qubit" - ): - _ = qasm3_to_qir(out_of_bounds) - - duplicate = """ - OPENQASM 3; - - qubit[2] q1; - - barrier q1, q1; - """ - - with pytest.raises(Qasm3ConversionError, match=r"Duplicate qubit .*argument"): - _ = qasm3_to_qir(duplicate) + qasm3_to_qir(subset) diff --git a/tests/qasm3_qir/converter/test_expressions.py b/tests/qasm3_qir/converter/test_expressions.py index b16e8d3..6bc6fb0 100644 --- a/tests/qasm3_qir/converter/test_expressions.py +++ b/tests/qasm3_qir/converter/test_expressions.py @@ -1,20 +1,19 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing unit tests for QASM3 to QIR conversion functions. """ -import pytest -from qbraid_qir.qasm3 import Qasm3ConversionError, qasm3_to_qir +from qbraid_qir.qasm3 import qasm3_to_qir from tests.qir_utils import check_attributes, check_expressions @@ -49,22 +48,3 @@ def test_correct_expressions(): expression_values = [1.57, 3 - 2 * 3, 3 - 2 * 3 * (8 / 2), -1.57, 4 % 2] qubits = [0, 0, 0, 0, 0] check_expressions(generated_qir, 5, gates, expression_values, qubits) - - -def test_incorrect_expressions(): - with pytest.raises(Qasm3ConversionError, match=r"Unsupported expression type .*"): - qasm3_to_qir("OPENQASM 3; qubit q; rz(1 - 2 + 32im) q;") - with pytest.raises( - Qasm3ConversionError, match=r"Unsupported expression type .* in ~ operation" - ): - qasm3_to_qir("OPENQASM 3; qubit q; rx(~1.3) q;") - with pytest.raises( - Qasm3ConversionError, match=r"Unsupported expression type .* in ~ operation" - ): - qasm3_to_qir("OPENQASM 3; qubit q; rx(~1.3+5im) q;") - - with pytest.raises(Qasm3ConversionError, match="Undefined identifier x in expression"): - qasm3_to_qir("OPENQASM 3; qubit q; rx(x) q;") - - with pytest.raises(Qasm3ConversionError, match="Uninitialized variable x in expression"): - qasm3_to_qir("OPENQASM 3; qubit q; int x; rx(x) q;") diff --git a/tests/qasm3_qir/converter/test_gates.py b/tests/qasm3_qir/converter/test_gates.py index 95262fd..518458c 100644 --- a/tests/qasm3_qir/converter/test_gates.py +++ b/tests/qasm3_qir/converter/test_gates.py @@ -1,12 +1,12 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing unit tests for QASM3 to QIR conversion functions. @@ -14,10 +14,8 @@ """ import pytest -from qbraid_qir.qasm3 import Qasm3ConversionError, qasm3_to_qir +from qbraid_qir.qasm3 import qasm3_to_qir from tests.qasm3_qir.fixtures.gates import ( - CUSTOM_GATE_INCORRECT_TESTS, - SINGLE_QUBIT_GATE_INCORRECT_TESTS, custom_op_tests, double_op_tests, rotation_tests, @@ -160,13 +158,6 @@ def test_qasm_u2_gates(): check_single_qubit_rotation_op(generated_qir, 1, [0], [0.5, 0.5], "u2") -@pytest.mark.parametrize("test_name", SINGLE_QUBIT_GATE_INCORRECT_TESTS.keys()) -def test_incorrect_single_qubit_gates(test_name): - qasm_input, error_message = SINGLE_QUBIT_GATE_INCORRECT_TESTS[test_name] - with pytest.raises(Qasm3ConversionError, match=error_message): - _ = qasm3_to_qir(qasm_input) - - @pytest.mark.parametrize("test_name", custom_op_tests) def test_custom_ops(test_name, request): qasm3_string = request.getfixturevalue(test_name) @@ -258,10 +249,3 @@ def test_unsupported_modifiers(): {modifier} @ h q[0], q[1]; """ ) - - -@pytest.mark.parametrize("test_name", CUSTOM_GATE_INCORRECT_TESTS.keys()) -def test_incorrect_custom_ops(test_name): - qasm_input, error_message = CUSTOM_GATE_INCORRECT_TESTS[test_name] - with pytest.raises(Qasm3ConversionError, match=error_message): - _ = qasm3_to_qir(qasm_input) diff --git a/tests/qasm3_qir/converter/test_if.py b/tests/qasm3_qir/converter/test_if.py index 7f17991..bea8744 100644 --- a/tests/qasm3_qir/converter/test_if.py +++ b/tests/qasm3_qir/converter/test_if.py @@ -1,12 +1,12 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing unit tests for QASM3 to QIR conversion functions. @@ -17,9 +17,8 @@ from pathlib import Path import pyqir -import pytest -from qbraid_qir.qasm3 import Qasm3ConversionError, qasm3_to_qir +from qbraid_qir.qasm3 import qasm3_to_qir from tests.qir_utils import check_attributes, get_entry_point_body RESOURCES_DIR = os.path.join( @@ -101,146 +100,3 @@ def test_complex_if(): check_attributes(generated_qir, 4, 8) complex_if = resources_file("complex_if.ll") compare_reference_ir(result.bitcode, complex_if) - - -def test_incorrect_if(): - with pytest.raises(Qasm3ConversionError, match=r"Unsupported expression type .*"): - _ = qasm3_to_qir( - """ - OPENQASM 3; - include "stdgates.inc"; - qubit[2] q; - bit[2] c; - - h q; - measure q->c; - - if(c == 4){ - cx q; - } - """ - ) - - with pytest.raises(Qasm3ConversionError, match=r"Missing if block"): - _ = qasm3_to_qir( - """ - OPENQASM 3; - include "stdgates.inc"; - qubit[2] q; - bit[2] c; - - h q; - measure q->c; - - if(c[0]){ - } - """ - ) - - with pytest.raises(Qasm3ConversionError, match=r"Missing register declaration for c2 .*"): - _ = qasm3_to_qir( - """ - OPENQASM 3; - include "stdgates.inc"; - qubit[2] q; - bit[2] c; - - h q; - measure q->c; - - if(c2[0]){ - cx q; - } - """ - ) - - with pytest.raises(Qasm3ConversionError, match=r"Unsupported unary expression .*"): - _ = qasm3_to_qir( - """ - OPENQASM 3; - include "stdgates.inc"; - qubit[2] q; - bit[2] c; - - h q; - measure q->c; - - if(~c[0]){ - cx q; - } - """ - ) - with pytest.raises(Qasm3ConversionError, match=r"Unsupported binary expression .*"): - _ = qasm3_to_qir( - """ - OPENQASM 3; - include "stdgates.inc"; - qubit[2] q; - bit[2] c; - - h q; - measure q->c; - - if(c[0] >= 1){ - cx q; - } - """ - ) - with pytest.raises( - Qasm3ConversionError, - match=r"Unsupported expression type .* in if condition. Can only be a simple comparison", - ): - _ = qasm3_to_qir( - """ - OPENQASM 3; - include "stdgates.inc"; - qubit[2] q; - bit[2] c; - - h q; - measure q->c; - - if(c){ - cx q; - } - """ - ) - with pytest.raises( - Qasm3ConversionError, - match=r"RangeDefinition not supported in branching condition", - ): - _ = qasm3_to_qir( - """ - OPENQASM 3; - include "stdgates.inc"; - qubit[2] q; - bit[2] c; - - h q; - measure q->c; - - if(c[0:1]){ - cx q; - } - """ - ) - - with pytest.raises( - Qasm3ConversionError, - match=r"DiscreteSet not supported in branching condition", - ): - _ = qasm3_to_qir( - """ - OPENQASM 3; - include "stdgates.inc"; - qubit[2] q; - bit[2] c; - - h q; - measure q->c; - - if(c[{0,1}]){ - cx q; - } - """ - ) diff --git a/tests/qasm3_qir/converter/test_loop.py b/tests/qasm3_qir/converter/test_loop.py index bc5be6e..71891b7 100644 --- a/tests/qasm3_qir/converter/test_loop.py +++ b/tests/qasm3_qir/converter/test_loop.py @@ -1,12 +1,12 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing unit tests for parsing, unrolling, and @@ -17,7 +17,6 @@ import pytest from qbraid_qir.qasm3 import qasm3_to_qir -from qbraid_qir.qasm3.visitor import Qasm3ConversionError from tests.qir_utils import ( check_attributes, check_single_qubit_gate_op, @@ -47,7 +46,7 @@ %Qubit = type opaque %Result = type opaque -define void @main() #0 { +define void @test() #0 { entry: call void @__quantum__rt__initialize(i8* null) call void @__quantum__qis__h__body(%Qubit* null) @@ -383,31 +382,3 @@ def my_function_2(qubit q2, int[32] b){ check_attributes(generated_qir, 1, 0) check_single_qubit_rotation_op(generated_qir, 3, [0, 0, 0], [0, 3, 6], "rx") - - -def test_convert_qasm3_for_loop_unsupported_type(): - """Test correct error when converting a QASM3 program that contains a for loop initialized from - an unsupported type.""" - with pytest.raises( - Qasm3ConversionError, - match=( - "Unexpected type " - " of set_declaration in loop." - ), - ): - _ = qasm3_to_qir( - """ - OPENQASM 3.0; - include "stdgates.inc"; - - qubit[4] q; - bit[4] c; - - h q; - for bit b in "001" { - x q[b]; - } - measure q->c; - """, - name="test", - ) diff --git a/tests/qasm3_qir/converter/test_measurement.py b/tests/qasm3_qir/converter/test_measurement.py index a37a42d..6ea9b13 100644 --- a/tests/qasm3_qir/converter/test_measurement.py +++ b/tests/qasm3_qir/converter/test_measurement.py @@ -1,20 +1,19 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing unit tests for QASM3 to QIR conversion functions. """ -import pytest -from qbraid_qir.qasm3 import Qasm3ConversionError, qasm3_to_qir +from qbraid_qir.qasm3 import qasm3_to_qir from tests.qir_utils import check_attributes, check_measure_op @@ -46,77 +45,3 @@ def test_measure(): bit_list = [0, 1, 0, 1, 2, 1, 1, 0] check_measure_op(generated_qir, 8, qubit_list, bit_list) - - -def test_incorrect_measure(): - def run_test(qasm3_code, error_message): - with pytest.raises(Qasm3ConversionError, match=error_message): - _ = qasm3_to_qir(qasm3_code) - - # Test for undeclared register q2 - run_test( - """ - OPENQASM 3; - qubit[2] q1; - bit[2] c1; - c1[0] = measure q2[0]; // undeclared register - """, - r"Missing register declaration for q2 .*", - ) - - # Test for undeclared register c2 - run_test( - """ - OPENQASM 3; - qubit[2] q1; - bit[2] c1; - measure q1 -> c2; // undeclared register - """, - r"Missing register declaration for c2 .*", - ) - - # Test for size mismatch between q1 and c2 - run_test( - """ - OPENQASM 3; - qubit[2] q1; - bit[2] c1; - bit[1] c2; - c2 = measure q1; // size mismatch - """, - r"Register sizes of q1 and c2 do not match .*", - ) - - # Test for size mismatch between q1 and c2 in ranges - run_test( - """ - OPENQASM 3; - qubit[5] q1; - bit[4] c1; - bit[1] c2; - c1[:3] = measure q1; // size mismatch - """, - r"Register sizes of q1 and c1 do not match .*", - ) - - # Test for out of bounds index for q1 - run_test( - """ - OPENQASM 3; - qubit[2] q1; - bit[2] c1; - measure q1[3] -> c1[0]; // out of bounds - """, - r"Index 3 out of range for register of size 2 in qubit", - ) - - # Test for out of bounds index for c1 - run_test( - """ - OPENQASM 3; - qubit[2] q1; - bit[2] c1; - measure q1 -> c1[3]; // out of bounds - """, - r"Index 3 out of range for register of size 2 in clbit", - ) diff --git a/tests/qasm3_qir/converter/declarations/test_quantum.py b/tests/qasm3_qir/converter/test_quantum_decl.py similarity index 62% rename from tests/qasm3_qir/converter/declarations/test_quantum.py rename to tests/qasm3_qir/converter/test_quantum_decl.py index ba4b609..39fdfcd 100644 --- a/tests/qasm3_qir/converter/declarations/test_quantum.py +++ b/tests/qasm3_qir/converter/test_quantum_decl.py @@ -1,21 +1,19 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing unit tests for QASM3 to QIR conversion functions. """ -import pytest from qbraid_qir.qasm3.convert import qasm3_to_qir -from qbraid_qir.qasm3.exceptions import Qasm3ConversionError from tests.qir_utils import check_attributes @@ -76,31 +74,3 @@ def test_qubit_clbit_declarations(): result = qasm3_to_qir(qasm3_string) generated_qir = str(result).splitlines() check_attributes(generated_qir, 7, 7) - - -def test_qubit_redeclaration_error(): - """Test redeclaration of qubit""" - with pytest.raises( - Qasm3ConversionError, match="Invalid declaration of register with name 'q1'" - ): - qasm3_string = """ - OPENQASM 3; - include "stdgates.inc"; - qubit q1; - qubit q1; - """ - qasm3_to_qir(qasm3_string) - - -def test_clbit_redeclaration_error(): - """Test redeclaration of clbit""" - with pytest.raises( - Qasm3ConversionError, match="Invalid declaration of register with name 'c1'" - ): - qasm3_string = """ - OPENQASM 3; - include "stdgates.inc"; - bit c1; - bit[4] c1; - """ - qasm3_to_qir(qasm3_string) diff --git a/tests/qasm3_qir/converter/test_reset.py b/tests/qasm3_qir/converter/test_reset.py index 0676a88..e70fdc7 100644 --- a/tests/qasm3_qir/converter/test_reset.py +++ b/tests/qasm3_qir/converter/test_reset.py @@ -1,20 +1,19 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing unit tests for QASM3 to QIR conversion functions. """ -import pytest -from qbraid_qir.qasm3 import Qasm3ConversionError, qasm3_to_qir +from qbraid_qir.qasm3 import qasm3_to_qir from tests.qir_utils import check_attributes, check_resets @@ -60,29 +59,3 @@ def my_function(qubit a) { generated_qir = str(result).splitlines() check_attributes(generated_qir, 3, 0) check_resets(generated_qir, 1, [1]) - - -def test_incorrect_resets(): - undeclared = """ - OPENQASM 3; - include "stdgates.inc"; - - qubit[3] q1; - - // undeclared register - reset q2[0]; - """ - with pytest.raises(Qasm3ConversionError): - _ = qasm3_to_qir(undeclared) - - index_error = """ - OPENQASM 3; - include "stdgates.inc"; - - qubit[2] q1; - - // out of bounds - reset q1[4]; - """ - with pytest.raises(Qasm3ConversionError): - _ = qasm3_to_qir(index_error) diff --git a/tests/qasm3_qir/converter/test_sizeof.py b/tests/qasm3_qir/converter/test_sizeof.py index 0e076dc..b51810b 100644 --- a/tests/qasm3_qir/converter/test_sizeof.py +++ b/tests/qasm3_qir/converter/test_sizeof.py @@ -1,21 +1,19 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing unit tests for QASM3 to QIR conversion functions. """ -import pytest from qbraid_qir.qasm3 import qasm3_to_qir -from qbraid_qir.qasm3.exceptions import Qasm3ConversionError from tests.qir_utils import check_attributes, check_single_qubit_rotation_op @@ -47,78 +45,3 @@ def test_simple_sizeof(): check_attributes(generated_qir, 2, 0) check_single_qubit_rotation_op(generated_qir, 4, [0, 0, 1, 1], [3, 3, 2, 3], "rx") - - -def test_sizeof_multiple_types(): - """Test sizeof over an array of bit, int and float""" - qasm3_string = """ - OPENQASM 3; - include "stdgates.inc"; - - array[bit, 3, 2] my_bits; - array[int[32], 3, 2] my_ints; - array[float[64], 3, 2] my_floats; - - int[32] size1 = sizeof(my_bits); // this is 3 - - int[32] size2 = sizeof(my_ints, 1); // this is 2 - - int[32] size3 = sizeof(my_floats, 0); // this is 3 too - qubit[2] q; - - rx(size1) q[0]; - rx(size2) q[1]; - rx(size3) q[1]; - """ - - result = qasm3_to_qir(qasm3_string) - generated_qir = str(result).splitlines() - check_attributes(generated_qir, 2, 0) - - check_single_qubit_rotation_op(generated_qir, 2, [0, 1, 1], [3, 2, 3], "rx") - - -def test_unsupported_target(): - """Test sizeof over index expressions""" - with pytest.raises(Qasm3ConversionError, match=r"Unsupported target type .*"): - qasm3_string = """ - OPENQASM 3; - include "stdgates.inc"; - - array[int[32], 3, 2] my_ints; - - int[32] size1 = sizeof(my_ints[0]); // this is invalid - """ - qasm3_to_qir(qasm3_string) - - -def test_sizeof_on_non_array(): - """Test sizeof on a non-array""" - with pytest.raises( - Qasm3ConversionError, match="Invalid sizeof usage, variable my_int is not an array." - ): - qasm3_string = """ - OPENQASM 3; - include "stdgates.inc"; - - int[32] my_int = 3; - - int[32] size1 = sizeof(my_int); // this is invalid - """ - qasm3_to_qir(qasm3_string) - - -def test_out_of_bounds_reference(): - """Test sizeof on an out of bounds reference""" - with pytest.raises( - Qasm3ConversionError, match="Index 3 out of bounds for array my_ints with 2 dimensions" - ): - qasm3_string = """ - OPENQASM 3; - include "stdgates.inc"; - - array[int[32], 3, 2] my_ints; - - int[32] size1 = sizeof(my_ints, 3); // this is invalid - """ - qasm3_to_qir(qasm3_string) diff --git a/tests/qasm3_qir/converter/test_subroutine.py b/tests/qasm3_qir/converter/test_subroutine.py deleted file mode 100644 index 77a0693..0000000 --- a/tests/qasm3_qir/converter/test_subroutine.py +++ /dev/null @@ -1,341 +0,0 @@ -# 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 containing unit tests for parsing, unrolling, and -converting OpenQASM3 programs that contain subroutines. - -""" - -import pytest - -from qbraid_qir.qasm3 import qasm3_to_qir -from qbraid_qir.qasm3.exceptions import Qasm3ConversionError -from tests.qasm3_qir.fixtures.subroutines import SUBROUTINE_INCORRECT_TESTS -from tests.qir_utils import ( - check_attributes, - check_single_qubit_gate_op, - check_single_qubit_rotation_op, -) - - -def test_function_declaration(): - """Test that a function declaration is correctly parsed.""" - qasm_str = """OPENQASM 3; - include "stdgates.inc"; - def my_function(qubit q) { - h q; - return; - } - qubit q; - my_function(q); - """ - - result = qasm3_to_qir(qasm_str) - generated_qir = str(result).splitlines() - - check_attributes(generated_qir, 1, 0) - check_single_qubit_gate_op(generated_qir, 1, [0], "h") - - -def test_simple_function_call(): - """Test that a simple function call is correctly parsed.""" - qasm_str = """OPENQASM 3.0; - include "stdgates.inc"; - - def my_function(qubit a, float[32] b) { - rx(b) a; - float[64] c = 2*b; - rx(c) a; - return; - } - qubit q; - bit c; - float[32] r = 3.14; - my_function(q, r); - - measure q -> c[0]; - """ - - result = qasm3_to_qir(qasm_str) - generated_qir = str(result).splitlines() - - check_attributes(generated_qir, 1, 1) - check_single_qubit_rotation_op(generated_qir, 2, [0, 0], [3.14, 6.28], "rx") - - -def test_const_visible_in_function_call(): - """Test that a constant is visible in a function call.""" - qasm_str = """OPENQASM 3; - include "stdgates.inc"; - const float[32] pi2 = 3.14; - - def my_function(qubit q) { - rx(pi2) q; - return; - } - qubit q; - my_function(q); - """ - - result = qasm3_to_qir(qasm_str) - generated_qir = str(result).splitlines() - - check_attributes(generated_qir, 1, 0) - check_single_qubit_rotation_op(generated_qir, 1, [0], [3.14], "rx") - - -def test_update_variable_in_function(): - """Test that variable update works correctly in a function.""" - qasm_str = """OPENQASM 3; - include "stdgates.inc"; - - def my_function(qubit q) { - float[32] a = 3.14; - a = 2*a; - rx(a) q; - return; - } - qubit q; - my_function(q); - """ - - result = qasm3_to_qir(qasm_str) - generated_qir = str(result).splitlines() - - check_attributes(generated_qir, 1, 0) - check_single_qubit_rotation_op(generated_qir, 1, [0], [6.28], "rx") - - -def test_function_call_in_expression(): - """Test that a function call in an expression is correctly parsed.""" - qasm_str = """OPENQASM 3; - include "stdgates.inc"; - - def my_function(qubit q) -> bool{ - h q; - return true; - } - qubit q; - bool b = my_function(q); - """ - - result = qasm3_to_qir(qasm_str) - generated_qir = str(result).splitlines() - - check_attributes(generated_qir, 1, 0) - check_single_qubit_gate_op(generated_qir, 1, [0], "h") - - -def test_multiple_function_calls(): - """Test that multiple function calls are correctly parsed.""" - qasm_str = """OPENQASM 3; - include "stdgates.inc"; - - def my_function(int[32] a, qubit q_arg) { - h q_arg; - rx (a) q_arg; - return; - } - qubit[3] q; - my_function(2, q[2]); - my_function(1, q[1]); - my_function(0, q[0]); - """ - - result = qasm3_to_qir(qasm_str) - generated_qir = str(result).splitlines() - - check_attributes(generated_qir, 3, 0) - check_single_qubit_gate_op(generated_qir, 3, [2, 1, 0], "h") - check_single_qubit_rotation_op(generated_qir, 3, [2, 1, 0], [2, 1, 0], "rx") - - -def test_function_call_with_return(): - """Test that a function call with a return value is correctly parsed.""" - qasm_str = """OPENQASM 3; - include "stdgates.inc"; - - def my_function(qubit q) -> float[32] { - h q; - return 3.14; - } - qubit q; - float[32] r = my_function(q); - """ - - result = qasm3_to_qir(qasm_str) - generated_qir = str(result).splitlines() - - check_attributes(generated_qir, 1, 0) - check_single_qubit_gate_op(generated_qir, 1, [0], "h") - - -def test_return_values_from_function(): - """Test that the values returned from a function are used correctly in other function.""" - qasm_str = """OPENQASM 3; - include "stdgates.inc"; - - def my_function(qubit q) -> float[32] { - h q; - return 3.14; - } - def my_function_2(qubit q, float[32] r) { - rx(r) q; - return; - } - qubit[2] q; - float[32] r1 = my_function(q[0]); - my_function_2(q[0], r1); - - array[float[32], 1, 1] r2 = {{3.14}}; - my_function_2(q[1], r2[0,0]); - - """ - - result = qasm3_to_qir(qasm_str) - generated_qir = str(result).splitlines() - - check_attributes(generated_qir, 2, 0) - check_single_qubit_gate_op(generated_qir, 1, [0], "h") - check_single_qubit_rotation_op(generated_qir, 2, [0, 1], [3.14, 3.14], "rx") - - -def test_function_call_with_custom_gate(): - """Test that a function call with a custom gate is correctly parsed.""" - qasm_str = """OPENQASM 3.0; - include "stdgates.inc"; - - gate my_gate(a) q2 { - rx(a) q2; - } - - def my_function(qubit a, float[32] b) { - float[64] c = 2*b; - my_gate(b) a; - my_gate(c) a; - return; - } - qubit q; - bit c; - float[32] r = 3.14; - my_function(q, r); - - measure q -> c[0]; - """ - - result = qasm3_to_qir(qasm_str) - generated_qir = str(result).splitlines() - - check_attributes(generated_qir, 1, 1) - check_single_qubit_rotation_op(generated_qir, 2, [0, 0], [3.14, 6.28], "rx") - - -@pytest.mark.skip(reason="Not implemented nested functions yet") -def test_function_call_from_within_fn(): - """Test that a function call from within another function is correctly converted.""" - qasm_str = """OPENQASM 3; - include "stdgates.inc"; - - def my_function(qubit q1) { - h q1; - return; - } - - def my_function_2(qubit[2] q2) { - my_function(q2[1]); - return; - } - qubit[2] q; - my_function_2(q); - """ - - result = qasm3_to_qir(qasm_str) - generated_qir = str(result).splitlines() - check_attributes(generated_qir, 2, 0) - check_single_qubit_gate_op(generated_qir, 1, [1], "h") - - -@pytest.mark.parametrize("data_type", ["int[32] a = 1;", "float[32] a = 1.0;", "bit a = 0;"]) -def test_return_value_mismatch(data_type): - """Test that returning a value of incorrect type raises error.""" - qasm_str = ( - """OPENQASM 3; - include "stdgates.inc"; - - def my_function(qubit q) { - h q; - """ - + data_type - + """ - return a; - } - qubit q; - my_function(q); - """ - ) - - with pytest.raises( - Qasm3ConversionError, match=r"Return type mismatch for subroutine 'my_function'.*" - ): - qasm3_to_qir(qasm_str) - - -@pytest.mark.parametrize("keyword", ["pi", "euler", "tau"]) -def test_subroutine_keyword_naming(keyword): - """Test that using a keyword as a subroutine name raises error.""" - qasm_str = f"""OPENQASM 3; - include "stdgates.inc"; - - def {keyword}(qubit q) {{ - h q; - return; - }} - qubit q; - {keyword}(q); - """ - - with pytest.raises( - Qasm3ConversionError, match=f"Subroutine name '{keyword}' is a reserved keyword" - ): - qasm3_to_qir(qasm_str) - - -@pytest.mark.parametrize("qubit_params", ["q", "q[:2]", "q[{0,1}]"]) -def test_qubit_size_arg_mismatch(qubit_params): - """Test that passing a qubit of different size raises error.""" - qasm_str = ( - """OPENQASM 3; - include "stdgates.inc"; - - def my_function(qubit[3] q) { - h q; - return; - } - qubit[2] q; - my_function(""" - + qubit_params - + """); - """ - ) - - with pytest.raises( - Qasm3ConversionError, - match="Qubit register size mismatch for function 'my_function'. " - "Expected 3 in variable 'q' but got 2", - ): - qasm3_to_qir(qasm_str) - - -@pytest.mark.parametrize("test_name", SUBROUTINE_INCORRECT_TESTS.keys()) -def test_incorrect_custom_ops(test_name): - qasm_input, error_message = SUBROUTINE_INCORRECT_TESTS[test_name] - with pytest.raises(Qasm3ConversionError, match=error_message): - _ = qasm3_to_qir(qasm_input) diff --git a/tests/qasm3_qir/converter/test_subroutine_arrays.py b/tests/qasm3_qir/converter/test_subroutine_arrays.py deleted file mode 100644 index 8e5b705..0000000 --- a/tests/qasm3_qir/converter/test_subroutine_arrays.py +++ /dev/null @@ -1,405 +0,0 @@ -# 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 containing unit tests for parsing, unrolling, and -converting OpenQASM3 programs that contain arrays in subroutines. - -""" - -import pytest - -from qbraid_qir.qasm3 import qasm3_to_qir -from qbraid_qir.qasm3.exceptions import Qasm3ConversionError -from tests.qir_utils import check_attributes, check_single_qubit_rotation_op - - -def test_simple_function_call(): - """Test that a simple function call is correctly parsed.""" - qasm_str = """OPENQASM 3.0; - include "stdgates.inc"; - - def my_function(qubit a, readonly array[int[8], 2, 2] my_arr) { - return; - } - qubit q; - array[int[8], 2, 2] arr; - my_function(q, arr); - - """ - - result = qasm3_to_qir(qasm_str) - generated_qir = str(result).splitlines() - - check_attributes(generated_qir, 1, 0) - - -def test_passing_array_to_function(): - """Test that passing an array to a function is correctly parsed.""" - qasm_str = """OPENQASM 3.0; - include "stdgates.inc"; - - def my_function(readonly array[int[8], 2, 2] my_arr, qubit q) { - rx(my_arr[0][0]) q; - rx(my_arr[0][1]) q; - rx(my_arr[1][0]) q; - rx(my_arr[1][1]) q; - - return; - } - qubit my_q; - array[int[8], 2, 2] arr = { {1, 2}, {3, 4} }; - my_function(arr, my_q); - """ - - result = qasm3_to_qir(qasm_str) - generated_qir = str(result).splitlines() - - check_attributes(generated_qir, 1, 0) - check_single_qubit_rotation_op( - generated_qir, 4, qubit_list=[0, 0, 0, 0], param_list=[1, 2, 3, 4], gate_name="rx" - ) - - -def test_passing_subarray_to_function(): - """Test that passing a smaller subarray to a function is correctly parsed.""" - qasm_str = """OPENQASM 3.0; - include "stdgates.inc"; - - def my_function(readonly array[int[8], 2, 2] my_arr, qubit q) { - rx(my_arr[0][0]) q; - rx(my_arr[0][1]) q; - return; - } - qubit my_q; - array[int[8], 4, 3] arr_1 = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12} }; - array[int[8], 2, 2] arr_2 = { {1, 2}, {3, 4} }; - my_function(arr_1[1:2][1:2], my_q); - my_function(arr_2[0:1, :], my_q); - - """ - - result = qasm3_to_qir(qasm_str) - generated_qir = str(result).splitlines() - - check_attributes(generated_qir, 1, 0) - check_single_qubit_rotation_op( - generated_qir, 2, qubit_list=[0] * 4, param_list=[5, 6, 1, 2], gate_name="rx" - ) - - -def test_passing_array_with_dim_identifier(): - """Test that passing an array with a dimension identifier is correctly parsed.""" - qasm_str = """OPENQASM 3.0; - include "stdgates.inc"; - - def my_function(readonly array[int[8], #dim = 2] my_arr, qubit q) { - rx(my_arr[0][0]) q; - rx(my_arr[0][1]) q; - return; - } - qubit my_q; - array[int[8], 2, 2, 2] arr = { { {1, 2}, {3, 4} }, { {5, 6}, {7, 8} } }; - my_function(arr[0, :, :], my_q); - """ - - result = qasm3_to_qir(qasm_str) - generated_qir = str(result).splitlines() - - check_attributes(generated_qir, 1, 0) - check_single_qubit_rotation_op( - generated_qir, 2, qubit_list=[0] * 2, param_list=[1, 2], gate_name="rx" - ) - - -def test_pass_multiple_arrays_to_function(): - """Test that passing multiple arrays to a function is correctly parsed.""" - qasm_str = """OPENQASM 3.0; - include "stdgates.inc"; - - def my_function(readonly array[int[8], 2, 2] my_arr1, - readonly array[int[8], 2, 2] my_arr2, - qubit q) { - for int[8] i in [0:1] { - rx(my_arr1[i][0]) q; - rx(my_arr2[i][1]) q; - } - - return; - } - qubit my_q; - array[int[8], 2, 2] arr_1 = { {1, 2}, {3, 4} }; - array[int[8], 2, 2] arr_2 = { {5, 6}, {7, 8} }; - my_function(arr_1, arr_2, my_q); - """ - - result = qasm3_to_qir(qasm_str) - generated_qir = str(result).splitlines() - - check_attributes(generated_qir, 1, 0) - check_single_qubit_rotation_op( - generated_qir, 4, qubit_list=[0] * 4, param_list=[1, 6, 3, 8], gate_name="rx" - ) - - -def test_non_array_raises_error(): - """Test that passing a non-array to an array parameter raises error.""" - qasm_str = """OPENQASM 3.0; - include "stdgates.inc"; - - def my_function(qubit a, readonly array[int[8], 2, 2] my_arr) { - return; - } - qubit q; - int[8] arr; - my_function(q, arr); - - """ - - with pytest.raises( - Qasm3ConversionError, - match=r"Expecting type 'array\[int\[8\],...\]' for 'my_arr' in function 'my_function'." - r" Variable 'arr' has type 'int\[8\]'.", - ): - qasm3_to_qir(qasm_str) - - -def test_literal_raises_error(): - """Test that passing a literal to an array parameter raises error.""" - qasm_str = """OPENQASM 3.0; - include "stdgates.inc"; - - def my_function(qubit a, readonly array[int[8], 2, 2] my_arr) { - return; - } - qubit q; - my_function(q, 5); - - """ - - with pytest.raises( - Qasm3ConversionError, - match=r"Expecting type 'array\[int\[8\],...\]' for 'my_arr' in function 'my_function'." - r" Literal 5 found in function call", - ): - qasm3_to_qir(qasm_str) - - -def test_type_mismatch_in_array(): - """Test that passing an array of different type raises error.""" - qasm_str = """OPENQASM 3.0; - include "stdgates.inc"; - - def my_function(qubit a, readonly array[int[8], 2, 2] my_arr) { - return; - } - qubit q; - array[uint[32], 2, 2] arr; - my_function(q, arr); - - """ - - with pytest.raises( - Qasm3ConversionError, - match=r"Expecting type 'array\[int\[8\],...\]' for 'my_arr' in function 'my_function'." - r" Variable 'arr' has type 'array\[uint\[32\], 2, 2\]'.", - ): - qasm3_to_qir(qasm_str) - - -def test_dimension_count_mismatch_1(): - """Test that passing an array with different dimension count raises error.""" - qasm_str = """OPENQASM 3.0; - include "stdgates.inc"; - - def my_function(qubit a, readonly array[int[8], 2, 2] my_arr) { - return; - } - qubit q; - array[int[8], 2] arr; - my_function(q, arr); - - """ - - with pytest.raises( - Qasm3ConversionError, - match=r"Dimension mismatch for 'my_arr' in function 'my_function'. Expected 2 dimensions" - r" but variable 'arr' has 1", - ): - qasm3_to_qir(qasm_str) - - -def test_dimension_count_mismatch_2(): - """Test that passing an array with different dimension count raises error.""" - qasm_str = """OPENQASM 3.0; - include "stdgates.inc"; - - def my_function(qubit a, readonly array[int[8], #dim = 4] my_arr) { - return; - } - qubit q; - array[int[8], 2, 2] arr; - my_function(q, arr); - - """ - - with pytest.raises( - Qasm3ConversionError, - match=r"Dimension mismatch for 'my_arr' in function 'my_function'. Expected 4 dimensions " - r"but variable 'arr' has 2", - ): - qasm3_to_qir(qasm_str) - - -def test_qubit_passed_as_array(): - """Test that passing a qubit as an array raises error.""" - qasm_str = """OPENQASM 3.0; - include "stdgates.inc"; - - def my_function(mutable array[int[8], 2, 2] my_arr) { - return; - } - qubit[2] q; - my_function(q); - - """ - - with pytest.raises( - Qasm3ConversionError, - match=r"Expecting type 'array\[int\[8\],...\]' for 'my_arr' in function 'my_function'." - r" Qubit register 'q' found for function call", - ): - qasm3_to_qir(qasm_str) - - -def test_invalid_dimension_number(): - """Test that passing an array with invalid dimension number raises error.""" - qasm_str = """OPENQASM 3.0; - include "stdgates.inc"; - - def my_function(qubit a, readonly array[int[8], #dim = -3] my_arr) { - return; - } - qubit q; - array[int[8], 2, 2, 2] arr; - my_function(q, arr); - - """ - - with pytest.raises( - Qasm3ConversionError, - match=r"Invalid number of dimensions -3 for 'my_arr' in function 'my_function'", - ): - qasm3_to_qir(qasm_str) - - -def test_invalid_non_int_dimensions_1(): - """Test that passing an array with non-integer dimensions raises error.""" - qasm_str = """OPENQASM 3.0; - include "stdgates.inc"; - - def my_function(qubit a, mutable array[int[8], #dim = 2.5] my_arr) { - return; - } - qubit q; - array[int[8], 2, 2] arr; - my_function(q, arr); - - """ - - with pytest.raises( - Qasm3ConversionError, - match=r"Invalid value 2.5 with type for required type " - r"", - ): - qasm3_to_qir(qasm_str) - - -def test_invalid_non_int_dimensions_2(): - """Test that passing an array with non-integer dimensions raises error.""" - qasm_str = """OPENQASM 3.0; - include "stdgates.inc"; - - def my_function(qubit a, readonly array[int[8], 2.5, 2] my_arr) { - return; - } - qubit q; - array[int[8], 2, 2] arr; - my_function(q, arr); - - """ - - with pytest.raises( - Qasm3ConversionError, - match=r"Invalid value 2.5 with type for required type" - r" ", - ): - qasm3_to_qir(qasm_str) - - -def test_extra_dimensions_for_array(): - """Test that passing an array with extra dimensions raises error.""" - qasm_str = """OPENQASM 3.0; - include "stdgates.inc"; - - def my_function(qubit a, mutable array[int[8], 4, 2] my_arr) { - return; - } - qubit q; - array[int[8], 2, 2] arr; - my_function(q, arr); - - """ - - with pytest.raises( - Qasm3ConversionError, - match=r"Dimension mismatch for 'my_arr' in function 'my_function'. " - r"Expected dimension 0 with size >= 4 but got 2", - ): - qasm3_to_qir(qasm_str) - - -def test_invalid_array_dimensions_formal_arg(): - """Test that passing an array with invalid dimensions raises error.""" - qasm_str = """ - OPENQASM 3; - include "stdgates.inc"; - - def my_function(readonly array[int[32], -1, 2] a) { - return; - } - array[int[32], 1, 2] b; - my_function(b); - """ - with pytest.raises( - Qasm3ConversionError, - match=r"Invalid dimension size -1 for 'a' in function 'my_function'", - ): - qasm3_to_qir(qasm_str) - - -def test_invalid_array_mutation_for_readonly_arg(): - """Test that mutating an array passed as readonly raises error.""" - qasm_str = """ - OPENQASM 3; - include "stdgates.inc"; - - def my_function(readonly array[int[32], 1, 2] a) { - a[1][0] = 5; - return; - } - array[int[32], 1, 2] b; - my_function(b); - """ - with pytest.raises( - Qasm3ConversionError, - match=r"Assignment to readonly variable 'a' not allowed in function call", - ): - qasm3_to_qir(qasm_str) diff --git a/tests/qasm3_qir/converter/test_switch.py b/tests/qasm3_qir/converter/test_switch.py index efb9e79..d519258 100644 --- a/tests/qasm3_qir/converter/test_switch.py +++ b/tests/qasm3_qir/converter/test_switch.py @@ -1,12 +1,12 @@ # Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing unit tests for converting OpenQASM 3 programs @@ -14,12 +14,8 @@ """ -import re - -import pytest from qbraid_qir.qasm3 import qasm3_to_qir -from qbraid_qir.qasm3.exceptions import Qasm3ConversionError from tests.qir_utils import ( check_attributes, check_single_qubit_gate_op, @@ -143,55 +139,6 @@ def test_switch_const_int(): check_single_qubit_gate_op(generated_qir, 1, [0], "x") -def test_switch_duplicate_cases(): - """Test that switch raises error if duplicate values are present in case.""" - - with pytest.raises( - Qasm3ConversionError, match=re.escape("Duplicate case value 4 in switch statement") - ): - qasm3_switch_program = """ - OPENQASM 3.0; - include "stdgates.inc"; - - const int i = 4; - qubit q; - - switch(i) { - case 4, 4 { - x q; - } - default { - z q; - } - } - """ - - qasm3_to_qir(qasm3_switch_program, name="test") - - -def test_no_case_switch(): - """Test that switch raises error if no case is present.""" - - with pytest.raises( - Qasm3ConversionError, match=re.escape("Switch statement must have at least one case") - ): - qasm3_switch_program = """ - OPENQASM 3.0; - include "stdgates.inc"; - - const int i = 4; - qubit q; - - switch(i) { - default { - z q; - } - } - """ - - qasm3_to_qir(qasm3_switch_program, name="test") - - def test_nested_switch(): """Test that switch works correctly in case of nested switch""" @@ -263,183 +210,3 @@ def my_function(qubit q, float[32] b) { check_attributes(generated_qir, 2, 0) check_single_qubit_rotation_op(generated_qir, 1, [0], [3.14], "rx") - - -@pytest.mark.parametrize("invalid_type", ["float", "bool", "bit"]) -def test_invalid_scalar_switch_target(invalid_type): - """Test that switch raises error if target is not an integer.""" - - base_invalid_program = ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - """ - + invalid_type - + """ i; - - qubit q; - - switch(i) { - case 4 { - x q; - } - default { - z q; - } - } - """ - ) - - with pytest.raises( - Qasm3ConversionError, match=re.escape("Switch target i must be of type int") - ): - qasm3_switch_program = base_invalid_program - qasm3_to_qir(qasm3_switch_program, name="test") - - -@pytest.mark.parametrize("invalid_type", ["float", "bool", "bit"]) -def test_invalid_array_switch_target(invalid_type): - """Test that switch raises error if target is array element and not an integer.""" - - base_invalid_program = ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - array[""" - + invalid_type - + """, 3, 2] i; - - qubit q; - - switch(i[0][1]) { - case 4 { - x q; - } - default { - z q; - } - } - """ - ) - - with pytest.raises( - Qasm3ConversionError, match=re.escape("Switch target i must be of type int") - ): - qasm3_switch_program = base_invalid_program - qasm3_to_qir(qasm3_switch_program, name="test") - - -@pytest.mark.parametrize( - "invalid_stmt", - ["def test1() { int i = 1; }", "array[int[32], 3, 2] arr_int;", "gate test_1() q { h q;}"], -) -def test_unsupported_statements_in_case(invalid_stmt): - """Test that switch raises error if invalid statements are present in the case block""" - - base_invalid_program = ( - """ - - OPENQASM 3.0; - include "stdgates.inc"; - qubit q; - int i = 4; - - switch(i) { - case 4 { - x q; - """ - + invalid_stmt - + """ - } - default { - z q; - } - } - """ - ) - with pytest.raises(Qasm3ConversionError, match=r"Unsupported statement .*"): - qasm3_switch_program = base_invalid_program - qasm3_to_qir(qasm3_switch_program, name="test") - - -def test_non_int_expression_case(): - """Test that switch raises error if case expression is not an integer.""" - - base_invalid_program = """ - OPENQASM 3.0; - include "stdgates.inc"; - const int i = 4; - qubit q; - - switch(i) { - case 4.3, 2 { - x q; - } - default { - z q; - } - } - """ - - with pytest.raises( - Qasm3ConversionError, - match=r"Invalid value 4.3 with type .* for required type ", - ): - qasm3_switch_program = base_invalid_program - qasm3_to_qir(qasm3_switch_program, name="test") - - -def test_non_int_variable_expression(): - """Test that switch raises error if case expression has a non-int - variable in expression.""" - - base_invalid_program = """ - OPENQASM 3.0; - include "stdgates.inc"; - const int i = 4; - const float f = 4.0; - qubit q; - - switch(i) { - case f, 2 { - x q; - } - default { - z q; - } - } - """ - with pytest.raises( - Qasm3ConversionError, - match=r"Invalid type of variable .* for required type ", - ): - qasm3_switch_program = base_invalid_program - qasm3_to_qir(qasm3_switch_program, name="test") - - -def test_non_constant_expression_case(): - """Test that switch raises error if case expression is not a constant.""" - - base_invalid_program = """ - OPENQASM 3.0; - include "stdgates.inc"; - int i = 4; - qubit q; - int j = 3; - int k = 2; - - switch(i) { - case j + k { - x q; - } - default { - z q; - } - } - """ - - with pytest.raises( - Qasm3ConversionError, match=r"Variable .* is not a constant in given expression" - ): - qasm3_switch_program = base_invalid_program - qasm3_to_qir(qasm3_switch_program, name="test") diff --git a/tests/qasm3_qir/fixtures/gates.py b/tests/qasm3_qir/fixtures/gates.py index 931b3a9..47d836e 100644 --- a/tests/qasm3_qir/fixtures/gates.py +++ b/tests/qasm3_qir/fixtures/gates.py @@ -1,12 +1,12 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module defining Cirq basic gate fixtures for use in tests. @@ -164,7 +164,7 @@ def test_fixture(): locals()[name] = _generate_custom_op_fixture(test_name) single_op_tests = [_fixture_name(s) for s in PYQIR_ONE_QUBIT_OP_MAP] -already_tested_single_op = ["i", "id", "si", "ti", "v", "sx", "vi", "sxdg"] +already_tested_single_op = ["id", "si", "ti", "v", "sx", "vi", "sxdg"] for gate in already_tested_single_op: single_op_tests.remove(_fixture_name(gate)) @@ -202,160 +202,3 @@ def test_fixture(): triple_op_tests.remove(_fixture_name(gate)) custom_op_tests = [_fixture_name(s) for s in CUSTOM_OPS] - -# qasm_input, expected_error -SINGLE_QUBIT_GATE_INCORRECT_TESTS = { - "missing_register": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - qubit[2] q1; - h q2; // undeclared register - """, - "Missing register declaration for q2 .*", - ), - "undeclared_1qubit_op": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - qubit[2] q1; - u_abc(0.5, 0.5, 0.5) q1; // unsupported gate - """, - "Unsupported / undeclared QASM operation: u_abc", - ), - "undeclared_1qubit_op_with_indexing": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - qubit[2] q1; - u_abc(0.5, 0.5, 0.5) q1[0], q1[1]; // unsupported gate - """, - "Unsupported / undeclared QASM operation: u_abc", - ), - "undeclared_3qubit_op": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - qubit[3] q1; - u_abc(0.5, 0.5, 0.5) q1[0], q1[1], q1[2]; // unsupported gate - """, - "Unsupported / undeclared QASM operation: u_abc", - ), - "invalid_gate_application": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - qubit[3] q1; - cx q1; // invalid application of gate, as we apply it to 3 qubits in blocks of 2 - """, - "Invalid number of qubits 3 for operation .*", - ), - "unsupported_parameter_type": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - qubit[2] q1; - rx(a) q1; // unsupported parameter type - """, - "Undefined identifier a in.*", - ), -} - -# qasm_input, expected_error -CUSTOM_GATE_INCORRECT_TESTS = { - "undeclared_custom": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - qubit[2] q1; - custom_gate q1; // undeclared gate - """, - "Unsupported / undeclared QASM operation: custom_gate", - ), - "parameter_mismatch": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - gate custom_gate(a,b) p, q{ - rx(a) p; - ry(b) q; - } - - qubit[2] q1; - custom_gate(0.5) q1; // parameter count mismatch - """, - "Parameter count mismatch for gate custom_gate: expected 2 arguments, but got 1 instead.", - ), - "qubit_mismatch": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - gate custom_gate(a,b) p, q{ - rx(a) p; - ry(b) q; - } - - qubit[3] q1; - custom_gate(0.5, 0.5) q1; // qubit count mismatch - """, - "Qubit count mismatch for gate custom_gate: expected 2 qubits, but got 3 instead.", - ), - "indexing_not_supported": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - gate custom_gate(a,b) p, q{ - rx(a) p; - ry(b) q[0]; - } - - qubit[2] q1; - custom_gate(0.5, 0.5) q1; // indexing not supported - """, - "Indexing .* not supported in gate definition", - ), - "recursive_definition": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - gate custom_gate(a,b) p, q{ - custom_gate(a,b) p, q; - } - - qubit[2] q1; - custom_gate(0.5, 0.5) q1; // recursive definition - """, - "Recursive definitions not allowed .*", - ), - "duplicate_definition": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - gate custom_gate(a,b) p, q{ - rx(a) p; - ry(b) q; - } - - gate custom_gate(a,b) p, q{ - rx(a) p; - ry(b) q; - } - - qubit[2] q1; - custom_gate(0.5, 0.5) q1; // duplicate definition - """, - "Duplicate gate definition for custom_gate", - ), -} diff --git a/tests/qasm3_qir/fixtures/subroutines.py b/tests/qasm3_qir/fixtures/subroutines.py deleted file mode 100644 index 9970015..0000000 --- a/tests/qasm3_qir/fixtures/subroutines.py +++ /dev/null @@ -1,240 +0,0 @@ -# Copyright (C) 2024 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 defining subroutine tests. - -""" - -SUBROUTINE_INCORRECT_TESTS = { - "undeclared_call": ( - """ - OPENQASM 3; - include "stdgates.inc"; - qubit q; - my_function(1); - """, - "Undefined subroutine 'my_function' was called", - ), - "redefinition_raises_error": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - def my_function(qubit q) -> int[32] { - h q; - return; - } - def my_function(qubit q) -> float[32] { - x q; - return; - } - qubit q; - """, - "Redefinition of subroutine 'my_function'", - ), - "redefinition_raises_error_2": ( - """ - OPENQASM 3; - include "stdgates.inc"; - def my_function(qubit q) { - int[32] q = 1; - return; - } - qubit q; - my_function(q); - """, - "Re-declaration of variable q", - ), - "incorrect_param_count_1": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - def my_function(qubit q, qubit r) { - h q; - return; - } - qubit q; - my_function(q); - """, - "Parameter count mismatch for subroutine 'my_function'. Expected 2 but got 1 in call", - ), - "incorrect_param_count_2": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - def my_function(int[32] q) { - h q; - return; - } - qubit q; - my_function(q, q); - """, - "Parameter count mismatch for subroutine 'my_function'. Expected 1 but got 2 in call", - ), - "return_value_mismatch": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - def my_function(qubit q) { - h q; - int[32] a = 1; - return a; - } - qubit q; - my_function(q); - """, - "Return type mismatch for subroutine 'my_function'.", - ), - "return_value_mismatch_2": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - def my_function(qubit q) -> int[32] { - h q; - int[32] a = 1; - return ; - } - qubit q; - my_function(q); - """, - "Return type mismatch for subroutine 'my_function'.", - ), - "subroutine_keyword_naming": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - def pi(qubit q) { - h q; - return; - } - qubit q; - pi(q); - """, - "Subroutine name 'pi' is a reserved keyword", - ), - "qubit_size_arg_mismatch": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - def my_function(qubit[3] q) { - h q; - return; - } - qubit[2] q; - my_function(q); - """, - "Qubit register size mismatch for function 'my_function'.", - ), - "subroutine_var_name_conflict": ( - """ - OPENQASM 3; - include "stdgates.inc"; - const int a = 4; - def a(qubit q) { - h q; - return; - } - qubit q; - a(q); - """, - r"Can not declare subroutine with name 'a' .*", - ), - "undeclared_register_usage": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - def my_function(qubit q) { - h q; - return; - } - qubit q; - int b; - my_function(b); - """, - "Expecting qubit argument for 'q'. Qubit register 'b' not found for function 'my_function'", - ), - "test_invalid_qubit_size": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - def my_function(qubit[-3] q) { - h q; - return; - } - qubit[4] q; - my_function(q); - """, - "Invalid qubit size -3 for variable 'q' in function 'my_function'", - ), - "test_type_mismatch_for_function": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - def my_function(int[32] a, qubit q) { - h q; - return; - } - qubit q; - int[32] b = 4; - my_function(q, b); - """, - "Expecting classical argument for 'a'. Qubit register 'q' found for function 'my_function'", - ), - "test_duplicate_qubit_args": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - def my_function(qubit[3] p, qubit[1] q) { - h q; - return; - } - qubit[4] q; - my_function(q[0:3], q[2]); - """, - r"Duplicate qubit argument for register 'q' in function call for 'my_function'", - ), - "undefined_variable_in_actual_arg_1": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - def my_function(int [32] a) { - h q; - return; - } - qubit q; - my_function(b); - """, - "Undefined variable 'b' used for function call 'my_function'", - ), - "undefined_array_arg_in_function_call": ( - """ - OPENQASM 3; - include "stdgates.inc"; - - def my_function(readonly array[int[32], 1, 2] a) { - return; - } - my_function(b); - """, - "Undefined variable 'b' used for function call 'my_function'", - ), -} diff --git a/tests/qasm3_qir/fixtures/variables.py b/tests/qasm3_qir/fixtures/variables.py deleted file mode 100644 index 846ccc8..0000000 --- a/tests/qasm3_qir/fixtures/variables.py +++ /dev/null @@ -1,264 +0,0 @@ -# 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 defining QASM3 incorrect variable tests. - -""" - -DECLARATION_TESTS = { - "keyword_redeclaration": ( - """ - OPENQASM 3.0; - include "stddgates.inc"; - int pi; - """, - "Can not declare variable with keyword name pi", - ), - "const_keyword_redeclaration": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - const int pi = 3; - """, - "Can not declare variable with keyword name pi", - ), - "variable_redeclaration": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - int x; - float y = 3.4; - uint x; - """, - "Re-declaration of variable x", - ), - "variable_redeclaration_with_qubits_1": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - int x; - qubit x; - """, - "Invalid declaration of register with name 'x'", - ), - "variable_redeclaration_with_qubits_2": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - qubit x; - int x; - """, - "Re-declaration of variable x", - ), - "const_variable_redeclaration": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - const int x = 3; - const float x = 3.4; - """, - "Re-declaration of variable x", - ), - "invalid_int_size": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - int[32.1] x; - """, - "Invalid base size 32.1 for variable x", - ), - "invalid_const_int_size": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - const int[32.1] x = 3; - """, - "Invalid base size 32.1 for variable x", - ), - "const_declaration_with_non_const": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - int[32] x = 5; - const int[32] y = x + 5; - """, - "Variable 'x' is not a constant in given expression", - ), - "const_declaration_with_non_const_size": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - int[32] x = 5; - const int[x] y = 5; - """, - "Variable 'x' is not a constant in given expression", - ), - "invalid_float_size": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - - float[23] x; - """, - "Invalid base size 23 for float variable x", - ), - "unsupported_types": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - - angle x = 3.4; - """, - "Invalid type for variable x", - ), - "imaginary_variable": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - - int x = 1 + 3im; - """, - "Unsupported expression type ", - ), - "invalid_array_dimensions": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - - array[int[32], 1, 2.1] x; - """, - "Invalid dimension size 2.1 in array declaration for x", - ), - "extra_array_dimensions": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - - array[int[32], 1, 2, 3, 4, 5, 6, 7, 8] x; - """, - "Invalid dimensions 8 for array declaration for x. Max allowed dimensions is 7", - ), - "dimension_mismatch_1": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - - array[int[32], 1, 2] x = {1,2,3}; - """, - "Invalid dimensions for array assignment to variable x. Expected 1 but got 3", - ), - "dimension_mismatch_2": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - - array[int[32], 3, 1, 2] x = {1,2,3}; - """, - "Invalid dimensions for array assignment to variable x. Expected 3 but got 1", - ), -} - -ASSIGNMENT_TESTS = { - "undefined_variable_assignment": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - - float k; - - x = 3; - - """, - "Undefined variable x in assignment", - ), - "assignment_to_constant": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - - const int x = 3; - x = 4; - """, - "Assignment to constant variable x not allowed", - ), - "invalid_assignment_type": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - - bit x = 3.3; - """, - ( - "Cannot cast to . " - "Invalid assignment of type to variable x of type " - "" - ), - ), - "int_out_of_range": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - - int[32] x = 1<<64; - """, - f"Value {2**64} out of limits for variable x with base size 32", - ), - "float32_out_of_range": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - - float[32] x = 123456789123456789123456789123456789123456789.1; - """, - "Value .* out of limits for variable x with base size 32", - ), - "indexing_non_array": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - - int x = 3; - x[0] = 4; - """, - "Indexing error. Variable x is not an array", - ), - "incorrect_num_dims": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - - array[int[32], 1, 2, 3] x; - x[0] = 3; - """, - "Invalid number of indices for variable x. Expected 3 but got 1", - ), - "non_nnint_index": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - - array[int[32], 3] x; - x[0.1] = 3; - """, - "Invalid value 0.1 with type for " - "required type ", - ), - "index_out_of_range": ( - """ - OPENQASM 3.0; - include "stdgates.inc"; - - array[int[32], 3] x; - x[3] = 3; - """, - "Index 3 out of bounds for dimension 0 of variable x", - ), -} diff --git a/tests/qasm3_qir/test_checker.py b/tests/qasm3_qir/test_checker.py deleted file mode 100644 index d8b0576..0000000 --- a/tests/qasm3_qir/test_checker.py +++ /dev/null @@ -1,45 +0,0 @@ -# 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. - -""" -Tests the checker module of qasm3 - -""" - -import pytest - -from qbraid_qir.qasm3.checker import QasmValidationError, validate_qasm - - -def test_correct_check(): - assert validate_qasm("OPENQASM 3; include 'stdgates.inc'; qubit q;") is None - - -def test_incorrect_check(): - with pytest.raises(QasmValidationError): - validate_qasm( - """ - //semantically incorrect program - OPENQASM 3; - include 'stdgates.inc'; - qubit q; - for int[32] i in [0:10] { - h q; - } - rx(3.14) q[2]; - """ - ) - - -def test_incorrect_program_type(): - with pytest.raises( - TypeError, match="Input quantum program must be of type 'str' or 'openqasm3.ast.Program'." - ): - validate_qasm(1234) diff --git a/tests/qasm3_qir/test_convert.py b/tests/qasm3_qir/test_convert.py index 28eacd2..04d32c6 100644 --- a/tests/qasm3_qir/test_convert.py +++ b/tests/qasm3_qir/test_convert.py @@ -1,12 +1,12 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Tests the convert module of qasm3 to qir diff --git a/tests/qasm3_qir/test_linalg.py b/tests/qasm3_qir/test_linalg.py index ecc3ae8..e0faddf 100644 --- a/tests/qasm3_qir/test_linalg.py +++ b/tests/qasm3_qir/test_linalg.py @@ -1,12 +1,12 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module containing unit tests for linalg.py functions. diff --git a/tests/qir_utils.py b/tests/qir_utils.py index 810c0db..4738ee0 100644 --- a/tests/qir_utils.py +++ b/tests/qir_utils.py @@ -1,12 +1,12 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # -# This file is part of the qBraid-SDK +# This file is part of qbraid-qir # -# The qBraid-SDK is free software released under the GNU General Public License v3 +# Qbraid-qir 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. +# THERE IS NO WARRANTY for qbraid-qir, as per Section 15 of the GPL v3. """ Module for containing QIR code utils functions used for unit tests. diff --git a/tox.ini b/tox.ini index 6e09380..4f0699d 100644 --- a/tox.ini +++ b/tox.ini @@ -58,9 +58,9 @@ commands = [testenv:headers] envdir = .tox/linters skip_install = true -deps = qbraid-cli>=0.8.2 +deps = qbraid-cli>=0.8.7 commands = - qbraid admin headers tests tools qbraid_qir --type=gpl {posargs} + qbraid admin headers tests tools qbraid_qir --type=gpl -p qbraid-qir {posargs} [testenv:linters] allowlist_externals = qbraid