def find_local_equivalents(unitary1: np.ndarray, unitary2: np.ndarray): """ Given two unitaries with the same interaction coefficients but different local unitary rotations determine the local unitaries that turns one type of gate into another. 1) perform the kak decomp on each unitary and confirm interaction terms are equivalent 2) identify the elements of SU(2) to transform unitary2 into unitary1 Args: unitary1: unitary that we target unitary2: unitary that we transform the local gates to the target Returns: four 2x2 unitaries. first two are pre-rotations last two are post rotations. """ kak_u1 = cirq.kak_decomposition(unitary1) kak_u2 = cirq.kak_decomposition(unitary2) u_0 = kak_u1.single_qubit_operations_after[ 0] @ kak_u2.single_qubit_operations_after[0].conj().T u_1 = kak_u1.single_qubit_operations_after[ 1] @ kak_u2.single_qubit_operations_after[1].conj().T v_0 = (kak_u2.single_qubit_operations_before[0].conj().T @ kak_u1.single_qubit_operations_before[0]) v_1 = (kak_u2.single_qubit_operations_before[1].conj().T @ kak_u1.single_qubit_operations_before[1]) return v_0, v_1, u_0, u_1
def _find_local_equivalents(target_unitary: np.ndarray, source_unitary: np.ndarray): """Determine the local 1q rotations that turn one equivalent 2q unitary into the other. Given two 2q unitaries with the same interaction coefficients but different local unitary rotations determine the local unitaries that turns one type of gate into another. 1) Perform the KAK Decomposition on each unitary and confirm interaction terms are equivalent. 2) Identify the elements of SU(2) to transform source_unitary into target_unitary Args: target_unitary: The unitary that we need to transform `source_unitary` to. source_unitary: The unitary that we need to transform by adding local gates, and make it equivalent to the target_unitary. Returns: Four 2x2 unitaries. The first two are pre-rotations and last two are post rotations. """ kak_u1 = cirq.kak_decomposition(target_unitary) kak_u2 = cirq.kak_decomposition(source_unitary) u_0 = kak_u1.single_qubit_operations_after[ 0] @ kak_u2.single_qubit_operations_after[0].conj().T u_1 = kak_u1.single_qubit_operations_after[ 1] @ kak_u2.single_qubit_operations_after[1].conj().T v_0 = (kak_u2.single_qubit_operations_before[0].conj().T @ kak_u1.single_qubit_operations_before[0]) v_1 = (kak_u2.single_qubit_operations_before[1].conj().T @ kak_u1.single_qubit_operations_before[1]) return v_0, v_1, u_0, u_1
def _decompose_xx_yy( desired_interaction: Union[cirq.Operation, np.ndarray, 'cirq.SupportsUnitary'], *, available_gate: cirq.Gate, atol: float = 1e-8, qubits: Optional[Sequence[cirq.Qid]] = None, ) -> cirq.Circuit: if qubits is None: if isinstance(desired_interaction, cirq.Operation): qubits = desired_interaction.qubits else: qubits = cirq.LineQubit.range(2) kak = cirq.kak_decomposition(desired_interaction) x, y, z = kak.interaction_coefficients if abs(z) > atol: raise ValueError( f'zz term present in {desired_interaction!r} -> {kak}') if isinstance(available_gate, cirq.ISwapPowGate): available_gate = cirq.FSimGate(-np.pi / 2 * available_gate.exponent, 0) if min(x, y) > np.pi / 4 - atol and abs( available_gate.phi) < atol and abs(available_gate.theta - np.pi / 4) < atol: ops = [ available_gate(*qubits), cirq.Y.on_each(*qubits), available_gate(*qubits), ] elif min(x, y) > np.pi / 4 - atol and abs( available_gate.phi) < atol and abs(available_gate.theta + np.pi / 4) < atol: ops = [ available_gate(*qubits), available_gate(*qubits), ] else: ops = _decompose_xx_yy_into_two_fsims_ignoring_single_qubit_ops( qubits=qubits, fsim_gate=available_gate, canonical_x_kak_coefficient=x, canonical_y_kak_coefficient=y, ) result = _fix_single_qubit_gates_around_kak_interaction( desired=kak, operations=ops, qubits=qubits, ) output = cirq.Circuit(result) cirq.testing.assert_allclose_up_to_global_phase( output.unitary(qubit_order=qubits), cirq.unitary(desired_interaction), atol=1e-4) return output
def _outer_locals_for_unitary( target: np.ndarray, base: np.ndarray ) -> Tuple[_SingleQubitGatePair, _SingleQubitGatePair, np.ndarray]: """Local unitaries mapping between locally equivalent 2-local unitaries. Finds the left and right 1-local unitaries kL, kR such that U_target = kL @ U_base @ kR Args: target: The unitary to which we want to map. base: The base unitary which maps to target. Returns: kR: The right 1-local unitaries in the equation above, expressed as 2-tuples of (2x2) single qubit unitaries. kL: The left 1-local unitaries in the equation above, expressed as 2-tuples of (2x2) single qubit unitaries. actual: The outcome of kL @ base @ kR """ target_decomp = cirq.kak_decomposition(target) base_decomp = cirq.kak_decomposition(base) # From the KAK decomposition, we have # kLt At kRt = kL kLb Ab KRb kR # If At=Ab, we can solve for kL and kR as # kLt = kL kLb --> kL = kLt kLb^\dagger # kRt = kRb kR --> kR = kRb\dagger kRt # 0 and 1 are qubit indices. kLt0, kLt1 = target_decomp.single_qubit_operations_after kLb0, kLb1 = base_decomp.single_qubit_operations_after kL = kLt0 @ kLb0.conj().T, kLt1 @ kLb1.conj().T kRt0, kRt1 = target_decomp.single_qubit_operations_before kRb0, kRb1 = base_decomp.single_qubit_operations_before kR = kRb0.conj().T @ kRt0, kRb1.conj().T @ kRt1 actual = np.kron(*kL) @ base actual = actual @ np.kron(*kR) actual *= np.conj(target_decomp.global_phase) return kR, kL, actual
def test_scatter_plot_normalized_kak_interaction_coefficients(): a, b = cirq.LineQubit.range(2) data = [ cirq.kak_decomposition(cirq.unitary(cirq.CZ)), cirq.unitary(cirq.CZ), cirq.CZ, cirq.Circuit(cirq.H(a), cirq.CNOT(a, b)), ] ax = cirq.scatter_plot_normalized_kak_interaction_coefficients(data) assert ax is not None ax2 = cirq.scatter_plot_normalized_kak_interaction_coefficients( data, s=1, c='blue', ax=ax, include_frame=False, label=f'test') assert ax2 is ax
def test_decompose_xx_yy_into_two_fsims_ignoring_single_qubit_ops_fail(): c = _decompose_xx_yy_into_two_fsims_ignoring_single_qubit_ops( qubits=cirq.LineQubit.range(2), fsim_gate=cirq.FSimGate(theta=np.pi / 2, phi=0), canonical_x_kak_coefficient=np.pi / 4, canonical_y_kak_coefficient=np.pi / 8) np.testing.assert_allclose( cirq.kak_decomposition(cirq.Circuit(c)).interaction_coefficients, [np.pi / 4, np.pi / 8, 0]) with pytest.raises(ValueError, match='Failed to synthesize'): _ = _decompose_xx_yy_into_two_fsims_ignoring_single_qubit_ops( qubits=cirq.LineQubit.range(2), fsim_gate=cirq.FSimGate(theta=np.pi / 5, phi=0), canonical_x_kak_coefficient=np.pi / 4, canonical_y_kak_coefficient=np.pi / 8)
def _decompose_two_qubit(self, operation: cirq.Operation) -> cirq.OP_TREE: """Decomposes a two qubit gate into XXPow, YYPow, and ZZPow plus single qubit gates.""" mat = cirq.unitary(operation) kak = cirq.kak_decomposition(mat, check_preconditions=False) for qubit, mat in zip(operation.qubits, kak.single_qubit_operations_before): gates = cirq.single_qubit_matrix_to_gates(mat, self.atol) for gate in gates: yield gate(qubit) two_qubit_gates = [cirq.XX, cirq.YY, cirq.ZZ] for two_qubit_gate, coefficient in zip(two_qubit_gates, kak.interaction_coefficients): yield (two_qubit_gate ** (-coefficient * 2 / np.pi))(*operation.qubits) for qubit, mat in zip(operation.qubits, kak.single_qubit_operations_after): for gate in cirq.single_qubit_matrix_to_gates(mat, self.atol): yield gate(qubit)
def perturbations_unitary(u, amount=1e-10): """Returns several unitaries in the neighborhood of u to test for numerical corner cases near critical values.""" kak = cirq.kak_decomposition(u) yield u for i in range(3): for neg in (-1, 1): perturb_xyz = list(kak.interaction_coefficients) perturb_xyz[i] += neg * amount yield cirq.unitary( cirq.KakDecomposition( global_phase=kak.global_phase, single_qubit_operations_before=kak.single_qubit_operations_before, single_qubit_operations_after=kak.single_qubit_operations_after, interaction_coefficients=tuple(perturb_xyz), ) )
def test_kak_decomposition_invalid_object(): with pytest.raises(TypeError, match='unitary effect'): _ = cirq.kak_decomposition('test') with pytest.raises(ValueError, match='4x4 unitary matrix'): _ = cirq.kak_decomposition(np.eye(3)) with pytest.raises(ValueError, match='4x4 unitary matrix'): _ = cirq.kak_decomposition(np.eye(8)) with pytest.raises(ValueError, match='4x4 unitary matrix'): _ = cirq.kak_decomposition(np.ones((4, 4))) with pytest.raises(ValueError, match='4x4 unitary matrix'): _ = cirq.kak_decomposition(np.zeros((4, 4))) nil = cirq.kak_decomposition(np.zeros((4, 4)), check_preconditions=False) np.testing.assert_allclose(cirq.unitary(nil), np.eye(4), atol=1e-8)
def test_kak_decompose(unitary: np.ndarray): kak = cirq.kak_decomposition(unitary) circuit = cirq.Circuit(kak._decompose_(cirq.LineQubit.range(2))) np.testing.assert_allclose(cirq.unitary(circuit), unitary, atol=1e-8) assert len(circuit) == 5 assert len(list(circuit.all_operations())) == 8
def test_kak_decomposition_unitary_object(): op = cirq.ISWAP(*cirq.LineQubit.range(2))**0.5 kak = cirq.kak_decomposition(op) np.testing.assert_allclose(cirq.unitary(kak), cirq.unitary(op), atol=1e-8) assert cirq.kak_decomposition(kak) is kak
def test_kak_decomposition(target): kak = cirq.kak_decomposition(target) np.testing.assert_allclose(cirq.unitary(kak), target, atol=1e-8)
def test_kak_decomposition(m): g, (a1, a0), (x, y, z), (b1, b0) = cirq.kak_decomposition(m) m2 = recompose_kak(g, (a1, a0), (x, y, z), (b1, b0)) assert np.allclose(m, m2)
def time_kak_decomposition(target): """Benchmark kak_decomposition kak_decomposition is benchmarked because it was historically slow. See https://github.com/quantumlib/Cirq/issues/3840 for status of other benchmarks. """ cirq.kak_decomposition(target)