class TestUnitaryLog: def test_invalid(self, not_a_seq_float: Any) -> None: with pytest.raises(TypeError): unitary_log_no_i(not_a_seq_float) @pytest.mark.parametrize( 'reU', PauliMatrices(1).paulis + PauliMatrices(2).paulis + PauliMatrices(3).paulis + PauliMatrices(4).paulis + [unitary_group.rvs(16) for _ in range(100)], ) def test_valid(self, reU: np.ndarray) -> None: H = unitary_log_no_i(reU) assert np.allclose(H, H.conj().T, rtol=0, atol=1e-15) U = sp.linalg.expm(1j * H) assert 1 - (np.abs(np.trace(U.conj().T @ reU)) / U.shape[0]) <= 1e-15 assert np.allclose( U.conj().T @ U, np.identity(len(U)), rtol=0, atol=1e-14, ) assert np.allclose( U @ U.conj().T, np.identity(len(U)), rtol=0, atol=1e-14, )
def __init__(self, size: int) -> None: """Create a PauliGate acting on `size` qubits.""" if size <= 0: raise ValueError('Expected positive integer, got %d' % size) self.size = size self.paulis = PauliMatrices(self.size) self.num_params = len(self.paulis) self.sigmav = (-1j / self.get_dim()) * self.paulis.get_numpy()
def test_single(self, alpha: np.ndarray) -> None: paulis = PauliMatrices(2) H = paulis.dot_product(alpha) for p in paulis: F0, dF0 = dexpm_exact(H, p) F1, dF1 = dexpmv(H, p) assert np.allclose(F0, F1) assert np.allclose(dF0, dF1)
def test_vector(self, alpha: np.ndarray) -> None: paulis = PauliMatrices(2) H = paulis.dot_product(alpha) dFs0 = [] for p in paulis: _, dF = dexpm_exact(H, p) dFs0.append(dF) dFs0_np = np.array(dFs0) _, dFs1 = dexpmv(H, paulis.get_numpy()) assert np.allclose(dFs0_np, dFs1)
class TestPauliExpansion: def test_invalid(self, not_a_seq_float: Any) -> None: with pytest.raises(TypeError): pauli_expansion(not_a_seq_float) @pytest.mark.parametrize( 'reH', PauliMatrices(1).paulis + PauliMatrices(2).paulis + PauliMatrices(3).paulis + PauliMatrices(4).paulis, ) def test_valid(self, reH: np.ndarray) -> None: alpha = pauli_expansion(reH) print(alpha) H = PauliMatrices(int(np.log2(reH.shape[0]))).dot_product(alpha) assert np.linalg.norm(H - reH) < 1e-16
class PauliGate(QubitGate, DifferentiableUnitary, LocallyOptimizableUnitary): """A gate representing an arbitrary rotation.""" def __init__(self, size: int) -> None: """Create a PauliGate acting on `size` qubits.""" if size <= 0: raise ValueError('Expected positive integer, got %d' % size) self.size = size self.paulis = PauliMatrices(self.size) self.num_params = len(self.paulis) self.sigmav = (-1j / self.get_dim()) * self.paulis.get_numpy() def get_unitary(self, params: Sequence[float] = []) -> UnitaryMatrix: """Returns the unitary for this gate, see Unitary for more info.""" self.check_parameters(params) H = dot_product(params, self.sigmav) eiH = sp.linalg.expm(H) return UnitaryMatrix(eiH, check_arguments=False) def get_grad(self, params: Sequence[float] = []) -> np.ndarray: """Returns the gradient for this gate, see Gate for more info.""" self.check_parameters(params) H = dot_product(params, self.sigmav) _, dU = dexpmv(H, self.sigmav) return dU def get_unitary_and_grad( self, params: Sequence[float] = [], ) -> tuple[UnitaryMatrix, np.ndarray]: """Returns the unitary and gradient, see Gate for more info.""" self.check_parameters(params) H = dot_product(params, self.sigmav) U, dU = dexpmv(H, self.sigmav) return UnitaryMatrix(U, check_arguments=False), dU def optimize(self, env_matrix: np.ndarray) -> list[float]: """Returns optimal parameters with respect to an environment matrix.""" self.check_env_matrix(env_matrix) U, _, Vh = sp.linalg.svd(env_matrix) return list(pauli_expansion(unitary_log_no_i( Vh.conj().T @ U.conj().T)))
def pauli_expansion(H: np.ndarray, tol: float = 1e-8) -> np.ndarray: """ Computes a Pauli expansion of the hermitian matrix H. Args: H (np.ndarray): The hermitian matrix to expand. Returns: (np.ndarray): The coefficients of a Pauli expansion for H, i.e., X dot Sigma = H where Sigma is Pauli matrices of same size of H. """ if not is_hermitian(H, tol): # TODO: Re-evaluate check raise TypeError('Expected H to be hermitian, got %s.' % type(H)) # Change basis of H to Pauli Basis (solve for coefficients -> X) n = int(np.log2(len(H))) paulis = PauliMatrices(n) flatten_paulis = [np.reshape(pauli, 4**n) for pauli in paulis] flatten_H = np.reshape(H, 4**n) A = np.stack(flatten_paulis, axis=-1) X = np.real(np.matmul(np.linalg.inv(A), flatten_H)) return np.array(X)
def test_valid(self, reH: np.ndarray) -> None: alpha = pauli_expansion(reH) print(alpha) H = PauliMatrices(int(np.log2(reH.shape[0]))).dot_product(alpha) assert np.linalg.norm(H - reH) < 1e-16