Guppy to QIR (Experimental)¶
Guppy is a quantum-first programming language built for Quantinuum Helios. An experimental tool, HUGR-QIR, enables conversion of a Guppy program into QIR. As a result, experimental QIR conversion enables execution on System Model H2 with Guppy programs. QIR is an industry standard tool used to access different hardware platforms.
HUGR (Hierarchical Unified Graph Representation) is a compact graph-based representation of Guppy programs. HUGR and QIR are equivalents. Whereas opensource off-the-shelf tools (NVIDIA CUDA-Q) generate QIR, HUGR is generated from Guppy source. The HUGR-QIS package requires the user first generate HUGR, before transpiling to QIR. HUGR-generated QIR is verified for backwards compatibility on System Model H2, using the quantinuum qir checker [1].
For Guppy features supported by QIR, please see here. Not all Guppy features are available in the full QIR adaptive profile (specified in the linked table). In addition to this, HUGR-QIR does not support conversion of the following Guppy features:
Guppy functions accepting or returning arrays (See caveats)
Unbounded Loops
Dynamic Qubit Allocation
Installation¶
HUGR-QIR is available on PyPi and requires Python 3.10. The corresponding GitHub repository is available at https://github.com/quantinuum/hugr-qir.git.
pip install hugr-qir
User Workflow¶
from typing import no_type_check
from guppylang import guppy, qubit
from guppylang.std.builtins import result
from guppylang.std.quantum import h, measure
@guppy
@no_type_check
def main() -> None:
q0 = qubit()
q1 = qubit()
h(q0)
h(q1)
b0 = measure(q0)
b1 = measure(q1)
b2 = b0 ^ b1
result("0", b2)
By default, the function will automatically validate the generated QIR. Using the keyword argument, output_format=OutputFormat.BITCODE, returns QIR bitcode.
from hugr_qir.hugr_to_qir import hugr_to_qir
from hugr_qir.output import OutputFormat
guppy_qir_bitcode = hugr_to_qir(main.compile(), output_format=OutputFormat.BITCODE)
Specifying OutputFormat.LLVM_IR returns textual QIR.
guppy_qir_string = hugr_to_qir(main.compile(), output_format=OutputFormat.LLVM_IR)
print(guppy_qir_string)
; ModuleID = 'hugr-qir'
source_filename = "hugr-qir"
target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128"
target triple = "aarch64-unknown-linux-gnu"
%Qubit = type opaque
%Result = type opaque
@0 = private unnamed_addr constant [2 x i8] c"0\00", align 1
define dso_local void @__hugr__.main.1() local_unnamed_addr #0 {
alloca_block:
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* null)
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* null)
tail call void @__quantum__qis__mz__body(%Qubit* null, %Result* null)
%0 = tail call i1 @__quantum__rt__read_result(%Result* null)
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 1 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 1 to %Qubit*))
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 1 to %Qubit*), %Result* nonnull inttoptr (i64 1 to %Result*))
%1 = tail call i1 @__quantum__rt__read_result(%Result* nonnull inttoptr (i64 1 to %Result*))
%2 = xor i1 %0, %1
tail call void @__quantum__rt__bool_record_output(i1 %2, i8* getelementptr inbounds ([2 x i8], [2 x i8]* @0, i64 0, i64 0))
ret void
}
declare void @__quantum__qis__phasedx__body(double, double, %Qubit*) local_unnamed_addr
declare void @__quantum__qis__rz__body(double, %Qubit*) local_unnamed_addr
declare void @__quantum__qis__mz__body(%Qubit*, %Result*) local_unnamed_addr
declare i1 @__quantum__rt__read_result(%Result*) local_unnamed_addr
declare void @__quantum__rt__bool_record_output(i1, i8*) local_unnamed_addr
attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="2" "required_num_results"="2" }
!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}
Only QIR bitcode can be uploaded to Nexus and submitted to Quantinuum Systems. More information on submission is available here.
Supported on H2¶
Supported Actions
Native gates (see below);
Measurements
Result Tagging
Conditional Branching
For Loops
Random Number Generation (RNG)
Shot number retrieval
All hardware native gate operations are supported on System Model H2. Users cannot call the General \(SU(4)\) Entangler gate using Guppy. For a list of supported gate operations see here.
from typing import no_type_check
from guppylang import guppy
from guppylang.std.builtins import result
from guppylang.std.quantum import h, cx, qubit, measure
from hugr_qir.hugr_to_qir import hugr_to_qir
from hugr_qir.output import OutputFormat
@guppy
@no_type_check
def main() -> None:
q0 = qubit()
q1 = qubit()
h(q0)
cx(q0, q1)
result("0", measure(q0))
result("1", measure(q1))
hugr = main.compile()
guppy_llvm_ir = hugr_to_qir(main.compile(), output_format=OutputFormat.LLVM_IR)
print(guppy_llvm_ir)
; ModuleID = 'hugr-qir'
source_filename = "hugr-qir"
target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128"
target triple = "aarch64-unknown-linux-gnu"
%Qubit = type opaque
%Result = type opaque
@0 = private unnamed_addr constant [2 x i8] c"0\00", align 1
@1 = private unnamed_addr constant [2 x i8] c"1\00", align 1
define dso_local void @__hugr__.main.1() local_unnamed_addr #0 {
alloca_block:
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* null)
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* null)
tail call void @__quantum__qis__phasedx__body(double 0xBFF921FB54442D18, double 0x3FF921FB54442D18, %Qubit* nonnull inttoptr (i64 1 to %Qubit*))
tail call void @__quantum__qis__rzz__body(double 0x3FF921FB54442D18, %Qubit* null, %Qubit* nonnull inttoptr (i64 1 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0xBFF921FB54442D18, %Qubit* null)
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 1 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 1 to %Qubit*))
tail call void @__quantum__qis__mz__body(%Qubit* null, %Result* null)
%0 = tail call i1 @__quantum__rt__read_result(%Result* null)
tail call void @__quantum__rt__bool_record_output(i1 %0, i8* getelementptr inbounds ([2 x i8], [2 x i8]* @0, i64 0, i64 0))
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 1 to %Qubit*), %Result* nonnull inttoptr (i64 1 to %Result*))
%1 = tail call i1 @__quantum__rt__read_result(%Result* nonnull inttoptr (i64 1 to %Result*))
tail call void @__quantum__rt__bool_record_output(i1 %1, i8* getelementptr inbounds ([2 x i8], [2 x i8]* @1, i64 0, i64 0))
ret void
}
declare void @__quantum__qis__phasedx__body(double, double, %Qubit*) local_unnamed_addr
declare void @__quantum__qis__rz__body(double, %Qubit*) local_unnamed_addr
declare void @__quantum__qis__rzz__body(double, %Qubit*, %Qubit*) local_unnamed_addr
declare void @__quantum__qis__mz__body(%Qubit*, %Result*) local_unnamed_addr
declare i1 @__quantum__rt__read_result(%Result*) local_unnamed_addr
declare void @__quantum__rt__bool_record_output(i1, i8*) local_unnamed_addr
attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="2" "required_num_results"="2" }
!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}
Iterations are compiled to a non-compact representation using forward branching for compliance with H2.
from typing import no_type_check
from guppylang import guppy, qubit
from guppylang.std.builtins import result
from guppylang.std.quantum import h, measure
@guppy
@no_type_check
def main() -> None:
q0 = qubit()
q1 = qubit()
for _ in range(10):
q3 = qubit()
h(q3)
b = measure(q3)
if b:
h(q0)
result("0", measure(q0))
result("1", measure(q1))
from hugr_qir.hugr_to_qir import hugr_to_qir
from hugr_qir.output import OutputFormat
guppy_llvm_ir = hugr_to_qir(main.compile(), output_format=OutputFormat.LLVM_IR)
print(guppy_llvm_ir)
; ModuleID = 'hugr-qir'
source_filename = "hugr-qir"
target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128"
target triple = "aarch64-unknown-linux-gnu"
%Qubit = type opaque
%Result = type opaque
@0 = private unnamed_addr constant [2 x i8] c"0\00", align 1
@1 = private unnamed_addr constant [2 x i8] c"1\00", align 1
define dso_local void @__hugr__.main.1() local_unnamed_addr #0 {
alloca_block:
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 2 to %Qubit*), %Result* nonnull inttoptr (i64 2 to %Result*))
%0 = tail call i1 @__quantum__rt__read_result(%Result* nonnull inttoptr (i64 2 to %Result*))
br i1 %0, label %21, label %cond_374_case_0
cond_384_case_0: ; preds = %alloca_block, %21
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 2 to %Qubit*), %Result* nonnull inttoptr (i64 2 to %Result*))
%1 = tail call i1 @__quantum__rt__read_result(%Result* nonnull inttoptr (i64 2 to %Result*))
br i1 %1, label %2, label %cond_374_case_0.1
2: ; preds = %cond_384_case_0
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* null)
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* null)
br label %cond_384_case_0.1
cond_384_case_0.1: ; preds = %2, %cond_384_case_0
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 2 to %Qubit*), %Result* nonnull inttoptr (i64 2 to %Result*))
%3 = tail call i1 @__quantum__rt__read_result(%Result* nonnull inttoptr (i64 2 to %Result*))
br i1 %3, label %4, label %cond_374_case_0.2
4: ; preds = %cond_384_case_0.1
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* null)
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* null)
br label %cond_384_case_0.2
cond_384_case_0.2: ; preds = %4, %cond_384_case_0.1
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 2 to %Qubit*), %Result* nonnull inttoptr (i64 2 to %Result*))
%5 = tail call i1 @__quantum__rt__read_result(%Result* nonnull inttoptr (i64 2 to %Result*))
br i1 %5, label %6, label %cond_374_case_0.3
6: ; preds = %cond_384_case_0.2
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* null)
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* null)
br label %cond_384_case_0.3
cond_384_case_0.3: ; preds = %6, %cond_384_case_0.2
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 2 to %Qubit*), %Result* nonnull inttoptr (i64 2 to %Result*))
%7 = tail call i1 @__quantum__rt__read_result(%Result* nonnull inttoptr (i64 2 to %Result*))
br i1 %7, label %8, label %cond_374_case_0.4
8: ; preds = %cond_384_case_0.3
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* null)
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* null)
br label %cond_384_case_0.4
cond_384_case_0.4: ; preds = %8, %cond_384_case_0.3
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 2 to %Qubit*), %Result* nonnull inttoptr (i64 2 to %Result*))
%9 = tail call i1 @__quantum__rt__read_result(%Result* nonnull inttoptr (i64 2 to %Result*))
br i1 %9, label %10, label %cond_374_case_0.5
10: ; preds = %cond_384_case_0.4
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* null)
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* null)
br label %cond_384_case_0.5
cond_384_case_0.5: ; preds = %10, %cond_384_case_0.4
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 2 to %Qubit*), %Result* nonnull inttoptr (i64 2 to %Result*))
%11 = tail call i1 @__quantum__rt__read_result(%Result* nonnull inttoptr (i64 2 to %Result*))
br i1 %11, label %12, label %cond_374_case_0.6
12: ; preds = %cond_384_case_0.5
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* null)
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* null)
br label %cond_384_case_0.6
cond_384_case_0.6: ; preds = %12, %cond_384_case_0.5
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 2 to %Qubit*), %Result* nonnull inttoptr (i64 2 to %Result*))
%13 = tail call i1 @__quantum__rt__read_result(%Result* nonnull inttoptr (i64 2 to %Result*))
br i1 %13, label %14, label %cond_374_case_0.7
14: ; preds = %cond_384_case_0.6
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* null)
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* null)
br label %cond_384_case_0.7
cond_384_case_0.7: ; preds = %14, %cond_384_case_0.6
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 2 to %Qubit*), %Result* nonnull inttoptr (i64 2 to %Result*))
%15 = tail call i1 @__quantum__rt__read_result(%Result* nonnull inttoptr (i64 2 to %Result*))
br i1 %15, label %16, label %cond_374_case_0.8
16: ; preds = %cond_384_case_0.7
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* null)
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* null)
br label %cond_384_case_0.8
cond_384_case_0.8: ; preds = %16, %cond_384_case_0.7
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 2 to %Qubit*), %Result* nonnull inttoptr (i64 2 to %Result*))
%17 = tail call i1 @__quantum__rt__read_result(%Result* nonnull inttoptr (i64 2 to %Result*))
br i1 %17, label %18, label %cond_374_case_0.9
18: ; preds = %cond_384_case_0.8
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* null)
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* null)
br label %cond_384_case_0.9
cond_384_case_0.9: ; preds = %18, %cond_384_case_0.8
tail call void @__quantum__qis__mz__body(%Qubit* null, %Result* null)
%19 = tail call i1 @__quantum__rt__read_result(%Result* null)
tail call void @__quantum__rt__bool_record_output(i1 %19, i8* getelementptr inbounds ([2 x i8], [2 x i8]* @0, i64 0, i64 0))
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 1 to %Qubit*), %Result* nonnull inttoptr (i64 1 to %Result*))
%20 = tail call i1 @__quantum__rt__read_result(%Result* nonnull inttoptr (i64 1 to %Result*))
tail call void @__quantum__rt__bool_record_output(i1 %20, i8* getelementptr inbounds ([2 x i8], [2 x i8]* @1, i64 0, i64 0))
ret void
21: ; preds = %alloca_block
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* null)
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* null)
br label %cond_384_case_0
}
declare void @__quantum__qis__mz__body(%Qubit*, %Result*) local_unnamed_addr
declare i1 @__quantum__rt__read_result(%Result*) local_unnamed_addr
declare void @__quantum__rt__bool_record_output(i1, i8*) local_unnamed_addr
declare void @__quantum__qis__phasedx__body(double, double, %Qubit*) local_unnamed_addr
declare void @__quantum__qis__rz__body(double, %Qubit*) local_unnamed_addr
attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="3" "required_num_results"="3" }
!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}
Random Number Generator (RNG)¶
Conversion of the Guppy integrated RNG to QIR support the constructor with seed, guppylang.std.qsystem.random.RNG.random_int and guppylang.std.qsystem.random.RNG.random_int_bounded(int). The following example shows how this can be used:
from guppylang import guppy
from guppylang.std.builtins import result
from guppylang.std.angles import angle
from guppylang.std.quantum import measure, qubit
from guppylang.std.qsystem import phased_x, rz
from guppylang.std.qsystem.random import RNG
@guppy
def main() -> None:
q = qubit()
rng = RNG(8) # seed=8
index = rng.random_int_bounded(5)
if index == 1:
phased_x(q, angle(1), angle(0))
elif index == 2:
phased_x(q, angle(0), angle(1))
elif index == 3:
rz(q, angle(1))
elif index == 4:
phased_x(q, angle(1), angle(0))
phased_x(q, angle(0), angle(1))
res = measure(q)
result("result", res)
rng.discard()
hugr = main.compile()
guppy_qir = hugr_to_qir(main.compile(), output_format=OutputFormat.LLVM_IR, validate_qir=True)
print(guppy_qir)
; ModuleID = 'hugr-qir'
source_filename = "hugr-qir"
target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128"
target triple = "aarch64-unknown-linux-gnu"
%Qubit = type opaque
%Result = type opaque
@0 = private unnamed_addr constant [7 x i8] c"result\00", align 1
define dso_local void @__hugr__.main.1() local_unnamed_addr #0 {
alloca_block:
tail call void @___random_seed(i64 8)
%rintb = tail call i32 @___random_int_bounded(i32 5)
br label %NodeBlock554
NodeBlock554: ; preds = %alloca_block
%Pivot555 = icmp slt i32 %rintb, 3
br i1 %Pivot555, label %NodeBlock, label %NodeBlock552
NodeBlock552: ; preds = %NodeBlock554
%Pivot553 = icmp slt i32 %rintb, 4
br i1 %Pivot553, label %3, label %LeafBlock550
LeafBlock550: ; preds = %NodeBlock552
%SwitchLeaf551 = icmp eq i32 %rintb, 4
br i1 %SwitchLeaf551, label %4, label %NewDefault
NodeBlock: ; preds = %NodeBlock554
%Pivot = icmp slt i32 %rintb, 2
br i1 %Pivot, label %LeafBlock, label %2
LeafBlock: ; preds = %NodeBlock
%SwitchLeaf = icmp eq i32 %rintb, 1
br i1 %SwitchLeaf, label %1, label %NewDefault
NewDefault: ; preds = %LeafBlock550, %LeafBlock
br label %cond_318_case_1
cond_318_case_1: ; preds = %NewDefault, %2, %4, %3, %1
tail call void @__quantum__qis__mz__body(%Qubit* null, %Result* null)
%0 = tail call i1 @__quantum__qis__read_result__body(%Result* null)
tail call void @__quantum__rt__bool_record_output(i1 %0, i8* getelementptr inbounds ([7 x i8], [7 x i8]* @0, i64 0, i64 0))
ret void
1: ; preds = %LeafBlock
tail call void @__quantum__qis__phasedx__body(double 0x400921FB54442D18, double 0.000000e+00, %Qubit* null)
br label %cond_318_case_1
2: ; preds = %NodeBlock
tail call void @__quantum__qis__phasedx__body(double 0.000000e+00, double 0x400921FB54442D18, %Qubit* null)
br label %cond_318_case_1
3: ; preds = %NodeBlock552
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* null)
br label %cond_318_case_1
4: ; preds = %LeafBlock550
tail call void @__quantum__qis__phasedx__body(double 0x400921FB54442D18, double 0.000000e+00, %Qubit* null)
tail call void @__quantum__qis__phasedx__body(double 0.000000e+00, double 0x400921FB54442D18, %Qubit* null)
br label %cond_318_case_1
}
declare void @___random_seed(i64) local_unnamed_addr
declare i32 @___random_int_bounded(i32) local_unnamed_addr
declare void @__quantum__qis__phasedx__body(double, double, %Qubit*) local_unnamed_addr
declare void @__quantum__qis__rz__body(double, %Qubit*) local_unnamed_addr
declare void @__quantum__qis__mz__body(%Qubit*, %Result*) local_unnamed_addr
declare i1 @__quantum__qis__read_result__body(%Result*) local_unnamed_addr
declare void @__quantum__rt__bool_record_output(i1, i8*) local_unnamed_addr
attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="1" "required_num_results"="1" }
!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 of compile time arrays¶
Guppy arrays within a non-comptime context cannot be converted at the moment. The best way to still make use of arrays in guppy and convert them to QIR are by making sure you allocate them within a @guppy.comptime context. In this context, arrays are equivalent to python lists. The following rules for writing guppy programs should lead to a smooth conversion to QIR:
Only allocate arrays within
@guppy.comptimefunctions (or alternatively use python lists)Even with
@guppy.comptimefunctions you should not pass in or return arrays
Use regular python functions (called from
@guppy.comptimecontext) for repeatable operations on qubit arrays/lists at compile time.These functions can accept and return qubit lists.
To implement runtime logic: Use
@guppyannotated functions, but do not pass or allocate arrays. Only pass/allocate tuples or basic typesDo not use
measure_array/discard_array, use compile time loops on arrays to do measurements and report results
One example showing this:
from pathlib import Path
from typing import no_type_check
from guppylang import guppy
from guppylang.defs import GuppyFunctionDefinition
from guppylang.std.angles import angle
from guppylang.std.builtins import array, owned
from guppylang.std.platform import result
from guppylang.std.qsystem.random import RNG
from guppylang.std.quantum import crz, cx, h, measure, qubit
# One can define pure python functions that use guppy functions and objects and call them within guppy.comptime functions.
# Examples for this are: cx_registers, measure_qubits_conditionally and record_result_array
# Theses functions can accept and return arrays without breaking QIR generation.
def cx_registers(qreg1: array[qubit], qreg2: array[qubit]) -> None:
# This function can be called from a guppy comptime function
# it can accept and return qubit lists or qubit arrays.
size = len(qreg1)
for i in range(size):
cx(qreg1[i], qreg2[i])
def measure_qubits_conditionally(q: list[qubit]) -> list[bool]:
size = len(q)
creg_results: list[bool] = []
for i in range(0, size - 1, 2):
# Guppy functions (like conditional_measure) can be called from
# python functions that are later included in a guppy module.
qres_i, qres_ip1 = conditional_measure(q[i], q[i + 1])
creg_results.append(qres_i)
creg_results.append(qres_ip1)
return creg_results
def record_result_array(prefix: str, creg_results: list[bool]) -> None:
# Record all values as individual bools
size = len(creg_results)
for i in range(size):
result(f"{prefix}_{i}", creg_results[i])
def record_result_array_record_int(prefix: str, creg_results: list[bool]) -> None:
# Combine all bool values into one int for recording. (Consider endianness)
size = len(creg_results)
integer_value = 0
for b in creg_results:
integer_value = (integer_value << 1) | int(b) # for big-endian
# integer_value = (integer_value << 1) | int(b) # for little-endian
result(prefix, integer_value)
@guppy
@no_type_check
def conditional_measure(q0: qubit @ owned, q1: qubit @ owned) -> tuple[bool, bool]:
# This function must be a guppy function, not guppy comptime, because it conditions
# on runtime values (here: measurement results).
# For QIR generation: this type of function cannot use arrays.
q2 = qubit()
h(q2)
c = measure(q2)
if c:
h(q0)
a = measure(q0)
if a:
h(q1)
b = measure(q1)
return a, b
def main_generator(size: int, theta: float) -> GuppyFunctionDefinition[[None], None]:
# This function returns the guppy main function for a given size
# parameter and a given angle theta.
# expects an even int for the size
assert size % 2 == 0
@guppy.comptime # For QIR generation: this function must be comptime since it uses arrays.
@no_type_check
def main() -> None:
q = array(qubit() for _ in range(size))
q2 = array(qubit() for _ in range(size))
h(q[0])
cx_registers(q, q2)
r1 = measure_qubits_conditionally(q)
r2 = measure_qubits_conditionally(q2)
record_result_array("q1", r1)
record_result_array_record_int("q2", r2)
return main
from hugr_qir.hugr_to_qir import hugr_to_qir
from hugr_qir.output import OutputFormat
guppy_qir = hugr_to_qir(main_generator(size=6, theta=0.3).compile(), output_format=OutputFormat.LLVM_IR, validate_qir=True)
print(guppy_qir)
; ModuleID = 'hugr-qir'
source_filename = "hugr-qir"
target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128"
target triple = "aarch64-unknown-linux-gnu"
%Qubit = type opaque
%Result = type opaque
@0 = private unnamed_addr constant [5 x i8] c"q1_0\00", align 1
@1 = private unnamed_addr constant [5 x i8] c"q1_1\00", align 1
@2 = private unnamed_addr constant [5 x i8] c"q1_2\00", align 1
@3 = private unnamed_addr constant [5 x i8] c"q1_3\00", align 1
@4 = private unnamed_addr constant [5 x i8] c"q1_4\00", align 1
@5 = private unnamed_addr constant [5 x i8] c"q1_5\00", align 1
@6 = private unnamed_addr constant [3 x i8] c"q2\00", align 1
define dso_local void @__hugr__.main.1() local_unnamed_addr #0 {
alloca_block:
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* null)
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* null)
tail call void @__quantum__qis__phasedx__body(double 0xBFF921FB54442D18, double 0x3FF921FB54442D18, %Qubit* nonnull inttoptr (i64 6 to %Qubit*))
tail call void @__quantum__qis__rzz__body(double 0x3FF921FB54442D18, %Qubit* null, %Qubit* nonnull inttoptr (i64 6 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0xBFF921FB54442D18, %Qubit* null)
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 6 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 6 to %Qubit*))
tail call void @__quantum__qis__phasedx__body(double 0xBFF921FB54442D18, double 0x3FF921FB54442D18, %Qubit* nonnull inttoptr (i64 7 to %Qubit*))
tail call void @__quantum__qis__rzz__body(double 0x3FF921FB54442D18, %Qubit* nonnull inttoptr (i64 1 to %Qubit*), %Qubit* nonnull inttoptr (i64 7 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 1 to %Qubit*))
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 7 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 7 to %Qubit*))
tail call void @__quantum__qis__phasedx__body(double 0xBFF921FB54442D18, double 0x3FF921FB54442D18, %Qubit* nonnull inttoptr (i64 8 to %Qubit*))
tail call void @__quantum__qis__rzz__body(double 0x3FF921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*), %Qubit* nonnull inttoptr (i64 8 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 8 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 8 to %Qubit*))
tail call void @__quantum__qis__phasedx__body(double 0xBFF921FB54442D18, double 0x3FF921FB54442D18, %Qubit* nonnull inttoptr (i64 9 to %Qubit*))
tail call void @__quantum__qis__rzz__body(double 0x3FF921FB54442D18, %Qubit* nonnull inttoptr (i64 3 to %Qubit*), %Qubit* nonnull inttoptr (i64 9 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 3 to %Qubit*))
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 9 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 9 to %Qubit*))
tail call void @__quantum__qis__phasedx__body(double 0xBFF921FB54442D18, double 0x3FF921FB54442D18, %Qubit* nonnull inttoptr (i64 10 to %Qubit*))
tail call void @__quantum__qis__rzz__body(double 0x3FF921FB54442D18, %Qubit* nonnull inttoptr (i64 4 to %Qubit*), %Qubit* nonnull inttoptr (i64 10 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 4 to %Qubit*))
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 10 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 10 to %Qubit*))
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 12 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 12 to %Qubit*))
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 12 to %Qubit*), %Result* nonnull inttoptr (i64 2 to %Result*))
%0 = tail call i1 @__quantum__qis__read_result__body(%Result* nonnull inttoptr (i64 2 to %Result*))
br i1 %0, label %4, label %cond_1367_case_1
cond_1367_case_1: ; preds = %alloca_block, %4
tail call void @__quantum__qis__mz__body(%Qubit* null, %Result* null)
%1 = tail call i1 @__quantum__qis__read_result__body(%Result* null)
br i1 %1, label %5, label %cond_1306_case_1
cond_1306_case_1: ; preds = %cond_1367_case_1, %5
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 1 to %Qubit*), %Result* nonnull inttoptr (i64 1 to %Result*))
%2 = tail call i1 @__quantum__qis__read_result__body(%Result* nonnull inttoptr (i64 1 to %Result*))
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 13 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 13 to %Qubit*))
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 13 to %Qubit*), %Result* nonnull inttoptr (i64 5 to %Result*))
%3 = tail call i1 @__quantum__qis__read_result__body(%Result* nonnull inttoptr (i64 5 to %Result*))
br i1 %3, label %9, label %cond_1226_case_1
4: ; preds = %alloca_block
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* null)
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* null)
br label %cond_1367_case_1
5: ; preds = %cond_1367_case_1
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 1 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 1 to %Qubit*))
br label %cond_1306_case_1
cond_1226_case_1: ; preds = %cond_1306_case_1, %9
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 2 to %Qubit*), %Result* nonnull inttoptr (i64 3 to %Result*))
%6 = tail call i1 @__quantum__qis__read_result__body(%Result* nonnull inttoptr (i64 3 to %Result*))
br i1 %6, label %10, label %cond_267_case_1
cond_267_case_1: ; preds = %cond_1226_case_1, %10
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 3 to %Qubit*), %Result* nonnull inttoptr (i64 4 to %Result*))
%7 = tail call i1 @__quantum__qis__read_result__body(%Result* nonnull inttoptr (i64 4 to %Result*))
tail call void @__quantum__qis__phasedx__body(double 0xBFF921FB54442D18, double 0x3FF921FB54442D18, %Qubit* nonnull inttoptr (i64 11 to %Qubit*))
tail call void @__quantum__qis__rzz__body(double 0x3FF921FB54442D18, %Qubit* nonnull inttoptr (i64 5 to %Qubit*), %Qubit* nonnull inttoptr (i64 11 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 5 to %Qubit*))
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 11 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 11 to %Qubit*))
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 14 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 14 to %Qubit*))
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 14 to %Qubit*), %Result* nonnull inttoptr (i64 8 to %Result*))
%8 = tail call i1 @__quantum__qis__read_result__body(%Result* nonnull inttoptr (i64 8 to %Result*))
br i1 %8, label %14, label %cond_1085_case_1
9: ; preds = %cond_1306_case_1
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 2 to %Qubit*))
br label %cond_1226_case_1
10: ; preds = %cond_1226_case_1
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 3 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 3 to %Qubit*))
br label %cond_267_case_1
cond_1085_case_1: ; preds = %cond_267_case_1, %14
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 4 to %Qubit*), %Result* nonnull inttoptr (i64 6 to %Result*))
%11 = tail call i1 @__quantum__qis__read_result__body(%Result* nonnull inttoptr (i64 6 to %Result*))
br i1 %11, label %15, label %cond_1024_case_1
cond_1024_case_1: ; preds = %cond_1085_case_1, %15
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 5 to %Qubit*), %Result* nonnull inttoptr (i64 7 to %Result*))
%12 = tail call i1 @__quantum__qis__read_result__body(%Result* nonnull inttoptr (i64 7 to %Result*))
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 15 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 15 to %Qubit*))
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 15 to %Qubit*), %Result* nonnull inttoptr (i64 11 to %Result*))
%13 = tail call i1 @__quantum__qis__read_result__body(%Result* nonnull inttoptr (i64 11 to %Result*))
br i1 %13, label %19, label %cond_944_case_1
14: ; preds = %cond_267_case_1
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 4 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 4 to %Qubit*))
br label %cond_1085_case_1
15: ; preds = %cond_1085_case_1
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 5 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 5 to %Qubit*))
br label %cond_1024_case_1
cond_944_case_1: ; preds = %cond_1024_case_1, %19
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 6 to %Qubit*), %Result* nonnull inttoptr (i64 9 to %Result*))
%16 = tail call i1 @__quantum__qis__read_result__body(%Result* nonnull inttoptr (i64 9 to %Result*))
br i1 %16, label %20, label %cond_883_case_1
cond_883_case_1: ; preds = %cond_944_case_1, %20
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 7 to %Qubit*), %Result* nonnull inttoptr (i64 10 to %Result*))
%17 = tail call i1 @__quantum__qis__read_result__body(%Result* nonnull inttoptr (i64 10 to %Result*))
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 16 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 16 to %Qubit*))
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 16 to %Qubit*), %Result* nonnull inttoptr (i64 14 to %Result*))
%18 = tail call i1 @__quantum__qis__read_result__body(%Result* nonnull inttoptr (i64 14 to %Result*))
br i1 %18, label %24, label %cond_803_case_1
19: ; preds = %cond_1024_case_1
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 6 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 6 to %Qubit*))
br label %cond_944_case_1
20: ; preds = %cond_944_case_1
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 7 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 7 to %Qubit*))
br label %cond_883_case_1
cond_803_case_1: ; preds = %cond_883_case_1, %24
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 8 to %Qubit*), %Result* nonnull inttoptr (i64 12 to %Result*))
%21 = tail call i1 @__quantum__qis__read_result__body(%Result* nonnull inttoptr (i64 12 to %Result*))
br i1 %21, label %25, label %cond_742_case_1
cond_742_case_1: ; preds = %cond_803_case_1, %25
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 9 to %Qubit*), %Result* nonnull inttoptr (i64 13 to %Result*))
%22 = tail call i1 @__quantum__qis__read_result__body(%Result* nonnull inttoptr (i64 13 to %Result*))
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 17 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 17 to %Qubit*))
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 17 to %Qubit*), %Result* nonnull inttoptr (i64 17 to %Result*))
%23 = tail call i1 @__quantum__qis__read_result__body(%Result* nonnull inttoptr (i64 17 to %Result*))
br i1 %23, label %33, label %cond_662_case_1
24: ; preds = %cond_883_case_1
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 8 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 8 to %Qubit*))
br label %cond_803_case_1
25: ; preds = %cond_803_case_1
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 9 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 9 to %Qubit*))
br label %cond_742_case_1
cond_662_case_1: ; preds = %cond_742_case_1, %33
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 10 to %Qubit*), %Result* nonnull inttoptr (i64 15 to %Result*))
%26 = tail call i1 @__quantum__qis__read_result__body(%Result* nonnull inttoptr (i64 15 to %Result*))
br i1 %26, label %34, label %cond_287_case_1
cond_287_case_1: ; preds = %cond_662_case_1, %34
%"464_0.0" = phi i64 [ 0, %cond_662_case_1 ], [ 2, %34 ]
tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 11 to %Qubit*), %Result* nonnull inttoptr (i64 16 to %Result*))
%27 = tail call i1 @__quantum__qis__read_result__body(%Result* nonnull inttoptr (i64 16 to %Result*))
tail call void @__quantum__rt__bool_record_output(i1 %1, i8* getelementptr inbounds ([5 x i8], [5 x i8]* @0, i64 0, i64 0))
tail call void @__quantum__rt__bool_record_output(i1 %2, i8* getelementptr inbounds ([5 x i8], [5 x i8]* @1, i64 0, i64 0))
tail call void @__quantum__rt__bool_record_output(i1 %6, i8* getelementptr inbounds ([5 x i8], [5 x i8]* @2, i64 0, i64 0))
tail call void @__quantum__rt__bool_record_output(i1 %7, i8* getelementptr inbounds ([5 x i8], [5 x i8]* @3, i64 0, i64 0))
tail call void @__quantum__rt__bool_record_output(i1 %11, i8* getelementptr inbounds ([5 x i8], [5 x i8]* @4, i64 0, i64 0))
tail call void @__quantum__rt__bool_record_output(i1 %12, i8* getelementptr inbounds ([5 x i8], [5 x i8]* @5, i64 0, i64 0))
%"604_0.0" = select i1 %16, i64 32, i64 0
%"569_0.0" = select i1 %17, i64 16, i64 0
%"534_0.0" = select i1 %21, i64 8, i64 0
%"499_0.0" = select i1 %22, i64 4, i64 0
%"429_0.0" = zext i1 %27 to i64
%28 = or i64 %"569_0.0", %"604_0.0"
%29 = or i64 %28, %"534_0.0"
%30 = or i64 %29, %"499_0.0"
%31 = or i64 %30, %"464_0.0"
%32 = or i64 %31, %"429_0.0"
tail call void @__quantum__rt__int_record_output(i64 %32, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @6, i64 0, i64 0))
ret void
33: ; preds = %cond_742_case_1
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 10 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 10 to %Qubit*))
br label %cond_662_case_1
34: ; preds = %cond_662_case_1
tail call void @__quantum__qis__phasedx__body(double 0x3FF921FB54442D18, double 0xBFF921FB54442D18, %Qubit* nonnull inttoptr (i64 11 to %Qubit*))
tail call void @__quantum__qis__rz__body(double 0x400921FB54442D18, %Qubit* nonnull inttoptr (i64 11 to %Qubit*))
br label %cond_287_case_1
}
declare void @__quantum__qis__phasedx__body(double, double, %Qubit*) local_unnamed_addr
declare void @__quantum__qis__rz__body(double, %Qubit*) local_unnamed_addr
declare void @__quantum__qis__rzz__body(double, %Qubit*, %Qubit*) local_unnamed_addr
declare void @__quantum__qis__mz__body(%Qubit*, %Result*) local_unnamed_addr
declare i1 @__quantum__qis__read_result__body(%Result*) local_unnamed_addr
declare void @__quantum__rt__bool_record_output(i1, i8*) local_unnamed_addr
declare void @__quantum__rt__int_record_output(i64, i8*) local_unnamed_addr
attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="18" "required_num_results"="18" }
!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}
Unsupported Conversions¶
Use Case 1: Unbounded Loops¶
Constructing a similar example like the one above with a loop which numbers of execution depends on the measurement results inside the loop leads to the generation of invalid QIR.
from typing import no_type_check
from guppylang import guppy, qubit
from guppylang.std.builtins import result
from guppylang.std.quantum import h, measure
@guppy
@no_type_check
def main() -> None:
q0 = qubit()
q1 = qubit()
i = 0
while i < 10:
q3 = qubit()
h(q3)
b = measure(q3)
if b:
h(q0)
i += 1
result("0", measure(q0))
result("1", measure(q1))
hugr = main.compile()
try:
guppy_qir = hugr_to_qir(main.compile(), output_format=OutputFormat.BITCODE, validate_qir=True)
print(guppy_qir)
except Exception as e:
print('Validation failed as expected:')
print(e)
Validation failed as expected:
Found loop in CFG containing the block: cond_exit_182
Use Case 2: Non Comptime Qubit Arrays¶
A program using Arrays over qubits leads to invalid QIR if they are used in non comptime functions. See here for alternatives.
from guppylang import guppy
from guppylang.std.builtins import array, result
from guppylang.std.quantum import qubit, measure_array, h, cx
@guppy
def ghz_state() -> array[qubit, 4]:
qubit_array = array(qubit() for _ in range(4))
h(qubit_array[0])
for i in range(1, 4):
cx(qubit_array[0], qubit_array[i])
return qubit_array
@guppy
def main() -> None:
qubit_array = ghz_state()
measure_results = measure_array(qubit_array)
res = 0
for i in range(4):
if measure_results[i]:
res += 2**int(i)
result("result", res)
hugr = main.compile()
try:
guppy_qir = hugr_to_qir(main.compile(), output_format=OutputFormat.BITCODE, validate_qir=True)
print(guppy_qir)
except Exception as e:
print('Validation failed as expected:')
print(e)
Validation failed as expected:
QIR generation failed. This may be the result of a bug but can also happen when trying to convert a feature in HUGR/Guppylang which is not supported in QIR. Error details: Unknown type: borrow_array(4, [Bool]+[Future(Bool)])