Skip to content

Commit f790f9e

Browse files
authored
Add conversion of controlled unitary gates from Qiskit to TKET. (#372)
* 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.
1 parent 480227d commit f790f9e

File tree

3 files changed

+62
-35
lines changed

3 files changed

+62
-35
lines changed

docs/changelog.rst

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Unreleased
66

77
* Fix conversion of symbols into qiskit.
88
* Require qiskit >= 1.2.0.
9+
* Add conversion of controlled unitary gates from qiskit to tket.
910

1011
0.55.0 (July 2024)
1112
------------------

pytket/extensions/qiskit/qiskit_convert.py

+50-35
Original file line numberDiff line numberDiff line change
@@ -388,12 +388,15 @@ def add_qiskit_data(
388388
elif instr.base_gate.base_class is qiskit_gates.ZGate:
389389
optype = OpType.CnZ
390390
else:
391-
if instr.base_gate.base_class in _known_qiskit_gate:
391+
if (
392+
instr.base_gate.base_class in _known_qiskit_gate
393+
or instr.base_gate.base_class == UnitaryGate
394+
):
392395
optype = OpType.QControlBox # QControlBox case handled below
393396
else:
394397
raise NotImplementedError(
395398
f"qiskit ControlledGate with base gate {instr.base_gate}"
396-
+ "not implemented"
399+
+ "not implemented."
397400
)
398401
elif type(instr) in [PauliEvolutionGate, UnitaryGate]:
399402
pass # Special handling below
@@ -411,17 +414,24 @@ def add_qiskit_data(
411414
bits = [self.cbmap[bit] for bit in cargs]
412415

413416
if optype == OpType.QControlBox:
414-
base_tket_gate = _known_qiskit_gate[instr.base_gate.base_class]
415417
params = [param_to_tk(p) for p in instr.base_gate.params]
416418
n_base_qubits = instr.base_gate.num_qubits
417419
sub_circ = Circuit(n_base_qubits)
418420
# use base gate name for the CircBox (shows in renderer)
419421
sub_circ.name = instr.base_gate.name.capitalize()
420-
sub_circ.add_gate(base_tket_gate, params, list(range(n_base_qubits)))
422+
if type(instr.base_gate) == UnitaryGate:
423+
assert len(cargs) == 0
424+
add_qiskit_unitary_to_tkc(
425+
sub_circ, instr.base_gate, sub_circ.qubits, condition_kwargs
426+
)
427+
else:
428+
base_tket_gate = _known_qiskit_gate[instr.base_gate.base_class]
429+
sub_circ.add_gate(
430+
base_tket_gate, params, list(range(n_base_qubits))
431+
)
421432
c_box = CircBox(sub_circ)
422433
q_ctrl_box = QControlBox(c_box, instr.num_ctrl_qubits)
423434
self.tkc.add_qcontrolbox(q_ctrl_box, qubits)
424-
425435
elif isinstance(instr, (Initialize, StatePreparation)):
426436
# Check how Initialize or StatePrep is constructed
427437
if isinstance(instr.params[0], str):
@@ -462,37 +472,8 @@ def add_qiskit_data(
462472
ccbox = CircBox(circ)
463473
self.tkc.add_circbox(ccbox, qubits)
464474
elif type(instr) == UnitaryGate:
465-
# Note reversal of qubits, to account for endianness (pytket unitaries
466-
# are ILO-BE == DLO-LE; qiskit unitaries are ILO-LE == DLO-BE).
467-
params = instr.params
468-
assert len(params) == 1
469-
u = cast(np.ndarray, params[0])
470475
assert len(cargs) == 0
471-
n = len(qubits)
472-
if n == 0:
473-
assert u.shape == (1, 1)
474-
self.tkc.add_phase(np.angle(u[0][0]) / np.pi)
475-
elif n == 1:
476-
assert u.shape == (2, 2)
477-
u1box = Unitary1qBox(u)
478-
self.tkc.add_unitary1qbox(u1box, qubits[0], **condition_kwargs)
479-
elif n == 2:
480-
assert u.shape == (4, 4)
481-
u2box = Unitary2qBox(u)
482-
self.tkc.add_unitary2qbox(
483-
u2box, qubits[1], qubits[0], **condition_kwargs
484-
)
485-
elif n == 3:
486-
assert u.shape == (8, 8)
487-
u3box = Unitary3qBox(u)
488-
self.tkc.add_unitary3qbox(
489-
u3box, qubits[2], qubits[1], qubits[0], **condition_kwargs
490-
)
491-
else:
492-
raise NotImplementedError(
493-
f"Conversion of {n}-qubit unitary gates not supported"
494-
)
495-
476+
add_qiskit_unitary_to_tkc(self.tkc, instr, qubits, condition_kwargs)
496477
elif optype == OpType.Barrier:
497478
self.tkc.add_barrier(qubits)
498479
elif optype == OpType.CircBox:
@@ -529,6 +510,40 @@ def add_qiskit_data(
529510
self.add_xs(num_ctrl_qubits, ctrl_state, qargs)
530511

531512

513+
def add_qiskit_unitary_to_tkc(
514+
tkc: Circuit,
515+
u_gate: UnitaryGate,
516+
qubits: List[Qubit],
517+
condition_kwargs: Dict[str, Any],
518+
) -> None:
519+
# Note reversal of qubits, to account for endianness (pytket unitaries
520+
# are ILO-BE == DLO-LE; qiskit unitaries are ILO-LE == DLO-BE).
521+
params = u_gate.params
522+
assert len(params) == 1
523+
u = cast(np.ndarray, params[0])
524+
525+
n = len(qubits)
526+
if n == 0:
527+
assert u.shape == (1, 1)
528+
tkc.add_phase(np.angle(u[0][0]) / np.pi)
529+
elif n == 1:
530+
assert u.shape == (2, 2)
531+
u1box = Unitary1qBox(u)
532+
tkc.add_unitary1qbox(u1box, qubits[0], **condition_kwargs)
533+
elif n == 2:
534+
assert u.shape == (4, 4)
535+
u2box = Unitary2qBox(u)
536+
tkc.add_unitary2qbox(u2box, qubits[1], qubits[0], **condition_kwargs)
537+
elif n == 3:
538+
assert u.shape == (8, 8)
539+
u3box = Unitary3qBox(u)
540+
tkc.add_unitary3qbox(u3box, qubits[2], qubits[1], qubits[0], **condition_kwargs)
541+
else:
542+
raise NotImplementedError(
543+
f"Conversion of {n}-qubit unitary gates not supported."
544+
)
545+
546+
532547
def qiskit_to_tk(qcirc: QuantumCircuit, preserve_param_uuid: bool = False) -> Circuit:
533548
"""
534549
Converts a qiskit :py:class:`qiskit.QuantumCircuit` to a pytket :py:class:`Circuit`.

tests/qiskit_convert_test.py

+11
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,17 @@ def test_qcontrolbox_conversion() -> None:
855855
assert tkc2.n_gates_of_type(OpType.QControlBox) == 3
856856

857857

858+
def test_controlled_unitary_conversion() -> None:
859+
u = np.asarray([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])
860+
qc = QuantumCircuit(4)
861+
cu_gate = UnitaryGate(u).control(num_ctrl_qubits=2, ctrl_state="01")
862+
qc.append(cu_gate, [0, 1, 2, 3])
863+
u_qc = permute_rows_cols_in_unitary(Operator(qc).data, (3, 2, 1, 0))
864+
tkc = qiskit_to_tk(qc)
865+
u_tkc = tkc.get_unitary()
866+
assert np.allclose(u_qc, u_tkc)
867+
868+
858869
# Ensures that the tk_to_qiskit converter does not cancel redundant gates
859870
def test_tk_to_qiskit_redundancies() -> None:
860871
h_circ = Circuit(1).H(0).H(0)

0 commit comments

Comments
 (0)