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

feat: handle IfElseOp in qiskit_to_tk #437

Draft
wants to merge 33 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c424b24
initial attempt at CircBox handling
CalMacCQ Dec 17, 2024
3b9189a
add temporary if-else test
CalMacCQ Dec 17, 2024
692506f
try adding data for conditional subcircuits
CalMacCQ Dec 17, 2024
9be4d45
add a proper test
CalMacCQ Dec 17, 2024
80dddbe
cleanup
CalMacCQ Dec 17, 2024
e38ca3e
add another TODO
CalMacCQ Dec 17, 2024
3843234
formatting
CalMacCQ Dec 17, 2024
69f9e0a
Merge branch 'main' into support_if-else
CalMacCQ Dec 17, 2024
09a0ca2
try if else with register from qiskit circuit
cqc-melf Dec 17, 2024
89ac299
Merge branch 'main' into support_if-else
CalMacCQ Dec 17, 2024
96d9efd
minor cleanup
CalMacCQ Dec 17, 2024
6518628
more progress on circuit building (still gives RuntimeError)
CalMacCQ Dec 18, 2024
f87d814
try to fix kwargs
CalMacCQ Dec 18, 2024
816e2bd
update test
CalMacCQ Dec 18, 2024
2ff1b6f
remove import
CalMacCQ Dec 18, 2024
ea62c0b
push latest attempt
CalMacCQ Dec 18, 2024
73dc681
naming fixes
CalMacCQ Dec 31, 2024
d65a347
a little more progress
CalMacCQ Dec 31, 2024
3281184
Merge branch 'main' into support_if-else
CalMacCQ Dec 31, 2024
52458ab
refactor: handle IfElseOp separately
CalMacCQ Dec 31, 2024
8e1edce
make circuit builder function private
CalMacCQ Dec 31, 2024
4b382b0
add a test case for a single branch
CalMacCQ Jan 2, 2025
4729114
handle the single branch case
CalMacCQ Jan 2, 2025
f69aa04
improve validation for single branch case
CalMacCQ Jan 2, 2025
8573082
fix import
CalMacCQ Jan 2, 2025
4d311f3
add some comments
CalMacCQ Jan 2, 2025
eec8fe5
add comments to test, change variable name
CalMacCQ Jan 2, 2025
9d66595
attempt to debug C.I. test case issue
CalMacCQ Jan 2, 2025
4b4b40a
remove debug prints
CalMacCQ Jan 2, 2025
7950e77
correct some comments
CalMacCQ Jan 2, 2025
25bfcd5
update changelog
CalMacCQ Jan 2, 2025
ae6cbfe
Better varible names for testing
CalMacCQ Jan 2, 2025
e479c3e
link to issue
CalMacCQ Jan 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

# Changelog

## 0.63.0 (January 2025) - UNRELEASED

- Support conversion of qiskit circuits containing `IfElseOp` in the {py:func}`qiskit_to_tk` converter.

## 0.62.0 (December 2024)

- AerBackend now rejects circuits with too many qubits
Expand Down
81 changes: 78 additions & 3 deletions pytket/extensions/qiskit/qiskit_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
Clbit,
ControlledGate,
Gate,
IfElseOp,
Instruction,
InstructionSet,
Measure,
Expand Down Expand Up @@ -479,6 +480,70 @@ def _build_circbox(instr: Instruction, circuit: QuantumCircuit) -> CircBox:
return CircBox(subc)


# Used for handling of IfElseOp
# docs -> https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.IfElseOp
# Examples -> https://docs.quantum.ibm.com/guides/classical-feedforward-and-control-flow
# pytket-qiskit issue -> https://github.com/CQCL/pytket-qiskit/issues/415
def _pytket_boxes_from_ifelseop(
if_else_op: IfElseOp, qregs: list[QuantumRegister], cregs: list[ClassicalRegister]
) -> tuple[CircBox, Optional[CircBox]]:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not so pleased with this Optional return type in the function signature. This is to deal with the case when the IfElseOp only has a true_body rather than a true_body and a false_body.

There's probably a cleaner way to deal with this.

# Extract the QuantumCircuit implementing true_body
if_qc: QuantumCircuit = if_else_op.blocks[0]

if_builder = CircuitBuilder(qregs, cregs)
if_builder.add_qiskit_data(if_qc)
CalMacCQ marked this conversation as resolved.
Show resolved Hide resolved
if_circuit = if_builder.circuit()
if_circuit.name = "If"
# Remove blank wires to ensure CircBox is the correct size.
if_circuit.remove_blank_wires()

# The false_body arg is optional
if len(if_else_op.blocks) == 2:
else_qc: QuantumCircuit = if_else_op.blocks[1]
else_builder = CircuitBuilder(qregs, cregs)
else_builder.add_qiskit_data(else_qc)
else_circuit = else_builder.circuit()
else_circuit.name = "Else"
else_circuit.remove_blank_wires()
return CircBox(if_circuit), CircBox(else_circuit)

# If no false_body is specified IfElseOp.blocks is of length 1.
# In this case we return a CircBox implementing true_body and None.
return CircBox(if_circuit), None


def _build_if_else_circuit(
if_else_op: IfElseOp,
qregs: list[QuantumRegister],
cregs: list[ClassicalRegister],
qubits: list[Qubit],
bits: list[Bit],
) -> Circuit:
# Get two CircBox objects which implement the true_body and false_body.
if_box, else_box = _pytket_boxes_from_ifelseop(if_else_op, qregs, cregs)
# else_box can be None if no false_body is specified.

circ_builder = CircuitBuilder(qregs, cregs)
circ = circ_builder.circuit()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

print(circ.qubits, circ.bits)
print(if_box.get_circuit().qubits, if_box.get_circuit().bits)
print(qubits)
print(bits)

prints

[q105[0], q105[1]] [c3[0], c3[1]]
[q105[0], q105[1]] [c3[0], c3[1]]
[q105[1]]
[c3[0]]

So the box has 2 qubits and 2 bits, but in add_circbox, args=qubits + bits only provides 1 qubit and 1 bit.

Copy link
Contributor Author

@CalMacCQ CalMacCQ Dec 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

I figured out what I was doing wrong. The thing that was confusing me was that the two circuits which define the IfElseOp do not have any registers. Their qubits are tied to the register of the circuit containing the IfElseOp. This meant that the subcircuits in my CircBoxes had too many qubits/bits as there were some additional blank wires.

Cleaning up the blank wires with Circuit.remove_blank_wires as in d65a347 seems to fix this.


circ.add_circbox(
circbox=if_box,
args=qubits,
condition_bits=bits,
condition_value=if_else_op.condition[1],
)
# If we have an else_box defined, add it to the circuit
if else_box is not None:
circ.add_circbox(
circbox=else_box,
args=qubits,
condition_bits=bits,
# TODO: handle conditions over multiple bits/registers?
condition_value=not bool(if_else_op.condition[1]),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still need to decide if its worth handling more complex conditionals here or just restrict to conditions over a single bit for now.

)
return circ


class CircuitBuilder:
def __init__(
self,
Expand Down Expand Up @@ -522,16 +587,16 @@ def add_qiskit_data(
bits: list[Bit] = [self.cbmap[bit] for bit in cargs]

condition_kwargs = {}
if instr.condition is not None:
if instr.condition is not None and type(instr) is not IfElseOp:
condition_kwargs = _get_pytket_condition_kwargs(
instruction=instr,
cregmap=self.cregmap,
circuit=circuit,
)

optype = None
if type(instr) not in (PauliEvolutionGate, UnitaryGate):
# Handling of PauliEvolutionGate and UnitaryGate below
if type(instr) not in (PauliEvolutionGate, UnitaryGate, IfElseOp):
# Handling of PauliEvolutionGate, UnitaryGate and IfElseOp below
optype = _optype_from_qiskit_instruction(instruction=instr)

if optype == OpType.QControlBox:
Expand All @@ -543,6 +608,16 @@ def add_qiskit_data(
# Append OpType found by stateprep helpers
_add_state_preparation(self.tkc, qubits, instr)

elif type(instr) is IfElseOp:
if_else_circ = _build_if_else_circuit(
if_else_op=instr,
qregs=self.qregs,
cregs=self.cregs,
qubits=qubits,
bits=bits,
)
self.tkc.append(if_else_circ)

elif type(instr) is PauliEvolutionGate:
qpo = _qpo_from_peg(instr, qubits)
empty_circ = Circuit(len(qargs))
Expand Down
59 changes: 59 additions & 0 deletions tests/qiskit_convert_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1193,6 +1193,65 @@ def test_nonregister_bits() -> None:
tk_to_qiskit(c)


# https://github.com/CQCL/pytket-qiskit/issues/415
def test_ifelseop_two_branches() -> None:
qubits = QuantumRegister(2)
clbits = ClassicalRegister(2)
circuit = QuantumCircuit(qubits, clbits)
(q0, q1) = qubits
(c0, c1) = clbits

circuit.h(q0)
circuit.measure(q0, c0)

with circuit.if_test((c0, 1)) as else_:
circuit.h(q1)
with else_:
circuit.x(q1)
circuit.measure(q1, c1)

tkc = qiskit_to_tk(circuit)
assert tkc.n_gates_of_type(OpType.Conditional) == 2
Copy link
Contributor Author

@CalMacCQ CalMacCQ Jan 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should test this more thoroughly. I ran into this bug -> CQCL/tket#1726 when trying to validate the contents of the two CircBoxes.



# https://github.com/CQCL/pytket-qiskit/issues/415
def test_ifelseop_one_branch() -> None:
qubits = QuantumRegister(1, "q1")
clbits = ClassicalRegister(1, "c1")
circuit = QuantumCircuit(qubits, clbits)
(q0,) = qubits
(c0,) = clbits

circuit.h(q0)
circuit.measure(q0, c0)
with circuit.if_test((c0, 1)):
circuit.x(q0)
circuit.measure(q0, c0)

tket_circ_if_else = qiskit_to_tk(circuit)
tket_circ_if_else.name = "test_circ"

# Manually build the expected pytket Circuit.
# Validate against tket_circ_if_else.
expected_circ = Circuit()
expected_circ.name = "test_circ"
q1_tk = expected_circ.add_q_register("q1", 1)
c1_tk = expected_circ.add_c_register("c1", 1)
expected_circ.H(q1_tk[0])
expected_circ.Measure(q1_tk[0], c1_tk[0])
x_circ = Circuit()
x_circ.name = "If"
xq1 = x_circ.add_q_register("q1", 1)
x_circ.X(xq1[0])
expected_circ.add_circbox(
CircBox(x_circ), [q1_tk[0]], condition_bits=[c1_tk[0]], condition_value=1
)

expected_circ.Measure(q1_tk[0], c1_tk[0])

assert tket_circ_if_else == expected_circ


def test_range_preds_with_conditionals() -> None:
# https://github.com/CQCL/pytket-qiskit/issues/375
c = Circuit(1, 1)
Expand Down
Loading