Repository: PennyLaneAI/pennylane

Issue:
Print all specified wires in `wire_order` in `tape.draw()` 
#### Issue description

In #992 an update was made to enable the tape drawer to print wires in a specified order. However, it prints only wires that have gates applied (in the correct wire order).

* *Expected behavior:* Any wires specified in the wire order are printed

* *Actual behavior:* Only wires with gates on them are printed

* *Reproduces how often:* Always

* *System information:* 
```
Name: PennyLane
Version: 0.14.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/olivia/Software/anaconda3/envs/xanadu/lib/python3.8/site-packages
Requires: toml, autograd, networkx, appdirs, semantic-version, numpy, scipy
Required-by: PennyLane-SF, pennylane-qulacs, PennyLane-qsharp, PennyLane-qiskit, PennyLane-Qchem, PennyLane-Forest, PennyLane-Cirq
Platform info:           Linux-5.4.0-64-generic-x86_64-with-glibc2.10
Python version:          3.8.5
Numpy version:           1.19.5
Scipy version:           1.4.1
Installed devices:
- default.gaussian (PennyLane-0.14.0.dev0)
- default.mixed (PennyLane-0.14.0.dev0)
- default.qubit (PennyLane-0.14.0.dev0)
- default.qubit.autograd (PennyLane-0.14.0.dev0)
- default.qubit.jax (PennyLane-0.14.0.dev0)
- default.qubit.tf (PennyLane-0.14.0.dev0)
- default.tensor (PennyLane-0.14.0.dev0)
- default.tensor.tf (PennyLane-0.14.0.dev0)
- strawberryfields.fock (PennyLane-SF-0.11.0)
- strawberryfields.gaussian (PennyLane-SF-0.11.0)
- strawberryfields.remote (PennyLane-SF-0.11.0)
- qulacs.simulator (pennylane-qulacs-0.12.0)
- microsoft.QuantumSimulator (PennyLane-qsharp-0.8.0)
- qiskit.aer (PennyLane-qiskit-0.13.0.dev0)
- qiskit.basicaer (PennyLane-qiskit-0.13.0.dev0)
- qiskit.ibmq (PennyLane-qiskit-0.13.0.dev0)
- forest.numpy_wavefunction (PennyLane-Forest-0.11.0)
- forest.qpu (PennyLane-Forest-0.11.0)
- forest.qvm (PennyLane-Forest-0.11.0)
- forest.wavefunction (PennyLane-Forest-0.11.0)
- cirq.mixedsimulator (PennyLane-Cirq-0.13.0)
- cirq.pasqal (PennyLane-Cirq-0.13.0)
- cirq.qsim (PennyLane-Cirq-0.13.0)
- cirq.qsimh (PennyLane-Cirq-0.13.0)
- cirq.simulator (PennyLane-Cirq-0.13.0)
```

#### Source code and tracebacks

Minimal example:

```
dev = qml.device('default.qubit', wires=5)

with qml.tape.QuantumTape() as tape:
    qml.PauliX(wires=0)
    qml.PauliX(wires=3)
    qml.PauliX(wires=1)

print(tape.draw(wire_order=dev.wires))
```

gives 
```
 0: ──X──┤  
 1: ──X──┤  
 3: ──X──┤  
```

instead of 

```
 0: ──X──┤  
 1: ──X──┤  
 2: ─────┤  
 3: ──X──┤  
 4: ─────┤  
```

Discussion:
github-actions[bot]: Hello. You may have forgotten to update the changelog!
Please edit [.github/CHANGELOG.md](/PennyLaneAI/pennylane/blob/master/.github/CHANGELOG.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/1033?src=pr&el=h1) Report
> Merging [#1033](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1033?src=pr&el=desc) (f80393a) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/2ae02637e60319be74f164286cc6991451ee886c?el=desc) (2ae0263) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1033/graphs/tree.svg?width=650&height=150&src=pr&token=9S3YzlUgGk)](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1033?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master    #1033   +/-   ##
=======================================
  Coverage   97.94%   97.94%           
=======================================
  Files         153      153           
  Lines       11384    11394   +10     
=======================================
+ Hits        11150    11160   +10     
  Misses        234      234           
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1033?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pennylane/circuit\_drawer/circuit\_drawer.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1033/diff?src=pr&el=tree#diff-cGVubnlsYW5lL2NpcmN1aXRfZHJhd2VyL2NpcmN1aXRfZHJhd2VyLnB5) | `100.00% <100.00%> (ø)` | |
| [pennylane/circuit\_graph.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1033/diff?src=pr&el=tree#diff-cGVubnlsYW5lL2NpcmN1aXRfZ3JhcGgucHk=) | `98.63% <100.00%> (+0.03%)` | :arrow_up: |
| [pennylane/qnodes/base.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1033/diff?src=pr&el=tree#diff-cGVubnlsYW5lL3Fub2Rlcy9iYXNlLnB5) | `98.67% <100.00%> (ø)` | |
| [pennylane/tape/qnode.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1033/diff?src=pr&el=tree#diff-cGVubnlsYW5lL3RhcGUvcW5vZGUucHk=) | `98.34% <100.00%> (+0.01%)` | :arrow_up: |
| [pennylane/tape/tapes/tape.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1033/diff?src=pr&el=tree#diff-cGVubnlsYW5lL3RhcGUvdGFwZXMvdGFwZS5weQ==) | `98.32% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1033?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1033?src=pr&el=footer). Last update [2ae0263...f80393a](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1033?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

glassnotes: Thanks for the quick fix @josh146 !

There is a slightly strange case where I'm not sure if it's worth covering: if a user has a device has n wires, applies  gates to m < n, then wants to print them out in a permuted order but still include the empty wires. For example:

```
@qml.qnode(dev)
def my_function(x, y, z):
    qml.PauliX(wires=0)
    qml.Rot(x, y, z, wires=2)
    qml.PauliZ(wires=4)
    return qml.expval(qml.PauliZ(0))
```

Running
```
my_function(0, 0, 0)
print(my_function.draw(wire_order=qml.wires.Wires([4, 2, 0]), show_all_wires=True))
```

gives 
```
 4: ──Z─────────────┤     
 2: ──Rot(0, 0, 0)──┤     
 0: ──X─────────────┤ ⟨Z⟩ 
```

which doesn't include wires 1 and 3 despite specifying to show all wires. I'm not sure if it would make sense to show
```
 4: ──Z─────────────┤     
 1: ────────────────┤     
 2: ──Rot(0, 0, 0)──┤     
 3: ────────────────┤
 0: ──X─────────────┤ ⟨Z⟩      
```

or maybe it should just produce an error in these cases where `show_all_wires=True` and `len(wire_order) < len(wires)`.

Patch:
diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md
index 0d2d61002..05e442b0c 100644
--- a/.github/CHANGELOG.md
+++ b/.github/CHANGELOG.md
@@ -302,6 +302,30 @@
 
 <h3>Improvements</h3>
 
+* The circuit drawer has been updated to support the inclusion of unused or inactive
+  wires, by passing the `show_all_wires` argument.
+  [(#1033)](https://github.com/PennyLaneAI/pennylane/pull/1033)
+
+  ```python
+  dev = qml.device('default.qubit', wires=[-1, "a", "q2", 0])
+
+  @qml.qnode(dev)
+  def circuit():
+      qml.Hadamard(wires=-1)
+      qml.CNOT(wires=[-1, "q2"])
+      return qml.expval(qml.PauliX(wires="q2"))
+  ```
+
+  ```pycon
+  >>> print(qml.draw(circuit, show_all_wires=True)())
+  >>>
+   -1: ──H──╭C──┤
+    a: ─────│───┤
+   q2: ─────╰X──┤ ⟨X⟩
+    0: ─────────┤
+  ```
+
+
 * The `default.qubit` device has been updated so that internally it applies operations in a more
   functional style, i.e., by accepting an input state and returning an evolved state.
   [(#1025)](https://github.com/PennyLaneAI/pennylane/pull/1025)  
diff --git a/pennylane/circuit_drawer/circuit_drawer.py b/pennylane/circuit_drawer/circuit_drawer.py
index 94f7ca244..214046bf5 100644
--- a/pennylane/circuit_drawer/circuit_drawer.py
+++ b/pennylane/circuit_drawer/circuit_drawer.py
@@ -48,6 +48,7 @@ class CircuitDrawer:
         wires (Wires): all wires on the device for which the circuit is drawn
         charset (pennylane.circuit_drawer.CharSet, optional): The CharSet that shall be used for drawing.
         show_variable_names (bool, optional): Show variable names instead of variable values.
+        show_all_wires (bool): If True, all wires, including empty wires, are printed.
     """
 
     def __init__(
@@ -57,6 +58,7 @@ class CircuitDrawer:
         wires,
         charset=UnicodeCharSet,
         show_variable_names=False,
+        show_all_wires=False,
     ):
         self.operation_grid = Grid(raw_operation_grid)
         self.observable_grid = Grid(raw_observable_grid)
@@ -65,6 +67,11 @@ class CircuitDrawer:
         self.charset = charset
         self.show_variable_names = show_variable_names
 
+        if show_all_wires:
+            # if the provided wires include empty wires, make sure they are included
+            # as active wires
+            self.active_wires = wires.all_wires([wires, self.active_wires])
+
         self.representation_resolver = RepresentationResolver(charset, show_variable_names)
         self.operation_representation_grid = Grid()
         self.observable_representation_grid = Grid()
diff --git a/pennylane/circuit_graph.py b/pennylane/circuit_graph.py
index 7d1197e45..0497ca7b7 100644
--- a/pennylane/circuit_graph.py
+++ b/pennylane/circuit_graph.py
@@ -528,7 +528,7 @@ class CircuitGraph:
             post_queue = self.descendants_in_order(ops)
             yield LayerData(pre_queue, ops, tuple(param_inds), post_queue)
 
-    def greedy_layers(self, wire_order=None):
+    def greedy_layers(self, wire_order=None, show_all_wires=False):
         """Greedily collected layers of the circuit. Empty slots are filled with ``None``.
 
         Layers are built by pushing back gates in the circuit as far as possible, so that
@@ -536,6 +536,7 @@ class CircuitGraph:
 
         Args:
             wire_order (Wires): the order (from top to bottom) to print the wires of the circuit
+            show_all_wires (bool): If True, all wires, including empty wires, are printed.
 
         Returns:
             Tuple[list[list[~.Operation]], list[list[~.Observable]]]:
@@ -596,9 +597,22 @@ class CircuitGraph:
             temp_op_grid = OrderedDict()
             temp_obs_grid = OrderedDict()
 
-            permutation = [self.wires.labels.index(i) for i in wire_order.labels if i in self.wires]
+            if show_all_wires:
+                permutation = [
+                    self.wires.labels.index(i) if i in self.wires else None
+                    for i in wire_order.labels
+                ]
+            else:
+                permutation = [
+                    self.wires.labels.index(i) for i in wire_order.labels if i in self.wires
+                ]
 
             for i, j in enumerate(permutation):
+                if j is None:
+                    temp_op_grid[i] = [None] * len(operations[0])
+                    temp_obs_grid[i] = [None] * len(observables[0])
+                    continue
+
                 if j in operations:
                     temp_op_grid[i] = operations[j]
                 if j in observables:
@@ -628,13 +642,16 @@ class CircuitGraph:
         new.queue_idx = old.queue_idx
         nx.relabel_nodes(self._graph, {old: new}, copy=False)  # change the graph in place
 
-    def draw(self, charset="unicode", show_variable_names=False, wire_order=None):
+    def draw(
+        self, charset="unicode", show_variable_names=False, wire_order=None, show_all_wires=False
+    ):
         """Draw the CircuitGraph as a circuit diagram.
 
         Args:
             charset (str, optional): The charset that should be used. Currently, "unicode" and "ascii" are supported.
             show_variable_names (bool, optional): Show variable names instead of variable values.
             wire_order (Wires or None): the order (from top to bottom) to print the wires of the circuit
+            show_all_wires (bool): If True, all wires, including empty wires, are printed.
 
         Raises:
             ValueError: If the given charset is not supported
@@ -645,7 +662,7 @@ class CircuitGraph:
         if wire_order is not None:
             wire_order = qml.wires.Wires.all_wires([wire_order, self.wires])
 
-        grid, obs = self.greedy_layers(wire_order=wire_order)
+        grid, obs = self.greedy_layers(wire_order=wire_order, show_all_wires=show_all_wires)
 
         if charset not in CHARSETS:
             raise ValueError(
@@ -660,6 +677,7 @@ class CircuitGraph:
             wires=wire_order or self.wires,
             charset=CHARSETS[charset],
             show_variable_names=show_variable_names,
+            show_all_wires=show_all_wires,
         )
 
         return drawer.draw()
diff --git a/pennylane/qnodes/base.py b/pennylane/qnodes/base.py
index 5248217c4..ef22a53d3 100644
--- a/pennylane/qnodes/base.py
+++ b/pennylane/qnodes/base.py
@@ -216,7 +216,9 @@ class BaseQNode(qml.QueuingContext):
             return
         self.circuit.print_contents()
 
-    def draw(self, charset="unicode", show_variable_names=False, wire_order=None):
+    def draw(
+        self, charset="unicode", show_variable_names=False, wire_order=None, show_all_wires=False
+    ):
         """Draw the QNode as a circuit diagram.
 
         Consider the following circuit as an example:
@@ -251,6 +253,7 @@ class BaseQNode(qml.QueuingContext):
             charset (str, optional): The charset that should be used. Currently, "unicode" and "ascii" are supported.
             show_variable_names (bool, optional): Show variable names instead of values.
             wire_order (Sequence[Any]): the order (from top to bottom) to print the wires of the circuit
+            show_all_wires (bool): If True, all wires, including empty wires, are printed.
 
         Raises:
             ValueError: If the given charset is not supported
@@ -261,7 +264,10 @@ class BaseQNode(qml.QueuingContext):
         """
         if self.circuit:
             return self.circuit.draw(
-                charset=charset, show_variable_names=show_variable_names, wire_order=wire_order
+                charset=charset,
+                show_variable_names=show_variable_names,
+                wire_order=wire_order,
+                show_all_wires=show_all_wires,
             )
 
         raise RuntimeError(
diff --git a/pennylane/tape/qnode.py b/pennylane/tape/qnode.py
index 9f434610e..51ee7ce38 100644
--- a/pennylane/tape/qnode.py
+++ b/pennylane/tape/qnode.py
@@ -489,7 +489,9 @@ class QNode:
             *args, **kwargs
         )
 
-    def draw(self, charset="unicode", wire_order=None, **kwargs):  # pylint: disable=unused-argument
+    def draw(
+        self, charset="unicode", wire_order=None, show_all_wires=False, **kwargs
+    ):  # pylint: disable=unused-argument
         """Draw the quantum tape as a circuit diagram.
 
         Args:
@@ -497,6 +499,7 @@ class QNode:
                 "ascii" are supported.
             wire_order (Sequence[Any]): The order (from top to bottom) to print the wires of the circuit.
                 If not provided, this defaults to the wire order of the device.
+            show_all_wires (bool): If True, all wires, including empty wires, are printed.
 
         Raises:
             ValueError: if the given charset is not supported
@@ -567,12 +570,19 @@ class QNode:
         wire_order = wire_order or self.device.wires
         wire_order = qml.wires.Wires(wire_order)
 
+        if show_all_wires and len(wire_order) < self.device.num_wires:
+            raise ValueError(
+                "When show_all_wires is enabled, the provided wire order must contain all wires on the device."
+            )
+
         if not self.device.wires.contains_wires(wire_order):
             raise ValueError(
                 f"Provided wire order {wire_order.labels} contains wires not contained on the device: {self.device.wires}."
             )
 
-        return self.qtape.draw(charset=charset, wire_order=wire_order)
+        return self.qtape.draw(
+            charset=charset, wire_order=wire_order, show_all_wires=show_all_wires
+        )
 
     def to_tf(self, dtype=None):
         """Apply the TensorFlow interface to the internal quantum tape.
@@ -945,8 +955,8 @@ def metric_tensor(_qnode, diag_approx=False, only_construct=False):
     return _metric_tensor_fn
 
 
-def draw(_qnode, charset="unicode", wire_order=None):
-    """draw(qnode, charset="unicode", wire_order=None)
+def draw(_qnode, charset="unicode", wire_order=None, show_all_wires=False):
+    """draw(qnode, charset="unicode", wire_order=None, show_all_wires=False)
     Create a function that draws the given _qnode.
 
     Args:
@@ -954,6 +964,7 @@ def draw(_qnode, charset="unicode", wire_order=None):
         charset (str, optional): The charset that should be used. Currently, "unicode" and
             "ascii" are supported.
         wire_order (Sequence[Any]): the order (from top to bottom) to print the wires of the circuit
+        show_all_wires (bool): If True, all wires, including empty wires, are printed.
 
     Returns:
         A function that has the same arguement signature as ``qnode``. When called,
@@ -1020,6 +1031,8 @@ def draw(_qnode, charset="unicode", wire_order=None):
     @wraps(_qnode)
     def wrapper(*args, **kwargs):
         _qnode.construct(args, kwargs)
-        return _qnode.qtape.draw(charset, wire_order=wire_order)
+        _wire_order = wire_order or _qnode.device.wires
+        _wire_order = qml.wires.Wires(_wire_order)
+        return _qnode.qtape.draw(charset, wire_order=_wire_order, show_all_wires=show_all_wires)
 
     return wrapper
diff --git a/pennylane/tape/tapes/tape.py b/pennylane/tape/tapes/tape.py
index 9e718222e..e56dac978 100644
--- a/pennylane/tape/tapes/tape.py
+++ b/pennylane/tape/tapes/tape.py
@@ -926,7 +926,7 @@ class QuantumTape(AnnotatedQueue):
 
         return self._depth
 
-    def draw(self, charset="unicode", wire_order=None):
+    def draw(self, charset="unicode", wire_order=None, show_all_wires=False):
         """Draw the quantum tape as a circuit diagram.
 
         Consider the following circuit as an example:
@@ -953,6 +953,7 @@ class QuantumTape(AnnotatedQueue):
             charset (str, optional): The charset that should be used. Currently, "unicode" and
                 "ascii" are supported.
             wire_order (Sequence[Any]): the order (from top to bottom) to print the wires of the circuit
+            show_all_wires (bool): If True, all wires, including empty wires, are printed.
 
         Raises:
             ValueError: if the given charset is not supported
@@ -960,7 +961,12 @@ class QuantumTape(AnnotatedQueue):
         Returns:
             str: the circuit representation of the tape
         """
-        return self.graph.draw(charset=charset, show_variable_names=False, wire_order=wire_order)
+        return self.graph.draw(
+            charset=charset,
+            show_variable_names=False,
+            wire_order=wire_order,
+            show_all_wires=show_all_wires,
+        )
 
     @property
     def data(self):


Test patch:
diff --git a/tests/circuit_drawer/test_circuit_drawer.py b/tests/circuit_drawer/test_circuit_drawer.py
index 8fe2e9483..0fa6c29df 100644
--- a/tests/circuit_drawer/test_circuit_drawer.py
+++ b/tests/circuit_drawer/test_circuit_drawer.py
@@ -847,6 +847,45 @@ class TestWireOrdering:
 
         assert res == "\n".join(expected)
 
+    def test_include_empty_wires(self, tape_only):
+        """Test that empty wires are correctly included"""
+
+        dev = qml.device('default.qubit', wires=[-1, "a", "q2", 0])
+
+        @qml.qnode(dev)
+        def circuit():
+            qml.Hadamard(wires=-1)
+            qml.CNOT(wires=[-1, "q2"])
+            return qml.expval(qml.PauliX(wires="q2"))
+
+        circuit()
+        res = circuit.draw(show_all_wires=True)
+        expected = [
+            " -1: ──H──╭C──┤     ",
+            "  a: ─────│───┤     ",
+            " q2: ─────╰X──┤ ⟨X⟩ ",
+            "  0: ─────────┤     \n"
+        ]
+
+        assert res == "\n".join(expected)
+
+    def test_show_all_wires_error(self, tape_only):
+        """Test that show_all_wires will raise an error if the provided wire
+        order does not contain all wires on the device"""
+
+        dev = qml.device('default.qubit', wires=[-1, "a", "q2", 0])
+
+        @qml.qnode(dev)
+        def circuit():
+            qml.Hadamard(wires=-1)
+            qml.CNOT(wires=[-1, "q2"])
+            return qml.expval(qml.PauliX(wires="q2"))
+
+        circuit()
+
+        with pytest.raises(ValueError, match="must contain all wires"):
+            circuit.draw(show_all_wires=True, wire_order=[-1, "a"])
+
     def test_missing_wire(self, tape_only):
         """Test that wires not specifically mentioned in the wire
         reordering are appended at the bottom of the circuit drawing"""