Pytket to QIR

pytket-qir is an client-side extension (plugin) for pytket, the quantum circuit toolkit & compiler framework by Quantinuum. Its purpose is to provide functionality for 1-way conversion of pytket circuits into the QIR (Quantum Intermediate Representation) format. It allows users of pytket to target QIR-compliant hardware providers or off-the-shelf simulation tool.

pytket-qir is cross-platform on Windows, Linux and macOS. It is compatible with Python 3.10 - 3.13, and available on PyPi. An API reference is available at https://docs.quantinuum.com/tket/extensions/pytket-qir/. The package is opensource (Apache 2.0), available at https://github.com/CQCL/pytket-qir.git, and downloadable via PyPI.

pip install pytket-qir

Basic Usage

from pytket.circuit import Circuit, Qubit
from pytket.circuit.clexpr import wired_clexpr_from_logic_exp

circ = Circuit(3)
a = circ.add_c_register("a", 5)
b = circ.add_c_register("b", 5)
c = circ.add_c_register("c", 5)
d = circ.add_c_register("d", 5)
circ.H(0)
circ.add_clexpr(*wired_clexpr_from_logic_exp(a | b, c))  # type: ignore
circ.add_clexpr(*wired_clexpr_from_logic_exp(c | b, d))  # type: ignore
circ.add_clexpr(*wired_clexpr_from_logic_exp(c | b, d), condition=a[4])  # type: ignore
circ.H(0)
circ.Measure(Qubit(0), d[4])
circ.H(1)
circ.Measure(Qubit(1), d[3])
circ.H(2)
circ.Measure(Qubit(2), d[2])
[ClExpr a[0], a[1], a[2], a[3], a[4], b[0], b[1], b[2], b[3], b[4], c[0], c[1], c[2], c[3], c[4]; H q[0]; H q[1]; H q[2]; ClExpr c[0], c[1], c[2], c[3], c[4], b[0], b[1], b[2], b[3], b[4], d[0], d[1], d[2], d[3], d[4]; H q[0]; IF ([a[4]] == 1) THEN ClExpr c[0], c[1], c[2], c[3], c[4], b[0], b[1], b[2], b[3], b[4], d[0], d[1], d[2], d[3], d[4]; Measure q[2] --> d[2]; Measure q[1] --> d[3]; Measure q[0] --> d[4]; ]
from pytket.qir.conversion.api import QIRFormat, QIRProfile, pytket_to_qir


gen_qir_ll = pytket_to_qir(
    circ,
    name="conditional",
    qir_format=QIRFormat.STRING,
    profile=QIRProfile.ADAPTIVE,
    cut_pytket_register=True,
)

print(gen_qir_ll)
; ModuleID = 'conditional'
source_filename = "conditional"

%Qubit = type opaque
%Result = type opaque

@0 = internal constant [2 x i8] c"a\00"
@1 = internal constant [2 x i8] c"b\00"
@2 = internal constant [2 x i8] c"c\00"
@3 = internal constant [2 x i8] c"d\00"

define void @main() #0 {
entry:
  call void @__quantum__qis__h__body(%Qubit* null)
  call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*))
  call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*))
  call void @__quantum__qis__h__body(%Qubit* null)
  br i1 false, label %condb0, label %contb0

condb0:                                           ; preds = %entry
  br label %contb0

contb0:                                           ; preds = %condb0, %entry
  %0 = phi i64 [ 0, %condb0 ], [ 0, %entry ]
  call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*))
  %1 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 2 to %Result*))
  %2 = zext i1 %1 to i64
  %3 = mul i64 %2, 4
  %4 = or i64 %3, %0
  %5 = sub i64 1, %2
  %6 = mul i64 %5, 4
  %7 = xor i64 9223372036854775807, %6
  %8 = and i64 %7, %4
  call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
  %9 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*))
  %10 = zext i1 %9 to i64
  %11 = mul i64 %10, 8
  %12 = or i64 %11, %8
  %13 = sub i64 1, %10
  %14 = mul i64 %13, 8
  %15 = xor i64 9223372036854775807, %14
  %16 = and i64 %15, %12
  call void @__quantum__qis__mz__body(%Qubit* null, %Result* null)
  %17 = call i1 @__quantum__qis__read_result__body(%Result* null)
  %18 = zext i1 %17 to i64
  %19 = mul i64 %18, 16
  %20 = or i64 %19, %16
  %21 = sub i64 1, %18
  %22 = mul i64 %21, 16
  %23 = xor i64 9223372036854775807, %22
  %24 = and i64 %23, %20
  call void @__quantum__rt__int_record_output(i64 0, i8* getelementptr inbounds ([2 x i8], [2 x i8]* @0, i32 0, i32 0))
  call void @__quantum__rt__int_record_output(i64 0, i8* getelementptr inbounds ([2 x i8], [2 x i8]* @1, i32 0, i32 0))
  call void @__quantum__rt__int_record_output(i64 0, i8* getelementptr inbounds ([2 x i8], [2 x i8]* @2, i32 0, i32 0))
  call void @__quantum__rt__int_record_output(i64 %24, i8* getelementptr inbounds ([2 x i8], [2 x i8]* @3, i32 0, i32 0))
  ret void
}

declare i1 @__quantum__qis__read_result__body(%Result*)

declare void @__quantum__rt__int_record_output(i64, i8*)

declare void @__quantum__qis__h__body(%Qubit*)

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

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

!llvm.module.flags = !{!0, !1, !2, !3}

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

Use Cases

Wasm Callouts

from pytket import wasm
from pytket.circuit import Bit, Circuit, Qubit
from pytket.wasm import WasmFileHandler

w = WasmFileHandler("testfile.wasm")
c = Circuit(6, 6)
c0 = c.add_c_register("c0", 3)
c1 = c.add_c_register("c1", 4)
c2 = c.add_c_register("c2", 5)
c.add_wasm_to_reg("multi", w, [c0, c1], [c2])
c.add_wasm_to_reg("add_one", w, [c2], [c2])
c.add_wasm_to_reg("no_return", w, [c2], [])
c.add_wasm_to_reg("init", w, [], [])
c.add_wasm_to_reg("no_parameters", w, [], [c2])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[3], line 5
      2 from pytket.circuit import Bit, Circuit, Qubit
      3 from pytket.wasm import WasmFileHandler
----> 5 w = WasmFileHandler("testfile.wasm")
      6 c = Circuit(6, 6)
      7 c0 = c.add_c_register("c0", 3)

File c:\Users\Irfan.Khan\Projects\nexus-docs\.venv\Lib\site-packages\pytket\wasm\wasm.py:315, in WasmFileHandler.__init__(self, filepath, check_file, int_size)
    305 """
    306 Construct a wasm file handler using a filepath to read a wasm module into
    307 memory.
   (...)    312 :param int_size: length of the integer that is used in the wasm file
    313 """
    314 if not exists(filepath):
--> 315     raise ValueError("wasm file not found at given path")
    317 with open(filepath, "rb") as file:
    318     self._wasm_file: bytes = file.read()

ValueError: wasm file not found at given path
from pytket.qir.conversion.api import QIRFormat, QIRProfile, pytket_to_qir

profile = QIRProfile.ADAPTIVE

result = pytket_to_qir(
    c,
    name=f"pytket_qir_wasm_2-{profile}",
    qir_format=QIRFormat.STRING,
    int_type=32,
    profile=profile,
)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[4], line 5
      1 from pytket.qir.conversion.api import QIRFormat, QIRProfile, pytket_to_qir
      3 profile = QIRProfile.ADAPTIVE
----> 5 result = pytket_to_qir(
      6     c,
      7     name=f"pytket_qir_wasm_2-{profile}",
      8     qir_format=QIRFormat.STRING,
      9     int_type=32,
     10     profile=profile,
     11 )

File c:\Users\Irfan.Khan\Projects\nexus-docs\.venv\Lib\site-packages\pytket\qir\conversion\api.py:117, in pytket_to_qir(circ, name, qir_format, int_type, cut_pytket_register, profile)
    114     cpass = scratch_reg_resize_pass(int_type)
    115     cpass.apply(circ)
--> 117 check_circuit(circ, int_type)
    119 m = tketqirModule(
    120     name=name,
    121     num_qubits=circ.n_qubits,
    122     num_results=circ.n_qubits,
    123 )
    125 trunc = False

File c:\Users\Irfan.Khan\Projects\nexus-docs\.venv\Lib\site-packages\pytket\qir\conversion\api.py:230, in check_circuit(circuit, int_type)
    217 def check_circuit(
    218     circuit: Circuit,
    219     int_type: int = 64,
    220 ) -> None:
    221     """Checks the validity of the circuit.
    222 
    223     Running this check before conversion is recommended for big circuits that
   (...)    228     :raises ValueError: with a suggestion on how to resolve the problems
    229     """
--> 230     if len(circuit.q_registers) > 1 or (
    231         len(circuit.q_registers) == 1 and circuit.q_registers[0].name != "q"
    232     ):
    233         raise ValueError(
    234             """The circuit that should be converted should only have the default
    235             quantum register. You can convert it using the pytket
    236             compiler pass `FlattenRelabelRegistersPass`.""",
    237         )
    239     if int_type not in {32, 64}:

AttributeError: 'pytket._tket.unit_id.BitRegister' object has no attribute 'q_registers'

RNG Callouts

circ = Circuit()
creg = circ.add_c_register("c", 64)
circ.add_c_setbits([True, True], [creg[3], creg[11]])
circ.set_rng_seed(creg)
[SetBits(11) c[3], c[11]; RNGSeed c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8], c[9], c[10], c[11], c[12], c[13], c[14], c[15], c[16], c[17], c[18], c[19], c[20], c[21], c[22], c[23], c[24], c[25], c[26], c[27], c[28], c[29], c[30], c[31], c[32], c[33], c[34], c[35], c[36], c[37], c[38], c[39], c[40], c[41], c[42], c[43], c[44], c[45], c[46], c[47], c[48], c[49], c[50], c[51], c[52], c[53], c[54], c[55], c[56], c[57], c[58], c[59], c[60], c[61], c[62], c[63], _r[0]; ]
from pytket.qir.conversion.api import QIRFormat, QIRProfile, pytket_to_qir

profile = QIRProfile.ADAPTIVE

result = pytket_to_qir(
    circ,
    name=f"pytket_rng_wasm-{profile}",
    qir_format=QIRFormat.STRING,
    int_type=64,
    profile=profile,
    cut_pytket_register=True
)