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 (Heirarchical 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 compatability 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-QIS does not support conversion of the following Guppy features:

  • Functions returing collections over Qubits;

  • Unbounded Loops;

  • RNG;

  • 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/CQCL/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 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__qis__read_result__body(%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__qis__read_result__body(%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__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"="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

  • Foor Loops

All hardware native gate operations are supported on System Model H2. Users cannnot call the General \(SU(4)\) Entangler gate using Guppy. Additionally, Random Number Generator (RNG) conversion is not supported (see here). 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 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__qis__read_result__body(%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__qis__read_result__body(%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__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"="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}

Interations 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 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__qis__read_result__body(%Result* nonnull inttoptr (i64 2 to %Result*))
  br i1 %0, label %21, label %cond_374_case_0

cond_374_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__qis__read_result__body(%Result* nonnull inttoptr (i64 2 to %Result*))
  br i1 %1, label %2, label %cond_374_case_0.1

2:                                                ; preds = %cond_374_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_374_case_0.1

cond_374_case_0.1:                                ; preds = %2, %cond_374_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__qis__read_result__body(%Result* nonnull inttoptr (i64 2 to %Result*))
  br i1 %3, label %4, label %cond_374_case_0.2

4:                                                ; preds = %cond_374_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_374_case_0.2

cond_374_case_0.2:                                ; preds = %4, %cond_374_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__qis__read_result__body(%Result* nonnull inttoptr (i64 2 to %Result*))
  br i1 %5, label %6, label %cond_374_case_0.3

6:                                                ; preds = %cond_374_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_374_case_0.3

cond_374_case_0.3:                                ; preds = %6, %cond_374_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__qis__read_result__body(%Result* nonnull inttoptr (i64 2 to %Result*))
  br i1 %7, label %8, label %cond_374_case_0.4

8:                                                ; preds = %cond_374_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_374_case_0.4

cond_374_case_0.4:                                ; preds = %8, %cond_374_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__qis__read_result__body(%Result* nonnull inttoptr (i64 2 to %Result*))
  br i1 %9, label %10, label %cond_374_case_0.5

10:                                               ; preds = %cond_374_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_374_case_0.5

cond_374_case_0.5:                                ; preds = %10, %cond_374_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__qis__read_result__body(%Result* nonnull inttoptr (i64 2 to %Result*))
  br i1 %11, label %12, label %cond_374_case_0.6

12:                                               ; preds = %cond_374_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_374_case_0.6

cond_374_case_0.6:                                ; preds = %12, %cond_374_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__qis__read_result__body(%Result* nonnull inttoptr (i64 2 to %Result*))
  br i1 %13, label %14, label %cond_374_case_0.7

14:                                               ; preds = %cond_374_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_374_case_0.7

cond_374_case_0.7:                                ; preds = %14, %cond_374_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__qis__read_result__body(%Result* nonnull inttoptr (i64 2 to %Result*))
  br i1 %15, label %16, label %cond_374_case_0.8

16:                                               ; preds = %cond_374_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_374_case_0.8

cond_374_case_0.8:                                ; preds = %16, %cond_374_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__qis__read_result__body(%Result* nonnull inttoptr (i64 2 to %Result*))
  br i1 %17, label %18, label %cond_374_case_0.9

18:                                               ; preds = %cond_374_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_374_case_0.9

cond_374_case_0.9:                                ; preds = %18, %cond_374_case_0.8
  tail call void @__quantum__qis__mz__body(%Qubit* null, %Result* null)
  %19 = tail call i1 @__quantum__qis__read_result__body(%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__qis__read_result__body(%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_374_case_0
}

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__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}

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_176

Use Case 2: Qubit Arrays

A program using Arrays over qubits leads to invalid QIR.

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:
Unknown type: array(4, []+[[Bool]+[Future(Bool)]])

Use Case 3: Random Number Generator (RNG)

Conversion of the Guppy integrated RNG to QIR leads to a validation error. If users wish to use the RNG capability with QIR, see the Quantinuum QIR extension for RNG here.

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(0)
    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()
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:
Unknown type: context