def test_is_unitary_tolerance(): tol = Tolerance(atol=0.5) # Pays attention to specified tolerance. assert predicates.is_unitary(np.array([[1, 0], [-0.5, 1]]), tol) assert not predicates.is_unitary(np.array([[1, 0], [-0.6, 1]]), tol) # Error isn't accumulated across entries. assert predicates.is_unitary( np.array([[1.2, 0, 0], [0, 1.2, 0], [0, 0, 1.2]]), tol) assert not predicates.is_unitary( np.array([[1.2, 0, 0], [0, 1.3, 0], [0, 0, 1.2]]), tol)
def bidiagonalize_unitary_with_special_orthogonals( mat: np.ndarray, *, rtol: float = 1e-5, atol: float = 1e-8, check_preconditions: bool = True ) -> Tuple[np.ndarray, np.array, np.ndarray]: """Finds orthogonal matrices L, R such that L @ matrix @ R is diagonal. Args: mat: A unitary matrix. rtol: Relative numeric error threshold. atol: Absolute numeric error threshold. check_preconditions: If set, verifies that the input is a unitary matrix (to the given tolerances). Defaults to set. Returns: A triplet (L, d, R) such that L @ mat @ R = diag(d). Both L and R will be orthogonal matrices with determinant equal to 1. Raises: ValueError: Matrices don't meet preconditions (e.g. not real). """ if check_preconditions: if not predicates.is_unitary(mat, rtol=rtol, atol=atol): raise ValueError('matrix must be unitary.') # Note: Because mat is unitary, setting A = real(mat) and B = imag(mat) # guarantees that both A @ B.T and A.T @ B are Hermitian. left, right = bidiagonalize_real_matrix_pair_with_symmetric_products( np.real(mat), np.imag(mat), rtol=rtol, atol=atol, check_preconditions=check_preconditions) # Convert to special orthogonal w/o breaking diagonalization. if np.linalg.det(left) < 0: left[0, :] *= -1 if np.linalg.det(right) < 0: right[:, 0] *= -1 diag = combinators.dot(left, mat, right) return left, np.diag(diag), right
def axis_angle(single_qubit_unitary: np.ndarray) -> AxisAngleDecomposition: """Decomposes a single-qubit unitary into axis, angle, and global phase. Args: single_qubit_unitary: The unitary of the single-qubit operation to decompose. Returns: An AxisAngleDecomposition equivalent to the given unitary. """ u = single_qubit_unitary assert u.shape == (2, 2) assert predicates.is_unitary(single_qubit_unitary, atol=1e-8) # Extract phased quaternion components. [a, b], [c, d] = u wp = (a + d) / 2 xp = (b + c) / 2j yp = (b - c) / 2 zp = (a - d) / 2j # Extract global phase factor from largest component. p = max(wp, xp, yp, zp, key=abs) p /= abs(p) # Cancel global phase factor, pushing components onto the real line. w = min(1, max(-1, np.real(wp / p))) x = np.real(xp / p) y = np.real(yp / p) z = np.real(zp / p) angle = -2 * math.acos(w) # Normalize axis. n = math.sqrt(x * x + y * y + z * z) if n < 0.0000001: # There's an axis singularity near θ=0. # Default to no rotation around the X axis. return AxisAngleDecomposition(global_phase=p, angle=0, axis=(1, 0, 0)) x /= n y /= n z /= n return AxisAngleDecomposition(axis=(x, y, z), angle=angle, global_phase=p).canonicalize()
def bidiagonalize_unitary_with_special_orthogonals( mat: np.ndarray, tolerance: Tolerance = Tolerance.DEFAULT ) -> Tuple[np.ndarray, np.array, np.ndarray]: """Finds orthogonal matrices L, R such that L @ matrix @ R is diagonal. Args: mat: A unitary matrix. tolerance: Numeric error thresholds. Returns: A triplet (L, d, R) such that L @ mat @ R = diag(d). Both L and R will be orthogonal matrices with determinant equal to 1. Raises: ValueError: Matrices don't meet preconditions (e.g. not real). ArithmeticError: Failed to meet specified tolerance. """ if not predicates.is_unitary(mat, tolerance): raise ValueError('matrix must be unitary.') # Note: Because mat is unitary, setting A = real(mat) and B = imag(mat) # guarantees that both A @ B.T and A.T @ B are Hermitian. left, right = bidiagonalize_real_matrix_pair_with_symmetric_products( np.real(mat), np.imag(mat), tolerance) # Convert to special orthogonal w/o breaking diagonalization. if np.linalg.det(left) < 0: left[0, :] *= -1 if np.linalg.det(right) < 0: right[:, 0] *= -1 diag = combinators.dot(left, mat, right) # Check acceptability vs tolerances. if not predicates.is_diagonal(diag, tolerance): raise ArithmeticError('Failed to diagonalize to specified tolerance.') return left, np.diag(diag), right
def kak_decomposition( unitary_object: Union[np.ndarray, 'cirq.SupportsUnitary'], *, rtol: float = 1e-5, atol: float = 1e-8, check_preconditions: bool = True, ) -> KakDecomposition: """Decomposes a 2-qubit unitary into 1-qubit ops and XX/YY/ZZ interactions. Args: unitary_object: The value to decompose. Can either be a 4x4 unitary matrix, or an object that has a 4x4 unitary matrix (via the `cirq.SupportsUnitary` protocol). rtol: Per-matrix-entry relative tolerance on equality. atol: Per-matrix-entry absolute tolerance on equality. check_preconditions: If set, verifies that the input corresponds to a 4x4 unitary before decomposing. Returns: A `cirq.KakDecomposition` canonicalized such that the interaction coefficients x, y, z satisfy: 0 ≤ abs(z2) ≤ y2 ≤ x2 ≤ π/4 if x2 = π/4, z2 >= 0 Raises: ValueError: Bad matrix. ArithmeticError: Failed to perform the decomposition. References: 'An Introduction to Cartan's KAK Decomposition for QC Programmers' https://arxiv.org/abs/quant-ph/0507171 """ if isinstance(unitary_object, KakDecomposition): return unitary_object if isinstance(unitary_object, np.ndarray): mat = unitary_object else: mat = protocols.unitary(unitary_object) if check_preconditions and ( mat.shape != (4, 4) or not predicates.is_unitary(mat, rtol=rtol, atol=atol)): raise ValueError( 'Input must correspond to a 4x4 unitary matrix. Received matrix:\n' + str(mat)) # Diagonalize in magic basis. left, d, right = diagonalize.bidiagonalize_unitary_with_special_orthogonals( KAK_MAGIC_DAG @ mat @ KAK_MAGIC, atol=atol, rtol=rtol, check_preconditions=False) # Recover pieces. a1, a0 = so4_to_magic_su2s(left.T, atol=atol, rtol=rtol, check_preconditions=False) b1, b0 = so4_to_magic_su2s(right.T, atol=atol, rtol=rtol, check_preconditions=False) w, x, y, z = (KAK_GAMMA @ np.vstack(np.angle(d))).flatten() g = np.exp(1j * w) # Canonicalize. inner_cannon = kak_canonicalize_vector(x, y, z) b1 = np.dot(inner_cannon.single_qubit_operations_before[0], b1) b0 = np.dot(inner_cannon.single_qubit_operations_before[1], b0) a1 = np.dot(a1, inner_cannon.single_qubit_operations_after[0]) a0 = np.dot(a0, inner_cannon.single_qubit_operations_after[1]) return KakDecomposition( interaction_coefficients=inner_cannon.interaction_coefficients, global_phase=g * inner_cannon.global_phase, single_qubit_operations_before=(b1, b0), single_qubit_operations_after=(a1, a0), )
def test_is_unitary(): assert predicates.is_unitary(np.empty((0, 0))) assert not predicates.is_unitary(np.empty((1, 0))) assert not predicates.is_unitary(np.empty((0, 1))) assert predicates.is_unitary(np.array([[1]])) assert predicates.is_unitary(np.array([[-1]])) assert predicates.is_unitary(np.array([[1j]])) assert not predicates.is_unitary(np.array([[5]])) assert not predicates.is_unitary(np.array([[3j]])) assert not predicates.is_unitary(np.array([[1, 0]])) assert not predicates.is_unitary(np.array([[1], [0]])) assert not predicates.is_unitary(np.array([[1, 0], [0, -2]])) assert predicates.is_unitary(np.array([[1, 0], [0, -1]])) assert predicates.is_unitary(np.array([[1j, 0], [0, 1]])) assert not predicates.is_unitary(np.array([[1, 0], [1, 1]])) assert not predicates.is_unitary(np.array([[1, 1], [0, 1]])) assert not predicates.is_unitary(np.array([[1, 1], [1, 1]])) assert not predicates.is_unitary(np.array([[1, -1], [1, 1]])) assert predicates.is_unitary(np.array([[1, -1], [1, 1]]) * np.sqrt(0.5)) assert predicates.is_unitary(np.array([[1, 1j], [1j, 1]]) * np.sqrt(0.5)) assert not predicates.is_unitary( np.array([[1, -1j], [1j, 1]]) * np.sqrt(0.5)) assert predicates.is_unitary( np.array([[1, 1j + 1e-11], [1j, 1 + 1j * 1e-9]]) * np.sqrt(0.5))