Skip to content
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

Closed
cabreraam opened this issue May 28, 2024 · 6 comments
Closed

Support for loops #277

cabreraam opened this issue May 28, 2024 · 6 comments
Labels
enhancement New feature or request

Comments

@cabreraam
Copy link

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 that for 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:

I 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.

@cabreraam cabreraam added the enhancement New feature or request label May 28, 2024
@cabreraam
Copy link
Author

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 qir-runner

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

@ausbin
Copy link
Contributor

ausbin commented Jun 4, 2024

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 qiskit.qasm3) would be nice:

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.

@cabreraam
Copy link
Author

#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.

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 qiskit.qasm3) would be nice:

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))

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.

@idavis
Copy link
Collaborator

idavis commented Jun 4, 2024

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).

@cabreraam
Copy link
Author

Thank you. @idavis!

@idavis
Copy link
Collaborator

idavis commented Oct 24, 2024

Closing as tracked by #280, and #279 has been merged.

@idavis idavis closed this as completed Oct 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants