Repository: PennyLaneAI/pennylane
Pull request #2251 (2022-02-28 20:15:01)

Circuit cutting: Add input validation checks
**Context:**

Adds input validation to the `cut_circuit` transform and supporting tests.

trbromley: [sc-14724]
codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2251?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#2251](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2251?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (9bdb3a5) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/57237b2dfa83c3335a6232fe3f0848ea72e1af8e?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (57237b2) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2251/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/2251?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)

```diff
@@           Coverage Diff           @@
##           master    #2251   +/-   ##
=======================================
  Coverage   96.55%   96.55%           
=======================================
  Files         231      231           
  Lines       18359    18370   +11     
=======================================
+ Hits        17726    17737   +11     
  Misses        633      633           
```


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

------

[Continue to review full report at Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2251?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/2251?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Last update [57237b2...9bdb3a5](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2251?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/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index a0017ec47..78b02db38 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -99,6 +99,7 @@
   A suite of integration tests has been added.
   [(#2231)](https://github.com/PennyLaneAI/pennylane/pull/2231)
   [(#2234)](https://github.com/PennyLaneAI/pennylane/pull/2234)
+  [(#2251)](https://github.com/PennyLaneAI/pennylane/pull/2251)
 
 <h3>Improvements</h3>
 
diff --git a/pennylane/transforms/qcut.py b/pennylane/transforms/qcut.py
index 701f59bdf..c74138b54 100644
--- a/pennylane/transforms/qcut.py
+++ b/pennylane/transforms/qcut.py
@@ -34,7 +34,6 @@ from pennylane.measure import MeasurementProcess
 from pennylane.operation import Expectation, Operation, Operator, Tensor
 from pennylane.ops.qubit.non_parametric_ops import WireCut
 from pennylane.tape import QuantumTape
-from pennylane.transforms import batch_transform
 from pennylane.wires import Wires
 
 from .batch_transform import batch_transform
@@ -941,6 +940,31 @@ def cut_circuit(
         >>> qml.grad(cut_circuit)(x)
         -0.506395895364911
     """
+    if len(tape.measurements) != 1:
+        raise ValueError(
+            "The circuit cutting workflow only supports circuits with a single output "
+            "measurement"
+        )
+
+    if not all(m.return_type is Expectation for m in tape.measurements):
+        raise ValueError(
+            "The circuit cutting workflow only supports circuits with expectation "
+            "value measurements"
+        )
+
+    if use_opt_einsum:
+        try:
+            import opt_einsum  # pylint: disable=import-outside-toplevel,unused-import
+        except ImportError as e:
+            raise ImportError(
+                "The opt_einsum package is required when use_opt_einsum is set to "
+                "True in the cut_circuit function. This package can be "
+                "installed using:\npip install opt_einsum"
+            ) from e
+
+    num_cut = len([op for op in tape.operations if isinstance(op, WireCut)])
+    if num_cut == 0:
+        raise ValueError("Cannot apply the circuit cutting workflow to a circuit without any cuts")
 
     g = tape_to_graph(tape)
     replace_wire_cut_nodes(g)


Test patch:
diff --git a/tests/transforms/test_qcut.py b/tests/transforms/test_qcut.py
index d8f1422d4..5863af81b 100644
--- a/tests/transforms/test_qcut.py
+++ b/tests/transforms/test_qcut.py
@@ -2004,6 +2004,57 @@ class TestCutCircuitTransform:
         assert np.isclose(gradient, cut_gradient)
 
 
+class TestCutCircuitTransformValidation:
+    """Tests of validation checks in the cut_circuit function"""
+
+    def test_multiple_measurements_raises(self):
+        """Tests if a ValueError is raised when a tape with multiple measurements is requested
+        to be cut"""
+
+        with qml.tape.QuantumTape() as tape:
+            qml.expval(qml.PauliZ(0))
+            qml.expval(qml.PauliZ(1))
+
+        with pytest.raises(ValueError, match="The circuit cutting workflow only supports circuits"):
+            qcut.cut_circuit(tape)
+
+    def test_no_measurements_raises(self):
+        """Tests if a ValueError is raised when a tape with no measurement is requested
+        to be cut"""
+        with pytest.raises(ValueError, match="The circuit cutting workflow only supports circuits"):
+            qcut.cut_circuit(qml.tape.QuantumTape())
+
+    def test_non_expectation_raises(self):
+        """Tests if a ValueError is raised when a tape with measurements that are not expectation
+        values is requested to be cut"""
+
+        with qml.tape.QuantumTape() as tape:
+            qml.var(qml.PauliZ(0))
+
+        with pytest.raises(ValueError, match="workflow only supports circuits with expectation"):
+            qcut.cut_circuit(tape)
+
+    def test_fail_import(self, monkeypatch):
+        """Test if an ImportError is raised when opt_einsum is requested but not installed"""
+        with qml.tape.QuantumTape() as tape:
+            qml.expval(qml.PauliZ(0))
+
+        with monkeypatch.context() as m:
+            m.setitem(sys.modules, "opt_einsum", None)
+
+            with pytest.raises(ImportError, match="The opt_einsum package is required"):
+                qcut.cut_circuit(tape, use_opt_einsum=True)
+
+    def test_no_cuts_raises(self):
+        """Tests if a ValueError is raised when circuit cutting is to be applied to a circuit
+        without cuts"""
+        with qml.tape.QuantumTape() as tape:
+            qml.expval(qml.PauliZ(0))
+
+        with pytest.raises(ValueError, match="to a circuit without any cuts"):
+            qcut.cut_circuit(tape)
+
+
 class TestCutStrategy:
     """Tests for class CutStrategy"""