Skip to content

Commit e807fbf

Browse files
authored
feat: Improved support for IfElseOp in qiskit_to_tk (#463)
* refactor: use Circuit.has_implicit_wireswaps property * add a test case for register handling * initial rough handling of register conditions * delete duplicate test * delete comments * get classical register instead of creating a new one * rework logic of _build_if_else_circuit a bit * expand test case * add explanatory comment * update error handling * add assert to test * add missing measurements to test case * remove dodgy if statement for error handling * update changelog * fix changelog * add additonal test case for register conditions without an else branch * Improve single branch test
1 parent e7cc2cb commit e807fbf

File tree

3 files changed

+142
-47
lines changed

3 files changed

+142
-47
lines changed

docs/changelog.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@
44

55
# Changelog
66

7+
## 0.65.0 (UNRELEASED)
78

8-
## 0.64.0 (March 2025)
9+
- Improve {py:func}`qiskit_to_tk` to handle `IfElseOp` (generated by `QuantumCircuit.if_test`)
10+
with register conditions. Only conditions on an entire register are supported.
11+
12+
13+
## 0.64.0
914

1015
- Update pytket version requirement to 2.0.1.
1116

pytket/extensions/qiskit/qiskit_convert.py

+43-41
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
Unitary3qBox,
5959
UnitType,
6060
)
61+
from pytket.circuit.logic_exp import reg_eq, reg_neq
6162
from pytket.passes import AutoRebase
6263
from pytket.pauli import Pauli, QubitPauliString
6364
from pytket.unit_id import _TEMP_BIT_NAME
@@ -98,6 +99,7 @@
9899
from qiskit_ibm_runtime.models.backend_properties import Nduv
99100

100101
from pytket.circuit import UnitID
102+
from pytket.unit_id import BitRegister
101103
from qiskit.circuit.quantumcircuitdata import QuantumCircuitData # type: ignore
102104

103105
_qiskit_gates_1q = {
@@ -518,41 +520,49 @@ def _build_if_else_circuit(
518520
qubits: list[Qubit],
519521
bits: list[Bit],
520522
) -> Circuit:
521-
# Coniditions must be on a single bit (for now) TODO: support multiple bits.
522-
if len(bits) == 1:
523-
# Get two CircBox objects which implement the true_body and false_body.
524-
if_box, else_box = _pytket_boxes_from_ifelseop(if_else_op, qregs, cregs)
525-
# else_box can be None if no false_body is specified.
526-
circ_builder = CircuitBuilder(qregs, cregs)
527-
circ = circ_builder.circuit()
528-
else:
529-
raise NotImplementedError("Conditions over multiple bits not yet supported.")
530-
531-
# Coniditions must be on a single bit (for now)
532-
if not isinstance(if_else_op.condition[0], Clbit):
533-
raise NotImplementedError(
534-
"Handling of register conditions is not yet supported"
523+
# Get two CircBox objects which implement the true_body and false_body.
524+
if_box, else_box = _pytket_boxes_from_ifelseop(if_else_op, qregs, cregs)
525+
# else_box can be None if no false_body is specified.
526+
circ_builder = CircuitBuilder(qregs, cregs)
527+
circ = circ_builder.circuit()
528+
529+
if isinstance(if_else_op.condition[0], Clbit):
530+
if len(bits) != 1:
531+
raise NotImplementedError("Conditions on multiple bits not supported")
532+
circ.add_circbox(
533+
circbox=if_box,
534+
args=qubits,
535+
condition_bits=bits,
536+
condition_value=if_else_op.condition[1],
535537
)
536-
537-
circ.add_circbox(
538-
circbox=if_box,
539-
args=qubits,
540-
condition_bits=bits,
541-
condition_value=if_else_op.condition[1],
542-
)
543-
# If we have an else_box defined, add it to the circuit
544-
if else_box is not None:
545-
if if_else_op.condition[1] not in {0, 1}:
546-
raise ValueError(
547-
"A bit must have condition value 0 or 1"
548-
+ f", got {if_else_op.condition[1]}"
538+
# If we have an else_box defined, add it to the circuit
539+
if else_box is not None:
540+
circ.add_circbox(
541+
circbox=else_box,
542+
args=qubits,
543+
condition_bits=bits,
544+
condition_value=1 ^ if_else_op.condition[1],
549545
)
546+
547+
elif isinstance(if_else_op.condition[0], ClassicalRegister):
548+
pytket_bit_reg: BitRegister = circ.get_c_register(if_else_op.condition[0].name)
550549
circ.add_circbox(
551-
circbox=else_box,
550+
circbox=if_box,
552551
args=qubits,
553-
condition_bits=bits,
554-
condition_value=1 ^ if_else_op.condition[1],
552+
condition=reg_eq(pytket_bit_reg, if_else_op.condition[1]),
553+
)
554+
if else_box is not None:
555+
circ.add_circbox(
556+
circbox=else_box,
557+
args=qubits,
558+
condition=reg_neq(pytket_bit_reg, if_else_op.condition[1]),
559+
)
560+
else:
561+
raise TypeError(
562+
"Unrecognized type used to construct IfElseOp. Expected "
563+
+ f"ClBit or ClassicalRegister, got {type(if_else_op.condition[0])}"
555564
)
565+
556566
return circ
557567

558568

@@ -620,6 +630,8 @@ def add_qiskit_data(
620630
# Append OpType found by stateprep helpers
621631
_add_state_preparation(self.tkc, qubits, instr)
622632

633+
# Note: These IfElseOp/if_test type conditions are only handled
634+
# for single bit conditions and conditions on entire registers.
623635
elif type(instr) is IfElseOp:
624636
if_else_circ = _build_if_else_circuit(
625637
if_else_op=instr,
@@ -954,12 +966,6 @@ def append_tk_command_to_qiskit(
954966
supported_gate_rebase = AutoRebase(_protected_tket_gates)
955967

956968

957-
def _has_implicit_permutation(circ: Circuit) -> bool:
958-
"""Returns True if a Circuit has a non-trivial permutation
959-
of qubits, false otherwise."""
960-
return any(q0 != q1 for q0, q1 in circ.implicit_qubit_permutation().items())
961-
962-
963969
def tk_to_qiskit(
964970
tkcirc: Circuit,
965971
replace_implicit_swaps: bool = False,
@@ -987,11 +993,7 @@ def tk_to_qiskit(
987993
if replace_implicit_swaps:
988994
tkc.replace_implicit_wire_swaps()
989995

990-
if (
991-
_has_implicit_permutation(tkcirc)
992-
and perm_warning
993-
and not replace_implicit_swaps
994-
):
996+
if tkcirc.has_implicit_wireswaps and perm_warning and not replace_implicit_swaps:
995997
warnings.warn(
996998
"The pytket Circuit contains implicit qubit permutations"
997999
+ " which aren't handled by default."

tests/qiskit_convert_test.py

+93-5
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
Unitary2qBox,
6262
Unitary3qBox,
6363
reg_eq,
64+
reg_neq,
6465
)
6566
from pytket.extensions.qiskit import IBMQBackend, qiskit_to_tk, tk_to_qiskit
6667
from pytket.extensions.qiskit.backends import (
@@ -1274,7 +1275,61 @@ def test_ifelseop_one_branch() -> None:
12741275
assert tket_circ_if_else == expected_circ
12751276

12761277

1277-
def test_ifelseop_multi_bit_cond() -> None:
1278+
# https://github.com/CQCL/pytket-qiskit/issues/452
1279+
def test_ifelseop_reg_cond_if() -> None:
1280+
qreg = QuantumRegister(3, "q")
1281+
creg = ClassicalRegister(3, "c")
1282+
circuit = QuantumCircuit(creg, qreg)
1283+
(q0, q1, q2) = qreg
1284+
(c0, c1, c2) = creg
1285+
circuit.h(q0)
1286+
circuit.h(q1)
1287+
circuit.h(q2)
1288+
circuit.measure(q0, c0)
1289+
circuit.measure(q1, c1)
1290+
circuit.measure(q2, c2)
1291+
# Condition is on a register not a bit
1292+
with circuit.if_test((creg, 2)):
1293+
circuit.x(q0)
1294+
circuit.y(q1)
1295+
circuit.z(q2)
1296+
circuit.measure(q0, c0)
1297+
circuit.measure(q1, c1)
1298+
circuit.measure(q2, c2)
1299+
1300+
tkc: Circuit = qiskit_to_tk(circuit)
1301+
tkc.name = "test_circ"
1302+
1303+
expected_circ = Circuit()
1304+
expected_circ.name = "test_circ"
1305+
qreg_tk = expected_circ.add_q_register("q", 3)
1306+
creg_tk = expected_circ.add_c_register("c", 3)
1307+
expected_circ.H(qreg_tk[0])
1308+
expected_circ.H(qreg_tk[1])
1309+
expected_circ.H(qreg_tk[2])
1310+
expected_circ.Measure(qreg_tk[0], creg_tk[0])
1311+
expected_circ.Measure(qreg_tk[1], creg_tk[1])
1312+
expected_circ.Measure(qreg_tk[2], creg_tk[2])
1313+
1314+
pauli_circ = Circuit()
1315+
pauli_circ.name = "If"
1316+
pauli_qreg = pauli_circ.add_q_register("q", 3)
1317+
pauli_circ.X(pauli_qreg[0]).Y(pauli_qreg[1]).Z(pauli_qreg[2])
1318+
expected_circ.add_circbox(
1319+
CircBox(pauli_circ),
1320+
[qreg_tk[0], qreg_tk[1], qreg_tk[2]],
1321+
condition=reg_eq(creg_tk, 2),
1322+
)
1323+
1324+
expected_circ.Measure(qreg_tk[0], creg_tk[0])
1325+
expected_circ.Measure(qreg_tk[1], creg_tk[1])
1326+
expected_circ.Measure(qreg_tk[2], creg_tk[2])
1327+
1328+
assert expected_circ == tkc
1329+
1330+
1331+
# https://github.com/CQCL/pytket-qiskit/issues/452
1332+
def test_ifelseop_reg_cond_if_else() -> None:
12781333
qreg = QuantumRegister(2, "q")
12791334
creg = ClassicalRegister(2, "c")
12801335
circuit = QuantumCircuit(creg, qreg)
@@ -1285,14 +1340,47 @@ def test_ifelseop_multi_bit_cond() -> None:
12851340
circuit.h(q1)
12861341
circuit.measure(q0, c0)
12871342
circuit.measure(q1, c1)
1288-
with circuit.if_test((creg, 2)):
1343+
# Condition is on a register not a bit
1344+
with circuit.if_test((creg, 2)) as else_:
12891345
circuit.x(q0)
12901346
circuit.x(q1)
1347+
with else_:
1348+
circuit.y(q0)
1349+
circuit.y(q1)
12911350
circuit.measure(q0, c0)
12921351
circuit.measure(q1, c1)
1293-
# This currently gives an error as register exp not supported.
1294-
with pytest.raises(NotImplementedError):
1295-
qiskit_to_tk(circuit)
1352+
tkc: Circuit = qiskit_to_tk(circuit)
1353+
tkc.name = "test_circ"
1354+
1355+
expected_circ = Circuit()
1356+
expected_circ.name = "test_circ"
1357+
qreg_tk = expected_circ.add_q_register("q", 2)
1358+
creg_tk = expected_circ.add_c_register("c", 2)
1359+
expected_circ.H(qreg_tk[0])
1360+
expected_circ.H(qreg_tk[1])
1361+
expected_circ.Measure(qreg_tk[0], creg_tk[0])
1362+
expected_circ.Measure(qreg_tk[1], creg_tk[1])
1363+
1364+
x_circ2 = Circuit()
1365+
x_circ2.name = "If"
1366+
x_qreg = x_circ2.add_q_register("q", 2)
1367+
x_circ2.X(x_qreg[0]).X(x_qreg[1])
1368+
expected_circ.add_circbox(
1369+
CircBox(x_circ2), [qreg_tk[0], qreg_tk[1]], condition=reg_eq(creg_tk, 2)
1370+
)
1371+
1372+
y_circ2 = Circuit()
1373+
y_circ2.name = "Else"
1374+
y_qreg = y_circ2.add_q_register("q", 2)
1375+
y_circ2.Y(y_qreg[0]).Y(y_qreg[1])
1376+
expected_circ.add_circbox(
1377+
CircBox(y_circ2), [qreg_tk[0], qreg_tk[1]], condition=reg_neq(creg_tk, 2)
1378+
)
1379+
1380+
expected_circ.Measure(qreg_tk[0], creg_tk[0])
1381+
expected_circ.Measure(qreg_tk[1], creg_tk[1])
1382+
1383+
assert expected_circ == tkc
12961384

12971385

12981386
def test_range_preds_with_conditionals() -> None:

0 commit comments

Comments
 (0)