Skip to content

Commit

Permalink
Add conversion of controlled unitary gates from Qiskit to TKET. (#372)
Browse files Browse the repository at this point in the history
* Add conversion of controlled unitary gates from Qiskit to TKET.

* Add test of conversion of controlled unitary gates from Qiskit to TKET.

* Fixed formatting.

* Fixed type hinting.

* Add conversion of controlled unitary gates from qiskit to tket.
  • Loading branch information
gkpotter authored Aug 20, 2024
1 parent 480227d commit f790f9e
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 35 deletions.
1 change: 1 addition & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Unreleased

* Fix conversion of symbols into qiskit.
* Require qiskit >= 1.2.0.
* Add conversion of controlled unitary gates from qiskit to tket.

0.55.0 (July 2024)
------------------
Expand Down
85 changes: 50 additions & 35 deletions pytket/extensions/qiskit/qiskit_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,12 +388,15 @@ def add_qiskit_data(
elif instr.base_gate.base_class is qiskit_gates.ZGate:
optype = OpType.CnZ
else:
if instr.base_gate.base_class in _known_qiskit_gate:
if (
instr.base_gate.base_class in _known_qiskit_gate
or instr.base_gate.base_class == UnitaryGate
):
optype = OpType.QControlBox # QControlBox case handled below
else:
raise NotImplementedError(
f"qiskit ControlledGate with base gate {instr.base_gate}"
+ "not implemented"
+ "not implemented."
)
elif type(instr) in [PauliEvolutionGate, UnitaryGate]:
pass # Special handling below
Expand All @@ -411,17 +414,24 @@ def add_qiskit_data(
bits = [self.cbmap[bit] for bit in cargs]

if optype == OpType.QControlBox:
base_tket_gate = _known_qiskit_gate[instr.base_gate.base_class]
params = [param_to_tk(p) for p in instr.base_gate.params]
n_base_qubits = instr.base_gate.num_qubits
sub_circ = Circuit(n_base_qubits)
# use base gate name for the CircBox (shows in renderer)
sub_circ.name = instr.base_gate.name.capitalize()
sub_circ.add_gate(base_tket_gate, params, list(range(n_base_qubits)))
if type(instr.base_gate) == UnitaryGate:
assert len(cargs) == 0
add_qiskit_unitary_to_tkc(
sub_circ, instr.base_gate, sub_circ.qubits, condition_kwargs
)
else:
base_tket_gate = _known_qiskit_gate[instr.base_gate.base_class]
sub_circ.add_gate(
base_tket_gate, params, list(range(n_base_qubits))
)
c_box = CircBox(sub_circ)
q_ctrl_box = QControlBox(c_box, instr.num_ctrl_qubits)
self.tkc.add_qcontrolbox(q_ctrl_box, qubits)

elif isinstance(instr, (Initialize, StatePreparation)):
# Check how Initialize or StatePrep is constructed
if isinstance(instr.params[0], str):
Expand Down Expand Up @@ -462,37 +472,8 @@ def add_qiskit_data(
ccbox = CircBox(circ)
self.tkc.add_circbox(ccbox, qubits)
elif type(instr) == UnitaryGate:
# Note reversal of qubits, to account for endianness (pytket unitaries
# are ILO-BE == DLO-LE; qiskit unitaries are ILO-LE == DLO-BE).
params = instr.params
assert len(params) == 1
u = cast(np.ndarray, params[0])
assert len(cargs) == 0
n = len(qubits)
if n == 0:
assert u.shape == (1, 1)
self.tkc.add_phase(np.angle(u[0][0]) / np.pi)
elif n == 1:
assert u.shape == (2, 2)
u1box = Unitary1qBox(u)
self.tkc.add_unitary1qbox(u1box, qubits[0], **condition_kwargs)
elif n == 2:
assert u.shape == (4, 4)
u2box = Unitary2qBox(u)
self.tkc.add_unitary2qbox(
u2box, qubits[1], qubits[0], **condition_kwargs
)
elif n == 3:
assert u.shape == (8, 8)
u3box = Unitary3qBox(u)
self.tkc.add_unitary3qbox(
u3box, qubits[2], qubits[1], qubits[0], **condition_kwargs
)
else:
raise NotImplementedError(
f"Conversion of {n}-qubit unitary gates not supported"
)

add_qiskit_unitary_to_tkc(self.tkc, instr, qubits, condition_kwargs)
elif optype == OpType.Barrier:
self.tkc.add_barrier(qubits)
elif optype == OpType.CircBox:
Expand Down Expand Up @@ -529,6 +510,40 @@ def add_qiskit_data(
self.add_xs(num_ctrl_qubits, ctrl_state, qargs)


def add_qiskit_unitary_to_tkc(
tkc: Circuit,
u_gate: UnitaryGate,
qubits: List[Qubit],
condition_kwargs: Dict[str, Any],
) -> None:
# Note reversal of qubits, to account for endianness (pytket unitaries
# are ILO-BE == DLO-LE; qiskit unitaries are ILO-LE == DLO-BE).
params = u_gate.params
assert len(params) == 1
u = cast(np.ndarray, params[0])

n = len(qubits)
if n == 0:
assert u.shape == (1, 1)
tkc.add_phase(np.angle(u[0][0]) / np.pi)
elif n == 1:
assert u.shape == (2, 2)
u1box = Unitary1qBox(u)
tkc.add_unitary1qbox(u1box, qubits[0], **condition_kwargs)
elif n == 2:
assert u.shape == (4, 4)
u2box = Unitary2qBox(u)
tkc.add_unitary2qbox(u2box, qubits[1], qubits[0], **condition_kwargs)
elif n == 3:
assert u.shape == (8, 8)
u3box = Unitary3qBox(u)
tkc.add_unitary3qbox(u3box, qubits[2], qubits[1], qubits[0], **condition_kwargs)
else:
raise NotImplementedError(
f"Conversion of {n}-qubit unitary gates not supported."
)


def qiskit_to_tk(qcirc: QuantumCircuit, preserve_param_uuid: bool = False) -> Circuit:
"""
Converts a qiskit :py:class:`qiskit.QuantumCircuit` to a pytket :py:class:`Circuit`.
Expand Down
11 changes: 11 additions & 0 deletions tests/qiskit_convert_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,17 @@ def test_qcontrolbox_conversion() -> None:
assert tkc2.n_gates_of_type(OpType.QControlBox) == 3


def test_controlled_unitary_conversion() -> None:
u = np.asarray([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])
qc = QuantumCircuit(4)
cu_gate = UnitaryGate(u).control(num_ctrl_qubits=2, ctrl_state="01")
qc.append(cu_gate, [0, 1, 2, 3])
u_qc = permute_rows_cols_in_unitary(Operator(qc).data, (3, 2, 1, 0))
tkc = qiskit_to_tk(qc)
u_tkc = tkc.get_unitary()
assert np.allclose(u_qc, u_tkc)


# Ensures that the tk_to_qiskit converter does not cancel redundant gates
def test_tk_to_qiskit_redundancies() -> None:
h_circ = Circuit(1).H(0).H(0)
Expand Down

0 comments on commit f790f9e

Please sign in to comment.