def _convert_one(self, op: ops.Operation) -> ops.OP_TREE: # Known matrix? mat = protocols.unitary(op, None) if len(op.qubits) <= 2 else None if mat is not None and len(op.qubits) == 1: gates = transformers.single_qubit_matrix_to_phased_x_z(mat) return [g.on(op.qubits[0]) for g in gates] if mat is not None and len(op.qubits) == 2: return transformers.two_qubit_matrix_to_cz_operations( op.qubits[0], op.qubits[1], mat, allow_partial_czs=False, clean_operations=True ) return NotImplemented
def decompose_to_target_gateset(self, op: 'cirq.Operation', moment_idx: int) -> DecomposeResult: """Method to rewrite the given operation using gates from this gateset. Args: op: `cirq.Operation` to be rewritten using gates from this gateset. moment_idx: Moment index where the given operation `op` occurs in a circuit. Returns: - An equivalent `cirq.OP_TREE` implementing `op` using gates from this gateset. - `None` or `NotImplemented` if does not know how to decompose `op`. """ # Known matrix? mat = protocols.unitary(op, None) if len(op.qubits) <= 2 else None if mat is not None and len(op.qubits) == 1: gates = transformers.single_qubit_matrix_to_phased_x_z(mat) return [g.on(op.qubits[0]) for g in gates] if mat is not None and len(op.qubits) == 2: return transformers.two_qubit_matrix_to_cz_operations( op.qubits[0], op.qubits[1], mat, allow_partial_czs=False, clean_operations=True ) return NotImplemented
def _two_qubit_multiplexor_to_ops( q0: ops.Qid, q1: ops.Qid, q2: ops.Qid, u1: np.ndarray, u2: np.ndarray, shift_left: bool = True, diagonal: Optional[np.ndarray] = None, atol: float = 1e-8, ) -> Tuple[Optional[np.ndarray], List[ops.Operation]]: r"""Converts a two qubit double multiplexor to circuit. Input: U_1 ⊕ U_2, with select qubit a (i.e. a = |0> => U_1(b,c), a = |1> => U_2(b,c). We want this: $$ U_1 ⊕ U_2 = (V ⊕ V) @ (D ⊕ D^{\dagger}) @ (W ⊕ W) $$ We can get it via: $$ U_1 = V @ D @ W (1) U_2 = V @ D^{\dagger} @ W (2) $$ We can derive $$ U_1 U_2^{\dagger}= V @ D^2 @ V^{\dagger}, (3) $$ i.e the eigendecomposition of $U_1 U_2^{\dagger}$ will give us D and V. W is easy to derive from (2). This function, after calculating V, D and W, also returns the circuit that implements these unitaries: V, W on qubits b, c and the middle diagonal multiplexer on a,b,c qubits. The resulting circuit will have only known two-qubit and one-qubit gates, namely CZ, CNOT and rx, ry, PhasedXPow gates. Args: q0: first qubit q1: second qubit q2: third qubit u1: two-qubit operation on b,c for a = |0> u2: two-qubit operation on b,c for a = |1> shift_left: return the extracted diagonal or not diagonal: an incoming diagonal to be merged with atol: the absolute tolerance for the two-qubit sub-decompositions. Returns: The circuit implementing the two qubit multiplexor consisting only of known two-qubit and single qubit gates """ u1u2 = u1 @ u2.conj().T eigvals, v = cirq.unitary_eig(u1u2) d = np.diag(np.sqrt(eigvals)) w = d @ v.conj().T @ u2 circuit_u1u2_mid = _middle_multiplexor_to_ops(q0, q1, q2, eigvals) if diagonal is not None: v = diagonal @ v d_v, circuit_u1u2_r = opt.two_qubit_matrix_to_diagonal_and_cz_operations( q1, q2, v, atol=atol) w = d_v @ w d_w: Optional[np.ndarray] # if it's interesting to extract the diagonal then let's do it if shift_left: d_w, circuit_u1u2_l = opt.two_qubit_matrix_to_diagonal_and_cz_operations( q1, q2, w, atol=atol) # if we are at the end of the circuit, then just fall back to KAK else: d_w = None circuit_u1u2_l = opt.two_qubit_matrix_to_cz_operations( q1, q2, w, allow_partial_czs=False, atol=atol) return d_w, circuit_u1u2_l + circuit_u1u2_mid + circuit_u1u2_r