-
Notifications
You must be signed in to change notification settings - Fork 24
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
Support for loops #277
Comments
This bit of LLVM IR doesn't do anything useful, but this is something I rolled by hand and is what I had in mind. Also, it works in this build of ; ModuleID = 'qpe'
source_filename = "qpe"
%Qubit = type opaque
define void @main() #0 {
entry:
br label %loop0
loop0:
%i = phi i64 [ 0, %entry ], [ %nextval0, %loop0 ]
%tmp = inttoptr i64 %i to %Qubit*
call void @__quantum__qis__h__body(%Qubit* %tmp)
%nextval0 = add i64 %i, 1
%endcond0 = icmp ult i64 %i, 3
%loopcond0 = icmp ne i1 %endcond0, 0
br i1 %loopcond0, label %loop0, label %afterloop0
afterloop0:
ret void
}
declare void @__quantum__qis__h__body(%Qubit*)
attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="4" "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} This is the LLVM IR (generated from PyQIR) that I based that off of: ; ModuleID = 'qpe'
source_filename = "qpe"
%Qubit = type opaque
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*))
ret void
}
declare void @__quantum__qis__h__body(%Qubit*)
attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="4" "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} |
I hope #279 is somewhat helpful. Here's an example of generating an n-qubit GHZ state: #!/usr/bin/env python3
# Copyright (c) Austin J. Adams
# Licensed under CC-0
import pyqir
import pyqir.rt
num_qubits = 10
context = pyqir.Context()
builder = pyqir.Builder(context)
mod = pyqir.qir_module(context, "ghz")
entry_point = pyqir.entry_point(mod, "kernel", num_qubits, num_qubits)
header = pyqir.BasicBlock(context, "header", entry_point)
cond = pyqir.BasicBlock(context, "cond", entry_point)
body = pyqir.BasicBlock(context, "body", entry_point)
footer = pyqir.BasicBlock(context, "footer", entry_point)
builder.insert_at_end(header)
nullptr = pyqir.Constant.null(pyqir.PointerType(pyqir.IntType(context, 8)))
pyqir.rt.initialize(builder, nullptr)
pyqir.qis.h(builder, pyqir.qubit(context, 0))
builder.br(cond)
builder.insert_at_end(cond)
i64 = pyqir.IntType(mod.context, 64)
phi = builder.phi(i64)
zero_const = pyqir.const(i64, 0)
phi.add_incoming(zero_const, header)
num_qubits_const = pyqir.const(i64, num_qubits)
ub = builder.sub(num_qubits_const, pyqir.const(i64, 1))
icmp = builder.icmp(pyqir.IntPredicate.ULT, phi, ub)
builder.condbr(icmp, body, footer)
builder.insert_at_end(body)
one_const = pyqir.const(i64, 1)
incr = builder.add(phi, one_const)
pyqir.qis.cx(builder, builder.dyn_qubit(phi), builder.dyn_qubit(incr))
builder.br(cond)
phi.add_incoming(incr, body)
builder.insert_at_end(footer)
for i in range(num_qubits):
pyqir.qis.mz(builder, pyqir.qubit(context, i), pyqir.result(context, i))
pyqir.rt.tuple_record_output(builder, num_qubits_const, nullptr)
for i in range(num_qubits):
pyqir.rt.result_record_output(builder, pyqir.result(context, i), nullptr)
builder.ret()
mod.verify()
if __name__ == "__main__":
print(str(mod)) which generates ; ModuleID = 'ghz'
source_filename = "ghz"
%Qubit = type opaque
%Result = type opaque
define void @kernel() #0 {
header:
call void @__quantum__rt__initialize(i8* null)
call void @__quantum__qis__h__body(%Qubit* null)
br label %cond
cond: ; preds = %body, %header
%0 = phi i64 [ 0, %header ], [ %2, %body ]
%1 = icmp ult i64 %0, 9
br i1 %1, label %body, label %footer
body: ; preds = %cond
%2 = add i64 %0, 1
%3 = inttoptr i64 %0 to %Qubit*
%4 = inttoptr i64 %2 to %Qubit*
call void @__quantum__qis__cnot__body(%Qubit* %3, %Qubit* %4)
br label %cond
footer: ; preds = %cond
call void @__quantum__qis__mz__body(%Qubit* null, %Result* null)
call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*))
call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 3 to %Qubit*), %Result* inttoptr (i64 3 to %Result*))
call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 4 to %Qubit*), %Result* inttoptr (i64 4 to %Result*))
call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 5 to %Qubit*), %Result* inttoptr (i64 5 to %Result*))
call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 6 to %Qubit*), %Result* inttoptr (i64 6 to %Result*))
call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 7 to %Qubit*), %Result* inttoptr (i64 7 to %Result*))
call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 8 to %Qubit*), %Result* inttoptr (i64 8 to %Result*))
call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 9 to %Qubit*), %Result* inttoptr (i64 9 to %Result*))
call void @__quantum__rt__tuple_record_output(i64 10, i8* null)
call void @__quantum__rt__result_record_output(%Result* null, i8* null)
call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null)
call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 2 to %Result*), i8* null)
call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 3 to %Result*), i8* null)
call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 4 to %Result*), i8* null)
call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 5 to %Result*), i8* null)
call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 6 to %Result*), i8* null)
call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 7 to %Result*), i8* null)
call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 8 to %Result*), i8* null)
call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 9 to %Result*), i8* null)
ret void
}
declare void @__quantum__rt__initialize(i8*)
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
declare void @__quantum__rt__tuple_record_output(i64, i8*)
declare void @__quantum__rt__result_record_output(%Result*, i8*)
attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="10" "required_num_results"="10" }
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} qir-runner spits out all 0s or all 1s as you'd expect. Of course, this is not the perfect experience from a programmer perspective. You're basically just using IRBuilder through many complex layers of abstraction at this point. Some syntax like this (like in with builder.for_(pyqir.const(i64, 0), pyqir.const(i64, num_qubits)) as i:
incr = builder.add(i, pyqir.const(i64, 1))
pyqir.qis.cx(builder, builder.dyn_qubit(i), builder.dyn_qubit(incr)) I can look into that if you're interested. |
#279 is very helpful, thanks for looking into this! Apologies to the PyQIR devs; I didn't realize that part of my issue had already been raised in #242. I agree with @ausbin though.
It'd be nice to have a more user-friendly method of capturing loops, but I'll leave it up to @ausbin to decide if they find it meaningful to add. Or at least wait until after #279 gets merged, because it seems like that'd be a major component of a PR toward more user-friendly loop generation. |
I've created a tracking issue #280 which gives the high-level list of items needed to be able to generation adaptive profile QIR which includes backwards branching (loops). |
Thank you. @idavis! |
Is your feature request related to a problem? Please describe.
When a
for
loop in PyQIR is encountered, the resulting QIR that's emitted is effectively the loop unrolled equivalent of thatfor
loop. Is there a way to use the branching infrastructure in LLVM to reduce the size of the resulting.ll
file? I'm not sure if this is physically realizable on a QPU (I'm very new to this space), so forgive me if this is a naive question.Describe the solution you had in mind
It seems like most of the component pieces for a solution (in software) exist:
if_bool.py
example for executing different basic blocks based on some conditionphi
nodeI don't know anything about Rust, so I don't understand the function signatures for implementations, but it feels like the infrastructure for a solution is there.
Additional context
Maybe @wongey or @ausbin might be able to provide additional context/correct some things that I wrote.
The text was updated successfully, but these errors were encountered: