Repository: PennyLaneAI/pennylane
Pull request #1142 (2021-03-16 16:42:21)

Adds Hamiltonian Queueing and Transform to Compute Hamiltonian Expectation Value
**IMPORTANT NOTE**

Currently, there is a small issue with this PR: when initializing a Hamiltonian using the arithmetic operations between Observables/Tensors/other Hamiltonians, the queue gets slightly confused, and Observables with no owners get added to the queue (for reasons I have yet to fully understand).

As a result, I have had to comment out an error to get things to work properly. Funnily enough, if we didn't have this error implemented, then everything would work fine (as Observables without owners that are added to the queue have no effect on the results of running the circuit). Suggestions as to how we can deal with this issue are much appreciated!

**Context:**

**Description of the Change:**

- Small updates to allow ``qml.Hamiltonian`` instances to be queued in the quantum tape.
- Adds a transform which allows mapping of a tape ending in the expectation value of a Hamiltonian to multiple tapes, which can be recombined to compute the expectation value after execution.

For instance, we can define the following tape:

```python3
with qml.tape.QuantumTape() as tape:
     qml.PauliX(wires=1)
     H = qml.Hamiltonian([1, 2, 1], [qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliX(1), qml.PauliZ(2)])
     qml.expval(H)
```

Then, we can map it to multiple tapes, corresponding to different terms of the Hamiltonian

```python3
tapes, output_fn = qml.tape.transforms.hamiltonian_expval(tape)
```

Finally, we can execute each of the tapes and pass them through a post-processing function to return the expectation value:

```python3
dev = qml.device('default.qubit', wires=3)

results = dev.batch_execute(tapes)
expval = output_fn(results)
```

**Benefits:**

- More intuitive to use than `qml.ExpvalCost`, which does something similar (but instead splits the QNode into a QNodeCollection, allowing for parallelization)

**Possible Drawbacks:**

None

**Related GitHub Issues:**

codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1142?src=pr&el=h1) Report
> Merging [#1142](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1142?src=pr&el=desc) (cc11e53) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/c701627d2c5ee24adc752c0d4013e9a5d777e259?el=desc) (c701627) will **decrease** coverage by `0.29%`.
> The diff coverage is `32.72%`.

[![Impacted file tree graph](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1142/graphs/tree.svg?width=650&height=150&src=pr&token=9S3YzlUgGk)](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1142?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1142      +/-   ##
==========================================
- Coverage   97.78%   97.48%   -0.30%     
==========================================
  Files         156      157       +1     
  Lines       11900    11954      +54     
==========================================
+ Hits        11636    11653      +17     
- Misses        264      301      +37     
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1142?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pennylane/tape/operation.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1142/diff?src=pr&el=tree#diff-cGVubnlsYW5lL3RhcGUvb3BlcmF0aW9uLnB5) | `87.01% <18.18%> (-11.48%)` | :arrow_down: |
| [pennylane/tape/transforms/hamiltonian\_expval.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1142/diff?src=pr&el=tree#diff-cGVubnlsYW5lL3RhcGUvdHJhbnNmb3Jtcy9oYW1pbHRvbmlhbl9leHB2YWwucHk=) | `20.00% <20.00%> (ø)` | |
| [pennylane/vqe/vqe.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1142/diff?src=pr&el=tree#diff-cGVubnlsYW5lL3ZxZS92cWUucHk=) | `90.40% <50.00%> (-3.56%)` | :arrow_down: |
| [pennylane/tape/measure.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1142/diff?src=pr&el=tree#diff-cGVubnlsYW5lL3RhcGUvbWVhc3VyZS5weQ==) | `91.95% <100.00%> (+0.09%)` | :arrow_up: |
| [pennylane/tape/transforms/\_\_init\_\_.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1142/diff?src=pr&el=tree#diff-cGVubnlsYW5lL3RhcGUvdHJhbnNmb3Jtcy9fX2luaXRfXy5weQ==) | `100.00% <100.00%> (ø)` | |

------

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

josh146: Thanks @Lucaman99! Let me know when this is ready for review :)
Lucaman99: Hey @ixfoduap @josh146, I think this PR is pretty much ready for review! 

The only thing I haven't done yet is the tests for the actual queuing of the Hamiltonians, as this might change (since the queuing is currently causing some minor issues, as are described above). 

Other than that, the functionality is done, and the tests/docstring for the `hamiltonian_expval` transform are done!
josh146: @Lucaman99 a sudden thought - what happens if you try and draw a tape that has `expval(H)`? Does it work?

Patch:
diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md
index b58779bb7..27ac6c586 100644
--- a/.github/CHANGELOG.md
+++ b/.github/CHANGELOG.md
@@ -227,6 +227,30 @@ random_mat2 = rng.standard_normal(3, requires_grad=False)
 * PennyLane's test suite is now code-formatted using `black -l 100`.
   [(#1222)](https://github.com/PennyLaneAI/pennylane/pull/1222)
 
+* Adds a `hamiltonian_expand` tape transform. This takes a tape ending in 
+  `qml.expval(H)`, where `H` is a Hamiltonian, and maps it to a collection 
+  of tapes which can be executed and passed into a post-processing function yielding 
+  the expectation value.
+  [(#1142)](https://github.com/PennyLaneAI/pennylane/pull/1142)
+   
+  Example use:
+    
+  ```python
+  H = qml.PauliZ(0) + 3 * qml.PauliZ(0) @ qml.PauliX(1)
+  
+  with qml.tape.QuantumTape() as tape:  
+      qml.Hadamard(wires=1)
+      return qml.expval(H)
+  
+  tapes, fn = qml.transforms.hamiltonian_expand(tape)
+  dev = qml.device("default.qubit", wires=3)
+  res = dev.batch_execute(tapes)
+  ```
+  ```pycon
+  >>> fn(res)
+  4.0
+  ```
+
 * PennyLane's `qchem` package and tests are now code-formatted using `black -l 100`.
   [(#1311)](https://github.com/PennyLaneAI/pennylane/pull/1311)
 
@@ -272,8 +296,8 @@ random_mat2 = rng.standard_normal(3, requires_grad=False)
 
 This release contains contributions from (in alphabetical order):
 
-Marius Aglitoiu, Vishnu Ajith, Thomas Bromley, Olivia Di Matteo, Tanya Garg, Diego Guala,
-Anthony Hayes, Josh Izaac, Pavan Jayasinha, Ryan Levy, Nahum Sá, Brian Shi, Antal Száva, Vincent Wong
+Marius Aglitoiu, Vishnu Ajith, Thomas Bromley, Jack Ceroni, Olivia Di Matteo, Tanya Garg, Diego Guala,
+Anthony Hayes, Josh Izaac, Pavan Jayasinha, Ryan Levy, Nahum Sá, Brian Shi, Antal Száva, Vincent Wong.
 
 # Release 0.15.1 (current release)
 
@@ -841,7 +865,7 @@ fully differentiable.
 * Due to the addition of `density_matrix()` as a return type from a QNode, tuples are now supported
   by the `output_dim` parameter in `qnn.KerasLayer`.
   [(#1070)](https://github.com/PennyLaneAI/pennylane/pull/1070)
-
+  
 * Two new utility methods are provided for working with quantum tapes.
   [(#1175)](https://github.com/PennyLaneAI/pennylane/pull/1175)
 
diff --git a/pennylane/measure.py b/pennylane/measure.py
index 3d3410243..54e5b17bd 100644
--- a/pennylane/measure.py
+++ b/pennylane/measure.py
@@ -228,9 +228,9 @@ def expval(op):
     Raises:
         QuantumFunctionError: `op` is not an instance of :class:`~.Observable`
     """
-    if not isinstance(op, Observable):
+    if not isinstance(op, (Observable, qml.Hamiltonian)):
         raise qml.QuantumFunctionError(
-            "{} is not an observable: cannot be used with expval".format(op.name)
+            "{} is not an observable or Hamiltonian: cannot be used with expval".format(op.name)
         )
 
     return MeasurementProcess(Expectation, obs=op)
diff --git a/pennylane/operation.py b/pennylane/operation.py
index 03a355899..422ce461d 100644
--- a/pennylane/operation.py
+++ b/pennylane/operation.py
@@ -1062,6 +1062,7 @@ class Observable(Operator):
     def __mul__(self, a):
         r"""The scalar multiplication operation between a scalar and an Observable/Tensor."""
         if isinstance(a, (int, float)):
+
             return qml.Hamiltonian([a], [self], simplify=True)
 
         raise ValueError(f"Cannot multiply Observable by {type(a)}")
diff --git a/pennylane/transforms/__init__.py b/pennylane/transforms/__init__.py
index 9d5e06717..a1d9a3a2c 100644
--- a/pennylane/transforms/__init__.py
+++ b/pennylane/transforms/__init__.py
@@ -1,64 +1,66 @@
-# Copyright 2018-2021 Xanadu Quantum Technologies Inc.
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-#     http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-This subpackage contains QNode, quantum function, device, and tape transforms.
-
-
-.. currentmodule:: pennylane
-
-QNode transforms
-----------------
-
-The following transforms act on QNodes. They return new transformed functions
-that compute the desired quantity.
-
-.. autosummary::
-    :toctree: api
-
-    ~transforms.classical_jacobian
-    ~draw
-    ~metric_tensor
-
-Quantum function transforms
----------------------------
-
-The following transforms act on quantum functions (Python functions
-containing quantum operations) that are used *inside* QNodes.
-
-.. autosummary::
-    :toctree: api
-
-    ~adjoint
-    ~ctrl
-    ~transforms.invisible
-
-Tape transforms
----------------
-
-The following transforms act on quantum tapes, and return one or
-more tapes as well as a classical processing function.
-
-.. autosummary::
-    :toctree: api
-
-    ~transforms.measurement_grouping
-    ~transforms.metric_tensor_tape
-"""
-from .adjoint import adjoint
-from .classical_jacobian import classical_jacobian
-from .control import ControlledOperation, ctrl
-from .draw import draw
-from .invisible import invisible
-from .measurement_grouping import measurement_grouping
-from .metric_tensor import metric_tensor, metric_tensor_tape
+# Copyright 2018-2021 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+This subpackage contains QNode, quantum function, device, and tape transforms.
+
+
+.. currentmodule:: pennylane
+
+QNode transforms
+----------------
+
+The following transforms act on QNodes. They return new transformed functions
+that compute the desired quantity.
+
+.. autosummary::
+    :toctree: api
+
+    ~transforms.classical_jacobian
+    ~draw
+    ~metric_tensor
+
+Quantum function transforms
+---------------------------
+
+The following transforms act on quantum functions (Python functions
+containing quantum operations) that are used *inside* QNodes.
+
+.. autosummary::
+    :toctree: api
+
+    ~adjoint
+    ~ctrl
+    ~transforms.invisible
+
+Tape transforms
+---------------
+
+The following transforms act on quantum tapes, and return one or
+more tapes as well as a classical processing function.
+
+.. autosummary::
+    :toctree: api
+
+    ~transforms.measurement_grouping
+    ~transforms.metric_tensor_tape
+    ~transforms.hamiltonian_expand
+"""
+from .adjoint import adjoint
+from .classical_jacobian import classical_jacobian
+from .control import ControlledOperation, ctrl
+from .draw import draw
+from .invisible import invisible
+from .measurement_grouping import measurement_grouping
+from .metric_tensor import metric_tensor, metric_tensor_tape
+from .hamiltonian_expand import hamiltonian_expand
diff --git a/pennylane/transforms/hamiltonian_expand.py b/pennylane/transforms/hamiltonian_expand.py
new file mode 100644
index 000000000..305258f9e
--- /dev/null
+++ b/pennylane/transforms/hamiltonian_expand.py
@@ -0,0 +1,82 @@
+# Copyright 2018-2021 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+Contains the hamiltonian expand tape transform
+"""
+import pennylane as qml
+
+
+def hamiltonian_expand(tape):
+    r"""
+    Returns a list of tapes, and a classical processing function, for computing the expectation
+    value of a Hamiltonian.
+
+    Args:
+        tape (.QuantumTape) the tape used when calculating the expectation value
+            of the Hamiltonian.
+
+    Returns:
+        tuple[list[.QuantumTape], function]: Returns a tuple containing a list of
+        quantum tapes to be evaluated, and a function to be applied to these
+        tape executions to compute the expectation value.
+
+    **Example**
+
+    Given a Hamiltonian,
+
+    .. code-block:: python3
+
+        H = qml.PauliY(2) @ qml.PauliZ(1) + 0.5 * qml.PauliZ(2) + qml.PauliZ(1)
+
+    and a tape of the form,
+
+    .. code-block:: python3
+
+        with qml.tape.QuantumTape() as tape:
+            qml.Hadamard(wires=0)
+            qml.CNOT(wires=[0, 1])
+            qml.PauliX(wires=2)
+
+            qml.expval(H)
+
+    We can use the ``hamiltonian_expand`` transform to generate new tapes and a classical
+    post-processing function for computing the expectation value of the Hamiltonian.
+
+    >>> tapes, fn = qml.transforms.hamiltonian_expand(tape)
+
+    We can evaluate these tapes on a device:
+
+    >>> dev = qml.device("default.qubit", wires=3)
+    >>> res = dev.batch_execute(tapes)
+
+    Applying the processing function results in the expectation value of the Hamiltonian:
+
+    >>> fn(res)
+    -0.5
+
+    .. Warning::
+
+         Note that defining Hamiltonians inside of QNodes using arithmetic can lead to errors.
+         See :class:`~pennylane.Hamiltonian` for more information.
+    """
+
+    hamiltonian = tape.measurements[0].obs
+
+    if not isinstance(hamiltonian, qml.Hamiltonian) or len(tape.measurements) > 1:
+        raise ValueError(
+            "Passed tape must end in `qml.expval(H)`, where H is of type `qml.Hamiltonian`"
+        )
+
+    hamiltonian.simplify()
+    return qml.transforms.measurement_grouping(tape, hamiltonian.ops, hamiltonian.coeffs)
diff --git a/pennylane/transforms/measurement_grouping.py b/pennylane/transforms/measurement_grouping.py
index 3e2e992c0..82ff3dee9 100644
--- a/pennylane/transforms/measurement_grouping.py
+++ b/pennylane/transforms/measurement_grouping.py
@@ -89,6 +89,9 @@ def measurement_grouping(tape, obs_list, coeffs_list):
         tapes.append(new_tape)
 
     def processing_fn(res):
-        return qml.math.sum([qml.math.dot(c, r) for c, r in zip(coeffs_groupings, res)])
+        dot_products = [
+            qml.math.dot(qml.math.convert_like(c, r), r) for c, r in zip(coeffs_groupings, res)
+        ]
+        return qml.math.sum(qml.math.stack(dot_products))
 
     return tapes, processing_fn
diff --git a/pennylane/vqe/vqe.py b/pennylane/vqe/vqe.py
index 695ce354d..253294574 100644
--- a/pennylane/vqe/vqe.py
+++ b/pennylane/vqe/vqe.py
@@ -23,6 +23,7 @@ import warnings
 import pennylane as qml
 from pennylane import numpy as np
 from pennylane.operation import Observable, Tensor
+from pennylane.queuing import QueuingError
 from pennylane.wires import Wires
 
 OBS_MAP = {"PauliX": "X", "PauliY": "Y", "PauliZ": "Z", "Hadamard": "H", "Identity": "I"}
@@ -71,6 +72,22 @@ class Hamiltonian:
     Alternatively, the :func:`~.molecular_hamiltonian` function from the
     :doc:`/introduction/chemistry` module can be used to generate a molecular
     Hamiltonian.
+
+    .. Warning::
+
+        Hamiltonians can be constructed using Pythonic arithmetic operations. For example:
+
+        >>> qml.PauliX(0) + 2 * qml.PauliZ(0) @ qml.PauliZ(1)
+
+        is equivalent to the following Hamiltonian:
+
+        >>> qml.Hamiltonian([1, 2], [qml.PauliX(0), qml.PauliZ(0) @ qml.PauliZ(1)])
+
+        When Hamiltonians are defined using arithmetic operations **inside of QNodes**, constituent observables
+        may be queued as operations/an error may be thrown. Thus, Hamiltonians must be defined either outside of QNodes,
+        or inside of QNodes using the conventional method.
+
+        Note that this issue also arises when calling the ``simplify()`` method.
     """
 
     def __init__(self, coeffs, observables, simplify=False):
@@ -95,9 +112,14 @@ class Hamiltonian:
         self._coeffs = list(coeffs)
         self._ops = list(observables)
 
+        self.data = []
+        self.return_type = None
+
         if simplify:
             self.simplify()
 
+        self.queue()
+
     @property
     def coeffs(self):
         """Return the coefficients defining the Hamiltonian.
@@ -134,6 +156,10 @@ class Hamiltonian:
         """
         return qml.wires.Wires.all_wires([op.wires for op in self.ops], sort=True)
 
+    @property
+    def name(self):
+        return "Hamiltonian"
+
     def simplify(self):
         r"""Simplifies the Hamiltonian by combining like-terms.
 
@@ -146,7 +172,6 @@ class Hamiltonian:
           (-1) [X0]
         + (1) [Y2]
         """
-
         coeffs = []
         ops = []
 
@@ -194,6 +219,10 @@ class Hamiltonian:
 
         return "  " + "\n+ ".join(terms_ls)
 
+    def __repr__(self):
+        # Constructor-call-like representation
+        return f"<Hamiltonian: terms={len(self.coeffs)}, wires={self.wires.tolist()}>"
+
     def _obs_data(self):
         r"""Extracts the data from a Hamiltonian and serializes it in an order-independent fashion.
 
@@ -375,6 +404,20 @@ class Hamiltonian:
             return self
         raise ValueError(f"Cannot subtract {type(H)} from Hamiltonian")
 
+    def queue(self):
+        """Queues a qml.Hamiltonian instance"""
+        for o in self.ops:
+            try:
+                qml.QueuingContext.update_info(o, owner=self)
+            except QueuingError:
+                o.queue()
+                qml.QueuingContext.update_info(o, owner=self)
+            except NotImplementedError:
+                pass
+
+        qml.QueuingContext.append(self, owns=tuple(self.ops))
+        return self
+
 
 class ExpvalCost:
     """Create a cost function that gives the expectation value of an input Hamiltonian.


Test patch:
diff --git a/tests/test_vqe.py b/tests/test_vqe.py
index 6999d02f2..a8fc91906 100644
--- a/tests/test_vqe.py
+++ b/tests/test_vqe.py
@@ -92,6 +92,20 @@ valid_hamiltonians_str = [
     "  (0.5) [X0]\n+ (1.2) [X0 X1]",
 ]
 
+valid_hamiltonians_repr = [
+    "<Hamiltonian: terms=1, wires=[0, 1]>",
+    "<Hamiltonian: terms=1, wires=[0]>",
+    "<Hamiltonian: terms=1, wires=[0, 1]>",
+    "<Hamiltonian: terms=2, wires=[0, 1]>",
+    "<Hamiltonian: terms=2, wires=[1]>",
+    "<Hamiltonian: terms=2, wires=['a', 'b']>",
+    "<Hamiltonian: terms=3, wires=[0, 2]>",
+    "<Hamiltonian: terms=2, wires=[0, 1, 2]>",
+    "<Hamiltonian: terms=2, wires=[0, 2]>",
+    "<Hamiltonian: terms=2, wires=[0, 1]>",
+    "<Hamiltonian: terms=2, wires=[0, 1]>",
+]
+
 invalid_hamiltonians = [
     ((), (qml.PauliZ(0),)),
     ((), (qml.PauliZ(0), qml.PauliY(1))),
@@ -586,6 +600,42 @@ def mock_device(monkeypatch):
         yield get_device
 
 
+#####################################################
+# Queues
+
+QUEUE_HAMILTONIANS_1 = [
+    qml.Hamiltonian([1, 1], [qml.PauliX(0), qml.PauliZ(1)]),
+    qml.Hamiltonian([1, 1], [qml.PauliX(0), qml.PauliZ(1)]),
+]
+
+QUEUE_HAMILTONIANS_2 = [
+    qml.Hamiltonian([1], [qml.PauliX(0)]),
+    qml.Hamiltonian([5], [qml.PauliX(0) @ qml.PauliZ(1)]),
+]
+
+QUEUES = [
+    [
+        qml.PauliX(0),
+        qml.PauliZ(1),
+        qml.Hamiltonian([1, 1], [qml.PauliX(0), qml.PauliZ(1)]),
+        qml.PauliX(0),
+        qml.Hamiltonian([1], [qml.PauliX(0)]),
+        qml.Hamiltonian([2, 1], [qml.PauliX(0), qml.PauliZ(1)]),
+    ],
+    [
+        qml.PauliX(0),
+        qml.PauliZ(1),
+        qml.Hamiltonian([1, 1], [qml.PauliX(0), qml.PauliZ(1)]),
+        qml.PauliX(0),
+        qml.PauliZ(1),
+        qml.PauliX(0) @ qml.PauliZ(1),
+        qml.Hamiltonian([1], [qml.PauliX(0) @ qml.PauliZ(1)]),
+        qml.Hamiltonian([1, 1, 2], [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(0) @ qml.PauliZ(1)]),
+    ],
+]
+
+add_queue = zip(QUEUE_HAMILTONIANS_1, QUEUE_HAMILTONIANS_2, QUEUES)
+
 #####################################################
 # Tests
 
@@ -639,6 +689,17 @@ class TestHamiltonian:
         H = qml.vqe.Hamiltonian(*terms)
         assert H.__str__() == string
 
+    @pytest.mark.parametrize("terms, string", zip(valid_hamiltonians, valid_hamiltonians_repr))
+    def test_hamiltonian_repr(self, terms, string):
+        """Tests that the __repr__ function for printing is correct"""
+        H = qml.vqe.Hamiltonian(*terms)
+        assert H.__repr__() == string
+
+    def test_hamiltonian_name(self):
+        """Tests the name property of the Hamiltonian class"""
+        H = qml.vqe.Hamiltonian([], [])
+        assert H.name == "Hamiltonian"
+
     @pytest.mark.parametrize(("old_H", "new_H"), simplify_hamiltonians)
     def test_simplify(self, old_H, new_H):
         """Tests the simplify method"""
@@ -741,6 +802,55 @@ class TestHamiltonian:
         with pytest.raises(ValueError, match="Cannot subtract"):
             H -= A
 
+    def test_hamiltonian_queue(self):
+        """Tests that Hamiltonian are queued correctly"""
+
+        # Outside of tape
+
+        queue = [
+            qml.Hadamard(wires=1),
+            qml.PauliX(wires=0),
+            qml.PauliZ(0) @ qml.PauliZ(2),
+            qml.PauliX(1),
+            qml.PauliZ(1),
+            qml.Hamiltonian(
+                [1, 3, 1], [qml.PauliX(1), qml.PauliZ(0) @ qml.PauliZ(2), qml.PauliZ(1)]
+            ),
+        ]
+
+        H = qml.PauliX(1) + 3 * qml.PauliZ(0) @ qml.PauliZ(2) + qml.PauliZ(1)
+
+        with qml.tape.QuantumTape() as tape:
+            qml.Hadamard(wires=1)
+            qml.PauliX(wires=0)
+            H.queue()
+
+        assert np.all([q1.compare(q2) for q1, q2 in zip(tape.queue, queue)])
+
+        # Inside of tape
+
+        queue = [
+            qml.PauliX(1),
+            qml.PauliZ(0),
+            qml.PauliZ(2),
+            qml.PauliZ(0) @ qml.PauliZ(2),
+            qml.PauliZ(1),
+            qml.Hamiltonian(
+                [1, 3, 1], [qml.PauliX(1), qml.PauliZ(0) @ qml.PauliZ(2), qml.PauliZ(1)]
+            ),
+            qml.Hadamard(wires=1),
+            qml.PauliX(wires=0),
+        ]
+
+        with qml.tape.QuantumTape() as tape:
+            H = qml.Hamiltonian(
+                [1, 3, 1], [qml.PauliX(1), qml.PauliZ(0) @ qml.PauliZ(2), qml.PauliZ(1)]
+            )
+            qml.Hadamard(wires=1)
+            qml.PauliX(wires=0)
+
+        assert np.all([q1.compare(q2) for q1, q2 in zip(tape.queue, queue)])
+
 
 class TestVQE:
     """Test the core functionality of the VQE module"""
diff --git a/tests/transforms/test_hamiltonian_expand.py b/tests/transforms/test_hamiltonian_expand.py
new file mode 100644
index 000000000..2b8d080f6
--- /dev/null
+++ b/tests/transforms/test_hamiltonian_expand.py
@@ -0,0 +1,176 @@
+# Copyright 2018-2020 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import pytest
+import numpy as np
+import pennylane as qml
+import pennylane.tape
+from pennylane.interfaces.autograd import AutogradInterface
+
+"""Defines the device used for all tests"""
+
+dev = qml.device("default.qubit", wires=4)
+
+"""Defines circuits to be used in queueing/output tests"""
+
+with pennylane.tape.QuantumTape() as tape1:
+    qml.PauliX(0)
+    H1 = qml.Hamiltonian([1.5], [qml.PauliZ(0) @ qml.PauliZ(1)])
+    qml.expval(H1)
+
+with pennylane.tape.QuantumTape() as tape2:
+    qml.Hadamard(0)
+    qml.Hadamard(1)
+    qml.PauliZ(1)
+    qml.PauliX(2)
+
+    H2 = qml.Hamiltonian(
+        [1, 3, -2, 1, 1],
+        [
+            qml.PauliX(0) @ qml.PauliZ(2),
+            qml.PauliZ(2),
+            qml.PauliX(0),
+            qml.PauliX(2),
+            qml.PauliZ(0) @ qml.PauliX(1),
+        ],
+    )
+    qml.expval(H2)
+
+H3 = 1.5 * qml.PauliZ(0) @ qml.PauliZ(1) + 0.3 * qml.PauliX(1)
+
+with qml.tape.QuantumTape() as tape3:
+    qml.PauliX(0)
+    qml.expval(H3)
+
+
+H4 = (
+    qml.PauliX(0) @ qml.PauliZ(2)
+    + 3 * qml.PauliZ(2)
+    - 2 * qml.PauliX(0)
+    + qml.PauliZ(2)
+    + qml.PauliZ(2)
+)
+H4 += qml.PauliZ(0) @ qml.PauliX(1) @ qml.PauliY(2)
+
+with qml.tape.QuantumTape() as tape4:
+    qml.Hadamard(0)
+    qml.Hadamard(1)
+    qml.PauliZ(1)
+    qml.PauliX(2)
+
+    qml.expval(H4)
+
+TAPES = [tape1, tape2, tape3, tape4]
+OUTPUTS = [-1.5, -6, -1.5, -8]
+
+
+class TestHamiltonianExpval:
+    """Tests for the hamiltonian_expand transform"""
+
+    @pytest.mark.parametrize(("tape", "output"), zip(TAPES, OUTPUTS))
+    def test_hamiltonians(self, tape, output):
+        """Tests that the hamiltonian_expand transform returns the correct value"""
+
+        tapes, fn = qml.transforms.hamiltonian_expand(tape)
+        results = dev.batch_execute(tapes)
+        expval = fn(results)
+
+        assert np.isclose(output, expval)
+
+    def test_hamiltonian_error(self):
+
+        with pennylane.tape.QuantumTape() as tape:
+            qml.expval(qml.PauliZ(0))
+
+        with pytest.raises(ValueError, match=r"Passed tape must end in"):
+            tapes, fn = qml.transforms.hamiltonian_expand(tape)
+
+    def test_hamiltonian_dif_autograd(self):
+        """Tests that the hamiltonian_expand tape transform is differentiable with the Autograd interface"""
+
+        H = qml.Hamiltonian(
+            [-0.2, 0.5, 1], [qml.PauliX(1), qml.PauliZ(1) @ qml.PauliY(2), qml.PauliZ(0)]
+        )
+        var = np.array([0.1, 0.67, 0.3, 0.4, -0.5, 0.7])
+        output = 0.42294409781940356
+        output2 = [
+            9.68883500e-02,
+            -2.90832724e-01,
+            -1.04448033e-01,
+            -1.94289029e-09,
+            3.50307411e-01,
+            -3.41123470e-01,
+        ]
+
+        with qml.tape.JacobianTape() as tape:
+            for i in range(2):
+                qml.RX(np.array(0), wires=0)
+                qml.RX(np.array(0), wires=1)
+                qml.RX(np.array(0), wires=2)
+                qml.CNOT(wires=[0, 1])
+                qml.CNOT(wires=[1, 2])
+                qml.CNOT(wires=[2, 0])
+
+            qml.expval(H)
+
+        AutogradInterface.apply(tape)
+
+        def cost(x):
+            tape.set_parameters(x, trainable_only=False)
+            tapes, fn = qml.transforms.hamiltonian_expand(tape)
+            res = [t.execute(dev) for t in tapes]
+            return fn(res)
+
+        assert np.isclose(cost(var), output)
+        assert np.allclose(qml.grad(cost)(var), output2)
+
+    def test_hamiltonian_dif_tensorflow(self):
+        """Tests that the hamiltonian_expand tape transform is differentiable with the Tensorflow interface"""
+
+        tf = pytest.importorskip("tensorflow")
+        from pennylane.interfaces.tf import TFInterface
+
+        H = qml.Hamiltonian(
+            [-0.2, 0.5, 1], [qml.PauliX(1), qml.PauliZ(1) @ qml.PauliY(2), qml.PauliZ(0)]
+        )
+        var = tf.Variable([[0.1, 0.67, 0.3], [0.4, -0.5, 0.7]], dtype=tf.float64)
+        output = 0.42294409781940356
+        output2 = [
+            9.68883500e-02,
+            -2.90832724e-01,
+            -1.04448033e-01,
+            -1.94289029e-09,
+            3.50307411e-01,
+            -3.41123470e-01,
+        ]
+
+        with tf.GradientTape() as gtape:
+            with qml.tape.JacobianTape() as tape:
+                for i in range(2):
+                    qml.RX(var[i, 0], wires=0)
+                    qml.RX(var[i, 1], wires=1)
+                    qml.RX(var[i, 2], wires=2)
+                    qml.CNOT(wires=[0, 1])
+                    qml.CNOT(wires=[1, 2])
+                    qml.CNOT(wires=[2, 0])
+                qml.expval(H)
+
+            TFInterface.apply(tape)
+            tapes, fn = qml.transforms.hamiltonian_expand(tape)
+            res = fn([t.execute(dev) for t in tapes])
+
+            assert np.isclose(res, output)
+
+            g = gtape.gradient(res, var)
+            assert np.allclose(list(g[0]) + list(g[1]), output2)