-
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: Improved support for IfElseOp
in qiskit_to_tk
#463
Changes from all commits
bc8f2f9
37444c9
b093f33
78233bd
76c85ac
004ecce
8739a96
57f4b93
05936ec
35aac0f
bb54b3c
77813dd
3d706df
974bf60
f9885d0
493b3a4
520190f
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 |
---|---|---|
|
@@ -58,6 +58,7 @@ | |
Unitary3qBox, | ||
UnitType, | ||
) | ||
from pytket.circuit.logic_exp import reg_eq, reg_neq | ||
from pytket.passes import AutoRebase | ||
from pytket.pauli import Pauli, QubitPauliString | ||
from pytket.unit_id import _TEMP_BIT_NAME | ||
|
@@ -98,6 +99,7 @@ | |
from qiskit_ibm_runtime.models.backend_properties import Nduv | ||
|
||
from pytket.circuit import UnitID | ||
from pytket.unit_id import BitRegister | ||
from qiskit.circuit.quantumcircuitdata import QuantumCircuitData # type: ignore | ||
|
||
_qiskit_gates_1q = { | ||
|
@@ -518,41 +520,49 @@ def _build_if_else_circuit( | |
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): | ||
raise NotImplementedError( | ||
"Handling of register conditions is not yet supported" | ||
# 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() | ||
|
||
if isinstance(if_else_op.condition[0], Clbit): | ||
if len(bits) != 1: | ||
raise NotImplementedError("Conditions on multiple bits not supported") | ||
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 an issues / is this something we want to add in the future? 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. Possibly but it may be more annoying to handle and maintain. I'm not certain on the limits on what you can express with I think for now we should stick with single bit conditions and entire register command conditions. If theres a demand for different cases then we can decide to support that. |
||
circ.add_circbox( | ||
circbox=if_box, | ||
args=qubits, | ||
condition_bits=bits, | ||
condition_value=if_else_op.condition[1], | ||
) | ||
|
||
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]}" | ||
# 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, | ||
condition_value=1 ^ if_else_op.condition[1], | ||
) | ||
|
||
elif isinstance(if_else_op.condition[0], ClassicalRegister): | ||
pytket_bit_reg: BitRegister = circ.get_c_register(if_else_op.condition[0].name) | ||
circ.add_circbox( | ||
circbox=else_box, | ||
circbox=if_box, | ||
args=qubits, | ||
condition_bits=bits, | ||
condition_value=1 ^ if_else_op.condition[1], | ||
condition=reg_eq(pytket_bit_reg, if_else_op.condition[1]), | ||
) | ||
if else_box is not None: | ||
circ.add_circbox( | ||
circbox=else_box, | ||
args=qubits, | ||
condition=reg_neq(pytket_bit_reg, if_else_op.condition[1]), | ||
) | ||
else: | ||
raise TypeError( | ||
"Unrecognized type used to construct IfElseOp. Expected " | ||
+ f"ClBit or ClassicalRegister, got {type(if_else_op.condition[0])}" | ||
) | ||
|
||
return circ | ||
|
||
|
||
|
@@ -620,6 +630,8 @@ def add_qiskit_data( | |
# Append OpType found by stateprep helpers | ||
_add_state_preparation(self.tkc, qubits, instr) | ||
|
||
# Note: These IfElseOp/if_test type conditions are only handled | ||
# for single bit conditions and conditions on entire registers. | ||
elif type(instr) is IfElseOp: | ||
if_else_circ = _build_if_else_circuit( | ||
if_else_op=instr, | ||
|
@@ -954,12 +966,6 @@ def append_tk_command_to_qiskit( | |
supported_gate_rebase = AutoRebase(_protected_tket_gates) | ||
|
||
|
||
def _has_implicit_permutation(circ: Circuit) -> bool: | ||
"""Returns True if a Circuit has a non-trivial permutation | ||
of qubits, false otherwise.""" | ||
return any(q0 != q1 for q0, q1 in circ.implicit_qubit_permutation().items()) | ||
|
||
|
||
def tk_to_qiskit( | ||
tkcirc: Circuit, | ||
replace_implicit_swaps: bool = False, | ||
|
@@ -987,11 +993,7 @@ def tk_to_qiskit( | |
if replace_implicit_swaps: | ||
tkc.replace_implicit_wire_swaps() | ||
|
||
if ( | ||
_has_implicit_permutation(tkcirc) | ||
and perm_warning | ||
and not replace_implicit_swaps | ||
): | ||
if tkcirc.has_implicit_wireswaps and perm_warning and not replace_implicit_swaps: | ||
warnings.warn( | ||
"The pytket Circuit contains implicit qubit permutations" | ||
+ " which aren't handled by default." | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,6 +61,7 @@ | |
Unitary2qBox, | ||
Unitary3qBox, | ||
reg_eq, | ||
reg_neq, | ||
) | ||
from pytket.extensions.qiskit import IBMQBackend, qiskit_to_tk, tk_to_qiskit | ||
from pytket.extensions.qiskit.backends import ( | ||
|
@@ -1274,7 +1275,61 @@ def test_ifelseop_one_branch() -> None: | |
assert tket_circ_if_else == expected_circ | ||
|
||
|
||
def test_ifelseop_multi_bit_cond() -> None: | ||
# https://github.com/CQCL/pytket-qiskit/issues/452 | ||
def test_ifelseop_reg_cond_if() -> None: | ||
qreg = QuantumRegister(3, "q") | ||
creg = ClassicalRegister(3, "c") | ||
circuit = QuantumCircuit(creg, qreg) | ||
(q0, q1, q2) = qreg | ||
(c0, c1, c2) = creg | ||
circuit.h(q0) | ||
circuit.h(q1) | ||
circuit.h(q2) | ||
circuit.measure(q0, c0) | ||
circuit.measure(q1, c1) | ||
circuit.measure(q2, c2) | ||
# Condition is on a register not a bit | ||
with circuit.if_test((creg, 2)): | ||
circuit.x(q0) | ||
circuit.y(q1) | ||
circuit.z(q2) | ||
circuit.measure(q0, c0) | ||
circuit.measure(q1, c1) | ||
circuit.measure(q2, c2) | ||
|
||
tkc: Circuit = qiskit_to_tk(circuit) | ||
tkc.name = "test_circ" | ||
|
||
expected_circ = Circuit() | ||
expected_circ.name = "test_circ" | ||
qreg_tk = expected_circ.add_q_register("q", 3) | ||
creg_tk = expected_circ.add_c_register("c", 3) | ||
expected_circ.H(qreg_tk[0]) | ||
expected_circ.H(qreg_tk[1]) | ||
expected_circ.H(qreg_tk[2]) | ||
expected_circ.Measure(qreg_tk[0], creg_tk[0]) | ||
expected_circ.Measure(qreg_tk[1], creg_tk[1]) | ||
expected_circ.Measure(qreg_tk[2], creg_tk[2]) | ||
|
||
pauli_circ = Circuit() | ||
pauli_circ.name = "If" | ||
pauli_qreg = pauli_circ.add_q_register("q", 3) | ||
pauli_circ.X(pauli_qreg[0]).Y(pauli_qreg[1]).Z(pauli_qreg[2]) | ||
expected_circ.add_circbox( | ||
CircBox(pauli_circ), | ||
[qreg_tk[0], qreg_tk[1], qreg_tk[2]], | ||
condition=reg_eq(creg_tk, 2), | ||
) | ||
|
||
expected_circ.Measure(qreg_tk[0], creg_tk[0]) | ||
expected_circ.Measure(qreg_tk[1], creg_tk[1]) | ||
expected_circ.Measure(qreg_tk[2], creg_tk[2]) | ||
|
||
assert expected_circ == tkc | ||
|
||
|
||
# https://github.com/CQCL/pytket-qiskit/issues/452 | ||
def test_ifelseop_reg_cond_if_else() -> None: | ||
qreg = QuantumRegister(2, "q") | ||
creg = ClassicalRegister(2, "c") | ||
circuit = QuantumCircuit(creg, qreg) | ||
|
@@ -1285,14 +1340,47 @@ def test_ifelseop_multi_bit_cond() -> None: | |
circuit.h(q1) | ||
circuit.measure(q0, c0) | ||
circuit.measure(q1, c1) | ||
with circuit.if_test((creg, 2)): | ||
# Condition is on a register not a bit | ||
with circuit.if_test((creg, 2)) as else_: | ||
circuit.x(q0) | ||
circuit.x(q1) | ||
with else_: | ||
circuit.y(q0) | ||
circuit.y(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) | ||
tkc: Circuit = qiskit_to_tk(circuit) | ||
tkc.name = "test_circ" | ||
|
||
expected_circ = Circuit() | ||
expected_circ.name = "test_circ" | ||
qreg_tk = expected_circ.add_q_register("q", 2) | ||
creg_tk = expected_circ.add_c_register("c", 2) | ||
expected_circ.H(qreg_tk[0]) | ||
expected_circ.H(qreg_tk[1]) | ||
expected_circ.Measure(qreg_tk[0], creg_tk[0]) | ||
expected_circ.Measure(qreg_tk[1], creg_tk[1]) | ||
|
||
x_circ2 = Circuit() | ||
x_circ2.name = "If" | ||
x_qreg = x_circ2.add_q_register("q", 2) | ||
x_circ2.X(x_qreg[0]).X(x_qreg[1]) | ||
expected_circ.add_circbox( | ||
CircBox(x_circ2), [qreg_tk[0], qreg_tk[1]], condition=reg_eq(creg_tk, 2) | ||
) | ||
|
||
y_circ2 = Circuit() | ||
y_circ2.name = "Else" | ||
y_qreg = y_circ2.add_q_register("q", 2) | ||
y_circ2.Y(y_qreg[0]).Y(y_qreg[1]) | ||
expected_circ.add_circbox( | ||
CircBox(y_circ2), [qreg_tk[0], qreg_tk[1]], condition=reg_neq(creg_tk, 2) | ||
) | ||
|
||
expected_circ.Measure(qreg_tk[0], creg_tk[0]) | ||
expected_circ.Measure(qreg_tk[1], creg_tk[1]) | ||
|
||
assert expected_circ == tkc | ||
|
||
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. Can we add one more test where there is no 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. good idea. A test for a single branch using more qubits/bits and a different circuit. |
||
|
||
def test_range_preds_with_conditionals() -> None: | ||
|
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.
This should be under a new "Unreleased" heading above.
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.
Oops, I mistakenly thought v0.64 was not out yet. Will fix.
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.
fixed 493b3a4