From dbb26f2c7ca54798d70756b685ce566da776bfad Mon Sep 17 00:00:00 2001 From: Austin Huang <65315367+austingmhuang@users.noreply.github.com> Date: Fri, 12 Jul 2024 10:15:29 -0400 Subject: [PATCH] Change wire repr (#5958) **Context:** To accommodate the implementation of `qml.registers`, the `_repr_` of Wires should be something more copy-paste friendly rather than what it is currently **Description of the Change:** The `_repr_` of Wires is now `Wires([0])` rather than `` **Benefits:** Easier to copy and paste **Possible Drawbacks:** Might make docs confusing in other places. Not sure where else notation will be found in docs. **Related GitHub Issues:** --- doc/development/adding_operators.rst | 4 ++-- doc/development/guide/architecture.rst | 2 +- doc/development/plugins.rst | 10 +++++----- doc/releases/changelog-dev.md | 4 ++++ pennylane/_device.py | 2 +- pennylane/_qubit_device.py | 2 +- pennylane/operation.py | 10 +++++----- pennylane/ops/op_math/controlled.py | 6 +++--- pennylane/ops/qubit/hamiltonian.py | 4 ++-- pennylane/pytrees/pytrees.py | 4 ++-- pennylane/qchem/convert.py | 10 +++++----- pennylane/qchem/openfermion_pyscf.py | 2 +- pennylane/tape/tape.py | 2 +- .../templates/subroutines/qubitization.py | 2 +- pennylane/templates/subroutines/trotter.py | 6 +++--- pennylane/wires.py | 18 +++++++++--------- pennylane/workflow/return_types_spec.rst | 4 ++-- tests/measurements/test_state.py | 2 +- tests/pytrees/test_pytrees.py | 4 ++-- .../test_subroutines/test_prepselprep.py | 3 +-- tests/templates/test_subroutines/test_qrom.py | 2 +- .../templates/test_subroutines/test_select.py | 2 +- tests/test_wires.py | 4 ++-- 23 files changed, 56 insertions(+), 53 deletions(-) diff --git a/doc/development/adding_operators.rst b/doc/development/adding_operators.rst index 011d375a582..728ec26604a 100644 --- a/doc/development/adding_operators.rst +++ b/doc/development/adding_operators.rst @@ -42,7 +42,7 @@ The basic components of operators are the following: #. **The subsystems that the operator addresses** (:attr:`.Operator.wires`), which mathematically speaking defines the subspace that it acts on. >>> op.wires - + Wires(['a']) #. **Trainable parameters** (:attr:`.Operator.parameters`) that the map depends on, such as a rotation angle, which can be fed to the operator as tensor-like objects. For example, since we used jax arrays to @@ -237,7 +237,7 @@ If the above operator omitted the ``_unflatten`` custom definition, it would rai The above exception was the direct cause of the following exception: AssertionError: FlipAndRotate._unflatten must be able to reproduce the original operation - from (0.1,) and (, (('do_flip', True),)). You may need to override + from (0.1,) and (Wires(['q3', 'q1']), (('do_flip', True),)). You may need to override either the _unflatten or _flatten method. For local testing, try type(op)._unflatten(*op._flatten()) diff --git a/doc/development/guide/architecture.rst b/doc/development/guide/architecture.rst index 53fbe1beb07..47a8f63b064 100644 --- a/doc/development/guide/architecture.rst +++ b/doc/development/guide/architecture.rst @@ -73,7 +73,7 @@ Rot >>> op.hyperparameters {} >>> op.wires - +Wires(['a']) Operators can optionally define the transformation they implement via symbolic or numerical representations. Here are two examples, and you find more diff --git a/doc/development/plugins.rst b/doc/development/plugins.rst index 3f2d415dd5c..6115c85f68d 100644 --- a/doc/development/plugins.rst +++ b/doc/development/plugins.rst @@ -326,7 +326,7 @@ For example: from pennylane.wires import Wires wires = Wires(['auxiliary', 0, 1]) - print(wires[0]) # + print(wires[0]) # Wires(['auxiliary']) print(wires.labels) # ('auxiliary', 0, 1) As shown in the section on :doc:`/introduction/circuits`, a device can be created with custom wire labels: @@ -351,10 +351,10 @@ object and store it in their ``wires`` attribute. .. code-block:: python - print(dev.wires) # + print(dev.wires) # Wires(['q11', 'q12', 'q21', 'q22']) op = Gate2(wires=['q21','q11']) - print(op.wires) # + print(op.wires) # Wires(['q21', 'q11']) When the device applies operations, it needs to translate ``op.wires`` into wire labels that the backend "understands". This can be done with the @@ -365,7 +365,7 @@ but changes the labels according to the ``wire_map`` attribute of the device whi # inside the class defining 'my.device', which inherits from the base Device class device_wires = self.map_wires(op.wires) - print(device_wires) # + print(device_wires) # Wires([2, 0]) By default, the map translates the custom labels ``'q11'``, ``'q12'``, ``'q21'``, ``'q22'`` to consecutive integers ``0``, ``1``, ``2``, ``3``. If a device uses a different wire labeling, @@ -527,4 +527,4 @@ Users can then import this operator directly from your plugin, and use it when d If the custom operator is diagonal in the computational basis, it can be added to the ``diagonal_in_z_basis`` attribute in ``pennylane.ops.qubit.attributes``. Devices can use this -information to implement faster simulations. \ No newline at end of file +information to implement faster simulations. diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 84103a0372e..1376ed4bc96 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -24,6 +24,9 @@ * `QuantumScript.hash` is now cached, leading to performance improvements. [(#5919)](https://github.com/PennyLaneAI/pennylane/pull/5919) +* The representation for `Wires` has now changed to be more copy-paste friendly. + [(#5958)](https://github.com/PennyLaneAI/pennylane/pull/5958) + * Observable validation for `default.qubit` is now based on execution mode (analytic vs. finite shots) and measurement type (sample measurement vs. state measurement). [(#5890)](https://github.com/PennyLaneAI/pennylane/pull/5890) @@ -61,6 +64,7 @@ Astral Cai, Yushao Chen, Pietropaolo Frisoni, Christina Lee, +Austin Huang, William Maxwell, Vincent Michaud-Rioux, Mudit Pandey, diff --git a/pennylane/_device.py b/pennylane/_device.py index 53c95cee816..3668e817c76 100644 --- a/pennylane/_device.py +++ b/pennylane/_device.py @@ -331,7 +331,7 @@ def define_wire_map(self, wires): >>> dev = device('my.device', wires=['b', 'a']) >>> dev.wire_map() - OrderedDict( [(, ), (, )]) + OrderedDict( [(Wires(['a']), Wires([0])), (Wires(['b']), Wires([1]))]) """ consecutive_wires = Wires(range(self.num_wires)) diff --git a/pennylane/_qubit_device.py b/pennylane/_qubit_device.py index 963683a4107..5123d407ba5 100644 --- a/pennylane/_qubit_device.py +++ b/pennylane/_qubit_device.py @@ -533,7 +533,7 @@ def apply(self, operations, **kwargs): >>> op.name # returns the operation name "RX" >>> op.wires # returns a Wires object representing the wires that the operation acts on - + Wires([0]) >>> op.parameters # returns a list of parameters [0.2] diff --git a/pennylane/operation.py b/pennylane/operation.py index 1d68f0a215c..d31933747cb 100644 --- a/pennylane/operation.py +++ b/pennylane/operation.py @@ -557,7 +557,7 @@ def circuit(angle): >>> op = qml.PauliRot(1.2, "XY", wires=(0,1)) >>> op._flatten() - ((1.2,), (, (('pauli_word', 'XY'),))) + ((1.2,), (Wires([0, 1]), (('pauli_word', 'XY'),))) >>> qml.PauliRot._unflatten(*op._flatten()) PauliRot(1.2, XY, wires=[0, 1]) @@ -1629,7 +1629,7 @@ def _flatten(self): >>> op = qml.ctrl(qml.U2(3.4, 4.5, wires="a"), ("b", "c") ) >>> op._flatten() ((U2(3.4, 4.5, wires=['a']),), - (, (True, True), )) + (Wires(['b', 'c']), (True, True), Wires([]))) """ hashable_hyperparameters = tuple( @@ -1652,11 +1652,11 @@ def _unflatten(cls, data, metadata): >>> op = qml.Rot(1.2, 2.3, 3.4, wires=0) >>> op._flatten() - ((1.2, 2.3, 3.4), (, ())) + ((1.2, 2.3, 3.4), (Wires([0]), ())) >>> qml.Rot._unflatten(*op._flatten()) >>> op = qml.PauliRot(1.2, "XY", wires=(0,1)) >>> op._flatten() - ((1.2,), (, (('pauli_word', 'XY'),))) + ((1.2,), (Wires([0, 1]), (('pauli_word', 'XY'),))) >>> op = qml.ctrl(qml.U2(3.4, 4.5, wires="a"), ("b", "c") ) >>> type(op)._unflatten(*op._flatten()) Controlled(U2(3.4, 4.5, wires=['a']), control_wires=['b', 'c']) @@ -1968,7 +1968,7 @@ def _obs_data(self): >>> tensor = qml.X(0) @ qml.Z(1) >>> print(tensor._obs_data()) - {("PauliZ", , ()), ("PauliX", , ())} + {("PauliZ", Wires([1]), ()), ("PauliX", Wires([0]), ())} """ obs = Tensor(self).non_identity_obs tensor = set() diff --git a/pennylane/ops/op_math/controlled.py b/pennylane/ops/op_math/controlled.py index 8fcc69e6a39..d6c35e7dcdf 100644 --- a/pennylane/ops/op_math/controlled.py +++ b/pennylane/ops/op_math/controlled.py @@ -322,11 +322,11 @@ class Controlled(SymbolicOp): >>> op.data (1.234,) >>> op.wires - + Wires([0, 1]) >>> op.control_wires - + Wires([0]) >>> op.target_wires - + Wires([1]) Control values are lists of booleans, indicating whether or not to control on the ``0==False`` value or the ``1==True`` wire. diff --git a/pennylane/ops/qubit/hamiltonian.py b/pennylane/ops/qubit/hamiltonian.py index d7da5bffa32..521ba29584c 100644 --- a/pennylane/ops/qubit/hamiltonian.py +++ b/pennylane/ops/qubit/hamiltonian.py @@ -662,8 +662,8 @@ def _obs_data(self): >>> H = qml.Hamiltonian([1, 1], [qml.X(0) @ qml.X(1), qml.Z(0)]) >>> print(H._obs_data()) - {(1, frozenset({('PauliX', , ()), ('PauliX', , ())})), - (1, frozenset({('PauliZ', , ())}))} + {(1, frozenset({('PauliX', Wires([1]), ()), ('PauliX', Wires([0]), ())})), + (1, frozenset({('PauliZ', Wires([0]), ())}))} """ data = set() diff --git a/pennylane/pytrees/pytrees.py b/pennylane/pytrees/pytrees.py index 5dc97b32e0d..6e1581fc871 100644 --- a/pennylane/pytrees/pytrees.py +++ b/pennylane/pytrees/pytrees.py @@ -183,7 +183,7 @@ class PyTreeStructure: >>> op = qml.adjoint(qml.RX(0.1, 0)) >>> data, structure = qml.pytrees.flatten(op) >>> structure - PyTree(AdjointOperation, (), [PyTree(RX, (, ()), [Leaf])]) + PyTree(AdjointOperation, (), [PyTree(RX, (Wires([0]), ()), [Leaf])]) A leaf is defined as just a ``PyTreeStructure`` with ``type_=None``. """ @@ -236,7 +236,7 @@ def flatten(obj: Any) -> tuple[list[Any], PyTreeStructure]: [1.2, 2.3, 3.4] >>> structure - , ()), (Leaf, Leaf, Leaf))>,))> + ,))> """ flatten_fn = flatten_registrations.get(type(obj), None) if flatten_fn is None: diff --git a/pennylane/qchem/convert.py b/pennylane/qchem/convert.py index c1a46ca7ff1..dbba83fe2be 100644 --- a/pennylane/qchem/convert.py +++ b/pennylane/qchem/convert.py @@ -58,23 +58,23 @@ def _process_wires(wires, n_wires=None): >>> # consec int wires if no wires mapping provided, ie. identity map: 0<->0, 1<->1, 2<->2 >>> _process_wires(None, 3) - + Wires([0, 1, 2]) >>> # List as mapping, qubit indices with wire label values: 0<->w0, 1<->w1, 2<->w2 >>> _process_wires(['w0','w1','w2']) - + Wires(['w0', 'w1', 'w2']) >>> # Wires as mapping, qubit indices with wire label values: 0<->w0, 1<->w1, 2<->w2 >>> _process_wires(Wires(['w0', 'w1', 'w2'])) - + Wires(['w0', 'w1', 'w2']) >>> # Dict as partial mapping, int qubits keys to wire label values: 0->w0, 1 unchanged, 2->w2 >>> _process_wires({0:'w0',2:'w2'}) - + Wires(['w0', 1, 'w2']) >>> # Dict as mapping, wires label keys to consec int qubit values: w2->2, w0->0, w1->1 >>> _process_wires({'w2':2, 'w0':0, 'w1':1}) - + Wires(['w0', 'w1', 'w2']) """ # infer from wires, or assume 1 if wires is not of accepted types. diff --git a/pennylane/qchem/openfermion_pyscf.py b/pennylane/qchem/openfermion_pyscf.py index 6e6be705dfd..7e98ab2baa1 100644 --- a/pennylane/qchem/openfermion_pyscf.py +++ b/pennylane/qchem/openfermion_pyscf.py @@ -575,7 +575,7 @@ def dipole_of( >>> coordinates = np.array([0.028, 0.054, 0.0, 0.986, 1.610, 0.0, 1.855, 0.002, 0.0]) >>> dipole_obs = dipole_of(symbols, coordinates, charge=1) >>> print([(h.wires) for h in dipole_obs]) - [, , ] + [Wires([0, 1, 2, 3, 4, 5]), Wires([0, 1, 2, 3, 4, 5]), Wires([0])] >>> dipole_obs[0] # x-component of D ( diff --git a/pennylane/tape/tape.py b/pennylane/tape/tape.py index 9f2da08c509..c0f1ba68091 100644 --- a/pennylane/tape/tape.py +++ b/pennylane/tape/tape.py @@ -408,7 +408,7 @@ class QuantumTape(QuantumScript, AnnotatedQueue): >>> tape.get_parameters() [0.432, 0.543, 0.133] >>> tape.wires - + Wires([0, 'a']) >>> tape.num_params 3 diff --git a/pennylane/templates/subroutines/qubitization.py b/pennylane/templates/subroutines/qubitization.py index 5847d64c652..ac5eb64bb28 100644 --- a/pennylane/templates/subroutines/qubitization.py +++ b/pennylane/templates/subroutines/qubitization.py @@ -164,7 +164,7 @@ def compute_decomposition(*_, **kwargs): # pylint: disable=arguments-differ **Example:** >>> print(qml.Qubitization.compute_decomposition(hamiltonian = 0.1 * qml.Z(0), control = 1)) - [AmplitudeEmbedding(array([1., 0.]), wires=[1]), Select(ops=(Z(0),), control=), Adjoint(AmplitudeEmbedding(array([1., 0.]), wires=[1])), Reflection(, wires=[0])] + [AmplitudeEmbedding(array([1., 0.]), wires=[1]), Select(ops=(Z(0),), control=Wires([1])), Adjoint(AmplitudeEmbedding(array([1., 0.]), wires=[1])), Reflection(, wires=[0])] """ diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index be951e17c17..64c26dc4768 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -374,7 +374,7 @@ def _flatten(self): >>> op = qml.ctrl(qml.U2(3.4, 4.5, wires="a"), ("b", "c") ) >>> op._flatten() ((U2(3.4, 4.5, wires=['a']),), - (, (True, True), )) + (Wires(['b', 'c']), (True, True), Wires([]))) """ hamiltonian = self.hyperparameters["base"] time = self.data[-1] @@ -399,11 +399,11 @@ def _unflatten(cls, data, metadata): >>> op = qml.Rot(1.2, 2.3, 3.4, wires=0) >>> op._flatten() - ((1.2, 2.3, 3.4), (, ())) + ((1.2, 2.3, 3.4), (Wires([0]), ())) >>> qml.Rot._unflatten(*op._flatten()) >>> op = qml.PauliRot(1.2, "XY", wires=(0,1)) >>> op._flatten() - ((1.2,), (, (('pauli_word', 'XY'),))) + ((1.2,), (Wires([0, 1]), (('pauli_word', 'XY'),))) >>> op = qml.ctrl(qml.U2(3.4, 4.5, wires="a"), ("b", "c") ) >>> type(op)._unflatten(*op._flatten()) Controlled(U2(3.4, 4.5, wires=['a']), control_wires=['b', 'c']) diff --git a/pennylane/wires.py b/pennylane/wires.py index 773cdf56ce4..3ee494bba3a 100644 --- a/pennylane/wires.py +++ b/pennylane/wires.py @@ -144,7 +144,7 @@ def __contains__(self, item): def __repr__(self): """Method defining the string representation of this class.""" - return f"" + return f"Wires({list(self._labels)})" def __eq__(self, other): """Method to support the '==' operator. @@ -286,7 +286,7 @@ def map(self, wire_map): >>> wires = Wires(['a', 'b', 'c']) >>> wire_map = {'a': 4, 'b':2, 'c': 3} >>> wires.map(wire_map) - + Wires([4, 2, 3]) """ # Make sure wire_map has `Wires` keys and values so that the `in` operator always works @@ -322,9 +322,9 @@ def subset(self, indices, periodic_boundary=False): >>> wires = Wires([4, 0, 1, 5, 6]) >>> wires.subset([2, 3, 0]) - + Wires([1, 5, 4]) >>> wires.subset(1) - + Wires([0]) If ``periodic_boundary`` is True, the modulo of the number of wires of an index is used instead of an index, so that ``wires.subset(i) == wires.subset(i % n_wires)`` where ``n_wires`` is the number of wires of this @@ -332,7 +332,7 @@ def subset(self, indices, periodic_boundary=False): >>> wires = Wires([4, 0, 1, 5, 6]) >>> wires.subset([5, 1, 7], periodic_boundary=True) - + Wires([4, 0, 1]) """ @@ -389,9 +389,9 @@ def shared_wires(list_of_wires): >>> wires2 = Wires([3, 0, 4]) >>> wires3 = Wires([4, 0]) >>> Wires.shared_wires([wires1, wires2, wires3]) - + Wires([4, 0]) >>> Wires.shared_wires([wires2, wires1, wires3]) - + Wires([0, 4]) """ for wires in list_of_wires: @@ -431,7 +431,7 @@ def all_wires(list_of_wires, sort=False): >>> wires3 = Wires([5, 3]) >>> list_of_wires = [wires1, wires2, wires3] >>> Wires.all_wires(list_of_wires) - + Wires([4, 0, 1, 3, 5]) """ converted_wires = ( wires if isinstance(wires, Wires) else Wires(wires) for wires in list_of_wires @@ -463,7 +463,7 @@ def unique_wires(list_of_wires): >>> wires2 = Wires([0, 2, 3]) >>> wires3 = Wires([5, 3]) >>> Wires.unique_wires([wires1, wires2, wires3]) - + Wires([4, 1, 2, 5]) """ for wires in list_of_wires: diff --git a/pennylane/workflow/return_types_spec.rst b/pennylane/workflow/return_types_spec.rst index cb52f3819d2..e5e5a69a536 100644 --- a/pennylane/workflow/return_types_spec.rst +++ b/pennylane/workflow/return_types_spec.rst @@ -58,7 +58,7 @@ or ``qml.state()``. In such a case, the measurement process instance should have The shape of the result object may be dictated either by the device or the other operations present in the circuit. >>> qml.probs().wires - +Wires([]) >>> tape = qml.tape.QuantumScript([qml.S(0)], (qml.probs(),)) >>> qml.device('default.qubit').execute(tape) array([1., 0.]) @@ -176,4 +176,4 @@ where each entry corresponds to the result for the corresponding tape. >>> tape3 = qml.tape.QuantumScript([], [qml.expval(qml.Z(0)), qml.expval(qml.X(0))]) >>> batch = (tape1, tape2, tape3) >>> qml.device('default.qubit').execute(batch) -(array([0.+0.j, 1.+0.j]), {'0': 50, '1': 50}, (1.0, 0.0)) \ No newline at end of file +(array([0.+0.j, 1.+0.j]), {'0': 50, '1': 50}, (1.0, 0.0)) diff --git a/tests/measurements/test_state.py b/tests/measurements/test_state.py index 5c580c1eb76..07c8b8d5fe6 100644 --- a/tests/measurements/test_state.py +++ b/tests/measurements/test_state.py @@ -126,7 +126,7 @@ def get_state(ket): def test_wire_ordering_error(self): """Test that a wire order error is raised when unknown wires are given.""" - with pytest.raises(WireError, match=r"Unexpected unique wires found"): + with pytest.raises(WireError, match=r"Unexpected unique wires Wires\(\[0, 1, 2\]\) found"): StateMP(wires=[0, 1]).process_state([1, 0], wire_order=Wires(2)) @pytest.mark.parametrize( diff --git a/tests/pytrees/test_pytrees.py b/tests/pytrees/test_pytrees.py index 84336992d4e..64b18844879 100644 --- a/tests/pytrees/test_pytrees.py +++ b/tests/pytrees/test_pytrees.py @@ -27,9 +27,9 @@ def test_structure_repr_str(): """Test the repr of the structure class.""" op = qml.RX(0.1, wires=0) _, structure = qml.pytrees.flatten(op) - expected = "PyTreeStructure(RX, (, ()), [PyTreeStructure()])" + expected = "PyTreeStructure(RX, (Wires([0]), ()), [PyTreeStructure()])" assert repr(structure) == expected - expected_str = "PyTree(RX, (, ()), [Leaf])" + expected_str = "PyTree(RX, (Wires([0]), ()), [Leaf])" assert str(structure) == expected_str diff --git a/tests/templates/test_subroutines/test_prepselprep.py b/tests/templates/test_subroutines/test_prepselprep.py index ea6b71e9347..70708218786 100644 --- a/tests/templates/test_subroutines/test_prepselprep.py +++ b/tests/templates/test_subroutines/test_prepselprep.py @@ -48,8 +48,7 @@ def test_repr(): op = qml.PrepSelPrep(lcu, control) assert ( - repr(op) - == "PrepSelPrep(coeffs=(0.25, 0.75), ops=(Z(2), X(1) @ X(2)), control=)" + repr(op) == "PrepSelPrep(coeffs=(0.25, 0.75), ops=(Z(2), X(1) @ X(2)), control=Wires([0]))" ) diff --git a/tests/templates/test_subroutines/test_qrom.py b/tests/templates/test_subroutines/test_qrom.py index 35766febbc4..fc0ebe8f6d9 100644 --- a/tests/templates/test_subroutines/test_qrom.py +++ b/tests/templates/test_subroutines/test_qrom.py @@ -227,7 +227,7 @@ def test_repr(): ["1", "0", "0", "1"], control_wires=[0, 1], target_wires=[2], work_wires=[3], clean=True ) res = op.__repr__() - expected = "QROM(control_wires=, target_wires=, work_wires=, clean=True)" + expected = "QROM(control_wires=Wires([0, 1]), target_wires=Wires([2]), work_wires=Wires([3]), clean=True)" assert res == expected diff --git a/tests/templates/test_subroutines/test_select.py b/tests/templates/test_subroutines/test_select.py index 5aa6c02cbf0..abd83d28186 100644 --- a/tests/templates/test_subroutines/test_select.py +++ b/tests/templates/test_subroutines/test_select.py @@ -40,7 +40,7 @@ def test_repr(): control = [1] op = qml.Select(ops, control) - assert repr(op) == "Select(ops=(X(0), Y(0)), control=)" + assert repr(op) == "Select(ops=(X(0), Y(0)), control=Wires([1]))" class TestSelect: diff --git a/tests/test_wires.py b/tests/test_wires.py index b1dc5ac36d8..4164e48b377 100644 --- a/tests/test_wires.py +++ b/tests/test_wires.py @@ -184,8 +184,8 @@ def test_representation_and_string(self): wires_str = str(Wires([1, 2, 3])) wires_repr = repr(Wires([1, 2, 3])) - assert wires_str == "" - assert wires_repr == "" + assert wires_str == "Wires([1, 2, 3])" + assert wires_repr == "Wires([1, 2, 3])" def test_array_representation(self): """Tests that Wires object has an array representation."""