-
Notifications
You must be signed in to change notification settings - Fork 13
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 all 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, | ||
|
@@ -313,7 +314,7 @@ def _all_bits_set(integer: int, n_bits: int) -> bool: | |
|
||
|
||
def _get_controlled_tket_optype(c_gate: ControlledGate) -> OpType: | ||
"""Get a pytket contolled OpType from a qiskit ControlledGate.""" | ||
"""Get a pytket controlled OpType from a qiskit ControlledGate.""" | ||
|
||
# If the control state is not "all |1>", use QControlBox | ||
if not _all_bits_set(c_gate.ctrl_state, c_gate.num_ctrl_qubits): | ||
|
@@ -480,6 +481,82 @@ 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: | ||
# Coniditions must be on a single bit (for now) TODO: support multiple bits. | ||
if len(bits) == 1: | ||
# 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() | ||
else: | ||
raise NotImplementedError("Conditions over multiple bits not yet supported.") | ||
|
||
# Coniditions must be on a single bit (for now) | ||
if not isinstance(if_else_op.condition[0], Clbit): | ||
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. As I said in the description I've restricted to the case of single bit conditions for now. However it should be easy enough to generalise for conditons on values of entire registers in a follow up PR. 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. Should we check 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. I prefer the former suggestion as the restriction to a single bit should be removable soon |
||
raise NotImplementedError( | ||
"Handling of register conditions is not yet supported" | ||
) | ||
|
||
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: | ||
if if_else_op.condition[1] not in {0, 1}: | ||
raise ValueError( | ||
"A bit must have condition value 0 or 1" | ||
+ f", got {if_else_op.condition[1]}" | ||
) | ||
circ.add_circbox( | ||
circbox=else_box, | ||
args=qubits, | ||
condition_bits=bits, | ||
condition_value=1 ^ if_else_op.condition[1], | ||
) | ||
return circ | ||
|
||
|
||
class CircuitBuilder: | ||
def __init__( | ||
self, | ||
|
@@ -523,16 +600,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 +621,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 |
---|---|---|
|
@@ -1188,6 +1188,113 @@ 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(1, "r") | ||
creg = ClassicalRegister(1, "s") | ||
circuit = QuantumCircuit(qreg, creg) | ||
|
||
circuit.h(qreg[0]) | ||
circuit.measure(qreg[0], creg[0]) | ||
|
||
with circuit.if_test((creg[0], 1)) as else_: | ||
circuit.h(qreg[0]) | ||
with else_: | ||
circuit.x(qreg[0]) | ||
circuit.measure(qreg[0], creg[0]) | ||
|
||
tkc = qiskit_to_tk(circuit) | ||
tkc.name = "test_circ" | ||
|
||
# Manually build the expected pytket Circuit. | ||
# Validate against tkc. | ||
expected_circ = Circuit(name="test_circ") | ||
r_reg = expected_circ.add_q_register("r", 1) | ||
s_reg = expected_circ.add_c_register("s", 1) | ||
expected_circ.H(r_reg[0]) | ||
expected_circ.Measure(r_reg[0], s_reg[0]) | ||
|
||
h_circ = Circuit() | ||
h_reg = h_circ.add_q_register("r", 1) | ||
h_circ.name = "If" | ||
h_circ.H(h_reg[0]) | ||
|
||
x_circ = Circuit() | ||
x_reg = x_circ.add_q_register("r", 1) | ||
x_circ.name = "Else" | ||
x_circ.X(x_reg[0]) | ||
|
||
expected_circ.add_circbox( | ||
CircBox(h_circ), [r_reg[0]], condition_bits=[s_reg[0]], condition_value=1 | ||
) | ||
expected_circ.add_circbox( | ||
CircBox(x_circ), [r_reg[0]], condition_bits=[s_reg[0]], condition_value=0 | ||
) | ||
|
||
expected_circ.Measure(r_reg[0], s_reg[0]) | ||
|
||
assert expected_circ == tkc | ||
|
||
|
||
# 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_ifelseop_multi_bit_cond() -> None: | ||
qreg = QuantumRegister(2, "q") | ||
creg = ClassicalRegister(2, "c") | ||
circuit = QuantumCircuit(creg, qreg) | ||
(q0, q1) = qreg | ||
(c0, c1) = creg | ||
|
||
circuit.h(q0) | ||
circuit.h(q1) | ||
circuit.measure(q0, c0) | ||
circuit.measure(q1, c1) | ||
with circuit.if_test((creg, 2)): | ||
circuit.x(q0) | ||
circuit.x(q1) | ||
circuit.measure(q0, c0) | ||
circuit.measure(q1, c1) | ||
# This currently gives an error as register exp not supported. | ||
with pytest.raises(NotImplementedError): | ||
qiskit_to_tk(circuit) | ||
|
||
|
||
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.