Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
133 changes: 124 additions & 9 deletions pytket/extensions/qiskit/qiskit_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
Unitary3qBox,
UnitType,
)
from pytket.circuit.logic_exp import reg_eq, reg_neq
from pytket.circuit.logic_exp import BitLogicExp, reg_eq, reg_neq
from pytket.passes import AutoRebase
from pytket.pauli import Pauli, QubitPauliString
from pytket.unit_id import _TEMP_BIT_NAME
Expand Down Expand Up @@ -83,6 +83,7 @@
Reset,
)
from qiskit.circuit import Qubit as QCQubit
from qiskit.circuit.classical.expr import Binary, Unary, Var # type: ignore
from qiskit.circuit.library import (
CRYGate,
Initialize,
Expand Down Expand Up @@ -600,6 +601,58 @@ def _build_rename_map(
return rename_map


# Utility method to flatten conditions
def _flatten_condition(
_condition: Var | Unary | Binary, bits: list[Bit]
) -> BitLogicExp | Bit:
if isinstance(_condition, Var):
# Find the pytket Bit with the same register name and index as the Qiskit Clbit
for bit in bits:
if (
bit.reg_name == _condition.var._register.name # noqa: SLF001
and bit.index[0] == _condition.var._index # noqa: SLF001
):
return bit

raise ValueError(
"Failed to find any pytket Bit matching Qiskit Clbit in condition for IfElseOp."
)
if isinstance(_condition, Unary):
# Set the comparison value based on whether the Unary involves a NOT operation
val = 0 if _condition.op.name == "BIT_NOT" else 1

# Find the pytket Bit with the same register name and index as the Qiskit Clbit
for bit in bits:
if (
bit.reg_name == _condition.operand.var._register.name # noqa: SLF001
and bit.index[0] == _condition.operand.var._index # noqa: SLF001
):
return bit ^ val

raise ValueError(
"Failed to find any pytket Bit matching Qiskit Clbit in condition for IfElseOp."
)
if isinstance(_condition, Binary):
# Recursively handle both operands of the binary operation
if _condition.op.name == "BIT_AND":
return _flatten_condition(_condition.left, bits) & _flatten_condition(
_condition.right, bits
)
if _condition.op.name == "BIT_OR":
return _flatten_condition(_condition.left, bits) | _flatten_condition(
_condition.right, bits
)
if _condition.op.name == "BIT_XOR":
return _flatten_condition(_condition.left, bits) ^ _flatten_condition(
_condition.right, bits
)

raise NotImplementedError(
f"Binary condition with operation '{_condition.op.name}' not supported"
)
raise NotImplementedError(f"Condition of type {type(_condition)} not supported")


# 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
Expand Down Expand Up @@ -679,29 +732,91 @@ def _append_if_else_circuit(
if_circ, else_circ = _pytket_circuits_from_ifelseop(
if_else_op, outer_builder, qargs, cargs
)

# else_circ can be None if no false_body is specified.
if isinstance(if_else_op.condition[0], Clbit):
if len(bits) != 1:
raise NotImplementedError("Conditions on multiple bits not supported")
if isinstance(if_else_op.condition, (Var | Unary | Binary)):
# In this case, if_else_op.condition is a Binary operation which we must flatten
condition_flattened = _flatten_condition(if_else_op.condition, bits)

outer_builder.tkc.add_circbox(
circbox=CircBox(if_circ),
args=if_circ.qubits + if_circ.bits, # type: ignore
condition=condition_flattened,
)
# If we have an else_circ defined, add it to the circuit
if else_circ is not None:
outer_builder.tkc.add_circbox(
circbox=CircBox(else_circ),
args=else_circ.qubits + else_circ.bits, # type: ignore
condition=1 ^ condition_flattened,
)
elif isinstance(if_else_op.condition, Var) and isinstance(
if_else_op.condition.var, Clbit
):
condition_bits = [
bit
for bit in bits
if bit.reg_name == if_else_op.condition.var._register.name # noqa: SLF001
and bit.index[0] == if_else_op.condition.var._index # noqa: SLF001
]

if len(condition_bits) == 0:
raise ValueError(
"Failed to find any pytket Bit matching Qiskit Clbit in condition for IfElseOp."
)

# In this case, if_else_op.condition is a single tuple of shape (Clbit, value)
outer_builder.tkc.add_circbox(
circbox=CircBox(if_circ),
args=if_circ.qubits + if_circ.bits, # type: ignore
condition_bits=condition_bits,
condition_value=1,
)
# If we have an else_circ defined, add it to the circuit
if else_circ is not None:
outer_builder.tkc.add_circbox(
circbox=CircBox(else_circ),
args=else_circ.qubits + else_circ.bits, # type: ignore
condition_bits=condition_bits,
condition_value=0,
)
elif hasattr(if_else_op.condition, "__getitem__") and isinstance(
if_else_op.condition[0], Clbit
):
condition_bits = [
bit
for bit in bits
if bit.reg_name == if_else_op.condition[0]._register.name # noqa: SLF001
and bit.index[0] == if_else_op.condition[0]._index # noqa: SLF001
]

if len(condition_bits) == 0:
raise ValueError(
"Failed to find any pytket Bit matching Qiskit Clbit in condition for IfElseOp."
)

# In this case, if_else_op.condition is a single tuple of shape (Clbit, value)
outer_builder.tkc.add_circbox(
circbox=CircBox(if_circ),
args=if_circ.qubits + if_circ.bits, # type: ignore
condition_bits=bits,
condition_bits=condition_bits,
condition_value=if_else_op.condition[1],
)
# If we have an else_circ defined, add it to the circuit
if else_circ is not None:
outer_builder.tkc.add_circbox(
circbox=CircBox(else_circ),
args=else_circ.qubits + else_circ.bits, # type: ignore
condition_bits=bits,
condition_bits=condition_bits,
condition_value=1 ^ if_else_op.condition[1],
)

elif isinstance(if_else_op.condition[0], ClassicalRegister):
elif hasattr(if_else_op.condition, "__getitem__") and isinstance(
if_else_op.condition[0], ClassicalRegister
):
pytket_bit_reg: BitRegister = outer_builder.tkc.get_c_register(
if_else_op.condition[0].name
)

outer_builder.tkc.add_circbox(
circbox=CircBox(if_circ),
args=if_circ.qubits + if_circ.bits, # type: ignore
Expand All @@ -716,7 +831,7 @@ def _append_if_else_circuit(
else:
raise TypeError(
"Unrecognized type used to construct IfElseOp. Expected " # noqa: ISC003
+ f"ClBit or ClassicalRegister, got {type(if_else_op.condition[0])}"
+ f"Var, Unary, Binary, or ClBit or ClassicalRegister tuple, got {type(if_else_op.condition)}"
)


Expand Down
24 changes: 23 additions & 1 deletion tests/qiskit_convert_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
RebaseTket,
SequencePass,
)
from pytket.unit_id import _TEMP_BIT_NAME
from pytket.unit_id import _TEMP_BIT_NAME, BitRegister
from pytket.utils.results import (
compare_statevectors,
compare_unitaries,
Expand Down Expand Up @@ -1495,6 +1495,28 @@ def test_qiskitv2_conversions() -> None:
assert if_tk1.condition == (ClassicalRegister(2, "c"), 2)


def test_bit_ref_circuit() -> None:
qreg = QuantumRegister(1)
qreg_setter = QuantumRegister(2)
creg_A = ClassicalRegister(1)
creg_B = ClassicalRegister(1)

qc = QuantumCircuit(qreg, qreg_setter, creg_A, creg_B)
qc.x(qreg_setter[1])

with qc.if_test((creg_A[0], 0)) as _else:
qc.measure(qreg_setter[1], creg_B[0])
with _else:
qc.measure(qreg_setter[0], creg_B[0])
tkc = qiskit_to_tk(qc)
cregs = tkc.c_registers
assert len(cregs) == 2
a_creg: BitRegister = cregs[0]
b_creg: BitRegister = cregs[1]
assert a_creg.size == 1
assert b_creg.size == 1


def test_round_trip_with_qiskit_transpilation() -> None:
circ = Circuit(4, 1)
circ.H(0).Measure(0, 0)
Expand Down