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 37 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.
- Reject circuits containing nested conditionals when converting to qiskit.
- Update pytket version requirement to 1.38.0.

Expand Down
81 changes: 78 additions & 3 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 @@ -480,6 +481,70 @@ 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:
# 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()
CalMacCQ marked this conversation as resolved.
Show resolved Hide resolved

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:
circ.add_circbox(
circbox=else_box,
args=qubits,
condition_bits=bits,
# TODO: handle conditions over multiple bits/registers?
condition_value=not bool(if_else_op.condition[1]),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Still need to decide if its worth handling more complex conditionals here or just restrict to conditions over a single bit for now.

Copy link
Contributor Author

@CalMacCQ CalMacCQ Jan 16, 2025

Choose a reason for hiding this comment

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

I've now decided to give a NotImplementedError for when the condition isn't on a single bit see (aadfe5e). For the more general case we would use "req equals", "reg not equals"

Copy link
Collaborator

Choose a reason for hiding this comment

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

Is if_else_op.condition[1] an int? Can we check that it is 0 or 1?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a check in cebf4dd

)
return circ


class CircuitBuilder:
def __init__(
self,
Expand Down Expand Up @@ -523,16 +588,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 +609,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
74 changes: 74 additions & 0 deletions tests/qiskit_convert_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1193,6 +1193,80 @@ 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(2, "r")
creg = ClassicalRegister(2, "s")
circuit = QuantumCircuit(qreg, creg)
(q0, q1) = qreg
(c0, c1) = creg

circuit.h(q0)
circuit.measure(q0, c0)

with circuit.if_test((c0, 1)) as else_:
circuit.h(q1)
with else_:
circuit.x(q1)
circuit.measure(q1, c1)

tkc = qiskit_to_tk(circuit)
assert tkc.n_gates_of_type(OpType.Conditional) == 2
CalMacCQ marked this conversation as resolved.
Show resolved Hide resolved
if_cond, else_cond = tuple(tkc.commands_of_type(OpType.Conditional))

if_circ = if_cond.op.op.get_circuit()
Copy link
Collaborator

Choose a reason for hiding this comment

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

The mypy check is failing. Probably need to assert that op is a Conditional.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, this was the issue. It doesn't matter anymore as I've redone the test.

else_circ = else_cond.op.op.get_circuit()

exp_if_circ = Circuit()
r = exp_if_circ.add_q_register("r", 1)
exp_if_circ.H(r[0])

exp_else_circ = Circuit()
s = exp_else_circ.add_q_register("s", 1)
exp_else_circ.H(s[0])

assert if_circ == exp_if_circ
CalMacCQ marked this conversation as resolved.
Show resolved Hide resolved
assert else_circ == exp_else_circ


# 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_range_preds_with_conditionals() -> None:
# https://github.com/CQCL/pytket-qiskit/issues/375
c = Circuit(1, 1)
Expand Down
Loading