Repository: PennyLaneAI/pennylane
Pull request #1394 (2021-06-08 21:46:49)

Add function to compute sparse Hamiltonian matrix
**Context:**
This PR adds the function `sparse_hamiltonian` to the `utils.py` module of PennyLane. This function allows the users to compute the sparse matrix representation of a `qml.Hamiltonian` object.



codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1394?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#1394](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1394?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (6519755) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/e1a05d22eb38bbcfed18cbcbe3f245a0a1e39fe0?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (e1a05d2) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1394/graphs/tree.svg?width=650&height=150&src=pr&token=9S3YzlUgGk&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1394?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)

```diff
@@           Coverage Diff           @@
##           master    #1394   +/-   ##
=======================================
  Coverage   98.18%   98.18%           
=======================================
  Files         157      157           
  Lines       11687    11700   +13     
=======================================
+ Hits        11475    11488   +13     
  Misses        212      212           
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1394?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/utils.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1394/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3V0aWxzLnB5) | `96.42% <100.00%> (+0.29%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1394?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1394?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Last update [e1a05d2...6519755](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1394?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).


Patch:
diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md
index 949fc6d98..55a3086f8 100644
--- a/.github/CHANGELOG.md
+++ b/.github/CHANGELOG.md
@@ -2,6 +2,36 @@
 
 <h3>New features since last release</h3>
 
+* Added functionality to compute the sparse matrix representation of a `qml.Hamiltonian` object.
+  [(#1394)](https://github.com/PennyLaneAI/pennylane/pull/1394)
+
+  Example:
+  
+  ```python
+  coeffs = [1, -0.45]
+  obs = [qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliY(0) @ qml.PauliZ(1)]
+  H = qml.Hamiltonian(coeffs, obs)
+  H_sparse = sparse_hamiltonian(H)
+  ```
+ 
+  The resulting matrix is a sparse matrix in scipy coordinate list (COO) format:
+
+  ```python
+  >>> H_sparse
+  <4x4 sparse matrix of type '<class 'numpy.complex128'>'
+      with 2 stored elements in COOrdinate format>
+  ```
+
+  The sparse matrix can be converted to an array as:
+
+  ```python
+  >>> H_sparse.toarray()
+  array([[ 1.+0.j  ,  0.+0.j  ,  0.+0.45j,  0.+0.j  ],
+         [ 0.+0.j  , -1.+0.j  ,  0.+0.j  ,  0.-0.45j],
+         [ 0.-0.45j,  0.+0.j  , -1.+0.j  ,  0.+0.j  ],
+         [ 0.+0.j  ,  0.+0.45j,  0.+0.j  ,  1.+0.j  ]])
+  ```
+
 * The `specs` QNode transform creates a function that produces the specifications for a circuit
   at given arguments and keywords. Specifications can also be viewed after execution of a QNode or
   tape by accessing their `specs` property. 
diff --git a/pennylane/utils.py b/pennylane/utils.py
index 8ee746eaf..cc0de309f 100644
--- a/pennylane/utils.py
+++ b/pennylane/utils.py
@@ -25,6 +25,7 @@ from operator import matmul
 import warnings
 
 import numpy as np
+import scipy
 
 import pennylane as qml
 
@@ -105,6 +106,56 @@ def decompose_hamiltonian(H, hide_identity=False):
     return coeffs, obs
 
 
+def sparse_hamiltonian(H):
+    r"""Computes the sparse matrix representation a Hamiltonian in the computational basis.
+
+    Args:
+        H (~.Hamiltonian): Hamiltonian operator for which the matrix representation should be
+         computed
+
+    Returns:
+        coo_matrix: a sparse matrix in scipy coordinate list (COO) format with dimension
+        :math:`(2^n, 2^n)`, where :math:`n` is the number of wires
+
+    **Example:**
+
+    This function can be used by passing a `qml.Hamiltonian` object as:
+
+    >>> coeffs = [1, -0.45]
+    >>> obs = [qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliY(0) @ qml.PauliZ(1)]
+    >>> H = qml.Hamiltonian(coeffs, obs)
+    >>> H_sparse = sparse_hamiltonian(H)
+    >>> H_sparse
+    <4x4 sparse matrix of type '<class 'numpy.complex128'>'
+        with 2 stored elements in COOrdinate format>
+
+    The resulting sparse matrix can be either used directly or transformed into a numpy array:
+
+    >>> H_sparse.toarray()
+    array([[ 1.+0.j  ,  0.+0.j  ,  0.+0.45j,  0.+0.j  ],
+           [ 0.+0.j  , -1.+0.j  ,  0.+0.j  ,  0.-0.45j],
+           [ 0.-0.45j,  0.+0.j  , -1.+0.j  ,  0.+0.j  ],
+           [ 0.+0.j  ,  0.+0.45j,  0.+0.j  ,  1.+0.j  ]])
+    """
+    if not isinstance(H, qml.Hamiltonian):
+        raise TypeError("Passed Hamiltonian must be of type `qml.Hamiltonian`")
+
+    n = len(H.wires)
+    matrix = scipy.sparse.coo_matrix((2 ** n, 2 ** n), dtype="complex128")
+
+    for coeffs, ops in zip(H.coeffs, H.ops):
+
+        obs = [scipy.sparse.coo_matrix(o.matrix) for o in qml.operation.Tensor(ops).obs]
+        mat = [scipy.sparse.eye(2, format="coo")] * n
+
+        for i, j in enumerate(ops.wires):
+            mat[j] = obs[i]
+
+        matrix += functools.reduce(lambda i, j: scipy.sparse.kron(i, j, format="coo"), mat) * coeffs
+
+    return matrix.tocoo()
+
+
 def _flatten(x):
     """Iterate recursively through an arbitrarily nested structure in depth-first order.
 


Test patch:
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 201fa9e35..4ac4f60e3 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -24,6 +24,7 @@ import numpy as np
 import pennylane as qml
 import pennylane.queuing
 import pennylane.utils as pu
+import scipy.sparse
 
 from pennylane import Identity, PauliX, PauliY, PauliZ
 from pennylane.operation import Tensor
@@ -135,6 +136,72 @@ class TestDecomposition:
         assert np.allclose(hamiltonian, linear_comb)
 
 
+class TestSparse:
+    """Tests the sparse_hamiltonian function"""
+
+    @pytest.mark.parametrize(
+        ("coeffs", "obs", "ref_matrix"),
+        [
+            (
+                [1, -0.45],
+                [qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliY(0) @ qml.PauliZ(1)],
+                np.array(
+                    [
+                        [1.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.45j, 0.0 + 0.0j],
+                        [0.0 + 0.0j, -1.0 + 0.0j, 0.0 + 0.0j, 0.0 - 0.45j],
+                        [0.0 - 0.45j, 0.0 + 0.0j, -1.0 + 0.0j, 0.0 + 0.0j],
+                        [0.0 + 0.0j, 0.0 + 0.45j, 0.0 + 0.0j, 1.0 + 0.0j],
+                    ]
+                ),
+            ),
+            (
+                [0.21, -0.78, 0.52],
+                [
+                    qml.PauliZ(0) @ qml.PauliZ(1),
+                    qml.PauliX(0) @ qml.PauliZ(1),
+                    qml.PauliY(0) @ qml.PauliZ(1),
+                ],
+                np.array(
+                    [
+                        [0.21 + 0.0j, 0.0 + 0.0j, -0.78 - 0.52j, 0.0 + 0.0j],
+                        [0.0 + 0.0j, -0.21 + 0.0j, 0.0 + 0.0j, 0.78 + 0.52j],
+                        [-0.78 + 0.52j, 0.0 + 0.0j, -0.21 + 0.0j, 0.0 + 0.0j],
+                        [0.0 + 0.0j, 0.78 - 0.52j, 0.0 + 0.0j, 0.21 + 0.0j],
+                    ]
+                ),
+            ),
+        ],
+    )
+    def test_sparse_matrix(self, coeffs, obs, ref_matrix):
+        """Tests that sparse_hamiltonian returns a correct sparse matrix"""
+        H = qml.Hamiltonian(coeffs, obs)
+
+        sparse_matrix = qml.utils.sparse_hamiltonian(H)
+
+        assert np.allclose(sparse_matrix.toarray(), ref_matrix)
+
+    def test_sparse_format(self):
+        """Tests that sparse_hamiltonian returns a scipy.sparse.coo_matrix object"""
+
+        coeffs = [-0.25, 0.75]
+        obs = [
+            qml.PauliX(wires=[0]) @ qml.PauliZ(wires=[1]),
+            qml.PauliY(wires=[0]) @ qml.PauliZ(wires=[1]),
+        ]
+        H = qml.Hamiltonian(coeffs, obs)
+
+        sparse_matrix = qml.utils.sparse_hamiltonian(H)
+
+        assert isinstance(sparse_matrix, scipy.sparse.coo_matrix)
+
+    def test_sparse_typeerror(self):
+        """Tests that sparse_hamiltonian raises an error if the given Hamiltonian is not of type
+        `qml.Hamiltonian`"""
+
+        with pytest.raises(TypeError, match="Passed Hamiltonian must be of type"):
+            qml.utils.sparse_hamiltonian(np.eye(2))
+
+
 class TestFlatten:
     """Tests the flatten and unflatten functions"""