def decompose_4x4_optimal(U): """Builds optimal decomposition of general 4x4 unitary matrix. This decomposition consists of at most 3 CNOT gates, 15 Rx/Ry gates and one R1 gate. Returns list of `Gate`s. """ assert is_unitary(U) magic_decomp = decompose_to_magic_diagonal(U) result = [] result += apply_on_qubit(su_to_gates(magic_decomp['VA']), 1) result += apply_on_qubit(su_to_gates(magic_decomp['VB']), 0) result += decompose_magic_N(magic_decomp['alpha']) result += apply_on_qubit(su_to_gates(magic_decomp['UA']), 1) result += apply_on_qubit(su_to_gates(magic_decomp['UB']), 0) # Adding global phase using Rz and R1. gl_phase = magic_decomp['global_phase'] + 0.25 * np.pi if np.abs(gl_phase) > 1e-9: result.append(GateSingle(Gate2('Rz', 2 * gl_phase), 0)) result.append(GateSingle(Gate2('R1', 2 * gl_phase), 0)) result = skip_identities(result) assert _allclose(U, gates_to_matrix(result, 2)) return result
def is_maximally_entangled_basis(A): """ Checks if columns of matrx form a maximally entanfgled basis. Maximally entanfgled basis is basis consisting of maximally entangled orthonormal states. """ assert A.shape == (4, 4) assert is_unitary(A) return all([is_maximally_entangled_state(A[:, i]) for i in range(4)])
def __init__(self, matrix2x2, matrix_size, index1, index2): assert index1 != index2 assert index1 < matrix_size and index2 < matrix_size assert matrix2x2.shape == (2, 2) assert is_unitary(matrix2x2) self.matrix_size = matrix_size self.index1 = index1 self.index2 = index2 self.matrix_2x2 = matrix2x2 self.order_indices()
def unitary2x2_to_gates(A): """Decomposes 2x2 unitary to gates Ry, Rz, R1. R1(x) = diag(1, exp(i*x)). """ assert is_unitary(A) phi = np.angle(np.linalg.det(A)) if np.abs(phi) < 1e-9: return su_to_gates(A) elif np.allclose(A, PAULI_X): return [Gate2('X')] else: A = np.diag([1.0, np.exp(-1j * phi)]) @ A return su_to_gates(A) + [Gate2('R1', phi)]
def correct_phase(A, cur_phase): """Correct phases of 2x2 unitary to make it speical unitary.""" assert is_unitary(A) n = A.shape[0] f = np.angle(np.linalg.det(A)) / n return A * np.exp(-1j * f), cur_phase + f
def decompose_to_magic_diagonal(U): """Decomposes unitary U as follows: U = e^(if) * (UAxUB) * N(alpha) * (VAxVB) f - global phase. UA, UB, VA, VB - 1-qubit _special_ unitaries (det=1). N(alpha) - "Magic N" matrix. Implements algorithm described in Appendix A in [2]. """ assert is_unitary(U) # Step 1 in [2]. U_mb = Phi_dag @ U @ Phi Psi, eig_values = orthonormal_eigensystem(U_mb.T @ U_mb) eps = 0.5 * np.angle(eig_values) # Step 3 in [2]. Psi_tilde = U_mb @ Psi @ np.diag(np.exp(-1j * eps)) assert is_real(Psi_tilde) # Go back from magical to computational basis. Psi = Phi @ Psi Psi_tilde = Phi @ Psi_tilde assert is_maximally_entangled_basis(Psi) assert is_maximally_entangled_basis(Psi_tilde) # Step 2 in [2]. VA, VB, xi = decompose_4x4_partial(Psi) # Step 4 in [2]. UA_dag, UB_dag, lxe = decompose_4x4_partial(Psi_tilde) lmbda = lxe - xi - eps UA = UA_dag.conj().T UB = UB_dag.conj().T UAUB = np.kron(UA, UB) VAVB = np.kron(VA, VB) Ud = Phi @ np.diag(np.exp(-1j * lmbda)) @ Phi.conj().T assert np.allclose(VAVB @ Psi @ np.diag(np.exp(1j * xi)), Phi) assert np.allclose(U @ Psi @ np.diag(np.exp(-1j * eps)), Psi_tilde) assert np.allclose( UAUB.conj().T @ Psi_tilde @ np.diag(np.exp(1j * (eps + xi + lmbda))), Phi) assert _allclose(U, np.kron(UA, UB) @ Ud @ np.kron(VA, VB)) # Restore coefficients of non-local unitary. gl_phase = -0.25 * np.sum(lmbda) alpha_x = -0.5 * (lmbda[0] + lmbda[2] + 2 * gl_phase) alpha_y = -0.5 * (lmbda[1] + lmbda[2] + 2 * gl_phase) alpha_z = -0.5 * (lmbda[0] + lmbda[1] + 2 * gl_phase) alpha = np.array([alpha_x, alpha_y, alpha_z]) assert np.allclose(lmbda[0], -gl_phase - alpha_x + alpha_y - alpha_z) assert np.allclose(lmbda[1], -gl_phase + alpha_x - alpha_y - alpha_z) assert np.allclose(lmbda[2], -gl_phase - alpha_x - alpha_y + alpha_z) assert np.allclose(lmbda[3], -gl_phase + alpha_x + alpha_y + alpha_z) assert _allclose(Ud, np.exp(1j * gl_phase) * magic_N(alpha)) assert _allclose(U, np.exp(1j * gl_phase) * UAUB @ magic_N(alpha) @ VAVB) UA, gl_phase = correct_phase(UA, gl_phase) UB, gl_phase = correct_phase(UB, gl_phase) VA, gl_phase = correct_phase(VA, gl_phase) VB, gl_phase = correct_phase(VB, gl_phase) for mx in [UA, UB, VA, VB]: assert np.allclose(np.linalg.det(mx), 1) UAUB = np.kron(UA, UB) VAVB = np.kron(VA, VB) assert _allclose(U, np.exp(1j * gl_phase) * UAUB @ magic_N(alpha) @ VAVB) return { 'UA': UA, 'UB': UB, 'VA': VA, 'VB': VB, 'alpha': alpha, 'global_phase': gl_phase, }
def decompose_4x4_partial(Psi): """Partially decomposes 4x4 unitary matrix. Takes matrix Psi, columns of which form fully entangled basis. Returns matrices UA, UB and vector zeta, such that: (UAxUB) * Psi * exp(i * diag(zeta)) = Phi. Implements algorithm in Lemma 1 in Appendix A from [2]. """ assert is_maximally_entangled_basis(Psi) Psi_bar = np.zeros_like(Psi) for i in range(4): Psi_bar[:, i] = to_real_in_magic_basis(Psi[:, i]) e_f = (Psi_bar[:, 0] + 1j * Psi_bar[:, 1]) / np.sqrt(2) e_ort_f_ort = (Psi_bar[:, 0] - 1j * Psi_bar[:, 1]) / np.sqrt(2) e, f = decompose_product_state(e_f) e_ort, f_ort = decompose_product_state(e_ort_f_ort) e_f_ort = np.kron(e, f_ort) e_ort_f = np.kron(e_ort, f) assert np.allclose(np.dot(e.conj(), e_ort), 0) assert np.allclose(np.dot(f.conj(), f_ort), 0) def restore_phase(a, b, c): """Finds such delta, that a*exp(i*delta) + b*exp(-i*delta)=c.""" assert np.abs(a) >= 1e-9 x1 = (c + np.sqrt(c * c - 4 * a * b)) / (2 * a) x2 = (c - np.sqrt(c * c - 4 * a * b)) / (2 * a) xs = [v for v in [x1, x2] if np.allclose(np.abs(v), 1)] deltas = [np.angle(x) for x in xs] assert len(deltas) > 0 for d in deltas: assert np.allclose(a * np.exp(1j * d) + b * np.exp(-1j * d), c) return deltas a_d = np.kron(e, f_ort) b_d = np.kron(e_ort, f) c_d = np.sqrt(2) * 1j * Psi_bar[:, 2] i_d = 0 while np.abs(a_d[i_d]) < 1e-9: i_d += 1 deltas = restore_phase(a_d[i_d], b_d[i_d], c_d[i_d]) # If there are 2 solutions for delta, we need to choose correct one. delta = deltas[0] if len(deltas) == 2: delta = None for d in deltas: p2 = -1j * (e_f_ort * np.exp(1j * d) + e_ort_f * np.exp(-1j * d)) / np.sqrt(2) if _allclose(p2, Psi_bar[:, 2]): delta = d assert delta is not None # Correcting ambiguity in sign. # Formula A5b has "+/-", and we need to choose correct sign. p3_1 = Psi_bar[:, 3] p3_2 = (e_f_ort * np.exp(1j * delta) - e_ort_f * np.exp(-1j * delta)) / np.sqrt(2) negate_k = 1 for i in range(4): if abs(p3_2[i]) > 1e-9 and np.real(p3_1[i] / p3_2[i]) < -0.5: negate_k = -1 Psi_bar[:, 3] *= negate_k # Check formulas A3-A5. assert is_unitary(np.array([e_f, e_f_ort, e_ort_f, e_ort_f_ort])) assert np.allclose(Psi_bar[:, 0], (e_f + e_ort_f_ort) / np.sqrt(2)) assert np.allclose(Psi_bar[:, 1], -1j * (e_f - e_ort_f_ort) / np.sqrt(2)) p2 = -1j * (e_f_ort * np.exp(1j * delta) + e_ort_f * np.exp(-1j * delta)) / np.sqrt(2) assert np.allclose(Psi_bar[:, 2], p2) p3 = (e_f_ort * np.exp(1j * delta) - e_ort_f * np.exp(-1j * delta)) / np.sqrt(2) assert _allclose(Psi_bar[:, 3], p3) UA = np.array([e.conj(), e_ort.conj() * np.exp(1j * delta)]) UB = np.array([f.conj(), f_ort.conj() * np.exp(-1j * delta)]) assert is_unitary(UA) assert is_unitary(UB) UAUB = np.kron(UA, UB) UAUB_alt = np.array([ e_f.conj(), e_f_ort.conj() * np.exp(-1j * delta), e_ort_f.conj() * np.exp(1j * delta), e_ort_f_ort.conj() ]) assert np.allclose(UAUB, UAUB_alt) assert is_unitary(UAUB) assert np.allclose(UAUB @ Psi_bar, Phi) D = (UAUB @ Psi).conj().T @ Phi zeta = np.angle(np.array([D[i, i] for i in range(4)])) assert _allclose(D, np.diag(np.exp(1j * zeta))) assert np.allclose(np.kron(UA, UB) @ Psi @ np.diag(np.exp(1j * zeta)), Phi) return UA, UB, zeta