Repository: PennyLaneAI/pennylane

Issue:
Noise channels do not validate parameter values, leading to errors
#### Issue description

Description of the issue - include code snippets and screenshots here
if relevant. You may use the following template below

* *Expected behavior:* Raise an error showing that the probability (or sum of probabilities) are not compatible with the expected channel behaviour.

* *Actual behavior:* Raises a `RuntimeWarning` for an invalid value encountered in sqrt.

* *Reproduces how often:* 100%

* *System information:* 
* Name: PennyLane
Version: 0.16.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/nahum/Documents/pennylane-training/pen-env/lib/python3.6/site-packages
Requires: scipy, toml, appdirs, autoray, semantic-version, networkx, autograd, numpy
Required-by: PennyLane-qiskit
Platform info:           Linux-5.4.0-73-generic-x86_64-with-debian-buster-sid
Python version:          3.6.8
Numpy version:           1.19.5
Scipy version:           1.5.4
Installed devices:
- default.gaussian (PennyLane-0.16.0.dev0)
- default.mixed (PennyLane-0.16.0.dev0)
- default.qubit (PennyLane-0.16.0.dev0)
- default.qubit.autograd (PennyLane-0.16.0.dev0)
- default.qubit.jax (PennyLane-0.16.0.dev0)
- default.qubit.tf (PennyLane-0.16.0.dev0)
- default.tensor (PennyLane-0.16.0.dev0)
- default.tensor.tf (PennyLane-0.16.0.dev0)
- qiskit.aer (PennyLane-qiskit-0.15.0)
- qiskit.basicaer (PennyLane-qiskit-0.15.0)
- qiskit.ibmq (PennyLane-qiskit-0.15.0)

#### Source code and tracebacks
```python
from pennylane.ops.channel import AmplitudeDamping

print(AmplitudeDamping(2, wires=0).kraus_matrices)
```

```
pennylane/ops/channel.py:61: RuntimeWarning: invalid value encountered in sqrt
  K0 = np.diag([1, np.sqrt(1 - gamma)])
[array([[ 1.,  0.],
       [ 0., nan]]), array([[0.        , 1.41421356],
       [0.        , 0.        ]])]
```



Discussion:
Thanks for reporting this @nahumsa! 

Could you post the code that created this error? It will help us track down the noisy channel that is causing this bug.

(and if you already have a solution, feel free to make a PR!)
Hi @josh146. Actually, all noise channels have this error, thus maybe the solution can be on the `Channel` base class.  I have added the code sample to reproduce the error.

I plan on making a PR for this issue later this week. 😄 
> I plan on making a PR for this issue later this week. 😄

Nice, great to hear 🙂 

Resolving patch:
diff --git a/pennylane/ops/channel.py b/pennylane/ops/channel.py
--- a/pennylane/ops/channel.py
+++ b/pennylane/ops/channel.py
@@ -58,6 +58,10 @@ class AmplitudeDamping(Channel):
     @classmethod
     def _kraus_matrices(cls, *params):
         gamma = params[0]
+
+        if not 0.0 <= gamma <= 1.0:
+            raise ValueError("gamma must be between [0,1].")
+
         K0 = np.diag([1, np.sqrt(1 - gamma)])
         K1 = np.sqrt(gamma) * np.array([[0, 1], [0, 0]])
         return [K0, K1]
@@ -115,6 +119,13 @@ class GeneralizedAmplitudeDamping(Channel):
     @classmethod
     def _kraus_matrices(cls, *params):
         gamma, p = params
+
+        if not 0.0 <= gamma <= 1.0:
+            raise ValueError("gamma must be between [0,1].")
+
+        if not 0.0 <= p <= 1.0:
+            raise ValueError("p must be between [0,1].")
+
         K0 = np.sqrt(p) * np.diag([1, np.sqrt(1 - gamma)])
         K1 = np.sqrt(p) * np.sqrt(gamma) * np.array([[0, 1], [0, 0]])
         K2 = np.sqrt(1 - p) * np.diag([np.sqrt(1 - gamma), 1])
@@ -161,6 +172,10 @@ class PhaseDamping(Channel):
     @classmethod
     def _kraus_matrices(cls, *params):
         gamma = params[0]
+
+        if not 0.0 <= gamma <= 1.0:
+            raise ValueError("gamma must be between [0,1].")
+
         K0 = np.diag([1, np.sqrt(1 - gamma)])
         K1 = np.diag([0, np.sqrt(gamma)])
         return [K0, K1]
@@ -217,6 +232,10 @@ class DepolarizingChannel(Channel):
     @classmethod
     def _kraus_matrices(cls, *params):
         p = params[0]
+
+        if not 0.0 <= p <= 1.0:
+            raise ValueError("p must be between [0,1]")
+
         K0 = np.sqrt(1 - p) * np.eye(2)
         K1 = np.sqrt(p / 3) * np.array([[0, 1], [1, 0]])
         K2 = np.sqrt(p / 3) * np.array([[0, -1j], [1j, 0]])
@@ -262,6 +281,10 @@ class BitFlip(Channel):
     @classmethod
     def _kraus_matrices(cls, *params):
         p = params[0]
+
+        if not 0.0 <= p <= 1.0:
+            raise ValueError("p must be between [0,1]")
+
         K0 = np.sqrt(1 - p) * np.eye(2)
         K1 = np.sqrt(p) * np.array([[0, 1], [1, 0]])
         return [K0, K1]
@@ -324,6 +347,16 @@ class ResetError(Channel):
     @classmethod
     def _kraus_matrices(cls, *params):
         p_0, p_1 = params[0], params[1]
+
+        if not 0.0 <= p_0 <= 1.0:
+            raise ValueError("p_0 must be between [0,1]")
+
+        if not 0.0 <= p_1 <= 1.0:
+            raise ValueError("p_1 must be between [0,1]")
+
+        if not 0.0 <= p_0 + p_1 <= 1.0:
+            raise ValueError("p_0 + p_1 must be between [0,1]")
+
         K0 = np.sqrt(1 - p_0 - p_1) * np.eye(2)
         K1 = np.sqrt(p_0) * np.array([[1, 0], [0, 0]])
         K2 = np.sqrt(p_0) * np.array([[0, 1], [0, 0]])
@@ -370,6 +403,10 @@ class PhaseFlip(Channel):
     @classmethod
     def _kraus_matrices(cls, *params):
         p = params[0]
+
+        if not 0.0 <= p <= 1.0:
+            raise ValueError("p must be between [0,1]")
+
         K0 = np.sqrt(1 - p) * np.eye(2)
         K1 = np.sqrt(p) * np.array([[1, 0], [0, -1]])
         return [K0, K1]


Test patch:
diff --git a/tests/ops/test_channel_ops.py b/tests/ops/test_channel_ops.py
--- a/tests/ops/test_channel_ops.py
+++ b/tests/ops/test_channel_ops.py
@@ -73,6 +73,10 @@ def test_gamma_arbitrary(self, tol):
         ]
         assert np.allclose(op(0.1, wires=0).kraus_matrices, expected, atol=tol, rtol=0)
 
+    def test_gamma_invalid_parameter(self):
+        with pytest.raises(ValueError, match="gamma must be between"):
+            channel.AmplitudeDamping(1.5, wires=0).kraus_matrices
+
 
 class TestGeneralizedAmplitudeDamping:
     """Tests for the quantum channel GeneralizedAmplitudeDamping"""
@@ -95,6 +99,14 @@ def test_gamma_p_arbitrary(self, tol):
         expected_K3 = np.array([[0.0, 0.0], [0.2236068, 0.0]])
         assert np.allclose(op(0.1, 0.5, wires=0).kraus_matrices[3], expected_K3, atol=tol, rtol=0)
 
+    def test_gamma_invalid_parameter(self):
+        with pytest.raises(ValueError, match="gamma must be between"):
+            channel.GeneralizedAmplitudeDamping(1.5, 0.0, wires=0).kraus_matrices
+
+    def test_p_invalid_parameter(self):
+        with pytest.raises(ValueError, match="p must be between"):
+            channel.GeneralizedAmplitudeDamping(0.0, 1.5, wires=0).kraus_matrices
+
 
 class TestPhaseDamping:
     """Tests for the quantum channel PhaseDamping"""
@@ -114,6 +126,10 @@ def test_gamma_arbitrary(self, tol):
         ]
         assert np.allclose(op(0.1, wires=0).kraus_matrices, expected, atol=tol, rtol=0)
 
+    def test_gamma_invalid_parameter(self):
+        with pytest.raises(ValueError, match="gamma must be between"):
+            channel.PhaseDamping(1.5, wires=0).kraus_matrices
+
 
 class TestBitFlip:
     """Tests for the quantum channel BitFlipChannel"""
@@ -147,6 +163,10 @@ def circuit(p):
         assert gradient == circuit(1) - circuit(0)
         assert np.allclose(gradient, (-2 * np.cos(angle)))
 
+    def test_p_invalid_parameter(self):
+        with pytest.raises(ValueError, match="p must be between"):
+            channel.BitFlip(1.5, wires=0).kraus_matrices
+
 
 class TestPhaseFlip:
     """Test that various values of p give correct Kraus matrices"""
@@ -180,6 +200,10 @@ def circuit(p):
         assert gradient == circuit(1) - circuit(0)
         assert np.allclose(gradient, 0.0)
 
+    def test_p_invalid_parameter(self):
+        with pytest.raises(ValueError, match="p must be between"):
+            channel.PhaseFlip(1.5, wires=0).kraus_matrices
+
 
 class TestDepolarizingChannel:
     """Tests for the quantum channel DepolarizingChannel"""
@@ -215,6 +239,10 @@ def circuit(p):
         assert gradient == circuit(1) - circuit(0)
         assert np.allclose(gradient, -(4 / 3) * np.cos(angle))
 
+    def test_p_invalid_parameter(self):
+        with pytest.raises(ValueError, match="p must be between"):
+            channel.DepolarizingChannel(1.5, wires=0).kraus_matrices
+
 
 class TestResetError:
     """Tests for the quantum channel ResetError"""
@@ -239,6 +267,18 @@ def test_p0_p1_arbitrary(self, p_0, p_1, tol):
         expected_K4 = np.sqrt(p_1) * np.array([[0, 0], [0, 1]])
         assert np.allclose(op(p_0, p_1, wires=0).kraus_matrices[4], expected_K4, atol=tol, rtol=0)
 
+    def test_p0_invalid_parameter(self):
+        with pytest.raises(ValueError, match="p_0 must be between"):
+            channel.ResetError(1.5, 0.0, wires=0).kraus_matrices
+
+    def test_p1_invalid_parameter(self):
+        with pytest.raises(ValueError, match="p_1 must be between"):
+            channel.ResetError(0.0, 1.5, wires=0).kraus_matrices
+
+    def test_p0_p1_sum_not_normalized(self):
+        with pytest.raises(ValueError, match="must be between"):
+            channel.ResetError(1.0, 1.0, wires=0).kraus_matrices
+
 
 class TestQubitChannel:
     """Tests for the quantum channel QubitChannel"""