From 859aecb494f87b2eea4c81843aefecc14c140e3e Mon Sep 17 00:00:00 2001 From: ncrubin Date: Thu, 5 Oct 2017 09:51:08 -0700 Subject: [PATCH 1/3] added tensor_up feature the `tensor_up()` method allows the user to easily construct the matrix form of operators represented as PauliSums. Though there is a slight degree of overlap with some of the existing functionality in unitary_generators, this method provides a very easy interface to the users of pyQuil and reference-qvm to examine operators and Hamiltonains. In the future when the gate matrices are converted to sparse operators we can use sparse kron in scipy to speed up construction of these operators. Right now there is a lot of wasted computation by multiplying by known zeros. --- referenceqvm/tests/test_unitary_generator.py | 37 +++++++++++++++++- referenceqvm/unitary_generator.py | 40 ++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/referenceqvm/tests/test_unitary_generator.py b/referenceqvm/tests/test_unitary_generator.py index 4294e64..5fc35f8 100644 --- a/referenceqvm/tests/test_unitary_generator.py +++ b/referenceqvm/tests/test_unitary_generator.py @@ -1,11 +1,13 @@ import pytest import numpy as np -from referenceqvm.unitary_generator import lifted_gate, apply_gate, tensor_gates +from referenceqvm.unitary_generator import (lifted_gate, apply_gate, + tensor_gates, tensor_up) from referenceqvm.gates import gate_matrix, utility_gates from pyquil.quil import Program from pyquil.gates import H as Hgate from pyquil.gates import RX as RXgate from pyquil.gates import CNOT as CNOTgate +from pyquil.paulis import PauliTerm, PauliSum def test_lifted_swap(): @@ -208,3 +210,36 @@ def test_tensor_gates_two_qubit(): test_unitary = tensor_gates(gate_matrix, {}, prog.actions[0][1], 4).toarray() true_unitary = apply_gate(gate_matrix['CNOT'], [1, 3], 4).toarray() assert np.allclose(test_unitary, true_unitary) + + +def test_tensor_up(): + """Testing tesnor up type checking and correctness""" + x_term = PauliTerm("X", 5) + + # testing type rejection + with pytest.raises(TypeError): + tensor_up(x_term, 5) + + # testing index rejection + with pytest.raises(IndexError): + tensor_up(PauliSum([x_term]), 3) + + xy_term = PauliSum([PauliTerm("X", 0)*PauliTerm("Y", 1)]) + + # test correctness + trial_matrix = tensor_up(xy_term, 2) + true_matrix = np.kron(gate_matrix['Y'], gate_matrix['X']) + assert np.allclose(trial_matrix, true_matrix) + + x1_term = PauliSum([PauliTerm("X", 1)]) + trial_matrix = tensor_up(x1_term, 2) + true_matrix = np.kron(gate_matrix['X'], gate_matrix['I']) + assert np.allclose(trial_matrix, true_matrix) + + zpz_term = PauliTerm("Z", 0) + PauliTerm("Z", 1) + trial_matrix = tensor_up(zpz_term, 2) + true_matrix = np.zeros((4, 4)) + true_matrix[0, 0] = 2 + true_matrix[-1, -1] = -2 + assert np.allclose(trial_matrix, true_matrix) + diff --git a/referenceqvm/unitary_generator.py b/referenceqvm/unitary_generator.py index 2349cf5..a2bbc45 100644 --- a/referenceqvm/unitary_generator.py +++ b/referenceqvm/unitary_generator.py @@ -24,6 +24,7 @@ import scipy.sparse as sps from referenceqvm.gates import gate_matrix from pyquil.quilbase import * +from pyquil.paulis import PauliSum """ If True, only physically-implementable operations allowed! @@ -355,6 +356,45 @@ def tensor_gates(gate_set, defgate_set, pyquil_gate, num_qubits): return gate +def tensor_up(pauli_terms, num_qubits): + """ + Takes a PauliSum object along with a total number of + qubits and returns a matrix corresponding the tensor representation of the + object. + + Useful for generating the full Hamiltonian after a particular fermion to + pauli transformation. + + :param pauli_terms: (PauliSum) object of PauliTerm + :param num_qubits: (int) number of qubits in the system + :returns: (numpy array) representation of the paui_terms operator + """ + if not isinstance(pauli_terms, PauliSum): + raise TypeError("can only tensor PauliSum") + + if __debug__: + for term in pauli_terms.terms: + if len(term._ops.keys()) > 0: + if max(term._ops.keys()) >= num_qubits: + raise IndexError("pauli_terms has higher index than qubits") + + big_hilbert = np.zeros((2 ** num_qubits, 2 ** num_qubits)) + # left kronecker product corresponds to the correct basis ordering + for term in pauli_terms.terms: + tmp_big_hilbert = np.array([1]) + + for index in range(num_qubits): + pauli_mat = gate_matrix[term[index]] + + tmp_big_hilbert = np.kron(pauli_mat, tmp_big_hilbert) + + tmp_big_hilbert = tmp_big_hilbert * term.coefficient + + big_hilbert = big_hilbert + tmp_big_hilbert + + return big_hilbert + + def value_get(param_obj): """ Function that returns the raw number / string stored in certain pyQuil From 569c750e5cf459001daa2d74479469728fc62921 Mon Sep 17 00:00:00 2001 From: ncrubin Date: Thu, 5 Oct 2017 16:08:15 -0700 Subject: [PATCH 2/3] PR review changes 1. Example in docstring 2. Separated testing and use of numpy testing infrastructure 3. cleaned up kronecker product and explicit array types. --- referenceqvm/tests/test_unitary_generator.py | 13 +++++++---- referenceqvm/unitary_generator.py | 24 ++++++++++++-------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/referenceqvm/tests/test_unitary_generator.py b/referenceqvm/tests/test_unitary_generator.py index 5fc35f8..67f3ccd 100644 --- a/referenceqvm/tests/test_unitary_generator.py +++ b/referenceqvm/tests/test_unitary_generator.py @@ -212,8 +212,8 @@ def test_tensor_gates_two_qubit(): assert np.allclose(test_unitary, true_unitary) -def test_tensor_up(): - """Testing tesnor up type checking and correctness""" +def test_tensor_up_error_catch(): + """Testing tensor up type checking""" x_term = PauliTerm("X", 5) # testing type rejection @@ -224,22 +224,25 @@ def test_tensor_up(): with pytest.raises(IndexError): tensor_up(PauliSum([x_term]), 3) + +def test_tensor_up_correctness(): + """Check the correctness of the tensor up routine""" xy_term = PauliSum([PauliTerm("X", 0)*PauliTerm("Y", 1)]) # test correctness trial_matrix = tensor_up(xy_term, 2) true_matrix = np.kron(gate_matrix['Y'], gate_matrix['X']) - assert np.allclose(trial_matrix, true_matrix) + np.testing.assert_allclose(trial_matrix, true_matrix) x1_term = PauliSum([PauliTerm("X", 1)]) trial_matrix = tensor_up(x1_term, 2) true_matrix = np.kron(gate_matrix['X'], gate_matrix['I']) - assert np.allclose(trial_matrix, true_matrix) + np.testing.assert_allclose(trial_matrix, true_matrix) zpz_term = PauliTerm("Z", 0) + PauliTerm("Z", 1) trial_matrix = tensor_up(zpz_term, 2) true_matrix = np.zeros((4, 4)) true_matrix[0, 0] = 2 true_matrix[-1, -1] = -2 - assert np.allclose(trial_matrix, true_matrix) + np.testing.assert_allclose(trial_matrix, true_matrix) diff --git a/referenceqvm/unitary_generator.py b/referenceqvm/unitary_generator.py index a2bbc45..b6ced63 100644 --- a/referenceqvm/unitary_generator.py +++ b/referenceqvm/unitary_generator.py @@ -363,7 +363,17 @@ def tensor_up(pauli_terms, num_qubits): object. Useful for generating the full Hamiltonian after a particular fermion to - pauli transformation. + pauli transformation. For example: + + Converting a PauliSum X0Y1 + Y1X0 into the matrix + + .. code-block:: python + + [[ 0.+0.j, 0.+0.j, 0.+0.j, 0.-2.j], + [ 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], + [ 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], + [ 0.+2.j, 0.+0.j, 0.+0.j, 0.+0.j]] + :param pauli_terms: (PauliSum) object of PauliTerm :param num_qubits: (int) number of qubits in the system @@ -378,19 +388,15 @@ def tensor_up(pauli_terms, num_qubits): if max(term._ops.keys()) >= num_qubits: raise IndexError("pauli_terms has higher index than qubits") - big_hilbert = np.zeros((2 ** num_qubits, 2 ** num_qubits)) + big_hilbert = np.zeros((2 ** num_qubits, 2 ** num_qubits), dtype=complex) # left kronecker product corresponds to the correct basis ordering for term in pauli_terms.terms: - tmp_big_hilbert = np.array([1]) + tmp_big_hilbert = np.array([1]) for index in range(num_qubits): - pauli_mat = gate_matrix[term[index]] - - tmp_big_hilbert = np.kron(pauli_mat, tmp_big_hilbert) - - tmp_big_hilbert = tmp_big_hilbert * term.coefficient + tmp_big_hilbert = np.kron(gate_matrix[term[index]], tmp_big_hilbert) - big_hilbert = big_hilbert + tmp_big_hilbert + big_hilbert += tmp_big_hilbert * term.coefficient return big_hilbert From f934ca6844d5827ef5c3c02b187b10d434ec1e84 Mon Sep 17 00:00:00 2001 From: ncrubin Date: Fri, 6 Oct 2017 15:37:29 -0700 Subject: [PATCH 3/3] PR final changes --- referenceqvm/tests/test_unitary_generator.py | 2 +- referenceqvm/unitary_generator.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/referenceqvm/tests/test_unitary_generator.py b/referenceqvm/tests/test_unitary_generator.py index 67f3ccd..24df494 100644 --- a/referenceqvm/tests/test_unitary_generator.py +++ b/referenceqvm/tests/test_unitary_generator.py @@ -1,4 +1,5 @@ import pytest +import warnings import numpy as np from referenceqvm.unitary_generator import (lifted_gate, apply_gate, tensor_gates, tensor_up) @@ -245,4 +246,3 @@ def test_tensor_up_correctness(): true_matrix[0, 0] = 2 true_matrix[-1, -1] = -2 np.testing.assert_allclose(trial_matrix, true_matrix) - diff --git a/referenceqvm/unitary_generator.py b/referenceqvm/unitary_generator.py index b6ced63..15a7fe0 100644 --- a/referenceqvm/unitary_generator.py +++ b/referenceqvm/unitary_generator.py @@ -21,6 +21,7 @@ Note: uses SciPy sparse diagonal (DIA) representation to increase space and timeefficiency. """ +import warnings import scipy.sparse as sps from referenceqvm.gates import gate_matrix from pyquil.quilbase import * @@ -382,11 +383,11 @@ def tensor_up(pauli_terms, num_qubits): if not isinstance(pauli_terms, PauliSum): raise TypeError("can only tensor PauliSum") - if __debug__: - for term in pauli_terms.terms: - if len(term._ops.keys()) > 0: - if max(term._ops.keys()) >= num_qubits: - raise IndexError("pauli_terms has higher index than qubits") + # check if operator is valid w.r.t the input number of qubits + for term in pauli_terms.terms: + if len(term._ops.keys()) > 0: + if max(term._ops.keys()) >= num_qubits: + raise IndexError("pauli_terms has higher index than qubits") big_hilbert = np.zeros((2 ** num_qubits, 2 ** num_qubits), dtype=complex) # left kronecker product corresponds to the correct basis ordering