From a24386e534942265f69c8b62c9730ae7a11dae13 Mon Sep 17 00:00:00 2001 From: andrijapau Date: Thu, 6 Feb 2025 16:36:16 -0500 Subject: [PATCH 01/11] initial commit --- pennylane/wires.py | 2 ++ tests/test_wires.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pennylane/wires.py b/pennylane/wires.py index 14b5174679f..cea46e1a4a5 100644 --- a/pennylane/wires.py +++ b/pennylane/wires.py @@ -93,6 +93,7 @@ def _process(wires): if len(set_of_wires) != len(tuple_of_wires): raise WireError(f"Wires must be unique; got {wires}.") + tuple_of_wires = tuple(qml.pytrees.flatten(tuple(wires), is_leaf=lambda x: False)[0]) return tuple_of_wires @@ -155,6 +156,7 @@ def __len__(self): def contains_wires(self, wires): """Method to determine if Wires object contains wires in another Wires object.""" if isinstance(wires, Wires): + wires = Wires(tuple(qml.pytrees.flatten(tuple(wires), is_leaf=lambda x: False)[0])) return set(wires.labels).issubset(set(self._labels)) return False diff --git a/tests/test_wires.py b/tests/test_wires.py index 0ae3e85b65d..511da0d9655 100644 --- a/tests/test_wires.py +++ b/tests/test_wires.py @@ -74,7 +74,7 @@ def test_creation_from_wires_lists(self): """Tests that a Wires object can be created from a list of Wires.""" wires = Wires([Wires([0]), Wires([1]), Wires([2])]) - assert wires.labels == (Wires([0]), Wires([1]), Wires([2])) + assert wires.labels == (0, 1, 2) @pytest.mark.parametrize( "iterable", [[1, 0, 4], ["a", "b", "c"], [0, 1, None], ["a", 1, "ancilla"]] @@ -148,7 +148,7 @@ def test_contains( wires = Wires([0, 1, 2, 3, Wires([4, 5]), None]) assert 0 in wires - assert Wires([4, 5]) in wires + assert Wires([4, 5]) not in wires assert None in wires assert Wires([1]) not in wires assert Wires([0, 3]) not in wires @@ -170,7 +170,7 @@ def test_contains_wires( assert not wires.contains_wires(0) # wrong type assert not wires.contains_wires([0, 1]) # wrong type - assert not wires.contains_wires( + assert wires.contains_wires( Wires([4, 5]) ) # looks up 4 and 5 in wires, which are not present From eae9fbc7ba42c3f7782221b7ea39e6768ed1cdd4 Mon Sep 17 00:00:00 2001 From: andrijapau Date: Fri, 7 Feb 2025 10:04:54 -0500 Subject: [PATCH 02/11] new attempt --- pennylane/wires.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pennylane/wires.py b/pennylane/wires.py index cea46e1a4a5..980deb01294 100644 --- a/pennylane/wires.py +++ b/pennylane/wires.py @@ -93,10 +93,17 @@ def _process(wires): if len(set_of_wires) != len(tuple_of_wires): raise WireError(f"Wires must be unique; got {wires}.") - tuple_of_wires = tuple(qml.pytrees.flatten(tuple(wires), is_leaf=lambda x: False)[0]) + tuple_of_wires = tuple(itertools.chain(*(wire_map(x) for x in tuple_of_wires))) return tuple_of_wires +def wire_map(wires): + """Converts the input to a tuple of wire labels.""" + if isinstance(wires, Wires): + return wires.labels + return [wires] + + class Wires(Sequence): r""" A bookkeeping class for wires, which are ordered collections of unique objects. @@ -121,7 +128,7 @@ class Wires(Sequence): """ def _flatten(self): - """Serialize Wires into a flattened representation according to the PyTree convension.""" + """Serialize Wires into a flattened representation according to the PyTree convention.""" return self._labels, () @classmethod @@ -156,7 +163,6 @@ def __len__(self): def contains_wires(self, wires): """Method to determine if Wires object contains wires in another Wires object.""" if isinstance(wires, Wires): - wires = Wires(tuple(qml.pytrees.flatten(tuple(wires), is_leaf=lambda x: False)[0])) return set(wires.labels).issubset(set(self._labels)) return False From e2097b4ae98d1a3776d8ed9d89c2d83352aab4b6 Mon Sep 17 00:00:00 2001 From: andrijapau Date: Fri, 7 Feb 2025 10:09:39 -0500 Subject: [PATCH 03/11] reformat --- pennylane/wires.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pennylane/wires.py b/pennylane/wires.py index 980deb01294..31295cb4664 100644 --- a/pennylane/wires.py +++ b/pennylane/wires.py @@ -93,8 +93,7 @@ def _process(wires): if len(set_of_wires) != len(tuple_of_wires): raise WireError(f"Wires must be unique; got {wires}.") - tuple_of_wires = tuple(itertools.chain(*(wire_map(x) for x in tuple_of_wires))) - return tuple_of_wires + return tuple(itertools.chain(*(wire_map(x) for x in tuple_of_wires))) def wire_map(wires): From f19e7dfa112ddfde8fd461384a5d234e3ac9b034 Mon Sep 17 00:00:00 2001 From: andrijapau Date: Fri, 7 Feb 2025 10:11:58 -0500 Subject: [PATCH 04/11] changelog --- doc/releases/changelog-dev.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index ea1a8d5184c..cda9397e4b3 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -227,6 +227,9 @@

Internal changes ⚙️

+* Fix `qml.wires.Wires` initialization to disallow `Wires` objects as wires labels. + [(#6933)](https://github.com/PennyLaneAI/pennylane/pull/6933) + * Remove `QNode.get_gradient_fn` from source code. [(#6898)](https://github.com/PennyLaneAI/pennylane/pull/6898) From 1353bb527252c05cfbb9bba0a70146bacd9ac12b Mon Sep 17 00:00:00 2001 From: andrijapau Date: Fri, 7 Feb 2025 10:29:49 -0500 Subject: [PATCH 05/11] fixes --- pennylane/wires.py | 10 +++++----- tests/test_wires.py | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pennylane/wires.py b/pennylane/wires.py index 31295cb4664..64c5207c715 100644 --- a/pennylane/wires.py +++ b/pennylane/wires.py @@ -93,14 +93,14 @@ def _process(wires): if len(set_of_wires) != len(tuple_of_wires): raise WireError(f"Wires must be unique; got {wires}.") - return tuple(itertools.chain(*(wire_map(x) for x in tuple_of_wires))) + return tuple(itertools.chain(*(flatten_wires_object(x) for x in tuple_of_wires))) -def wire_map(wires): +def flatten_wires_object(wire_label): """Converts the input to a tuple of wire labels.""" - if isinstance(wires, Wires): - return wires.labels - return [wires] + if isinstance(wire_label, Wires): + return wire_label.labels + return [wire_label] class Wires(Sequence): diff --git a/tests/test_wires.py b/tests/test_wires.py index 511da0d9655..06b40fcdcd3 100644 --- a/tests/test_wires.py +++ b/tests/test_wires.py @@ -34,6 +34,11 @@ class TestWires: """Tests for the ``Wires`` class.""" + def test_wires_object_as_label(self): + """Tests that a Wires object can be used as a label for another Wires object.""" + assert Wires([0, 1]) == Wires([Wires([0]), Wires([1])]) + assert Wires(["a", "b", 1]) == Wires([Wires(["a", "b"]), Wires([1])]) + def test_error_if_wires_none(self): """Tests that a TypeError is raised if None is given as wires.""" with pytest.raises(TypeError, match="Must specify a set of wires."): From 8140768cf4536f59da2ea1bbaa854103b6283a7e Mon Sep 17 00:00:00 2001 From: andrijapau Date: Fri, 7 Feb 2025 10:48:55 -0500 Subject: [PATCH 06/11] try to fix sphinx --- pennylane/wires.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pennylane/wires.py b/pennylane/wires.py index 64c5207c715..7c9e9a31110 100644 --- a/pennylane/wires.py +++ b/pennylane/wires.py @@ -96,13 +96,6 @@ def _process(wires): return tuple(itertools.chain(*(flatten_wires_object(x) for x in tuple_of_wires))) -def flatten_wires_object(wire_label): - """Converts the input to a tuple of wire labels.""" - if isinstance(wire_label, Wires): - return wire_label.labels - return [wire_label] - - class Wires(Sequence): r""" A bookkeeping class for wires, which are ordered collections of unique objects. @@ -738,5 +731,13 @@ def __rxor__(self, other): WiresLike = Union[Wires, Iterable[Hashable], Hashable] + +def flatten_wires_object(wire_label): + """Converts the input to a tuple of wire labels.""" + if isinstance(wire_label, Wires): + return wire_label.labels + return [wire_label] + + # Register Wires as a PyTree-serializable class register_pytree(Wires, Wires._flatten, Wires._unflatten) # pylint: disable=protected-access From c132369cf67022c237f03999383a164811730afd Mon Sep 17 00:00:00 2001 From: andrijapau Date: Fri, 7 Feb 2025 11:13:45 -0500 Subject: [PATCH 07/11] try to fix sphinx --- pennylane/wires.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pennylane/wires.py b/pennylane/wires.py index 7c9e9a31110..afa6e4572cf 100644 --- a/pennylane/wires.py +++ b/pennylane/wires.py @@ -93,7 +93,7 @@ def _process(wires): if len(set_of_wires) != len(tuple_of_wires): raise WireError(f"Wires must be unique; got {wires}.") - return tuple(itertools.chain(*(flatten_wires_object(x) for x in tuple_of_wires))) + return tuple(itertools.chain(*(_flatten_wires_object(x) for x in tuple_of_wires))) class Wires(Sequence): @@ -732,7 +732,7 @@ def __rxor__(self, other): WiresLike = Union[Wires, Iterable[Hashable], Hashable] -def flatten_wires_object(wire_label): +def _flatten_wires_object(wire_label): """Converts the input to a tuple of wire labels.""" if isinstance(wire_label, Wires): return wire_label.labels From b8d6554cc7ca20bf1e29396f7fd6ff0d8f234a70 Mon Sep 17 00:00:00 2001 From: andrijapau Date: Fri, 7 Feb 2025 14:12:33 -0500 Subject: [PATCH 08/11] address code review --- doc/releases/changelog-dev.md | 1 + tests/test_wires.py | 1 + 2 files changed, 2 insertions(+) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index cda9397e4b3..13017238bfa 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -228,6 +228,7 @@

Internal changes ⚙️

* Fix `qml.wires.Wires` initialization to disallow `Wires` objects as wires labels. + Now, `Wires` is idempotent, e.g. `Wires([Wires([0]), Wires([1])])==Wires([0, 1])`. [(#6933)](https://github.com/PennyLaneAI/pennylane/pull/6933) * Remove `QNode.get_gradient_fn` from source code. diff --git a/tests/test_wires.py b/tests/test_wires.py index 06b40fcdcd3..d80f5e5cd66 100644 --- a/tests/test_wires.py +++ b/tests/test_wires.py @@ -38,6 +38,7 @@ def test_wires_object_as_label(self): """Tests that a Wires object can be used as a label for another Wires object.""" assert Wires([0, 1]) == Wires([Wires([0]), Wires([1])]) assert Wires(["a", "b", 1]) == Wires([Wires(["a", "b"]), Wires([1])]) + assert Wires([Wires([(0, 0), (0, 1)])]) == Wires([(0, 0), (0, 1)]) def test_error_if_wires_none(self): """Tests that a TypeError is raised if None is given as wires.""" From 5540ca47668b253214d579a44f85c25f36b8b7a4 Mon Sep 17 00:00:00 2001 From: andrijapau Date: Fri, 7 Feb 2025 16:14:30 -0500 Subject: [PATCH 09/11] changelog --- doc/releases/changelog-dev.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 16fc0552626..2c41c4cee33 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -230,10 +230,6 @@

Internal changes ⚙️

-* Fix `qml.wires.Wires` initialization to disallow `Wires` objects as wires labels. - Now, `Wires` is idempotent, e.g. `Wires([Wires([0]), Wires([1])])==Wires([0, 1])`. - [(#6933)](https://github.com/PennyLaneAI/pennylane/pull/6933) - * Remove `QNode.get_gradient_fn` from source code. [(#6898)](https://github.com/PennyLaneAI/pennylane/pull/6898) @@ -270,6 +266,10 @@

Bug fixes 🐛

+* Fix `qml.wires.Wires` initialization to disallow `Wires` objects as wires labels. + Now, `Wires` is idempotent, e.g. `Wires([Wires([0]), Wires([1])])==Wires([0, 1])`. + [(#6933)](https://github.com/PennyLaneAI/pennylane/pull/6933) + * `qml.capture.PlxprInterpreter` now correctly handles propagation of constants when interpreting higher-order primitives [(#6913)](https://github.com/PennyLaneAI/pennylane/pull/6913) From 2da3cbfa309ee5da0d2d0e4e0f062ccc36855f2f Mon Sep 17 00:00:00 2001 From: andrijapau Date: Mon, 10 Feb 2025 10:38:24 -0500 Subject: [PATCH 10/11] add comment with explanation --- pennylane/wires.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pennylane/wires.py b/pennylane/wires.py index afa6e4572cf..9b3f6e0a460 100644 --- a/pennylane/wires.py +++ b/pennylane/wires.py @@ -93,6 +93,9 @@ def _process(wires): if len(set_of_wires) != len(tuple_of_wires): raise WireError(f"Wires must be unique; got {wires}.") + # Tuple of wires are flattened by iterating through each wire label and + # checking if it is a Wires object. If so, flatten the Wires object into a tuple of wire labels. + # The nested tuple of wires are then stitched together using itertools.chain. return tuple(itertools.chain(*(_flatten_wires_object(x) for x in tuple_of_wires))) From 446a27ac74baeb6ddd30be98b76d2d0ed2e3b4d2 Mon Sep 17 00:00:00 2001 From: Andrija Paurevic <46359773+andrijapau@users.noreply.github.com> Date: Mon, 10 Feb 2025 11:22:10 -0500 Subject: [PATCH 11/11] Update pennylane/wires.py --- pennylane/wires.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pennylane/wires.py b/pennylane/wires.py index 9b3f6e0a460..ba1fc175a69 100644 --- a/pennylane/wires.py +++ b/pennylane/wires.py @@ -93,9 +93,7 @@ def _process(wires): if len(set_of_wires) != len(tuple_of_wires): raise WireError(f"Wires must be unique; got {wires}.") - # Tuple of wires are flattened by iterating through each wire label and - # checking if it is a Wires object. If so, flatten the Wires object into a tuple of wire labels. - # The nested tuple of wires are then stitched together using itertools.chain. + # required to make `Wires` object idempotent return tuple(itertools.chain(*(_flatten_wires_object(x) for x in tuple_of_wires)))