diff --git a/docs/changelog.rst b/docs/changelog.rst index e3b57ccf..043065bd 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -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) ------------------ diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index 116997aa..5df37010 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -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 @@ -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): @@ -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: @@ -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`. diff --git a/tests/qiskit_convert_test.py b/tests/qiskit_convert_test.py index 5d21ecc3..0cb50a30 100644 --- a/tests/qiskit_convert_test.py +++ b/tests/qiskit_convert_test.py @@ -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)