Repository: PennyLaneAI/pennylane

Issue:
Multi-threaded QNodeCollection does not work
#### Issue description
Thread-parallel execution of `QNodeCollection` fails by the following error in tape mode.

```
  File "/home/shukob/anaconda3/envs/***/lib/python3.7/site-packages/PennyLane-0.13.0.dev0-py3.7.egg/pennylane/collections/qnode_collection.py", line 271, in __call__
    results = self.evaluate(args, kwargs)
  File "/home/shukob/anaconda3/envs/***/lib/python3.7/site-packages/PennyLane-0.13.0.dev0-py3.7.egg/pennylane/collections/qnode_collection.py", line 229, in evaluate
    return dask.compute(*results, scheduler=_scheduler)
  File "/home/shukob/anaconda3/envs/***/lib/python3.7/site-packages/dask/base.py", line 452, in compute
    results = schedule(dsk, keys, **kwargs)
  File "/home/shukob/anaconda3/envs/***/lib/python3.7/site-packages/dask/threaded.py", line 84, in get
    **kwargs
  File "/home/shukob/anaconda3/envs/***/lib/python3.7/site-packages/dask/local.py", line 486, in get_async
    raise_exception(exc, tb)
  File "/home/shukob/anaconda3/envs/***/lib/python3.7/site-packages/dask/local.py", line 316, in reraise
    raise exc
  File "/home/shukob/anaconda3/envs/***/lib/python3.7/site-packages/dask/local.py", line 222, in execute_task
    result = _execute_task(task, data)
  File "/home/shukob/anaconda3/envs/***/lib/python3.7/site-packages/dask/core.py", line 121, in _execute_task
    return func(*(_execute_task(a, cache) for a in args))
  File "/home/shukob/anaconda3/envs/***/lib/python3.7/site-packages/dask/utils.py", line 29, in apply
    return func(*args, **kwargs)
  File "/home/shukob/anaconda3/envs/***/lib/python3.7/site-packages/PennyLane-0.13.0.dev0-py3.7.egg/pennylane/tape/qnode.py", line 433, in __call__
    self.construct(args, kwargs)
  File "/home/shukob/anaconda3/envs/***/lib/python3.7/site-packages/PennyLane-0.13.0.dev0-py3.7.egg/pennylane/tape/qnode.py", line 395, in construct
    "All measurements must be returned in the order they are measured."
pennylane.qnodes.base.QuantumFunctionError: All measurements must be returned in the order they are measured.
```
Reproduction code is:

```
import pennylane as qml


import numpy as np

n_qubits = 4
n_batches = 5
dev = qml.device("default.qubit", wires=n_qubits)

qml.enable_tape()

@qml.qnode(dev)
def circuit(inputs, weights):
    qml.templates.AngleEmbedding(inputs, wires=range(n_qubits))
    qml.templates.BasicEntanglerLayers(weights, wires=range(n_qubits))
    return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_qubits)]


n_layers = 1
weight_shapes = {"weights": (n_layers, n_qubits)}

qnode = QNodeCollection([circuit for _ in range(n_batches)])
x = np.random.rand((n_qubits)).astype(np.float64)#.to(torch.device('cuda'))
p = np.random.rand(*weight_shapes['weights']).astype(np.float64)#.to(torch.device('cuda'))
res = qnode(x, p, parallel=True)

```
The reason for this seems to be the empty wires in the `qtape` in `QNode`. I could not yet find the actual reason for this .

* *Expected behavior:* (What you expect to happen)
Thread-parallel execution is possible in tape mode.

* *Actual behavior:* (What actually happens)
Thread-parallel execution fails in tape mode.

* *Reproduces how often:* (What percentage of the time does it reproduce?)
~50%

* *System information:* (post the output of `import pennylane as qml; qml.about()`)
```
Name: PennyLane
Version: 0.13.0.dev0
Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
Home-page: https://github.com/XanaduAI/pennylane
Author: None
Author-email: None
License: Apache License 2.0
Location: /home/shukob/anaconda3/envs/***/lib/python3.7/site-packages/PennyLane-0.13.0.dev0-py3.7.egg
Requires: numpy, scipy, networkx, autograd, toml, appdirs, semantic-version
Required-by: PennyLane-SF, pennylane-qulacs, PennyLane-qiskit
Platform info:           Linux-5.4.0-53-generic-x86_64-with-debian-bullseye-sid
Python version:          3.7.9
Numpy version:           1.19.2
Scipy version:           1.5.2
Installed devices:
- strawberryfields.fock (PennyLane-SF-0.12.0)
- strawberryfields.gaussian (PennyLane-SF-0.12.0)
- strawberryfields.gbs (PennyLane-SF-0.12.0)
- strawberryfields.remote (PennyLane-SF-0.12.0)
- strawberryfields.tf (PennyLane-SF-0.12.0)
- qulacs.simulator (pennylane-qulacs-0.12.0)
- qiskit.aer (PennyLane-qiskit-0.12.0)
- qiskit.basicaer (PennyLane-qiskit-0.12.0)
- qiskit.ibmq (PennyLane-qiskit-0.12.0)
- default.gaussian (PennyLane-0.13.0.dev0)
- default.mixed (PennyLane-0.13.0.dev0)
- default.qubit (PennyLane-0.13.0.dev0)
- default.qubit.autograd (PennyLane-0.13.0.dev0)
- default.qubit.tf (PennyLane-0.13.0.dev0)
- default.tensor (PennyLane-0.13.0.dev0)
- default.tensor.tf (PennyLane-0.13.0.dev0)

```



Discussion:

Resolving patch:
diff --git a/pennylane/templates/utils.py b/pennylane/templates/utils.py
--- a/pennylane/templates/utils.py
+++ b/pennylane/templates/utils.py
@@ -91,8 +91,10 @@ def get_shape(inpt):
 
         try:
             shape = inpt.shape
-        except AttributeError:
-            raise ValueError("could not extract shape of object of type {}".format(type(inpt)))
+        except AttributeError as e:
+            raise ValueError(
+                "could not extract shape of object of type {}".format(type(inpt))
+            ) from e
 
         # turn result into tuple to avoid type TensorShape
         shape = tuple(shape)
diff --git a/qchem/pennylane_qchem/qchem/structure.py b/qchem/pennylane_qchem/qchem/structure.py
--- a/qchem/pennylane_qchem/qchem/structure.py
+++ b/qchem/pennylane_qchem/qchem/structure.py
@@ -211,7 +211,7 @@ def read_structure(filepath, outpath="."):
             raise RuntimeError(
                 "Open Babel error. See the following Open Babel "
                 "output for details:\n\n {}\n{}".format(e.stdout, e.stderr)
-            )
+            ) from e
     else:
         copyfile(file_in, file_out)
 


Test patch:
diff --git a/pennylane/devices/tests/test_compare_default_qubit.py b/pennylane/devices/tests/test_compare_default_qubit.py
new file mode 100755
--- /dev/null
+++ b/pennylane/devices/tests/test_compare_default_qubit.py
@@ -0,0 +1,201 @@
+# 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.
+"""Tests that a device gives the same output as the default device."""
+# pylint: disable=no-self-use,no-member
+import pytest
+from flaky import flaky
+
+import pennylane as qml
+from pennylane import numpy as np  # Import from PennyLane to mirror the standard approach in demos
+from pennylane.templates.layers import RandomLayers
+
+pytestmark = pytest.mark.skip_unsupported
+
+
+@flaky(max_runs=10)
+class TestComparison:
+    """Test that a device different to default.qubit gives the same result"""
+
+    def test_hermitian_expectation(self, device, tol):
+        """Test that arbitrary multi-mode Hermitian expectation values are correct"""
+        n_wires = 2
+        dev = device(n_wires)
+        dev_def = qml.device("default.qubit", wires=n_wires)
+
+        if not dev.analytic:
+            pytest.skip("Device is in non-analytical mode.")
+
+        if "Hermitian" not in dev.observables:
+            pytest.skip("Skipped because device does not support the Hermitian observable.")
+
+        if dev.name == dev_def.name:
+            pytest.skip("Device is default.qubit.")
+
+        theta = 0.432
+        phi = 0.123
+        A_ = np.array(
+            [
+                [-6, 2 + 1j, -3, -5 + 2j],
+                [2 - 1j, 0, 2 - 1j, -5 + 4j],
+                [-3, 2 + 1j, 0, -4 + 3j],
+                [-5 - 2j, -5 - 4j, -4 - 3j, -6],
+            ]
+        )
+
+        def circuit(theta, phi):
+            qml.RX(theta, wires=[0])
+            qml.RX(phi, wires=[1])
+            qml.CNOT(wires=[0, 1])
+            return qml.expval(qml.Hermitian(A_, wires=[0, 1]))
+
+        qnode_def = qml.QNode(circuit, dev_def)
+        qnode = qml.QNode(circuit, dev)
+
+        grad_def = qml.grad(qnode_def, argnum=[0, 1])
+        grad = qml.grad(qnode, argnum=[0, 1])
+
+        assert np.allclose(qnode(theta, phi), qnode_def(theta, phi), atol=tol(dev.analytic))
+        assert np.allclose(grad(theta, phi), grad_def(theta, phi), atol=tol(dev.analytic))
+
+    def test_pauliz_expectation_analytic(self, device, tol):
+        """Test that the tensor product of PauliZ expectation value is correct"""
+        n_wires = 2
+        dev = device(n_wires)
+        dev_def = qml.device("default.qubit", wires=n_wires)
+
+        if dev.name == dev_def.name:
+            pytest.skip("Device is default.qubit.")
+
+        supports_tensor = (
+            "supports_tensor_observables" in dev.capabilities()
+            and dev.capabilities()["supports_tensor_observables"]
+        )
+
+        if not supports_tensor:
+            pytest.skip("Device does not support tensor observables.")
+
+        if not dev.analytic:
+            pytest.skip("Device is in non-analytical mode.")
+
+        theta = 0.432
+        phi = 0.123
+
+        def circuit(theta, phi):
+            qml.RX(theta, wires=[0])
+            qml.RX(phi, wires=[1])
+            qml.CNOT(wires=[0, 1])
+            return qml.expval(qml.PauliZ(wires=0) @ qml.PauliZ(wires=1))
+
+        qnode_def = qml.QNode(circuit, dev_def)
+        qnode = qml.QNode(circuit, dev)
+
+        grad_def = qml.grad(qnode_def, argnum=[0, 1])
+        grad = qml.grad(qnode, argnum=[0, 1])
+
+        assert np.allclose(qnode(theta, phi), qnode_def(theta, phi), atol=tol(dev.analytic))
+        assert np.allclose(grad(theta, phi), grad_def(theta, phi), atol=tol(dev.analytic))
+
+    @pytest.mark.parametrize("ret", [qml.expval, qml.var])
+    def test_random_circuit(self, device, tol, ret):
+        """Test that the expectation value of a random circuit is correct"""
+        n_wires = 2
+        dev = device(n_wires)
+        dev_def = qml.device("default.qubit", wires=n_wires)
+
+        if dev.name == dev_def.name:
+            pytest.skip("Device is default.qubit.")
+
+        supports_tensor = (
+            "supports_tensor_observables" in dev.capabilities()
+            and dev.capabilities()["supports_tensor_observables"]
+        )
+
+        if not supports_tensor:
+            pytest.skip("Device does not support tensor observables.")
+
+        if not dev.analytic:
+            pytest.skip("Device is in non-analytical mode.")
+
+        n_layers = np.random.randint(1, 5)
+        weights = 2 * np.pi * np.random.rand(n_layers, 1)
+
+        def circuit(weights):
+            RandomLayers(weights, wires=range(n_wires))
+            return ret(qml.PauliZ(wires=0) @ qml.PauliX(wires=1))
+
+        qnode_def = qml.QNode(circuit, dev_def)
+        qnode = qml.QNode(circuit, dev)
+
+        grad_def = qml.grad(qnode_def, argnum=0)
+        grad = qml.grad(qnode, argnum=0)
+
+        assert np.allclose(qnode(weights), qnode_def(weights), atol=tol(dev.analytic))
+        assert np.allclose(grad(weights), grad_def(weights), atol=tol(dev.analytic))
+
+    def test_four_qubit_random_circuit(self, device, tol):
+        """Test a four-qubit random circuit with the whole set of possible gates"""
+        n_wires = 4
+        dev = device(n_wires)
+        dev_def = qml.device("default.qubit", wires=n_wires)
+
+        if dev.name == dev_def.name:
+            pytest.skip("Device is default.qubit.")
+
+        if not dev.analytic:
+            pytest.skip("Device is in non-analytical mode.")
+
+        gates = [
+            qml.PauliX,
+            qml.PauliY,
+            qml.PauliZ,
+            qml.S,
+            qml.T,
+            qml.RX,
+            qml.RY,
+            qml.RZ,
+            qml.Hadamard,
+            qml.Rot,
+            qml.CRot,
+            qml.Toffoli,
+            qml.SWAP,
+            qml.CSWAP,
+            qml.U1,
+            qml.U2,
+            qml.U3,
+            qml.CRX,
+            qml.CRY,
+            qml.CRZ,
+        ]
+
+        layers = 3
+        np.random.seed(1967)
+        gates_per_layers = [np.random.permutation(gates).numpy() for _ in range(layers)]
+
+        def circuit():
+            """4-qubit circuit with layers of randomly selected gates and random connections for
+            multi-qubit gates."""
+            np.random.seed(1967)
+            for gates in gates_per_layers:
+                for gate in gates:
+                    params = list(np.pi * np.random.rand(gate.num_params))
+                    gate(
+                        *params,
+                        wires=np.random.choice(range(n_wires), size=gate.num_wires, replace=False)
+                    )
+            return qml.expval(qml.PauliZ(0))
+
+        qnode_def = qml.QNode(circuit, dev_def)
+        qnode = qml.QNode(circuit, dev)
+
+        assert np.allclose(qnode(), qnode_def(), atol=tol(dev.analytic))