Repository: PennyLaneAI/pennylane

Issue:
[BUG] `qml.draw` fails for templates without a custom `adjoint` method (`expansion_strategy=None`)
### Expected behavior

```python
dev = qml.device("default.qubit", wires=2)

@qml.qnode(dev)
def qpe_circuit():
    qml.adjoint(qml.QuantumPhaseEstimation)(
        qml.PauliX.matrix,
        target_wires=[0],
        estimation_wires=[1],
    )
    return qml.state()

print(qml.draw(qpe_circuit)())
```
Executes correctly.

### Actual behavior

An error is raised:
```
~/xanadu/pennylane/pennylane/circuit_drawer/representation_resolver.py in element_representation(self, element, wire)
    487             return self.output_representation(element, wire)
    488 
--> 489         return self.operator_representation(element, wire)

~/xanadu/pennylane/pennylane/circuit_drawer/representation_resolver.py in operator_representation(self, op, wire)
    420         elif len(qml.math.shape(op.data[0])) != 0:
    421             print(name)
--> 422             representation = name + RepresentationResolver._format_matrix_arguments(
    423                 op.data, "M", self.matrix_cache
    424             )

TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'
```

### Additional information

The issue seems to arise with any template that doesn't define the `adjoint` method. The specific line in the traceback errors out because a `QuantumTape` object is being processed and it doesn't have a `name` attribute defined.

When `expansion_strategy=device` is set, drawing concludes successfully and draws the circuit as expected.

### Source code

_No response_

### Tracebacks

_No response_

### System information

```shell
Name: PennyLane
Version: 0.20.0.dev0
Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
Home-page: https://github.com/XanaduAI/pennylane
Author: 
Author-email: 
License: Apache License 2.0
Location: /home/antal/xanadu/pennylane
Requires: numpy, scipy, networkx, autograd, toml, appdirs, semantic_version, autoray, cachetools, pennylane-lightning
Required-by: PennyLane-Cirq, PennyLane-Orquestra, PennyLane-SF, pennylane-qulacs, PennyLane-IonQ, amazon-braket-pennylane-plugin, PennyLane-Honeywell, PennyLane-qiskit, PennyLane-AQT, PennyLane-Qchem, PennyLane-PQ, PennyLane-Forest, PennyLane-Lightning, PennyLane-qsharp
Platform info:           Linux-5.11.0-40-generic-x86_64-with-glibc2.10
Python version:          3.8.5
Numpy version:           1.21.4
Scipy version:           1.7.3
Installed devices:
- cirq.mixedsimulator (PennyLane-Cirq-0.17.1)
- cirq.pasqal (PennyLane-Cirq-0.17.1)
- cirq.qsim (PennyLane-Cirq-0.17.1)
- cirq.qsimh (PennyLane-Cirq-0.17.1)
- cirq.simulator (PennyLane-Cirq-0.17.1)
- orquestra.forest (PennyLane-Orquestra-0.15.0)
- orquestra.ibmq (PennyLane-Orquestra-0.15.0)
- orquestra.qiskit (PennyLane-Orquestra-0.15.0)
- orquestra.qulacs (PennyLane-Orquestra-0.15.0)
- strawberryfields.fock (PennyLane-SF-0.16.0.dev0)
- strawberryfields.gaussian (PennyLane-SF-0.16.0.dev0)
- strawberryfields.gbs (PennyLane-SF-0.16.0.dev0)
- strawberryfields.remote (PennyLane-SF-0.16.0.dev0)
- strawberryfields.tf (PennyLane-SF-0.16.0.dev0)
- qulacs.simulator (pennylane-qulacs-0.17.0.dev0)
- ionq.qpu (PennyLane-IonQ-0.17.0.dev0)
- ionq.simulator (PennyLane-IonQ-0.17.0.dev0)
- braket.aws.qubit (amazon-braket-pennylane-plugin-1.4.1.dev0)
- braket.local.qubit (amazon-braket-pennylane-plugin-1.4.1.dev0)
- honeywell.hqs (PennyLane-Honeywell-0.16.0.dev0)
- qiskit.aer (PennyLane-qiskit-0.18.0.dev0)
- qiskit.basicaer (PennyLane-qiskit-0.18.0.dev0)
- qiskit.ibmq (PennyLane-qiskit-0.18.0.dev0)
- aqt.noisy_sim (PennyLane-AQT-0.18.0)
- aqt.sim (PennyLane-AQT-0.18.0)
- projectq.classical (PennyLane-PQ-0.18.0.dev0)
- projectq.ibm (PennyLane-PQ-0.18.0.dev0)
- projectq.simulator (PennyLane-PQ-0.18.0.dev0)
- forest.numpy_wavefunction (PennyLane-Forest-0.18.0.dev0)
- forest.qvm (PennyLane-Forest-0.18.0.dev0)
- forest.wavefunction (PennyLane-Forest-0.18.0.dev0)
- lightning.qubit (PennyLane-Lightning-0.19.0.dev0)
- microsoft.QuantumSimulator (PennyLane-qsharp-0.19.0)
- default.gaussian (PennyLane-0.20.0.dev0)
- default.mixed (PennyLane-0.20.0.dev0)
- default.qubit (PennyLane-0.20.0.dev0)
- default.qubit.autograd (PennyLane-0.20.0.dev0)
- default.qubit.jax (PennyLane-0.20.0.dev0)
- default.qubit.tf (PennyLane-0.20.0.dev0)
- default.qubit.torch (PennyLane-0.20.0.dev0)
- default.tensor (PennyLane-0.20.0.dev0)
- default.tensor.tf (PennyLane-0.20.0.dev0)
```
```


### 

- [X] I have searched exisisting GitHub issues to make sure the issue does not already exist.


Discussion:
Thanks for catching this @antalszava!

> The issue seems to arise with any template that doesn't define the adjoint method. The specific line in the traceback errors out because a QuantumTape object is being processed and it doesn't have a name attribute defined.

This makes me think that the solution here is to set up the circuit drawer to support drawing quantum tapes, as 'containers' of operations?
@josh146's proposal sounds good to me, as long as having tapes be part of a circuit is something we expect? Presumably we could also ensure that the "adjoint tape" is replaced with the operations within, unless this is indeed the desired behaviour.

**EDIT**: Ok, I guess whether the adjoint is replaced with a tape or the operations within is controlled by the `expansion_strategy` keyword. Then, if we add support for drawing tapes, the question becomes do we draw an opaque tape, a tape including its operations, or just the operations?

**case 1:** opaque tape
```
 0: ──╭QuantumTape──Y──╭┤ State 
 1: ──╰QuantumTape─────╰┤ State 
```

**case 2:** tape including its operations
```
 0: ──╭QuantumTape:───────U0───╮──Y──╭┤ State 
 1: ──╰QuantumTape:─QFT⁻¹─U0─H─╯─────╰┤ State 
```

**case 3:** just operations
```
 0: ─────────╭U0─────Y──╭┤ State 
 1: ──QFT⁻¹──╰U0──H─────╰┤ State
```

Case 1 is easiest to implement, case 2 may be quite tricky to do correctly, case 3 is already the behaviour if `expansion_strategy` is specified and we probably don't want to default to hiding that the tape is part of the circuit.
I think that some form of  **case 1** would be great.

* **case 2** is nice as we see all details, but it might get bulky with deep circuits.
* **case 3** could correspond to `expansion_strategy='device'`, so in that case, we should actually expand the tape (and operation) itself to draw it like that.

Right now, a template would be drawn as:
```
 0: ──╭QuantumPhaseEstimation(M0)──╭┤ State 
 1: ──╰QuantumPhaseEstimation(M0)──╰┤ State 
M0 =
[[0 1]
 [1 0]]
```
What if we followed **case 1** and drew the tape to look as if it was its inverse? Could we get away with the user not knowing if there's a real operation object or a tape that represents the inverse of the operation?

So like
```
 0: ──╭QuantumPhaseEstimation(M0)⁻¹──╭┤ State 
 1: ──╰QuantumPhaseEstimation(M0)⁻¹──╰┤ State 
M0 =
[[0 1]
 [1 0]]
```
Where `QuantumPhaseEstimation(M0)⁻¹` is actually a tape under the hood (not sure if this conformed with the position where `⁻¹` is put in drawings, maybe not :smile: ).

In specific, as a user, I'd expect that simply using `qml.adjoint` will not alter the way the circuit is being drawn. If I see `QuantumPhaseEstimation`, then I'd expect its inverse to pop up as a single object. If I see the decomposition of `QuantumPhaseEstimation`, then I'd expect a list of operations to pop up for its inverse.
> What if we followed **case 1** and drew the tape to look as if it was its inverse? Could we get away with the user not knowing if there's a real operation object or a tape that represents the inverse of the operation?
> 
> So like
> 
> ```
>  0: ──╭QuantumPhaseEstimation(M0)⁻¹──╭┤ State 
>  1: ──╰QuantumPhaseEstimation(M0)⁻¹──╰┤ State 
> M0 =
> [[0 1]
>  [1 0]]
> ```

The problem is that the information about the template is lost. Since the adjoint method is not defined for `QuantumPhaseEstimation`, the template is expanded into a tape and the adjoint is computed on the tape. All the drawer then sees is a tape with operations inside.
So while in this instance the tape came from an adjointed template, it could just as well come from the manual placement of a tape in the operations list:
```py
@qml.qnode(dev)
def tape_circuit():
    with qml.tape.QuantumTape():
        qml.Hadamard(wires=0)
        qml.CNOT(wires=[0,1])
    return qml.state()
```

Note that the desired behaviour you describe does happen when a template defines the adjoint method via `.inv()`, for example the QFT template:
 ```py
@qml.qnode(dev)
def tape_circuit():
    qml.adjoint(qml.QFT)(wires=[1])
    return qml.state()
```
prints as:
```
1: ──QFT⁻¹──┤ State 
```

> In specific, as a user, I'd expect that simply using `qml.adjoint` will not alter the way the circuit is being drawn. If I see `QuantumPhaseEstimation`, then I'd expect its inverse to pop up as a single object. If I see the decomposition of `QuantumPhaseEstimation`, then I'd expect a list of operations to pop up for its inverse.

So I think in addition to supporting tapes in the drawer, a fix to this bug could include defining the adjoint method for the QuantumPhaseEstimation template so that its adjoint is properly drawn.
#1973 removed the issue for the QuantumPhaseEstimation template.

Resolving patch:
diff --git a/pennylane/drawer/circuit_drawer.py b/pennylane/drawer/circuit_drawer.py
--- a/pennylane/drawer/circuit_drawer.py
+++ b/pennylane/drawer/circuit_drawer.py
@@ -49,6 +49,7 @@ class CircuitDrawer:
         charset (str, pennylane.circuit_drawer.CharSet, optional): The CharSet that shall be used for drawing.
         show_all_wires (bool): If True, all wires, including empty wires, are printed.
         max_length (int, optional): Maximum string width (columns) when printing the circuit to the CLI.
+        _label_offsets (dict[strin, int], optional): Offset the printed index of different symbol types in nested circuits.
     """
 
     def __init__(
@@ -59,6 +60,7 @@ def __init__(
         charset=None,
         show_all_wires=False,
         max_length=None,
+        _label_offsets=None,
     ):
         self.operation_grid = Grid(raw_operation_grid)
         self.observable_grid = Grid(raw_observable_grid)
@@ -66,16 +68,16 @@ def __init__(
         self.active_wires = self.extract_active_wires(raw_operation_grid, raw_observable_grid)
 
         if charset is None:
-            self.charset = CHARSETS["unicode"]()
+            self.charset = CHARSETS["unicode"]
         elif isinstance(charset, type) and issubclass(charset, CharSet):
-            self.charset = charset()
+            self.charset = charset
         else:
             if charset not in CHARSETS:
                 supported_char = ", ".join(CHARSETS.keys())
                 raise ValueError(
                     f"Charset '{charset}' is not supported. Supported charsets: {supported_char}."
                 )
-            self.charset = CHARSETS[charset]()
+            self.charset = CHARSETS[charset]
 
         if show_all_wires:
             # if the provided wires include empty wires, make sure they are included
@@ -85,7 +87,9 @@ def __init__(
         # We add a -2 character offset to account for some downstream formatting
         self.max_length = max_length - 2 if max_length is not None else None
 
-        self.representation_resolver = RepresentationResolver(self.charset)
+        self.representation_resolver = RepresentationResolver(
+            self.charset, label_offsets=_label_offsets
+        )
         self.operation_representation_grid = Grid()
         self.observable_representation_grid = Grid()
         self.operation_decoration_indices = []
@@ -364,13 +368,33 @@ def draw(self):
 
             rendered_string += "\n"
 
-        for symbol, cache in [
-            ("U", self.representation_resolver.unitary_matrix_cache),
-            ("H", self.representation_resolver.hermitian_matrix_cache),
-            ("M", self.representation_resolver.matrix_cache),
+        for symbol, cache, offset in [
+            (
+                "U",
+                self.representation_resolver.unitary_matrix_cache,
+                self.representation_resolver.unitary_offset,
+            ),
+            (
+                "H",
+                self.representation_resolver.hermitian_matrix_cache,
+                self.representation_resolver.hermitian_offset,
+            ),
+            (
+                "M",
+                self.representation_resolver.matrix_cache,
+                self.representation_resolver.matrix_offset,
+            ),
+            (
+                "T",
+                [
+                    draw_fn(self.representation_resolver)
+                    for draw_fn in self.representation_resolver.tape_cache.values()
+                ],
+                self.representation_resolver.tape_offset,
+            ),
         ]:
             for idx, matrix in enumerate(cache):
-                rendered_string += f"{symbol}{idx} =\n{matrix}\n"
+                rendered_string += f"{symbol}{idx + offset} =\n{matrix}\n"
 
         # Restrict CLI print width to max_length
         if self.max_length is not None:
diff --git a/pennylane/drawer/representation_resolver.py b/pennylane/drawer/representation_resolver.py
--- a/pennylane/drawer/representation_resolver.py
+++ b/pennylane/drawer/representation_resolver.py
@@ -18,7 +18,7 @@
 import numpy as np
 import pennylane as qml
 
-from .charsets import UnicodeCharSet
+from .charsets import CHARSETS, UnicodeCharSet
 
 
 class RepresentationResolver:
@@ -26,13 +26,17 @@ class RepresentationResolver:
 
     Args:
         charset (CharSet, optional): The CharSet to be used for representation resolution.
+        label_offsets (dict[string, int], optional): Offset the printed index of different symbol types in nested circuits.
     """
 
-    def __init__(self, charset=UnicodeCharSet):
+    def __init__(self, charset=UnicodeCharSet, label_offsets=None):
         self.charset = charset
-        self.matrix_cache = []
-        self.unitary_matrix_cache = []
-        self.hermitian_matrix_cache = []
+        if not label_offsets:
+            label_offsets = {"matrix": 0, "unitary": 0, "hermitian": 0, "tape": 0}
+        self.matrix_cache, self.matrix_offset = [], label_offsets["matrix"]
+        self.unitary_matrix_cache, self.unitary_offset = [], label_offsets["unitary"]
+        self.hermitian_matrix_cache, self.hermitian_offset = [], label_offsets["hermitian"]
+        self.tape_cache, self.tape_offset = {}, label_offsets["tape"]
 
     # Symbol for uncontrolled wires
     resolution_dict = {
@@ -121,54 +125,57 @@ def single_parameter_representation(par):
         return f"{1.0 * par:.3g}"
 
     @staticmethod
-    def _format_matrix_operation(operation, symbol, cache):
+    def _format_matrix_operation(operation, symbol, cache, offset):
         """Format an operation that corresponds to a single matrix.
 
         Args:
             operation (~.Operation): Operation that shall be formatted
             symbol (str): The symbol that should be used to identify matrices
             cache (List[numpy.ndarray]): The cache of already known matrices
+            offset (int): Offset the printed index to the symbol for nested circuits.
 
         Returns:
             str: The formatted operation
         """
         mat = operation.data[0]
-        idx = RepresentationResolver.index_of_array_or_append(mat, cache)
+        idx = RepresentationResolver.index_of_array_or_append(mat, cache) + offset
 
         return f"{symbol}{idx}"
 
     @staticmethod
-    def _format_controlled_qubit_unitary(operation, symbol, cache):
+    def _format_controlled_qubit_unitary(operation, symbol, cache, offset):
         """Format an operation that corresponds to a single matrix with controls.
 
         Args:
             operation (~.Operation): Operation that shall be formatted
             symbol (str): The symbol that should be used to identify matrices
             cache (List[numpy.ndarray]): The cache of already known matrices
+            offset (int): Offset the printed index to the symbol for nested circuits.
 
         Returns:
             str: The formatted operation
         """
         mat = operation.U
-        idx = RepresentationResolver.index_of_array_or_append(mat, cache)
+        idx = RepresentationResolver.index_of_array_or_append(mat, cache) + offset
 
         return f"{symbol}{idx}"
 
     @staticmethod
-    def _format_matrix_arguments(params, symbol, cache):
+    def _format_matrix_arguments(params, symbol, cache, offset):
         """Format a sequence of matrix parameters.
 
         Args:
             params (List[numpy.ndarray]): List of matrix parameters
             symbol (str): The symbol that should be used to identify matrices
             cache (List[numpy.ndarray]): The cache of already known matrices
+            offset (int): Offset the printed index to the symbol for nested circuits.
 
         Returns:
             str: The formatted matrix arguments
         """
         param_strings = []
         for param in params:
-            idx = RepresentationResolver.index_of_array_or_append(param, cache)
+            idx = RepresentationResolver.index_of_array_or_append(param, cache) + offset
 
             param_strings.append(f"{symbol}{idx}")
 
@@ -331,6 +338,31 @@ def operator_representation(self, op, wire):
 
             return (" " + self.charset.OTIMES + " ").join(constituent_representations)
 
+        if isinstance(op, qml.tape.QuantumTape):
+            if op in self.tape_cache:
+                idx = list(self.tape_cache).index(op) + self.tape_offset
+            else:
+                from .circuit_drawer import CircuitDrawer
+
+                grid, obs = op.graph.greedy_layers()
+
+                def draw_nested_tape_callback(resolver):
+                    offsets = {
+                        "matrix": resolver.matrix_offset + len(resolver.matrix_cache),
+                        "unitary": resolver.unitary_offset + len(resolver.unitary_matrix_cache),
+                        "hermitian": resolver.hermitian_offset
+                        + len(resolver.hermitian_matrix_cache),
+                        "tape": resolver.tape_offset + len(resolver.tape_cache),
+                    }
+                    return CircuitDrawer(
+                        grid, obs, op.graph.wires, charset=self.charset, _label_offsets=offsets
+                    ).draw()
+
+                idx = len(self.tape_cache) + self.tape_offset
+                self.tape_cache[op] = draw_nested_tape_callback
+
+            return f"QuantumTape:T{idx}"
+
         representation = ""
         base_name = getattr(op, "base_name", op.name)
         name = base_name
@@ -360,7 +392,7 @@ def operator_representation(self, op, wire):
 
         elif base_name == "QubitUnitary":
             representation = RepresentationResolver._format_matrix_operation(
-                op, "U", self.unitary_matrix_cache
+                op, "U", self.unitary_matrix_cache, self.unitary_offset
             )
 
         elif base_name == "ControlledQubitUnitary":
@@ -368,12 +400,12 @@ def operator_representation(self, op, wire):
                 return self.charset.CONTROL
 
             representation = RepresentationResolver._format_controlled_qubit_unitary(
-                op, "U", self.unitary_matrix_cache
+                op, "U", self.unitary_matrix_cache, self.unitary_offset
             )
 
         elif base_name == "Hermitian":
             representation = RepresentationResolver._format_matrix_operation(
-                op, "H", self.hermitian_matrix_cache
+                op, "H", self.hermitian_matrix_cache, self.hermitian_offset
             )
 
         elif base_name == "QuadOperator":
@@ -402,7 +434,7 @@ def operator_representation(self, op, wire):
         # Operations that only have matrix arguments
         elif len(qml.math.shape(op.data[0])) != 0:
             representation = name + RepresentationResolver._format_matrix_arguments(
-                op.data, "M", self.matrix_cache
+                op.data, "M", self.matrix_cache, self.matrix_offset
             )
 
         else:


Test patch:
diff --git a/tests/drawer/test_circuit_drawer.py b/tests/drawer/test_circuit_drawer.py
--- a/tests/drawer/test_circuit_drawer.py
+++ b/tests/drawer/test_circuit_drawer.py
@@ -73,7 +73,7 @@ def test_charset_default(self):
             dummy_raw_operation_grid, dummy_raw_observable_grid, Wires(range(6)), charset=None
         )
 
-        assert isinstance(drawer_None.charset, UnicodeCharSet)
+        assert drawer_None.charset is UnicodeCharSet
 
     @pytest.mark.parametrize("charset", ("unicode", "ascii"))
     def test_charset_string(self, charset):
@@ -82,7 +82,7 @@ def test_charset_string(self, charset):
             dummy_raw_operation_grid, dummy_raw_observable_grid, Wires(range(6)), charset=charset
         )
 
-        assert isinstance(drawer_str.charset, CHARSETS[charset])
+        assert drawer_str.charset is CHARSETS[charset]
 
     @pytest.mark.parametrize("charset", (UnicodeCharSet, AsciiCharSet))
     def test_charset_class(self, charset):
@@ -91,7 +91,7 @@ def test_charset_class(self, charset):
             dummy_raw_operation_grid, dummy_raw_observable_grid, Wires(range(6)), charset=charset
         )
 
-        assert isinstance(drawer_class.charset, charset)
+        assert drawer_class.charset is charset
 
     def test_charset_error(self):
 
@@ -805,6 +805,46 @@ def test_qubit_circuit_length_under_max_length_kwdarg(self):
         )
         assert tape.draw(max_length=60) == expected
 
+    def test_nested_tapes(self):
+
+        with qml.tape.QuantumTape() as tape:
+            with qml.tape.QuantumTape():
+                qml.PauliX(0)
+                qml.CNOT(wires=[0, 2])
+                with qml.tape.QuantumTape():
+                    qml.QuantumPhaseEstimation(
+                        qml.PauliY.matrix, target_wires=[1], estimation_wires=[2]
+                    )
+                    qml.CNOT(wires=[1, 2])
+            qml.Hadamard(1)
+            with qml.tape.QuantumTape():
+                qml.SWAP(wires=[0, 1])
+            qml.state()
+
+        expected = (
+            " 0: ──╭QuantumTape:T0─────╭QuantumTape:T1──╭┤ State \n"
+            + " 1: ──├QuantumTape:T0──H──╰QuantumTape:T1──├┤ State \n"
+            + " 2: ──╰QuantumTape:T0──────────────────────╰┤ State \n"
+            + "T0 =\n"
+            + " 0: ──X──╭C───────────────────┤  \n"
+            + " 2: ─────╰X──╭QuantumTape:T2──┤  \n"
+            + " 1: ─────────╰QuantumTape:T2──┤  \n"
+            + "T2 =\n"
+            + " 1: ──╭QuantumPhaseEstimation(M0)──╭C──┤  \n"
+            + " 2: ──╰QuantumPhaseEstimation(M0)──╰X──┤  \n"
+            + "M0 =\n"
+            + "[[ 0.+0.j -0.-1.j]\n"
+            + " [ 0.+1.j  0.+0.j]]\n"
+            + "\n"
+            + "\n"
+            + "T1 =\n"
+            + " 0: ──╭SWAP──┤  \n"
+            + " 1: ──╰SWAP──┤  \n"
+            + "\n"
+        )
+
+        assert tape.draw(wire_order=qml.wires.Wires([0, 1, 2])) == expected
+
 
 class TestWireOrdering:
     """Tests for wire ordering functionality"""
diff --git a/tests/drawer/test_representation_resolver.py b/tests/drawer/test_representation_resolver.py
--- a/tests/drawer/test_representation_resolver.py
+++ b/tests/drawer/test_representation_resolver.py
@@ -35,6 +35,14 @@ def ascii_representation_resolver():
     return RepresentationResolver(charset=qml.drawer.charsets.AsciiCharSet)
 
 
+def two_wire_quantum_tape():
+    """An instance of a QuantumTape to be used as operation in a circuit."""
+    with qml.tape.QuantumTape() as quantum_tape:
+        qml.Hadamard(wires=0)
+        qml.CNOT(wires=[0, 1])
+    return quantum_tape
+
+
 class TestRepresentationResolver:
     """Test the RepresentationResolver class."""
 
@@ -205,6 +213,8 @@ def test_single_parameter_representation(self, unicode_representation_resolver,
             (qml.Toffoli(wires=[0, 2, 1]).inv(), 2, "C"),
             (qml.measure.sample(wires=[0, 1]), 0, "basis"),  # not providing an observable in
             (qml.measure.sample(wires=[0, 1]), 1, "basis"),  # sample gets displayed as raw
+            (two_wire_quantum_tape(), 0, "QuantumTape:T0"),
+            (two_wire_quantum_tape(), 1, "QuantumTape:T0"),
         ],
     )
     def test_operator_representation_unicode(
@@ -347,6 +357,8 @@ def test_operator_representation_unicode(
             (qml.Toffoli(wires=[0, 2, 1]).inv(), 2, "C"),
             (qml.measure.sample(wires=[0, 1]), 0, "basis"),  # not providing an observable in
             (qml.measure.sample(wires=[0, 1]), 1, "basis"),  # sample gets displayed as raw
+            (two_wire_quantum_tape(), 0, "QuantumTape:T0"),
+            (two_wire_quantum_tape(), 1, "QuantumTape:T0"),
         ],
     )
     def test_operator_representation_ascii(self, ascii_representation_resolver, op, wire, target):