-
Notifications
You must be signed in to change notification settings - Fork 12
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
Changes from 37 commits
c424b24
3b9189a
692506f
9be4d45
80dddbe
e38ca3e
3843234
69f9e0a
09a0ca2
89ac299
96d9efd
6518628
f87d814
816e2bd
2ff1b6f
ea62c0b
73dc681
d65a347
3281184
52458ab
8e1edce
4b382b0
4729114
f69aa04
8573082
4d311f3
eec8fe5
9d66595
4b4b40a
7950e77
25bfcd5
ae6cbfe
e479c3e
4c6453a
5fab61f
83b50e7
84e7fc1
dd4a694
ba3671d
aadfe5e
72554e8
cebf4dd
16c3718
cf2f717
ce035c0
1cbf960
dd5ce89
a40732d
7bc58d5
90c68e7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -77,6 +77,7 @@ | |
Clbit, | ||
ControlledGate, | ||
Gate, | ||
IfElseOp, | ||
Instruction, | ||
InstructionSet, | ||
Measure, | ||
|
@@ -480,6 +481,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]]: | ||
# 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() | ||
CalMacCQ marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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]), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've now decided to give a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a check in cebf4dd |
||
) | ||
return circ | ||
|
||
|
||
class CircuitBuilder: | ||
def __init__( | ||
self, | ||
|
@@ -523,16 +588,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: | ||
|
@@ -544,6 +609,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)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1193,6 +1193,80 @@ def test_nonregister_bits() -> None: | |
tk_to_qiskit(c) | ||
|
||
|
||
# https://github.com/CQCL/pytket-qiskit/issues/415 | ||
def test_ifelseop_two_branches() -> None: | ||
qreg = QuantumRegister(2, "r") | ||
creg = ClassicalRegister(2, "s") | ||
circuit = QuantumCircuit(qreg, creg) | ||
(q0, q1) = qreg | ||
(c0, c1) = creg | ||
|
||
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 | ||
CalMacCQ marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if_cond, else_cond = tuple(tkc.commands_of_type(OpType.Conditional)) | ||
|
||
if_circ = if_cond.op.op.get_circuit() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The mypy check is failing. Probably need to assert that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, this was the issue. It doesn't matter anymore as I've redone the test. |
||
else_circ = else_cond.op.op.get_circuit() | ||
|
||
exp_if_circ = Circuit() | ||
r = exp_if_circ.add_q_register("r", 1) | ||
exp_if_circ.H(r[0]) | ||
|
||
exp_else_circ = Circuit() | ||
s = exp_else_circ.add_q_register("s", 1) | ||
exp_else_circ.H(s[0]) | ||
|
||
assert if_circ == exp_if_circ | ||
CalMacCQ marked this conversation as resolved.
Show resolved
Hide resolved
|
||
assert else_circ == exp_else_circ | ||
|
||
|
||
# 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A similar test including an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Turned the two branch test into an explicit equality check in a40732d |
||
|
||
|
||
def test_range_preds_with_conditionals() -> None: | ||
# https://github.com/CQCL/pytket-qiskit/issues/375 | ||
c = Circuit(1, 1) | ||
|
There was a problem hiding this comment.
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 theIfElseOp
only has atrue_body
rather than atrue_body
and afalse_body
.There's probably a cleaner way to deal with this.