Skip to content
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

Merged
merged 50 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
c424b24
initial attempt at CircBox handling
CalMacCQ Dec 17, 2024
3b9189a
add temporary if-else test
CalMacCQ Dec 17, 2024
692506f
try adding data for conditional subcircuits
CalMacCQ Dec 17, 2024
9be4d45
add a proper test
CalMacCQ Dec 17, 2024
80dddbe
cleanup
CalMacCQ Dec 17, 2024
e38ca3e
add another TODO
CalMacCQ Dec 17, 2024
3843234
formatting
CalMacCQ Dec 17, 2024
69f9e0a
Merge branch 'main' into support_if-else
CalMacCQ Dec 17, 2024
09a0ca2
try if else with register from qiskit circuit
cqc-melf Dec 17, 2024
89ac299
Merge branch 'main' into support_if-else
CalMacCQ Dec 17, 2024
96d9efd
minor cleanup
CalMacCQ Dec 17, 2024
6518628
more progress on circuit building (still gives RuntimeError)
CalMacCQ Dec 18, 2024
f87d814
try to fix kwargs
CalMacCQ Dec 18, 2024
816e2bd
update test
CalMacCQ Dec 18, 2024
2ff1b6f
remove import
CalMacCQ Dec 18, 2024
ea62c0b
push latest attempt
CalMacCQ Dec 18, 2024
73dc681
naming fixes
CalMacCQ Dec 31, 2024
d65a347
a little more progress
CalMacCQ Dec 31, 2024
3281184
Merge branch 'main' into support_if-else
CalMacCQ Dec 31, 2024
52458ab
refactor: handle IfElseOp separately
CalMacCQ Dec 31, 2024
8e1edce
make circuit builder function private
CalMacCQ Dec 31, 2024
4b382b0
add a test case for a single branch
CalMacCQ Jan 2, 2025
4729114
handle the single branch case
CalMacCQ Jan 2, 2025
f69aa04
improve validation for single branch case
CalMacCQ Jan 2, 2025
8573082
fix import
CalMacCQ Jan 2, 2025
4d311f3
add some comments
CalMacCQ Jan 2, 2025
eec8fe5
add comments to test, change variable name
CalMacCQ Jan 2, 2025
9d66595
attempt to debug C.I. test case issue
CalMacCQ Jan 2, 2025
4b4b40a
remove debug prints
CalMacCQ Jan 2, 2025
7950e77
correct some comments
CalMacCQ Jan 2, 2025
25bfcd5
update changelog
CalMacCQ Jan 2, 2025
ae6cbfe
Better varible names for testing
CalMacCQ Jan 2, 2025
e479c3e
link to issue
CalMacCQ Jan 2, 2025
4c6453a
Merge branch 'main' into support_if-else
CalMacCQ Jan 15, 2025
5fab61f
Merge branch 'main' into support_if-else
CalMacCQ Jan 16, 2025
83b50e7
extend test
CalMacCQ Jan 16, 2025
84e7fc1
expand test (annoying failure)
CalMacCQ Jan 16, 2025
dd4a694
fix test inconsistency
CalMacCQ Jan 16, 2025
ba3671d
add a gate counting test instead (passing)
CalMacCQ Jan 16, 2025
aadfe5e
raise error for register conditions
CalMacCQ Jan 16, 2025
72554e8
add a test
CalMacCQ Jan 16, 2025
cebf4dd
clean up If-Else circuit building, error handling
CalMacCQ Jan 22, 2025
16c3718
fix a pylint issue
CalMacCQ Jan 22, 2025
cf2f717
Merge branch 'main' into support_if-else
CalMacCQ Jan 22, 2025
ce035c0
minor fixes
CalMacCQ Jan 22, 2025
1cbf960
fix mypy issue
CalMacCQ Jan 22, 2025
dd5ce89
fix a typo
CalMacCQ Jan 22, 2025
a40732d
add a validation test for two branch circuit
CalMacCQ Jan 22, 2025
7bc58d5
fix import
CalMacCQ Jan 22, 2025
90c68e7
explain limitations in changelog
CalMacCQ Jan 22, 2025
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
3 changes: 2 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

# Changelog

## Unreleased
## 0.63.0 (January 2025) - UNRELEASED

- Support conversion of qiskit circuits containing `IfElseOp` in the {py:func}`qiskit_to_tk` converter. Note: Only conditions on a single bit are supported currently. Handling of more general conditions will be added in future.
- Reject circuits containing nested conditionals when converting to qiskit.
- Update pytket version requirement to 1.38.0.
- Add support for fractional gates on backends that support them.
Expand Down
95 changes: 91 additions & 4 deletions pytket/extensions/qiskit/qiskit_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
Clbit,
ControlledGate,
Gate,
IfElseOp,
Instruction,
InstructionSet,
Measure,
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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]]:
Copy link
Contributor Author

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 the IfElseOp only has a true_body rather than a true_body and a false_body.

There's probably a cleaner way to deal with this.

# 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):
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check that len(bits) == 1, or perhaps change the signature of this function to take a single Bit?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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,
Expand Down Expand Up @@ -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:
Expand All @@ -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))
Expand Down
107 changes: 107 additions & 0 deletions tests/qiskit_convert_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A similar test including an else condition would be good to add.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)
Expand Down
Loading