Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes to remove use of Operator.__eq__ #4398

Merged
merged 16 commits into from
Jul 28, 2023
Merged
3 changes: 3 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@
* When given a callable, `qml.ctrl` now does its custom pre-processing on all queued operators from the callable.
[(#4370)](https://github.com/PennyLaneAI/pennylane/pull/4370)

* PennyLane no longer directly relies on `Operator.__eq__`.
[(#4398)](https://github.com/PennyLaneAI/pennylane/pull/4398)

<h3>Breaking changes 💔</h3>

* `Operator.expand` now uses the output of `Operator.decomposition` instead of what it queues.
Expand Down
45 changes: 21 additions & 24 deletions pennylane/circuit_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,16 +296,14 @@ def ancestors(self, ops):
ops (Iterable[Operator]): set of operators in the circuit
Returns:
set[Operator]: ancestors of the given operators
list[Operator]: ancestors of the given operators
timmysilv marked this conversation as resolved.
Show resolved Hide resolved
"""
anc = set(
self._graph.get_node_data(n)
for n in set().union(
# rx.ancestors() returns node indexes instead of node-values
*(rx.ancestors(self._graph, self._indices[id(o)]) for o in ops)
)
)
return anc - set(ops)
# rx.ancestors() returns node indices instead of node-values
all_indices = set().union(*(rx.ancestors(self._graph, self._indices[id(o)]) for o in ops))
double_op_indices = set(self._indices[id(o)] for o in ops)
ancestor_indices = all_indices - double_op_indices

return list(self._graph.get_node_data(n) for n in ancestor_indices)

def descendants(self, ops):
"""Descendants of a given set of operators.
Expand All @@ -314,16 +312,14 @@ def descendants(self, ops):
ops (Iterable[Operator]): set of operators in the circuit
Returns:
set[Operator]: descendants of the given operators
list[Operator]: descendants of the given operators
"""
des = set(
self._graph.get_node_data(n)
for n in set().union(
# rx.descendants() returns node indexes instead of node-values
*(rx.descendants(self._graph, self._indices[id(o)]) for o in ops)
)
)
return des - set(ops)
# rx.descendants() returns node indices instead of node-values
all_indices = set().union(*(rx.descendants(self._graph, self._indices[id(o)]) for o in ops))
double_op_indices = set(self._indices[id(o)] for o in ops)
ancestor_indices = all_indices - double_op_indices

return list(self._graph.get_node_data(n) for n in ancestor_indices)

def _in_topological_order(self, ops):
"""Sorts a set of operators in the circuit in a topological order.
Expand Down Expand Up @@ -376,13 +372,14 @@ def nodes_between(self, a, b):
b (Operator): final node
Returns:
set[Operator]: nodes on all the directed paths between a and b
list[Operator]: nodes on all the directed paths between a and b
"""
A = self.descendants([a])
A.add(a)
A.append(a)
B = self.ancestors([b])
B.add(b)
return A & B
B.append(b)

return [B.pop(i) for op1 in A for i, op2 in enumerate(B) if op1 is op2]

@property
def parametrized_layers(self):
Expand All @@ -405,7 +402,7 @@ def parametrized_layers(self):

# check if any of the dependents are in the
# currently assembled layer
if set(current.ops) & sub:
if any(o1 is o2 for o1 in current.ops for o2 in sub):
# operator depends on current layer, start a new layer
current = Layer([], [])
layers.append(current)
Expand Down Expand Up @@ -525,7 +522,7 @@ def has_path(self, a, b):
Returns:
bool: returns ``True`` if a path exists
"""
if a == b:
if a is b:
return True

return (
Expand Down
10 changes: 9 additions & 1 deletion pennylane/ops/functions/equal.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
This module contains the qml.equal function.
"""
# pylint: disable=too-many-arguments,too-many-return-statements
from collections.abc import Iterable
from functools import singledispatch
from typing import Union
import pennylane as qml
Expand Down Expand Up @@ -378,7 +379,14 @@ def _equal_shadow_measurements(op1: ShadowExpvalMP, op2: ShadowExpvalMP, **kwarg
"""Determine whether two ShadowExpvalMP objects are equal"""

wires_match = op1.wires == op2.wires
H_match = op1.H == op2.H

if isinstance(op1.H, Operator) and isinstance(op2.H, Operator):
H_match = qml.equal(op1.H, op2.H)
elif isinstance(op1.H, Iterable) and isinstance(op2.H, Iterable):
H_match = all(qml.equal(o1, o2) for o1, o2 in zip(op1.H, op2.H))
mudit2812 marked this conversation as resolved.
Show resolved Hide resolved
else:
return False

k_match = op1.k == op2.k

return wires_match and H_match and k_match
4 changes: 3 additions & 1 deletion pennylane/transforms/metric_tensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,9 @@ def _get_first_term_tapes(layer_i, layer_j, allow_nonunitary, aux_wire):
tapes = []
ids = []
# Exclude the backwards cone of layer_i from the backwards cone of layer_j
ops_between_cgens = [op for op in layer_j.pre_ops if op not in layer_i.pre_ops]
ops_between_cgens = [
op1 for op1 in layer_j.pre_ops if not any(op1 is op2 for op2 in layer_i.pre_ops)
]

# Iterate over differentiated operation in first layer
for diffed_op_i, par_idx_i in zip(layer_i.ops, layer_i.param_inds):
Expand Down
2 changes: 1 addition & 1 deletion tests/circuit_graph/test_circuit_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def test_ancestors_and_descendants_example(self, ops, obs):
assert queue[o_idx] in ancestors

descendants = circuit.descendants([queue[6]])
assert descendants == set([queue[8]])
assert descendants == [queue[8]]

def test_in_topological_order_example(self, ops, obs):
"""
Expand Down
21 changes: 9 additions & 12 deletions tests/legacy/test_legacy_qscript_old.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,10 +472,9 @@ def test_shallow_copy(self):
assert copied_qs is not qs

# the operations are simply references
assert copied_qs.operations == qs.operations
assert copied_qs.observables == qs.observables
assert copied_qs.measurements == qs.measurements
assert copied_qs.operations[0] is qs.operations[0]
assert all(o1 is o2 for o1, o2 in zip(copied_qs.operations, qs.operations))
assert all(o1 is o2 for o1, o2 in zip(copied_qs.observables, qs.observables))
assert all(m1 is m2 for m1, m2 in zip(copied_qs.measurements, qs.measurements))

# operation data is also a reference
assert copied_qs.operations[0].wires is qs.operations[0].wires
Expand Down Expand Up @@ -518,10 +517,9 @@ def test_shallow_copy_with_operations(self, copy_fn):
assert copied_qs is not qs

# the operations are not references; they are unique objects
assert copied_qs.operations != qs.operations
assert copied_qs.observables != qs.observables
assert copied_qs.measurements != qs.measurements
assert copied_qs.operations[0] is not qs.operations[0]
assert all(o1 is not o2 for o1, o2 in zip(copied_qs.operations, qs.operations))
assert all(o1 is not o2 for o1, o2 in zip(copied_qs.observables, qs.observables))
assert all(m1 is not m2 for m1, m2 in zip(copied_qs.measurements, qs.measurements))

# however, the underlying operation data *is still shared*
assert copied_qs.operations[0].wires is qs.operations[0].wires
Expand Down Expand Up @@ -559,10 +557,9 @@ def test_deep_copy(self):
assert copied_qs is not qs

# the operations are not references
assert copied_qs.operations != qs.operations
assert copied_qs.observables != qs.observables
assert copied_qs.measurements != qs.measurements
assert copied_qs.operations[0] is not qs.operations[0]
assert all(o1 is not o2 for o1, o2 in zip(copied_qs.operations, qs.operations))
assert all(o1 is not o2 for o1, o2 in zip(copied_qs.observables, qs.observables))
assert all(m1 is not m2 for m1, m2 in zip(copied_qs.measurements, qs.measurements))

# check that the output dim is identical
assert qs.output_dim == copied_qs.output_dim
Expand Down
30 changes: 27 additions & 3 deletions tests/ops/functions/test_equal.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@
qml.expval(qml.PauliX(1)),
qml.probs(wires=1),
qml.probs(wires=0),
qml.probs(qml.PauliZ(0)),
qml.probs(qml.PauliZ(1)),
qml.probs(op=qml.PauliZ(0)),
qml.probs(op=qml.PauliZ(1)),
qml.state(),
qml.vn_entropy(wires=0),
qml.vn_entropy(wires=0, log_base=np.e),
Expand All @@ -151,7 +151,31 @@
qml.shadow_expval(
H=qml.Hamiltonian(
[1.0, 1.0], [qml.PauliX(0) @ qml.PauliX(1), qml.PauliZ(0) @ qml.PauliZ(1)]
)
),
k=3,
),
qml.shadow_expval(
H=[
qml.Hamiltonian(
[1.0, 1.0], [qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliX(0) @ qml.PauliX(1)]
)
],
k=2,
),
qml.shadow_expval(
H=[
qml.Hamiltonian(
[1.0, 1.0], [qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliX(0) @ qml.PauliX(1)]
)
]
),
qml.shadow_expval(
H=[
qml.Hamiltonian(
[1.0, 1.0], [qml.PauliX(0) @ qml.PauliX(1), qml.PauliZ(0) @ qml.PauliZ(1)]
)
],
k=3,
),
ExpectationMP(eigvals=[1, -1]),
ExpectationMP(eigvals=[1, 2]),
Expand Down
21 changes: 9 additions & 12 deletions tests/tape/test_qscript.py
Original file line number Diff line number Diff line change
Expand Up @@ -482,10 +482,9 @@ def test_shallow_copy(self):
assert copied_qs is not qs

# the operations are simply references
assert copied_qs.operations == qs.operations
assert copied_qs.observables == qs.observables
assert copied_qs.measurements == qs.measurements
assert copied_qs.operations[0] is qs.operations[0]
assert all(o1 is o2 for o1, o2 in zip(copied_qs.operations, qs.operations))
assert all(o1 is o2 for o1, o2 in zip(copied_qs.observables, qs.observables))
assert all(m1 is m2 for m1, m2 in zip(copied_qs.measurements, qs.measurements))

# operation data is also a reference
assert copied_qs.operations[0].wires is qs.operations[0].wires
Expand Down Expand Up @@ -520,10 +519,9 @@ def test_shallow_copy_with_operations(self, copy_fn):
assert copied_qs is not qs

# the operations are not references; they are unique objects
assert copied_qs.operations != qs.operations
assert copied_qs.observables != qs.observables
assert copied_qs.measurements != qs.measurements
assert copied_qs.operations[0] is not qs.operations[0]
assert all(o1 is not o2 for o1, o2 in zip(copied_qs.operations, qs.operations))
assert all(o1 is not o2 for o1, o2 in zip(copied_qs.observables, qs.observables))
assert all(m1 is not m2 for m1, m2 in zip(copied_qs.measurements, qs.measurements))

# however, the underlying operation data *is still shared*
assert copied_qs.operations[0].wires is qs.operations[0].wires
Expand All @@ -550,10 +548,9 @@ def test_deep_copy(self):
assert copied_qs is not qs

# the operations are not references
assert copied_qs.operations != qs.operations
assert copied_qs.observables != qs.observables
assert copied_qs.measurements != qs.measurements
assert copied_qs.operations[0] is not qs.operations[0]
assert all(o1 is not o2 for o1, o2 in zip(copied_qs.operations, qs.operations))
assert all(o1 is not o2 for o1, o2 in zip(copied_qs.observables, qs.observables))
assert all(m1 is not m2 for m1, m2 in zip(copied_qs.measurements, qs.measurements))
assert copied_qs.shots is qs.shots

# check that the output dim is identical
Expand Down
21 changes: 9 additions & 12 deletions tests/tape/test_tape.py
Original file line number Diff line number Diff line change
Expand Up @@ -1696,10 +1696,9 @@ def test_shallow_copy(self):
assert copied_tape is not tape

# the operations are simply references
assert copied_tape.operations == tape.operations
assert copied_tape.observables == tape.observables
assert copied_tape.measurements == tape.measurements
assert copied_tape.operations[0] is tape.operations[0]
assert all(o1 is o2 for o1, o2 in zip(copied_tape.operations, tape.operations))
assert all(o1 is o2 for o1, o2 in zip(copied_tape.observables, tape.observables))
assert all(m1 is m2 for m1, m2 in zip(copied_tape.measurements, tape.measurements))

# operation data is also a reference
assert copied_tape.operations[0].wires is tape.operations[0].wires
Expand Down Expand Up @@ -1729,10 +1728,9 @@ def test_shallow_copy_with_operations(self, copy_fn):
assert copied_tape is not tape

# the operations are not references; they are unique objects
assert copied_tape.operations != tape.operations
assert copied_tape.observables != tape.observables
assert copied_tape.measurements != tape.measurements
assert copied_tape.operations[0] is not tape.operations[0]
assert all(o1 is not o2 for o1, o2 in zip(copied_tape.operations, tape.operations))
assert all(o1 is not o2 for o1, o2 in zip(copied_tape.observables, tape.observables))
assert all(m1 is not m2 for m1, m2 in zip(copied_tape.measurements, tape.measurements))

# however, the underlying operation data *is still shared*
assert copied_tape.operations[0].wires is tape.operations[0].wires
Expand All @@ -1758,10 +1756,9 @@ def test_deep_copy(self):
assert copied_tape is not tape

# the operations are not references
assert copied_tape.operations != tape.operations
assert copied_tape.observables != tape.observables
assert copied_tape.measurements != tape.measurements
assert copied_tape.operations[0] is not tape.operations[0]
assert all(o1 is not o2 for o1, o2 in zip(copied_tape.operations, tape.operations))
assert all(o1 is not o2 for o1, o2 in zip(copied_tape.observables, tape.observables))
assert all(m1 is not m2 for m1, m2 in zip(copied_tape.measurements, tape.measurements))

# check that the output dim is identical
assert tape.output_dim == copied_tape.output_dim
Expand Down
Loading