Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Developing the openqasm3 to QIR visitor #54

Merged
merged 36 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ff0bc92
qasm3 to qir convert function template
ryanhill1 Jan 18, 2024
6a0172a
Merge branch 'qasm3-to-qir' of https://github.com/olgOk/qbraid-qir in…
olgOk Jan 26, 2024
fd85ab3
Prototype Qasm3Module which utilizes openqasm3 parser to load program…
olgOk Jan 26, 2024
b5cc729
Merge branch 'main' into dev_qasm3_olga
ryanhill1 Feb 5, 2024
0bf45b1
Merge branch 'main' into qasm3-to-qir
ryanhill1 Feb 5, 2024
9a7272f
Merge branch 'qasm3-to-qir' into dev_qasm3_olga
ryanhill1 Feb 5, 2024
69a528f
Merge pull request #46 from olgOk/dev_qasm3_olga
ryanhill1 Feb 5, 2024
e7f48c4
qasm3 visitor, convert, docs template code cont
ryanhill1 Feb 5, 2024
f9ca6d7
qasm3 register / statement element types
ryanhill1 Feb 5, 2024
6a87ac1
Merge branch 'main' into qasm3-to-qir
ryanhill1 Feb 15, 2024
1daf009
started
TheGupta2012 Feb 18, 2024
f29ba38
complete first implementation
TheGupta2012 Mar 4, 2024
dd34f2c
starting classical declarations
TheGupta2012 Mar 5, 2024
cbcae25
Merge branch 'main' into qasm3-to-qir-harshit
ryanhill1 Mar 5, 2024
7fe7a88
consolidate test utils + linters
ryanhill1 Mar 5, 2024
c3dc638
fix docs workflow, rm qiskit import
ryanhill1 Mar 5, 2024
c9d58dc
add header and update headers script
ryanhill1 Mar 5, 2024
4742824
start custom gate unroll
TheGupta2012 Mar 7, 2024
38c20da
Add gate unfolding!
TheGupta2012 Mar 9, 2024
58055c6
unit tests for gates
TheGupta2012 Mar 11, 2024
426be66
start branching instr
TheGupta2012 Mar 14, 2024
fa83620
updates on expressions
TheGupta2012 Mar 18, 2024
95c303e
add expressions
TheGupta2012 Mar 20, 2024
fff05cb
unit test v1 for if
TheGupta2012 Mar 22, 2024
02b4690
fix tests for if
TheGupta2012 Mar 25, 2024
ce5b300
extend branches to binary and unary ops
TheGupta2012 Mar 26, 2024
881fef9
increase verbosity of exceptions
TheGupta2012 Mar 27, 2024
40814ea
fix bugs related to u3
TheGupta2012 Mar 27, 2024
55a9a09
qasm3 pr review tweaks
ryanhill1 Mar 28, 2024
8e95b4d
Merge pull request #68 from qBraid/rh1-qasm3-review
ryanhill1 Mar 28, 2024
9f24047
housekeeping (deps, workflows)
ryanhill1 Mar 28, 2024
53d8b62
custom exceptions + relative imports
ryanhill1 Mar 28, 2024
d726b86
update register definition
TheGupta2012 Mar 29, 2024
0604c15
add docs, simplify reset
TheGupta2012 Mar 29, 2024
9f8f3b6
fix doc
TheGupta2012 Mar 29, 2024
e3b5f65
fix doc
TheGupta2012 Mar 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/api/qbraid_qir.qasm3.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
:orphan:

qbraid_qir.qasm3
=================

.. automodule:: qbraid_qir.qasm3
:members:
:undoc-members:
:show-inheritance:
2 changes: 2 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ This project was conceived in cooperation with the Quantum Open Source Foundatio
:hidden:

userguide/cirq_qir
userguide/qasm3_qir

.. toctree::
:maxdepth: 1
Expand All @@ -173,6 +174,7 @@ This project was conceived in cooperation with the Quantum Open Source Foundatio

api/qbraid_qir
api/qbraid_qir.cirq
api/qbraid_qir.qasm3


Indices and Tables
Expand Down
75 changes: 75 additions & 0 deletions docs/userguide/qasm3_qir.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
.. _sdk_qir_qasm3:

QASM conversions
==================

Example Usage
--------------

Convert an ``OpenQASM 3`` program to ``QIR`` code:

.. code-block:: python

from qbraid_qir import dumps
from qbraid_qir.qasm3 import qasm3_to_qir

# create a test program
program = """
OPENQASM 3;
include "stdgates.inc";
qubit[2] q;
h q[0];
cx q[0], q[1];
measure q[0] -> c[0];
measure q[1] -> c[1];
"""

# convert to QIR
module = qasm3_to_qir(program, name="bell")

# saves to .ll and .bc files in working directory
dumps(module)

print(module)

.. code-block:: none

; ModuleID = 'bell'
source_filename = "bell"

%Qubit = type opaque
%Result = type opaque

define void @main() #0 {
entry:
call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*)) # Corrected %Qubit* null to inttoptr (i64 0 to %Qubit*)
call void @__quantum__qis__cnot__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Qubit* inttoptr (i64 1 to %Qubit*)) # Corrected %Qubit* null and added correct inttoptr conversion
call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) # Corrected %Qubit* and %Result* null to correct inttoptr conversion
call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) # Added correct inttoptr conversion
ret void
}

declare void @__quantum__qis__h__body(%Qubit*)
declare void @__quantum__qis__cnot__body(%Qubit*, %Qubit*)
declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1

attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="2" "required_num_results"="2" }
attributes #1 = { "irreversible" }

!llvm.module.flags = !{!0, !1, !2, !3}
!0 = !{i32 1, !"qir_major_version", i32 1}
!1 = !{i32 7, !"qir_minor_version", i32 0}
!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
!3 = !{i32 1, !"dynamic_result_management", i1 false}


Execute the QIR program using the `qir-runner <https://github.com/qir-alliance/qir-runner>`_ command line tool:

.. code-block:: bash

$ qir-runner -f bell.bc


.. seealso::

https://github.com/qBraid/qbraid-qir/tree/main/test-containers
1 change: 1 addition & 0 deletions qbraid_qir/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@
from ._version import __version__
from .cirq import cirq_to_qir
from .exceptions import QbraidQirError, QirConversionError
from .qasm3 import qasm3_to_qir
from .serialization import dumps
1 change: 0 additions & 1 deletion qbraid_qir/cirq/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

"""
Module defining Cirq LLVM Module elements.

"""

import hashlib
Expand Down
37 changes: 37 additions & 0 deletions qbraid_qir/qasm3/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 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 <https://www.gnu.org/licenses/gpl-3.0.html>.
#
# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3.

"""
Module containing OpenQASM QIR functionality.

.. currentmodule:: qbraid_qir.qasm3

Functions
-----------

.. autosummary::
:toctree: ../stubs/

qasm3_to_qir


Classes
---------

.. autosummary::
:toctree: ../stubs/

Qasm3Module
BasicQisVisitor

"""
from .convert import qasm3_to_qir
from .elements import Qasm3Module
from .visitor import BasicQisVisitor
79 changes: 79 additions & 0 deletions qbraid_qir/qasm3/convert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# 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 <https://www.gnu.org/licenses/gpl-3.0.html>.
#
# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3.

"""
Module containing OpenQASM to QIR conversion functions

"""
from typing import Optional, Union

import openqasm3
from pyqir import Context, Module, qir_module
from qiskit.qasm3 import loads
from qiskit.qasm3.exporter import Exporter

from qbraid_qir.exceptions import QirConversionError
from qbraid_qir.qasm3.elements import Qasm3Module, generate_module_id
from qbraid_qir.qasm3.visitor import BasicQisVisitor


def qasm3_to_qir(
program: Union[openqasm3.ast.Program, str], name: Optional[str] = None, **kwargs
) -> Module:
"""
Converts an OpenQASM 3 program to a PyQIR module.

Args:
program (openqasm3.ast.Program or str): The OpenQASM 3 program to convert.
name (str, optional): Identifier for created QIR module. Auto-generated if not provided.

Keyword Args:
initialize_runtime (bool): Whether to perform quantum runtime environment initialization,
default `True`.
record_output (bool): Whether to record output calls for registers, default `True`

Returns:
The QIR ``pyqir.Module`` representation of the input OpenQASM 3 program.

Raises:
TypeError: If the input is not a valid OpenQASM 3 program.
QirConversionError: If the conversion fails.
"""
if isinstance(program, str):
# Supported conversions qasm3 -> qiskit :
# https://github.com/Qiskit/qiskit-qasm3-import/blob/main/src/qiskit_qasm3_import/converter.py

# PROPOSED SEMANTIC + DECOMPOSITION PASS
# qiskit_circuit = loads(program).decompose(reps=3)
# decomposed_qasm = Exporter().dumps(qiskit_circuit)
# PROPOSED SEMANTIC + DECOMPOSITION PASS

# program = openqasm3.parse(decomposed_qasm)

program = openqasm3.parse(program)

elif not isinstance(program, openqasm3.ast.Program):
raise TypeError(
"Input quantum program must be of type openqasm3.ast.Program or str."
)

if name is None:
name = generate_module_id()

llvm_module = qir_module(Context(), name)
module = Qasm3Module.from_program(program, llvm_module)

visitor = BasicQisVisitor(**kwargs)
module.accept(visitor)

err = llvm_module.verify()
if err is not None:
raise QirConversionError(err)
return llvm_module
161 changes: 161 additions & 0 deletions qbraid_qir/qasm3/elements.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# 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 <https://www.gnu.org/licenses/gpl-3.0.html>.
#
# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3.

"""
Module defining Qasm3 Converter elements.

"""

import uuid
from abc import ABCMeta, abstractmethod
from typing import List, Optional, Tuple

from openqasm3.ast import (
BitType,
ClassicalDeclaration,
Program,
QubitDeclaration,
Statement,
)
from pyqir import Context, Module


def generate_module_id() -> str:
"""
Generates a QIR module ID from a given openqasm3 program.
"""

# TODO: Consider a better approach of generating a unique identifier.
generated_id = uuid.uuid1()
return f"circuit-{generated_id}"


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: Tuple[str, Optional[int]], is_qubit: bool = True):
self._register = register
self._is_qubit = is_qubit

def accept(self, visitor):
visitor.visit_register(self._register, self._is_qubit)

def __str__(self) -> str:
return f"Register({self._register}, is_qubit = {self._is_qubit})"
TheGupta2012 marked this conversation as resolved.
Show resolved Hide resolved


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:
"""
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.
"""

def __init__(
self, name: str, module: Module, num_qubits: int, num_clbits: int, elements
):
self._name = name
self._module = module
self._num_qubits = num_qubits
self._num_clbits = num_clbits
self._elements = elements

@property
def name(self) -> str:
"""Returns the name of the module."""
return self._name

@property
def module(self) -> Module:
"""Returns the QIR Module instance."""
return self._module

@property
def num_qubits(self) -> int:
"""Returns the number of qubits in the circuit."""
return self._num_qubits

@property
def num_clbits(self) -> int:
"""Returns the number of classical bits in the circuit."""
return self._num_clbits

@classmethod
def from_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[Statement] = []

num_qubits = 0
num_clbits = 0
for statement in program.statements:
if isinstance(statement, QubitDeclaration):
name = statement.qubit.name
size = None if statement.size is None else statement.size.value
num_qubits += 1 if size is None else size
elements.append(_Register((name, size), True))

elif isinstance(statement, ClassicalDeclaration) and isinstance(
statement.type, BitType
):
name = statement.identifier.name
size = (
None if statement.type.size is None else statement.type.size.value
)
num_clbits += 1 if size is None else size
elements.append(_Register((name, size), False))
else:
elements.append(_Statement(statement))

if module is None:
module = Module(Context(), generate_module_id(program))

# for element in elements:
# print(element,"\n")
return cls(
name="main",
module=module,
num_qubits=num_qubits,
num_clbits=num_clbits,
elements=elements,
)

def accept(self, visitor):
visitor.visit_qasm3_module(self)
for element in self._elements:
element.accept(visitor)
visitor.record_output(self)
visitor.finalize()
Loading