Repository: PennyLaneAI/pennylane
Pull request #1829 (2021-11-01 14:52:41)

Add identity op
**Context:**
Add the Identity operator as a callable operation for cv and qubit devices. 

Demo: 

```
dev = qml.device("default.qubit", wires=2)

@qml.qnode(dev)
def circuit():
    qml.Identity(wires=0)
    return qml.expval(qml.PauliZ(wires=1)) 

print(circuit())
print(qml.draw(circuit)())
``` 

```
1.0
 0: ──I──┤     
 1: ─────┤ ⟨Z⟩
```


**Description of the Change:**
`qml.Identity` now also inherits from the Operation class and `Identity` is added to the respective `operations` attributes in each device. 

**Related GitHub Issues:**
[1632](https://github.com/PennyLaneAI/pennylane/issues/1632)


github-actions[bot]: Hello. You may have forgotten to update the changelog!
Please edit [`doc/releases/changelog-dev.md`](/PennyLaneAI/pennylane/blob/master/doc/releases/changelog-dev.md) with:
* A one-to-two sentence description of the change. You may include a small working example for new features.
* A link back to this PR.
* Your name (or GitHub username) in the contributors section.
codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1829?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#1829](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1829?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (c25f9b1) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/95dfeda9a8be010b11c9e77b3a1daedcbc35b951?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (95dfeda) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

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

```diff
@@           Coverage Diff           @@
##           master    #1829   +/-   ##
=======================================
  Coverage   98.98%   98.98%           
=======================================
  Files         218      218           
  Lines       16453    16457    +4     
=======================================
+ Hits        16286    16290    +4     
  Misses        167      167           
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1829?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/devices/default\_mixed.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1829/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RldmljZXMvZGVmYXVsdF9taXhlZC5weQ==) | `100.00% <ø> (ø)` | |
| [pennylane/devices/default\_qubit.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1829/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RldmljZXMvZGVmYXVsdF9xdWJpdC5weQ==) | `100.00% <ø> (ø)` | |
| [pennylane/devices/default\_gaussian.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1829/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RldmljZXMvZGVmYXVsdF9nYXVzc2lhbi5weQ==) | `99.65% <100.00%> (+<0.01%)` | :arrow_up: |
| [pennylane/ops/\_\_init\_\_.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1829/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9fX2luaXRfXy5weQ==) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1829?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/1829?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Last update [95dfeda...c25f9b1](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1829?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).

antalszava: [sc-11165]
Jaybsoni: @antalszava, it seems codecov has dropped for some unknown reason. I think we may have merged in master with some changes that weren't covered 


Patch:
diff --git a/doc/introduction/operations.rst b/doc/introduction/operations.rst
index 8221f0850..72fe059d6 100644
--- a/doc/introduction/operations.rst
+++ b/doc/introduction/operations.rst
@@ -52,7 +52,8 @@ Non-parametric Ops
 
 .. autosummary::
     :nosignatures:
-
+    
+    ~pennylane.Identity
     ~pennylane.Hadamard
     ~pennylane.PauliX
     ~pennylane.PauliY
@@ -255,7 +256,8 @@ CV Gates
 
 .. autosummary::
     :nosignatures:
-
+    
+    ~pennylane.Identity
     ~pennylane.Beamsplitter
     ~pennylane.ControlledAddition
     ~pennylane.ControlledPhase
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 9800ef65f..1e5faae7a 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -6,6 +6,10 @@
 * A thermal relaxation channel is added to the Noisy channels. The channel description can be 
   found on the supplementary information of [Quantum classifier with tailored quantum kernels](https://arxiv.org/abs/1909.02611).
   [(#1766)](https://github.com/PennyLaneAI/pennylane/pull/1766)
+  
+* Added the identity observable to be an operator. Now we can explicitly call the identity 
+  operation on our quantum circuits for both qubit and CV devices.
+  [(#1829)](https://github.com/PennyLaneAI/pennylane/pull/1829) 
 
 <h3>Improvements</h3>
 
@@ -31,4 +35,4 @@
 
 This release contains contributions from (in alphabetical order): 
 
-Jalani Kanem, Christina Lee, Guillermo Alonso-Linaje, Alejandro Montanez
+Jalani Kanem, Christina Lee, Guillermo Alonso-Linaje, Alejandro Montanez, Jay Soni
diff --git a/pennylane/devices/default_gaussian.py b/pennylane/devices/default_gaussian.py
index 198e176a9..728b94661 100644
--- a/pennylane/devices/default_gaussian.py
+++ b/pennylane/devices/default_gaussian.py
@@ -29,6 +29,7 @@ import numpy as np
 from scipy.special import factorial as fac
 
 import pennylane as qml
+from pennylane.ops import Identity
 from pennylane import Device
 from .._version import __version__
 
@@ -655,6 +656,7 @@ class DefaultGaussian(Device):
     author = "Xanadu Inc."
 
     _operation_map = {
+        "Identity": Identity.identity_op,
         "Beamsplitter": beamsplitter,
         "ControlledAddition": controlled_addition,
         "ControlledPhase": controlled_phase,
diff --git a/pennylane/devices/default_mixed.py b/pennylane/devices/default_mixed.py
index db3e7d7e8..8a965e853 100644
--- a/pennylane/devices/default_mixed.py
+++ b/pennylane/devices/default_mixed.py
@@ -56,6 +56,7 @@ class DefaultMixed(QubitDevice):
     author = "Xanadu Inc."
 
     operations = {
+        "Identity",
         "BasisState",
         "QubitStateVector",
         "QubitUnitary",
diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py
index 9db32366b..6312b5e06 100644
--- a/pennylane/devices/default_qubit.py
+++ b/pennylane/devices/default_qubit.py
@@ -92,6 +92,7 @@ class DefaultQubit(QubitDevice):
     author = "Xanadu Inc."
 
     operations = {
+        "Identity",
         "BasisState",
         "QubitStateVector",
         "QubitUnitary",
diff --git a/pennylane/ops/__init__.py b/pennylane/ops/__init__.py
index 0af8e005d..82cf49c79 100644
--- a/pennylane/ops/__init__.py
+++ b/pennylane/ops/__init__.py
@@ -20,7 +20,7 @@ such as gates, state preparations and observables.
 """
 import numpy as np
 
-from pennylane.operation import AnyWires, Observable, CVObservable
+from pennylane.operation import AnyWires, Observable, CVObservable, Operation
 
 from .cv import *
 from .qubit import *
@@ -43,7 +43,7 @@ class AdjointError(Exception):
     pass
 
 
-class Identity(CVObservable):
+class Identity(CVObservable, Operation):
     r"""pennylane.Identity(wires)
     The identity observable :math:`\I`.
 
@@ -55,7 +55,7 @@ class Identity(CVObservable):
     corresponds to the trace of the quantum state, which in exact
     simulators should always be equal to 1.
     """
-    num_wires = AnyWires
+    num_wires = 1
     num_params = 0
     par_domain = None
     grad_method = None
@@ -64,7 +64,7 @@ class Identity(CVObservable):
     eigvals = np.array([1, 1])
 
     def label(self, decimals=None, base_label=None):
-        return super().label(decimals=decimals, base_label=base_label or "I")
+        return base_label or "I"
 
     @classmethod
     def _eigvals(cls, *params):
@@ -81,7 +81,12 @@ class Identity(CVObservable):
     def diagonalizing_gates(self):
         return []
 
+    @staticmethod
+    def identity_op(*params):
+        """Returns the matrix representation of the identity operator."""
+        return Identity._matrix(*params)
+
 
 __all__ = _cv__all__ + _qubit__all__ + _channel__ops__ + ["Identity"]
-__all_ops__ = list(_cv__ops__ | _qubit__ops__)
+__all_ops__ = list(_cv__ops__ | _qubit__ops__) + ["Identity"]
 __all_obs__ = list(_cv__obs__ | _qubit__obs__) + ["Identity"]


Test patch:
diff --git a/tests/devices/test_default_gaussian.py b/tests/devices/test_default_gaussian.py
index 36abff155..0650aa894 100644
--- a/tests/devices/test_default_gaussian.py
+++ b/tests/devices/test_default_gaussian.py
@@ -174,6 +174,22 @@ class TestAuxillaryFunctions:
 class TestGates:
     """Gate tests."""
 
+    input_state = [vacuum_state(1), coherent_state(a=0.5)]
+
+    @pytest.mark.parametrize("inp_state", input_state)
+    def test_identity(self, inp_state, tol):
+        inp_cov_mat = inp_state[0]
+        inp_means = inp_state[1]
+
+        O = qml.Identity.identity_op()
+        out_means = O @ inp_means
+        out_cov_mat = O @ inp_cov_mat @ O.T
+
+        assert np.allclose(out_means, inp_means, atol=tol)
+        assert np.allclose(
+            out_cov_mat, inp_cov_mat, atol=tol
+        )  # Identity op shouldn't change means or cov mat
+
     def test_rotation(self, tol):
         """Test the Fourier transform of a displaced state."""
         # pylint: disable=invalid-unary-operand-type
@@ -373,7 +389,8 @@ class TestDefaultGaussianDevice:
             "Kerr",
         }
 
-        assert set(qml.ops._cv__ops__) - non_supported == set(gaussian_dev._operation_map)
+        cv_ops = set(qml.ops._cv__ops__) | {"Identity"}  # we also support identity operation
+        assert cv_ops - non_supported == set(gaussian_dev._operation_map)
 
     def test_observable_map(self, gaussian_dev):
         """Test that default Gaussian device supports all PennyLane Gaussian continuous observables."""
diff --git a/tests/devices/test_default_mixed.py b/tests/devices/test_default_mixed.py
index 33a684129..5958c2dda 100644
--- a/tests/devices/test_default_mixed.py
+++ b/tests/devices/test_default_mixed.py
@@ -20,6 +20,7 @@ import pennylane as qml
 from pennylane import QubitStateVector, BasisState, DeviceError
 from pennylane.devices import DefaultMixed
 from pennylane.ops import (
+    Identity,
     PauliZ,
     CZ,
     PauliX,
@@ -662,6 +663,23 @@ class TestApply:
     """Unit tests for the main method `apply()`. We check that lists of operations are applied
     correctly, rather than single operations"""
 
+    ops_and_true_state = [(None, basis_state(0, 2)), (Hadamard, hadamard_state(2))]
+
+    @pytest.mark.parametrize("op, true_state", ops_and_true_state)
+    def test_identity(self, op, true_state, tol):
+        """Tests that applying the identity operator doesn't change the state"""
+        num_wires = 2
+        dev = qml.device("default.mixed", wires=num_wires)  # prepare basis state
+
+        if op is not None:
+            ops = [op(i) for i in range(num_wires)]
+            dev.apply(ops)
+
+        # Apply Identity:
+        dev.apply([Identity(i) for i in range(num_wires)])
+
+        assert np.allclose(dev.state, true_state, atol=tol, rtol=0)
+
     def test_bell_state(self, tol):
         """Tests that we correctly prepare a Bell state by applying a Hadamard then a CNOT"""
         dev = qml.device("default.mixed", wires=2)
diff --git a/tests/devices/test_default_qubit.py b/tests/devices/test_default_qubit.py
index a6d9e58fe..0a6cd7960 100644
--- a/tests/devices/test_default_qubit.py
+++ b/tests/devices/test_default_qubit.py
@@ -131,6 +131,8 @@ class TestApply:
         ),
         (qml.Hadamard, [1, 0], [1 / math.sqrt(2), 1 / math.sqrt(2)]),
         (qml.Hadamard, [1 / math.sqrt(2), -1 / math.sqrt(2)], [0, 1]),
+        (qml.Identity, [1, 0], [1, 0]),
+        (qml.Identity, [1 / math.sqrt(2), 1 / math.sqrt(2)], [1 / math.sqrt(2), 1 / math.sqrt(2)]),
     ]
 
     test_data_no_parameters_inverses = [
@@ -150,6 +152,8 @@ class TestApply:
         ),
         (qml.Hadamard, [1, 0], [1 / math.sqrt(2), 1 / math.sqrt(2)]),
         (qml.Hadamard, [1 / math.sqrt(2), -1 / math.sqrt(2)], [0, 1]),
+        (qml.Identity, [1, 0], [1, 0]),
+        (qml.Identity, [1 / math.sqrt(2), 1 / math.sqrt(2)], [1 / math.sqrt(2), 1 / math.sqrt(2)]),
     ]
 
     @pytest.mark.parametrize("operation,input,expected_output", test_data_no_parameters)
diff --git a/tests/ops/qubit/test_non_parametric_ops.py b/tests/ops/qubit/test_non_parametric_ops.py
index e5edc74ab..df1ef04fb 100644
--- a/tests/ops/qubit/test_non_parametric_ops.py
+++ b/tests/ops/qubit/test_non_parametric_ops.py
@@ -25,6 +25,7 @@ import pennylane as qml
 from pennylane.wires import Wires
 
 from gate_data import (
+    I,
     X,
     Y,
     Z,
@@ -43,6 +44,7 @@ from gate_data import (
 
 # Non-parametrized operations and their matrix representation
 NON_PARAMETRIZED_OPERATIONS = [
+    (qml.Identity, I),
     (qml.CNOT, CNOT),
     (qml.SWAP, SWAP),
     (qml.ISWAP, ISWAP),
@@ -711,6 +713,7 @@ class TestMultiControlledX:
 
 
 label_data = [
+    (qml.Identity(0), "I", "I"),
     (qml.Hadamard(0), "H", "H"),
     (qml.PauliX(0), "X", "X"),
     (qml.PauliY(0), "Y", "Y"),
@@ -741,6 +744,7 @@ def test_label_method(op, label1, label2):
 
 
 control_data = [
+    (qml.Identity(0), Wires([])),
     (qml.Hadamard(0), Wires([])),
     (qml.PauliX(0), Wires([])),
     (qml.PauliY(0), Wires([])),