Skip to content

Commit

Permalink
Add new qir_to_pytket method.
Browse files Browse the repository at this point in the history
  • Loading branch information
cqc-alec committed Nov 26, 2024
1 parent 25c092c commit 39c4058
Show file tree
Hide file tree
Showing 11 changed files with 342 additions and 143 deletions.
22 changes: 21 additions & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@ on:
- main

jobs:
lints:
name: Run black, ruff and mypy checks
runs-on: 'ubuntu-latest'
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install flit, black, ruff and mypy
run: pip install flit black ruff mypy
- name: Run black check
run: black --check .
- name: Run ruff check
run: ruff check .
- name: Run mypy check
run: |
flit install
mypy .
checks:
name: Run quick unit tests
strategy:
Expand All @@ -24,4 +44,4 @@ jobs:
- name: Install pytket-qirpass
run: flit install
- name: Run quick tests
run: python -m unittest test.test_qirpass
run: python -m unittest
27 changes: 27 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Release

on:
release:
types:
- created
- edited

jobs:
publish:
name: Publish to pypi
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install flit
run: pip install flit
- name: Install pytket-qirpass
run: flit install
- name: Publish package
env:
FLIT_USERNAME: '__token__'
FLIT_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: flit publish
2 changes: 1 addition & 1 deletion .github/workflows/schedule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ jobs:
- name: Install pytket-qirpass
run: flit install
- name: Run slow tests
run: PYTKET_QIRPASS_RUN_ALL_TESTS=1 python -m unittest test.test_qirpass
run: PYTKET_QIRPASS_RUN_ALL_TESTS=1 python -m unittest
5 changes: 3 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ flit install
To run unit tests:

```shell
python -m unittest test.test_qirpass
python -m unittest
```

(These take a few minutes to run.)
To run the full set of tests, whuich take a few minutes, run the above command
with the environment variable `PYTKET_QIRPASS_RUN_ALL_TESTS` set.
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# pytket-qirpass

This module provides a method to optimize QIR using pytket.
This module provides a method to optimize QIR using pytket, and a method to
convert QIR to pytket for simple circuits.

## Installation

Expand All @@ -14,7 +15,9 @@ pip install pytket-qirpass

## Usage

This module provides a single function, `apply_qirpass`, which takes as input
### Optimizing QIR with `apply_qirpass`

The function `apply_qirpass` takes as input

- some QIR bitcode
- a pytket compilation pass
Expand Down Expand Up @@ -42,3 +45,20 @@ Both the input and the output are Python `bytes` objects.

Provided the pass preserves the circuit semantics, `apply_qirpass` preserves
the QIR semantics.

### Converting QIR to pytket with `qir_to_pytket`

The function `qir_to_pytket` takes as input some QIR bitcode and outputs a
pytket circuit.

For example:

```python
from pytket_qirpass import qir_to_pytket

circ = qir_to_pytket(qir_bitcode=qir_in)
```

The program represented by the bitcode must consist of a single basic block
comprised of quantum operations, i.e. `__quantum__qis__*` instructions; any
`__quantum__rt__*` instructions are accepted but ignored.
5 changes: 3 additions & 2 deletions src/pytket_qirpass/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Module for optimizing QIR using pytket."""

__version__ = "0.8.0"
__version__ = "0.9.0"

from .apply_qirpass import apply_qirpass
from .apply_qirpass import apply_qirpass as apply_qirpass
from .qir_to_pytket import qir_to_pytket as qir_to_pytket
148 changes: 18 additions & 130 deletions src/pytket_qirpass/apply_qirpass.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
from math import pi
import struct
from typing import List, Optional, Set, Tuple

from llvmlite.binding import (
create_context,
parse_assembly,
parse_bitcode,
ModuleRef,
ValueRef,

from .utils import (
bc_to_module,
is_entry_point,
is_header_line,
ll_to_bc,
opdata,
to_circuit,
)
from pytket.circuit import Bit, Circuit, Node, OpType, Qubit

from llvmlite.binding import ValueRef # type: ignore

from pytket.circuit import OpType, UnitID
from pytket.passes import (
BasePass,
RemoveImplicitQubitPermutation,
Expand All @@ -19,32 +22,6 @@
AutoSquash,
)

# Gates taken from https://github.com/qir-alliance/qat/blob/main/targets/target_7ee0.yaml
opdata = {
# Gates taken from https://github.com/qir-alliance/qat/blob/main/targets/target_7ee0.yaml:
"__quantum__qis__cnot__body": (OpType.CX, "%Qubit*, %Qubit*"),
"__quantum__qis__cz__body": (OpType.CZ, "%Qubit*, %Qubit*"),
"__quantum__qis__h__body": (OpType.H, "%Qubit*"),
"__quantum__qis__mz__body": (OpType.Measure, "%Qubit*, %Result* writeonly"),
"__quantum__qis__reset__body": (OpType.Reset, "%Qubit*"),
"__quantum__qis__rx__body": (OpType.Rx, "double, %Qubit*"),
"__quantum__qis__ry__body": (OpType.Ry, "double, %Qubit*"),
"__quantum__qis__rz__body": (OpType.Rz, "double, %Qubit*"),
"__quantum__qis__rzz__body": (OpType.ZZPhase, "double, %Qubit*, %Qubit*"),
"__quantum__qis__s__body": (OpType.S, "%Qubit*"),
"__quantum__qis__t__body": (OpType.T, "%Qubit*"),
"__quantum__qis__t__adj": (OpType.Tdg, "%Qubit*"),
"__quantum__qis__x__body": (OpType.X, "%Qubit*"),
"__quantum__qis__y__body": (OpType.Y, "%Qubit*"),
"__quantum__qis__z__body": (OpType.Z, "%Qubit*"),
# Additional gates:
"__quantum__qis__phasedx__body": (OpType.PhasedX, "double, double, %Qubit*"),
"__quantum__qis__zzmax__body": (OpType.ZZMax, "%Qubit*, %Qubit*"),
"__quantum__qis__rxxyyzz__body": (
OpType.TK2,
"double, double, double, %Qubit*, %Qubit*",
),
}

tk_to_qir = {optype: (name, sig) for name, (optype, sig) in opdata.items()}

Expand All @@ -56,65 +33,7 @@ def encode_double(a: float) -> str:
return f'double {"0x{:016X}".format(encoding[0])}'


def decode_double(s: str) -> float:
assert isinstance(s, str)
words = s.split(" ")
assert len(words) == 2 and words[0] == "double"
encoding = words[1]
try:
return float(encoding)
except ValueError:
n = int(encoding, 16)
return struct.unpack("d", struct.pack("Q", n))[0]


def parse_instr(instr: ValueRef) -> Tuple[OpType, List[float], List[Qubit], List[Bit]]:
assert instr.is_instruction
assert instr.opcode == "call"
assert str(instr.type) == "void"
operands = list(instr.operands)
assert len(operands) >= 1
optype, _ = opdata[operands[-1].name]
params = []
q_args = []
c_args = []
for operand in operands[:-1]:
typename = str(operand.type)
if typename == "double":
params.append(decode_double(str(operand)) / pi)
elif typename == "%Qubit*":
optext = str(operand).split(" ")
assert optext[0] == "%Qubit*"
if optext[1] == "null":
assert len(optext) == 2
q_args.append(Qubit(0))
else:
q_args.append(Qubit(int(optext[3])))
else:
assert typename == "%Result*"
optext = str(operand).split(" ")
assert optext[0] == "%Result*"
if optext[1] == "null":
assert len(optext) == 2
c_args.append(Bit(0))
else:
c_args.append(Bit(int(optext[3])))
return (optype, params, q_args, c_args)


def to_circuit(instrs: List[ValueRef]) -> Circuit:
circuit = Circuit()
for instr in instrs:
optype, params, q_args, c_args = parse_instr(instr)
for q in q_args:
circuit.add_qubit(q, reject_dups=False)
for c in c_args:
circuit.add_bit(c, reject_dups=False)
circuit.add_gate(optype, params, q_args + c_args)
return circuit


def argrep(arg: Node) -> str:
def argrep(arg: UnitID) -> str:
if arg.reg_name == "q":
assert len(arg.index) == 1
q = arg.index[0]
Expand All @@ -140,8 +59,8 @@ def is_known_type(instr: ValueRef) -> bool:


def partition_instrs(
instrs: List[ValueRef],
) -> List[Tuple[List[ValueRef], List[ValueRef]]]:
instrs: list[ValueRef],
) -> list[tuple[list[ValueRef], list[ValueRef]]]:
# Organize the instructions into a list of pairs of lists, each pair consisting of
# all-known and all-unknown, preserving the original order.
n_instrs = len(instrs)
Expand Down Expand Up @@ -188,42 +107,11 @@ def compile_basic_block_ll(basic_block: ValueRef, comp_pass: BasePass):
return bb_ll


def bc_to_module(bc: bytes) -> ModuleRef:
ctx = create_context()
module = parse_bitcode(bc, context=ctx)
module.verify()
return module


def ll_to_module(ll: str) -> ModuleRef:
ctx = create_context()
module = parse_assembly(ll, context=ctx)
module.verify()
return module


def ll_to_bc(ll: str) -> bytes:
module = ll_to_module(ll)
return module.as_bitcode()


def is_header_line(line: str) -> bool:
if line == "":
return True
words = line.split(" ")
return len(words) >= 3 and "=" in words[1:-1]


def is_entry_point(function: ValueRef) -> bool:
assert function.is_function
return any(b'"EntryPoint"' in attrs for attrs in function.attributes)


def apply_qirpass(
qir_bitcode: bytes,
comp_pass: Optional[BasePass],
target_1q_gates: Set[OpType],
target_2q_gates: Set[OpType],
comp_pass: BasePass | None,
target_1q_gates: set[OpType],
target_2q_gates: set[OpType],
) -> bytes:
"""Apply the given pass to basic blocls of the QIR.
Expand Down
20 changes: 20 additions & 0 deletions src/pytket_qirpass/qir_to_pytket.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from .utils import bc_to_module, is_entry_point, to_circuit

from pytket.circuit import Circuit


def qir_to_pytket(qir_bitcode: bytes) -> Circuit:
"""Convert QIR to a pytket circuit.
:param qir_bitcode: QIR bitcode
:return: pytket circuit
"""
module = bc_to_module(qir_bitcode)
entries = [f for f in module.functions if is_entry_point(f)]
assert len(entries) == 1
blocks = list(entries[0].blocks)
assert len(blocks) == 1
instructions = list(
filter(lambda instr: instr.opcode == "call", blocks[0].instructions)
)
return to_circuit(instructions)
Loading

0 comments on commit 39c4058

Please sign in to comment.