def decompose_arbitrary_into_syc_analytic(qubit_a: ops.Qid, qubit_b: ops.Qid, op: ops.GateOperation): """Synthesize an arbitrary 2 qubit operation to a sycamore operation using the given Tabulation. Args: qubit_a: first qubit of the operation qubit_b: second qubit of the operation op: operation to decompose tabulation: A tabulation for the Sycamore gate. Returns: New operations iterable object """ new_ops = optimizers.two_qubit_matrix_to_operations(op.qubits[0], op.qubits[1], op, allow_partial_czs=True) gate_ops = [] for new_op in new_ops: num_qubits = len(new_op.qubits) if num_qubits == 1: gate_ops.extend([ term.on(new_op.qubits[0]) for term in optimizers.single_qubit_matrix_to_gates( protocols.unitary(new_op)) ]) elif num_qubits == 2: gate_ops.extend( ops.flatten_to_ops( known_two_q_operations_to_sycamore_operations( new_op.qubits[0], new_op.qubits[1], cast(ops.GateOperation, new_op)))) return gate_ops
def decompose_arbitrary_into_syc_analytic(qubit_a: ops.Qid, qubit_b: ops.Qid, op: ops.Operation) -> ops.OP_TREE: """Synthesize an arbitrary 2 qubit operation to a sycamore operation using the given Tabulation. Args: qubit_a: first qubit of the operation qubit_b: second qubit of the operation op: operation to decompose tabulation: A tabulation for the Sycamore gate. Returns: New operations iterable object """ new_ops = optimizers.two_qubit_matrix_to_operations(qubit_a, qubit_b, op, allow_partial_czs=True) for new_op in new_ops: num_qubits = len(new_op.qubits) if num_qubits == 1: (a, ) = new_op.qubits yield from _phased_x_z_ops(protocols.unitary(new_op), a) elif num_qubits == 2: a, b = op.qubits yield from ops.flatten_to_ops( known_two_q_operations_to_sycamore_operations(a, b, new_op))
def _convert_one(self, op: 'cirq.Operation') -> 'cirq.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 = optimizers.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 optimizers.two_qubit_matrix_to_operations( op.qubits[0], op.qubits[1], mat, allow_partial_czs=True, clean_operations=False ) return NotImplemented
def intercept_decompose_func(self, op: ops.Operation) -> ops.OP_TREE: # Drop measurements. if protocols.is_measurement(op): return [] # Immediately completely decompose two qubit unitary operations. if len(op.qubits) == 2: m = protocols.unitary(op, None) if m is not None: return optimizers.two_qubit_matrix_to_operations( op.qubits[0], op.qubits[1], mat=m, allow_partial_czs=False) # Fallback to default. 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 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_operations( q1, q2, v, atol=atol) w = d_v @ w # 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_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_operations( q1, q2, w, allow_partial_czs=False, atol=atol) return d_w, circuit_u1u2_l + circuit_u1u2_mid + circuit_u1u2_r
def matrix_to_sycamore_operations( target_qubits: List[cirq.GridQubit], matrix: np.ndarray) -> Tuple[cirq.OP_TREE, List[cirq.GridQubit]]: """A method to convert a unitary matrix to a list of Sycamore operations. This method will return a list of `cirq.Operation`s using the qubits and (optionally) ancilla qubits to implement the unitary matrix `matrix` on the target qubits `qubits`. The operations are also supported by `cirq.google.gate_sets.SYC_GATESET`. Args: target_qubits: list of qubits the returned operations will act on. The qubit order defined by the list is assumed to be used by the operations to implement `matrix`. matrix: a matrix that is guaranteed to be unitary and of size (2**len(qs), 2**len(qs)). Returns: A tuple of operations and ancilla qubits allocated. Operations: In case the matrix is supported, a list of operations `ops` is returned. `ops` acts on `qs` qubits and for which `cirq.unitary(ops)` is equal to `matrix` up to certain tolerance. In case the matrix is not supported, it might return NotImplemented to reduce the noise in the judge output. Ancilla qubits: In case ancilla qubits are allocated a list of ancilla qubits. Otherwise an empty list. . """ num_of_qubits = len(target_qubits) if np.all(np.equal(matrix, np.eye(2**num_of_qubits))): # Simple Identity Check ops_list = [] for qubit in target_qubits: op = cirq.Z(qubit)**0 cirq.google.Sycamore.validate_operation(op) ops_list.append(cirq.Z(qubit)**0) return ops_list, [] if num_of_qubits == 1: # single qubit gates gate = optimizers.single_qubit_matrix_to_phxz(matrix) cirq.google.Sycamore.validate_operation(gate(target_qubits[0])) return [gate(target_qubits[0])], [] elif num_of_qubits == 2: # two qubit gates ops_list = optimizers.two_qubit_matrix_to_operations( target_qubits[0], target_qubits[1], matrix, allow_partial_czs=True) ConvertToSycamoreGates = cirq.google.ConvertToSycamoreGates() converted_ops_list = ConvertToSycamoreGates.convert(op=ops_list) return converted_ops_list, [] elif is_incremental(matrix): ancilla = find_neighbor_available_qubit(target_qubits) return decompose_incrementer_matrix(target_qubits, ancilla), [ancilla] elif num_of_qubits == 3: # three qubit gates ops_list = optimizers.three_qubit_matrix_to_operations( target_qubits[0], target_qubits[1], target_qubits[2], matrix) return ops_list, [] elif np.count_nonzero(matrix - np.diag(np.diagonal(matrix))) == 0: # diagonal gates with more than 3 qubits angle_list = [] for i in np.arange(np.shape(matrix)[0]): angle_list.append(np.angle(matrix[i, i])) diagonal_gate = cirq.ops.DiagonalGate(angle_list) ops_list = diagonal_gate._decompose_(qubits=target_qubits) return ops_list, [] elif num_of_qubits == 4: ancilla = find_neighbor_available_qubit(target_qubits) CTOFFLI_mat = cirq.unitary( cirq.ops.ControlledOperation(controls=[ target_qubits[0], target_qubits[1], target_qubits[2] ], sub_operation=cirq.X( target_qubits[3]))) if np.all(np.equal(matrix, CTOFFLI_mat)): ops_list = [] ConvertToSycamoreGates = cirq.google.ConvertToSycamoreGates() decomposed_ops = cirq.optimizers.decompose_multi_controlled_x( controls=[ target_qubits[2], target_qubits[1], target_qubits[0] ], target=target_qubits[3], free_qubits=[ancilla]) ops_list.append(ConvertToSycamoreGates.convert(decomposed_ops)) return ops_list, [ancilla] else: ops_list = [] for qubit in target_qubits: op = cirq.Z(qubit)**0 cirq.google.Sycamore.validate_operation(op) ops_list.append(cirq.Z(qubit)**0) return ops_list, []
def known_two_q_operations_to_sycamore_operations( self, qubit_a: ops.Qid, qubit_b: ops.Qid, op: ops.GateOperation) -> ops.OP_TREE: """ Synthesize a known gate operation to a sycamore operation This function dispatches based on gate type Args: qubit_a: first qubit of GateOperation qubit_b: second qubit of GateOperation op: operation to decompose Returns: New operations iterable object """ gate = op.gate if isinstance(gate, ops.CNotPowGate): return [ ops.Y(qubit_b)**-0.5, cphase( cast(ops.CNotPowGate, gate).exponent * np.pi, qubit_a, qubit_b), ops.Y(qubit_b)**0.5, ] elif isinstance(gate, ops.CZPowGate): gate = cast(ops.CZPowGate, gate) if math.isclose(gate.exponent, 1.0): # check if CZ or CPHASE return decompose_cz_into_syc(qubit_a, qubit_b) else: # because CZPowGate == diag([1, 1, 1, e^{i pi phi}]) return cphase(gate.exponent * np.pi, qubit_a, qubit_b) elif isinstance(gate, ops.SwapPowGate) and math.isclose( cast(ops.SwapPowGate, gate).exponent, 1.0): return decompose_swap_into_syc(qubit_a, qubit_b) elif isinstance(gate, ops.ISwapPowGate) and math.isclose( cast(ops.ISwapPowGate, gate).exponent, 1.0): return decompose_iswap_into_syc(qubit_a, qubit_b) elif isinstance(gate, ops.ZZPowGate): return rzz( cast(ops.ZZPowGate, gate).exponent * np.pi / 2, *op.qubits) elif isinstance(gate, ops.MatrixGate) and len(op.qubits) == 2: new_ops = optimizers.two_qubit_matrix_to_operations( op.qubits[0], op.qubits[1], op, allow_partial_czs=True) gate_ops = [] for new_op in new_ops: num_qubits = len(new_op.qubits) if num_qubits == 1: gate_ops.extend([ term.on(new_op.qubits[0]) for term in optimizers.single_qubit_matrix_to_gates( protocols.unitary(new_op)) ]) elif num_qubits == 2: gate_ops.extend( ops.flatten_to_ops( self.known_two_q_operations_to_sycamore_operations( new_op.qubits[0], new_op.qubits[1], cast(ops.GateOperation, new_op)))) return gate_ops else: raise ValueError("Unrecognized gate: {!r}".format(op))