Repository: PennyLaneAI/pennylane
Pull request #3501 (2022-12-08 21:29:23)

preserve class type when using from_queue
**Context:**
`QuantumScript.from_queue(q)` would always return a `QuantumScript`, even if a user calls it from a child class. This would be confusing/unexpected.

**Description of the Change:**
Return a script with a type matching the calling class when doing `QuantumScript_or_child_class.from_queue(q)`.

**Benefits:**
Less confusion. I am mainly adding this to enable users to do `QuantumTape.from_queue`. Before, it would return a `QuantumScript`. With this PR, it will return a `QuantumTape`. If all goes to plan, we will change `QuantumTape` to become what is today known as `QuantumScript`, so this utility will allow for a consistent API for those users who do not wish to leave `QuantumTape` in the interim.

**Possible Drawbacks:**
N/A

**Related GitHub Issues:**
N/A

codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3501?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#3501](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3501?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (58ce59b) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/fb20bbc9db5d6b2aff93ea7062c58214c6101ce0?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (fb20bbc) will **not change** coverage.
> The diff coverage is `100.00%`.

```diff
@@           Coverage Diff           @@
##           master    #3501   +/-   ##
=======================================
  Coverage   99.71%   99.71%           
=======================================
  Files         309      309           
  Lines       27452    27452           
=======================================
  Hits        27375    27375           
  Misses         77       77           
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3501?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/tape/qscript.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3501/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RhcGUvcXNjcmlwdC5weQ==) | `100.00% <100.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)

timmysilv: I'll add something to the bug section, but it didn't really come from a bug concern. @josh146 and I were chatting about the future for class naming, and we will probably rename what is today `QuantumScript` back to `QuantumTape`, and delete what is today called `QuantumTape`. So - this allows people to do `QuantumTape.from_queue(q)`, receive a `QuantumTape`, and when we make the switch, the difference will be imperceptible!
timmysilv: my latest commit added two tests:
1. A test introducing a subclass of QuantumScript with a different __init__, and asserts that from_queue fails for that class
2. A test that iterates through _actual current_ subclasses of QuantumScript, and asserts that they all succeed in calling `from_queue`

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 9e8e4df35..5bf4511cb 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -1,8 +1,8 @@
 :orphan:
 
- # Release 0.29.0-dev (development release)
+# Release 0.29.0-dev (development release)
 
- <h3>New features since last release</h3>
+<h3>New features since last release</h3>
 
 * The JAX-JIT interface now supports higher-order gradient computation with the new return types system.
   [(#3498)](https://github.com/PennyLaneAI/pennylane/pull/3498)
@@ -40,22 +40,27 @@
      DeviceArray(0.41614684, dtype=float64, weak_type=True))))
   ```
 
- <h3>Improvements</h3>
+<h3>Improvements</h3>
+
 * The `qml.generator` function now checks if the generator is hermitian, rather than whether it is a subclass of 
   `Observable`, allowing it to return valid generators from `SymbolicOp` and `CompositeOp` classes.
  [(#3485)](https://github.com/PennyLaneAI/pennylane/pull/3485)
 
- <h3>Breaking changes</h3>
+<h3>Breaking changes</h3>
+
+<h3>Deprecations</h3>
 
- <h3>Deprecations</h3>
+<h3>Documentation</h3>
 
- <h3>Documentation</h3>
+<h3>Bug fixes</h3>
 
- <h3>Bug fixes</h3>
+* Child classes of `QuantumScript` now return their own type when using `SomeChildClass.from_queue`.
+  [(#3501)](https://github.com/PennyLaneAI/pennylane/pull/3501)
 
- <h3>Contributors</h3>
+<h3>Contributors</h3>
 
- This release contains contributions from (in alphabetical order):
+This release contains contributions from (in alphabetical order):
  
 Lillian M. A. Frederiksen
+Matthew Silverman
 Antal Száva
diff --git a/pennylane/tape/qscript.py b/pennylane/tape/qscript.py
index 8165f97aa..12ce56bad 100644
--- a/pennylane/tape/qscript.py
+++ b/pennylane/tape/qscript.py
@@ -1293,10 +1293,10 @@ class QuantumScript:
 
         return qasm_str
 
-    @staticmethod
-    def from_queue(queue):
+    @classmethod
+    def from_queue(cls, queue):
         """Construct a QuantumScript from an AnnotatedQueue."""
-        return QuantumScript(*process_queue(queue))
+        return cls(*process_queue(queue))
 
 
 def make_qscript(fn):


Test patch:
diff --git a/tests/tape/test_qscript.py b/tests/tape/test_qscript.py
index 0ff233922..ce111543b 100644
--- a/tests/tape/test_qscript.py
+++ b/tests/tape/test_qscript.py
@@ -784,13 +784,56 @@ class TestMakeQscript:
         assert qscript.operations[0].name == "PauliX"
 
 
-def test_from_queue():
-    """Test that QuantumScript.from_queue works correctly."""
-    with qml.queuing.AnnotatedQueue() as q:
-        op = qml.PauliX(0)
-        with qml.QueuingManager.stop_recording():
-            qml.Hadamard(0)
-        qml.expval(qml.PauliZ(0))
-    qscript = QuantumScript.from_queue(q)
-    assert qscript.operations == [op]
-    assert len(qscript.measurements) == 1
+class TestFromQueue:
+    """Test that QuantumScript.from_queue behaves properly."""
+
+    def test_from_queue(self):
+        """Test that QuantumScript.from_queue works correctly."""
+        with qml.queuing.AnnotatedQueue() as q:
+            op = qml.PauliX(0)
+            with qml.QueuingManager.stop_recording():
+                qml.Hadamard(0)
+            qml.expval(qml.PauliZ(0))
+        qscript = QuantumScript.from_queue(q)
+        assert qscript.operations == [op]
+        assert len(qscript.measurements) == 1
+
+    def test_from_queue_child_class_preserved(self):
+        """Test that a child of QuantumScript gets its own type when calling from_queue."""
+
+        class MyScript(QuantumScript):
+            pass
+
+        with qml.queuing.AnnotatedQueue() as q:
+            qml.PauliZ(0)
+
+        qscript = MyScript.from_queue(q)
+        assert type(qscript) == MyScript
+
+    def test_from_queue_child_with_different_init_fails(self):
+        """Test that if a child class overrides init to take different arguments, from_queue will fail."""
+
+        class ScriptWithNewInit(QuantumScript):
+            """An arbitrary class that has a different constructor from QuantumScript."""
+
+            def __init__(self, ops, measurements, prep, bonus):
+                super().__init__(ops, measurements, prep)
+                self.bonus = bonus
+
+        with qml.queuing.AnnotatedQueue() as q:
+            qml.PauliZ(0)
+
+        with pytest.raises(TypeError):
+            ScriptWithNewInit.from_queue(q)
+
+    @pytest.mark.parametrize("child", QuantumScript.__subclasses__())
+    def test_that_fails_if_a_subclass_does_not_match(self, child):
+        """
+        Makes sure that no subclasses for QuantumScript override the constructor.
+        If you have, and you stumbled onto this test, note that QuantumScript.from_queue
+        might need some modification for your PR to be complete.
+        """
+        with qml.queuing.AnnotatedQueue() as q:
+            x = qml.PauliZ(0)
+
+        assert child.from_queue(q).operations == [x]