Repository: PennyLaneAI/pennylane
Pull request #5248 (2024-02-22 15:35:41)

Override `__copy__` method for QSVT instances
**Context:**

Unfortunately, #5226 introduced a regression which causes the [QSVT in Practice](https://pennylane.ai/qml/demos/tutorial_apply_qsvt/) demo to [fail](https://github.com/PennyLaneAI/qml/actions/runs/7997320072/job/21841578276#step:19:1607). The issue is that the current implementation of `Operator.__copy__()` copies the data of an Operator before other attributes are copied; however, this is not suitable for QSVT since the QSVT `data` property depends on the `_hyperparameters` attribute.

**Description of the Change:**

* Added a `QSVT.__copy__()` method to ensure the `data` attribute is ignored when QSVT instances are copied.

**Benefits:**

* The QSVT in Practice demo succeeds.

**Possible Drawbacks:**

None.

**Related GitHub Issues:**

None.

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://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5248?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`e166f7e`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/e166f7eb69499b043ede63158be1eb95774471a2?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.68% compared to head [(`120efcd`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5248?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.67%.
> Report is 1 commits behind head on master.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #5248      +/-   ##
==========================================
- Coverage   99.68%   99.67%   -0.01%     
==========================================
  Files         399      399              
  Lines       36848    36567     -281     
==========================================
- Hits        36731    36449     -282     
- Misses        117      118       +1     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5248?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).


Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index f2a6f2795..752cbbfc9 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -326,6 +326,7 @@
 
 * The `QSVT` operation now determines its `data` from the block encoding and projector operator data.
   [(#5226)](https://github.com/PennyLaneAI/pennylane/pull/5226)
+  [(#5248)](https://github.com/PennyLaneAI/pennylane/pull/5248)
 
 * Faster `qml.probs` measurements due to an optimization in `_samples_to_counts`.
   [(#5145)](https://github.com/PennyLaneAI/pennylane/pull/5145)
diff --git a/pennylane/templates/subroutines/qsvt.py b/pennylane/templates/subroutines/qsvt.py
index 39c4dfebb..8985883b5 100644
--- a/pennylane/templates/subroutines/qsvt.py
+++ b/pennylane/templates/subroutines/qsvt.py
@@ -304,6 +304,23 @@ class QSVT(Operation):
                     op.data = new_data[: op.num_params]
                     new_data = new_data[op.num_params :]
 
+    def __copy__(self):
+        # Override Operator.__copy__() to avoid setting the "data" property before the new instance
+        # is assigned hyper-parameters since QSVT data is derived from the hyper-parameters.
+        clone = QSVT.__new__(QSVT)
+
+        # Ensure the operators in the hyper-parameters are copied instead of aliased.
+        clone._hyperparameters = {
+            "UA": copy.copy(self._hyperparameters["UA"]),
+            "projectors": list(map(copy.copy, self._hyperparameters["projectors"])),
+        }
+
+        for attr, value in vars(self).items():
+            if attr != "_hyperparameters":
+                setattr(clone, attr, value)
+
+        return clone
+
     @property
     def _operators(self) -> list[qml.operation.Operator]:
         """Flattened list of operators that compose this QSVT operation."""


Test patch:
diff --git a/tests/templates/test_subroutines/test_qsvt.py b/tests/templates/test_subroutines/test_qsvt.py
index 929586698..d9064e022 100644
--- a/tests/templates/test_subroutines/test_qsvt.py
+++ b/tests/templates/test_subroutines/test_qsvt.py
@@ -15,6 +15,8 @@
 Tests for the QSVT template and qsvt wrapper function.
 """
 # pylint: disable=too-many-arguments, import-outside-toplevel, no-self-use
+from copy import copy
+
 import pytest
 import pennylane as qml
 from pennylane import numpy as np
@@ -365,6 +367,20 @@ class TestQSVT:
         op.data = [4, 5, 6]
         assert op.data == (4, 5, 6)
 
+    def test_copy(self):
+        """Test that a QSVT operator can be copied."""
+        orig_op = qml.QSVT(qml.RX(1, wires=0), [qml.RY(2, wires=0), qml.RZ(3, wires=0)])
+        copy_op = copy(orig_op)
+        assert qml.equal(orig_op, copy_op)
+
+        # Ensure the (nested) operations are copied instead of aliased.
+        assert orig_op is not copy_op
+        assert orig_op.hyperparameters["UA"] is not copy_op.hyperparameters["UA"]
+
+        orig_projectors = orig_op.hyperparameters["projectors"]
+        copy_projectors = copy_op.hyperparameters["projectors"]
+        assert all(p1 is not p2 for p1, p2 in zip(orig_projectors, copy_projectors))
+
 
 class Testqsvt:
     """Test the qml.qsvt function."""