Repository: PennyLaneAI/pennylane
Pull request #1359 (2021-05-24 04:46:50)

[unitaryHACK] Added `__repr__` method for QNode
**Context:**

Adds `__repr__` method to print useful information when running `print(QNode)`.

**Related GitHub Issues:** #1352 


josh146: Thanks @paniash for this contribution 🙂 Let us know when it’s ready for review, or alternatively if you have any questions.

By the way, don’t forget to update the [changelog](.github/CHANGELOG.md), with:

- A small summary of the change
- A link back to this PR
- Your name added to the contributors section
paniash: @josh146 I think it's ready for review. Let me know if any further changes are required. :-)
codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1359?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#1359](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1359?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (bb86654) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/a9a914d9b6e475374dba1e91a22f2635b97df913?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (a9a914d) will **decrease** coverage by `0.02%`.
> The diff coverage is `78.57%`.

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

```diff
@@            Coverage Diff             @@
##           master    #1359      +/-   ##
==========================================
- Coverage   98.27%   98.25%   -0.03%     
==========================================
  Files         150      150              
  Lines       11256    11261       +5     
==========================================
+ Hits        11062    11064       +2     
- Misses        194      197       +3     
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1359?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/qnode.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1359/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3Fub2RlLnB5) | `97.41% <78.57%> (-1.27%)` | :arrow_down: |

------

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

paniash: @josh146 Could you help me with the failing tests? Not sure why they're failing.
albi3ro: Hi @paniash ! thanks for this PR.

It looks like your checks are failing because we have decreased coverage by adding in lines that are not getting tested.

The QNode tests are at [`tests/tape/test_qnode.py`](https://github.com/PennyLaneAI/pennylane/blob/master/tests/tape/test_qnode.py)

Could you add some tests for the `__repr__` function there?  You could either stick them at the bottom or wherever makes the most sense. You just need to create some simple qnode's and then compare its `__repr__` output to a fixed expected value.
paniash: It looks like the test is failing for the circuit diagrams.  Not sure what's the issue though. My editor merely removed the trailing whitespaces (is that the reason for failing tests?).
josh146: >My editor merely removed the trailing whitespaces (is that the reason for failing tests?).

Hi @paniash, unfortunately yes it is! The circuit diagrams contain trailing whitespace that is required for the equality assertion to pass. If you revert these changes, the tests should pass again 🙂 
paniash: @josh146 Thanks for letting me know! I'll fix it asap. :-)
paniash: Realizing for the first time that whitespaces are very important 😃
paniash: @josh146 @albi3ro Thanks a lot for the guidance and constructive comments. Really appreciate it 😃

Patch:
diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md
index b8863c015..bf107144e 100644
--- a/.github/CHANGELOG.md
+++ b/.github/CHANGELOG.md
@@ -21,7 +21,7 @@
   for an example.
 
 * PennyLane now has a ``kernels`` module.
-  It provides basic functionalities for working with quantum kernels as well as 
+  It provides basic functionalities for working with quantum kernels as well as
   post-processing methods to mitigate sampling errors and device noise:
 
 ```python
@@ -48,7 +48,7 @@ K = qml.kernels.square_kernel_matrix(X_train, kernel)
 # Add some (symmetric) Gaussian noise to the kernel matrix.
 N = np.random.randn(10, 10)
 K += (N + N.T) / 2
-        
+
 K1 = qml.kernels.displace_matrix(K)
 K2 = qml.kernels.closest_psd_matrix(K)
 K3 = qml.kernels.threshold_matrix(K)
@@ -62,10 +62,13 @@ X_test = np.random.random((5, 6))
 K_test = qml.kernels.kernel_matrix(X_train, X_test, kernel)
 ```
 
+* QNodes now display readable information when in interactive environments or when printed.
+  [(#1359)](https://github.com/PennyLaneAI/pennylane/pull/1359).
+
 * Added CPhase operation as an alias for ControlledPhaseShift operation
   [(#1319)](https://github.com/PennyLaneAI/pennylane/pull/1319).
 
-* The `qml.Toffoli` operation now has a decomposition over elementary gates. 
+* The `qml.Toffoli` operation now has a decomposition over elementary gates.
   [(#1320)](https://github.com/PennyLaneAI/pennylane/pull/1320)
 
 * Added a new noise channel, `qml.ResetError`.
@@ -137,9 +140,9 @@ K_test = qml.kernels.kernel_matrix(X_train, X_test, kernel)
   [(#1214)](https://github.com/PennyLaneAI/pennylane/pull/1214)
   [(#1283)](https://github.com/PennyLaneAI/pennylane/pull/1283)
   [(#1297)](https://github.com/PennyLaneAI/pennylane/pull/1297)
-  
+
   The `max_weight_cycle` function returns the appropriate cost and mixer Hamiltonians:
-  
+
   ```pycon
   >>> a = np.random.random((3, 3))
   >>> np.fill_diagonal(a, 0)
@@ -179,7 +182,7 @@ K_test = qml.kernels.kernel_matrix(X_train, X_test, kernel)
   + (0.25) [Y5 X4 Y0]
   >>> mapping
   {0: (0, 1), 1: (0, 2), 2: (1, 0), 3: (1, 2), 4: (2, 0), 5: (2, 1)}
-  ``` 
+  ```
  Additional functionality can be found in the `qml.qaoa.cycle` module.
 
 * Adds `QubitCarry` and `QubitSum` operations for basic arithmetic.
@@ -234,10 +237,10 @@ random_mat2 = rng.standard_normal(3, requires_grad=False)
 
 <h3>Improvements</h3>
 
-* The `benchmark` module was deleted, since it was outdated and is superseded by 
+* The `benchmark` module was deleted, since it was outdated and is superseded by
   the new separate [benchmark repository](https://github.com/PennyLaneAI/benchmark).
   [(#1343)](https://github.com/PennyLaneAI/pennylane/pull/1343)
-  
+
 *  A decomposition has been added for the `qml.CSWAP` operation.
   [(#1306)](https://github.com/PennyLaneAI/pennylane/issues/1306)
 
@@ -247,7 +250,7 @@ random_mat2 = rng.standard_normal(3, requires_grad=False)
 * The `MultiControlledX` gate now has a decomposition defined. When controlling on three or more wires,
   an ancilla register of worker wires is required to support the decomposition.
   [(#1287)](https://github.com/PennyLaneAI/pennylane/pull/1287)
-  
+
   ```python
   ctrl_wires = [f"c{i}" for i in range(5)]
   work_wires = [f"w{i}" for i in range(3)]
@@ -259,19 +262,19 @@ random_mat2 = rng.standard_normal(3, requires_grad=False)
   with qml.tape.QuantumTape() as tape:
       qml.MultiControlledX(control_wires=ctrl_wires, wires=target_wires, work_wires=work_wires)
   ```
-  
+
   ```pycon
   >>> tape = tape.expand(depth=2)
   >>> print(tape.draw(wire_order=Wires(all_wires)))
-   c0: ──────────────╭C──────────────────────╭C──────────┤  
-   c1: ──────────────├C──────────────────────├C──────────┤  
-   c2: ──────────╭C──│───╭C──────────────╭C──│───╭C──────┤  
-   c3: ──────╭C──│───│───│───╭C──────╭C──│───│───│───╭C──┤  
-   c4: ──╭C──│───│───│───│───│───╭C──│───│───│───│───│───┤  
-   w0: ──│───│───├C──╰X──├C──│───│───│───├C──╰X──├C──│───┤  
-   w1: ──│───├C──╰X──────╰X──├C──│───├C──╰X──────╰X──├C──┤  
-   w2: ──├C──╰X──────────────╰X──├C──╰X──────────────╰X──┤  
-   t0: ──╰X──────────────────────╰X──────────────────────┤  
+   c0: ──────────────╭C──────────────────────╭C──────────┤
+   c1: ──────────────├C──────────────────────├C──────────┤
+   c2: ──────────╭C──│───╭C──────────────╭C──│───╭C──────┤
+   c3: ──────╭C──│───│───│───╭C──────╭C──│───│───│───╭C──┤
+   c4: ──╭C──│───│───│───│───│───╭C──│───│───│───│───│───┤
+   w0: ──│───│───├C──╰X──├C──│───│───│───├C──╰X──├C──│───┤
+   w1: ──│───├C──╰X──────╰X──├C──│───├C──╰X──────╰X──├C──┤
+   w2: ──├C──╰X──────────────╰X──├C──╰X──────────────╰X──┤
+   t0: ──╰X──────────────────────╰X──────────────────────┤
   ```
 
 * The `qml.SingleExcitation` and `qml.DoubleExcitation` operations now
@@ -299,21 +302,21 @@ 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 
+* 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:  
+
+  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)
@@ -369,7 +372,7 @@ random_mat2 = rng.standard_normal(3, requires_grad=False)
 
 * Removes occurrences of the deprecated device argument ``analytic`` from the documentation.
   [(#1261)](https://github.com/PennyLaneAI/pennylane/pull/1261)
-  
+
 * Updated PyTorch and TensorFlow interface introductions.
   [(#1333)](https://github.com/PennyLaneAI/pennylane/pull/1333)
 
@@ -378,8 +381,8 @@ random_mat2 = rng.standard_normal(3, requires_grad=False)
 This release contains contributions from (in alphabetical order):
 
 Marius Aglitoiu, Vishnu Ajith, Thomas Bromley, Jack Ceroni, Miruna Daian, Olivia Di Matteo,
-Tanya Garg, Christian Gogolin, Diego Guala, Anthony Hayes, Ryan Hill, Josh Izaac, Pavan Jayasinha, Ryan Levy, Nahum Sá, Maria Schuld, 
-Johannes Jakob Meyer, Brian Shi, Antal Száva, David Wierichs, Vincent Wong, Alberto Maldonado.
+Tanya Garg, Christian Gogolin, Diego Guala, Anthony Hayes, Ryan Hill, Josh Izaac, Pavan Jayasinha, Ryan Levy, Nahum Sá, Maria Schuld,
+Johannes Jakob Meyer, Brian Shi, Antal Száva, David Wierichs, Vincent Wong, Alberto Maldonado, Ashish Panigrahi.
 
 
 # Release 0.15.1 (current release)
@@ -627,7 +630,7 @@ fully differentiable.
   It can be used to perform an SO(2) rotation in the subspace
   spanned by the states :math:`|01\rangle` and :math:`|10\rangle`.
   For example, the following circuit performs the transformation
-  :math:`|10\rangle \rightarrow \cos(\phi/2)|10\rangle - \sin(\phi/2)|01\rangle`:    
+  :math:`|10\rangle \rightarrow \cos(\phi/2)|10\rangle - \sin(\phi/2)|01\rangle`:
 
   ```python
   dev = qml.device('default.qubit', wires=2)
@@ -649,7 +652,7 @@ fully differentiable.
   It can be used to perform an SO(2) rotation in the subspace
   spanned by the states :math:`|1100\rangle` and :math:`|0011\rangle`.
   For example, the following circuit performs the transformation
-  :math:`|1100\rangle\rightarrow \cos(\phi/2)|1100\rangle - \sin(\phi/2)|0011\rangle`:   
+  :math:`|1100\rangle\rightarrow \cos(\phi/2)|1100\rangle - \sin(\phi/2)|0011\rangle`:
 
   ```python
   dev = qml.device('default.qubit', wires=2)
@@ -876,7 +879,7 @@ fully differentiable.
   ```pycon
   >>> print(qml.draw(circuit)())
   0: ──RY(1.57)──╭C─────────────╭C──╭C──╭C──┤ ⟨Z⟩
-  1: ──RY(1.35)──╰X──RY(0.422)──╰X──╰X──╰X──┤   
+  1: ──RY(1.35)──╰X──RY(0.422)──╰X──╰X──╰X──┤
   ```
 
   In this release, it now returns:
@@ -884,7 +887,7 @@ fully differentiable.
   ```pycon
   >>> print(qml.draw(circuit)())
   0: ──RY(1.57)──╭C─────────────╭C──┤ ⟨Z⟩
-  1: ──RY(1.35)──╰X──RY(0.422)──╰X──┤   
+  1: ──RY(1.35)──╰X──RY(0.422)──╰X──┤
   ```
 
 - The templates are now classes inheriting
@@ -948,7 +951,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)
 
@@ -1005,7 +1008,7 @@ fully differentiable.
   [(#1079)](https://github.com/PennyLaneAI/pennylane/pull/1079)
   [(#1196)](https://github.com/PennyLaneAI/pennylane/pull/1196)
 
-  ```python  
+  ```python
   dev_analytic = qml.device('default.qubit', wires=1, shots=None)
   dev_finite_shots = qml.device('default.qubit', wires=1, shots=1000)
 
diff --git a/pennylane/qnode.py b/pennylane/qnode.py
index 81102ccfb..5ddf8a57e 100644
--- a/pennylane/qnode.py
+++ b/pennylane/qnode.py
@@ -28,7 +28,12 @@ from pennylane import Device
 from pennylane.operation import State
 
 from pennylane.interfaces.autograd import AutogradInterface, np as anp
-from pennylane.tape import JacobianTape, QubitParamShiftTape, CVParamShiftTape, ReversibleTape
+from pennylane.tape import (
+    JacobianTape,
+    QubitParamShiftTape,
+    CVParamShiftTape,
+    ReversibleTape,
+)
 
 
 class QNode:
@@ -182,6 +187,16 @@ class QNode:
         self.dtype = np.float64
         self.max_expansion = max_expansion
 
+    def __repr__(self):
+        """String representation."""
+        detail = "<QNode: wires={}, device='{}', interface='{}', diff_method='{}'>"
+        return detail.format(
+            self.device.num_wires,
+            self.device.short_name,
+            self.interface,
+            self.diff_method,
+        )
+
     # pylint: disable=too-many-return-statements
     @staticmethod
     def get_tape(device, interface, diff_method="best"):
@@ -825,11 +840,21 @@ class QNode:
                 "version of JAX to enable the 'jax' interface."
             ) from e
 
-    INTERFACE_MAP = {"autograd": to_autograd, "torch": to_torch, "tf": to_tf, "jax": to_jax}
+    INTERFACE_MAP = {
+        "autograd": to_autograd,
+        "torch": to_torch,
+        "tf": to_tf,
+        "jax": to_jax,
+    }
 
 
 def qnode(
-    device, interface="autograd", diff_method="best", mutable=True, max_expansion=10, **diff_options
+    device,
+    interface="autograd",
+    diff_method="best",
+    mutable=True,
+    max_expansion=10,
+    **diff_options,
 ):
     """Decorator for creating QNodes.
 


Test patch:
diff --git a/tests/tape/test_qnode.py b/tests/tape/test_qnode.py
index 1cb3b2415..8dbad8f37 100644
--- a/tests/tape/test_qnode.py
+++ b/tests/tape/test_qnode.py
@@ -1,4 +1,4 @@
-# Copyright 2018-2020 Xanadu Quantum Technologies Inc.
+# 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.
@@ -259,6 +259,21 @@ class TestValidation:
         with pytest.raises(ValueError, match="The default.gaussian device does not"):
             QNode._validate_adjoint_method(dev, "tf")
 
+    def test_qnode_print(self):
+        """Test that printing a QNode object yields the right information."""
+        dev = qml.device("default.qubit", wires=1)
+
+        def func(x):
+            qml.RX(x, wires=0)
+            return qml.expval(qml.PauliZ(0))
+
+        qn = qml.QNode(func, dev)
+
+        assert (
+            qn.__repr__()
+            == "<QNode: wires=1, device='default.qubit.autograd', interface='autograd', diff_method='best'>"
+        )
+
 
 class TestTapeConstruction:
     """Tests for the tape construction"""