Skip to content

Commit

Permalink
Stop using _wire setter and use map_wires instead. (#3186)
Browse files Browse the repository at this point in the history
* feat (Operator): ✨ Add change_wires method.

* refactor (Operator): ♻️ Change name from change_wires to map_wires.

* Remove error

* change name

* feat (map_wires): ✨ Add top-level qml.map_wires function.

* Add tests

* Only check subset of wires present in Operator

* Add tests

* Add tests

* chore (changelog): ✏️ Add feature to changelog.

* More fixes

* More fixes

* Fix test

* Fix tests

* Revert

* Revert

* Update doc/releases/changelog-dev.md

Co-authored-by: Christina Lee <christina@xanadu.ai>

* Update changelog

* Add docs

* Remove _wire setter and use map_wires instead.

* Fix tests. Add map_wires to symbolic_op.

* Add test.

* Stop using _wires setter.

* Use qml.map_wires

* Fix pattern matching.

* Fix qcut.

* Improve changelog entry

* Use QuantumScript class.

* Add note to docstring

* Import warning

* Add more ops and measurements in test

* Deprecate remap_tape_wires

* Small fix

* Add removed docstring

* Add changelog entry

* Add test

* Fixes

* Update doc/releases/changelog-dev.md

Co-authored-by: Christina Lee <christina@xanadu.ai>

Co-authored-by: Christina Lee <christina@xanadu.ai>
  • Loading branch information
AlbertMitjans and albi3ro authored Oct 28, 2022
1 parent cf195af commit f206330
Show file tree
Hide file tree
Showing 11 changed files with 81 additions and 146 deletions.
8 changes: 8 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,11 @@ keyword argument when using `GellMann`, which determines which of the 8 Gell-Man

* `ControlledQubitUnitary` now has a `control_values` property.
[(#3206)](https://github.com/PennyLaneAI/pennylane/pull/3206)

* Remove `_wires` properties and setters from the `ControlledClass` and the `SymbolicClass`.
Stop using `op._wires = new_wires`, use `qml.map_wires(op, wire_map=dict(zip(op.wires, new_wires)))`
instead.
[(#3186)](https://github.com/PennyLaneAI/pennylane/pull/3186)

<h3>Breaking changes</h3>

Expand Down Expand Up @@ -319,6 +324,9 @@ keyword argument when using `GellMann`, which determines which of the 8 Gell-Man
composite operator.
[(#3204)](https://github.com/PennyLaneAI/pennylane/pull/3204)

* Deprecate `qml.transforms.qcut.remap_tape_wires`. Use `qml.map_wires` instead.
[(#3186)](https://github.com/PennyLaneAI/pennylane/pull/3186)

<h3>Contributors</h3>

This release contains contributions from (in alphabetical order):
Expand Down
34 changes: 11 additions & 23 deletions pennylane/ops/op_math/controlled_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"""
This submodule defines the symbolic operation that indicates the control of an operator.
"""
from copy import copy
import warnings
from copy import copy
from inspect import signature
from typing import List

Expand All @@ -29,6 +29,7 @@

from .symbolicop import SymbolicOp


# pylint: disable=protected-access
def _decompose_no_control_values(op: "operation.Operator") -> List["operation.Operator"]:
"""Provides a decomposition without considering control values. Returns None if
Expand Down Expand Up @@ -272,33 +273,20 @@ def active_wires(self):
def wires(self):
return self.control_wires + self.target_wires + self.work_wires

# pylint: disable=protected-access
@property
def _wires(self):
return self.wires

# pylint: disable=protected-access
@_wires.setter
def _wires(self, new_wires):
new_wires = new_wires if isinstance(new_wires, Wires) else Wires(new_wires)

num_control = len(self.control_wires)
num_base = len(self.base.wires)
num_control_and_base = num_control + num_base
def map_wires(self, wire_map: dict):
new_op = copy(self)

assert num_control_and_base <= len(new_wires), (
f"{self.name} needs at least {num_control_and_base} wires."
f" {len(new_wires)} provided."
new_op.hyperparameters["control_wires"] = Wires(
[wire_map.get(wire, wire) for wire in self.control_wires]
)

self.hyperparameters["control_wires"] = new_wires[:num_control]
new_op.base._wires = Wires([wire_map.get(wire, wire) for wire in self.base.wires])

self.base._wires = new_wires[num_control:num_control_and_base]
new_op.hyperparameters["work_wires"] = Wires(
[wire_map.get(wire, wire) for wire in self.work_wires]
)

if len(new_wires) > num_control_and_base:
self.hyperparameters["work_wires"] = new_wires[num_control_and_base:]
else:
self.hyperparameters["work_wires"] = Wires([])
return new_op

# Methods ##########################################

Expand Down
19 changes: 7 additions & 12 deletions pennylane/ops/op_math/symbolicop.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ def __copy__(self):
if attr not in {"_hyperparameters"}:
setattr(copied_op, attr, value)

copied_op._hyperparameters = copy(self._hyperparameters)
copied_op._hyperparameters["base"] = copy(self.base)
copied_op._hyperparameters = copy(self.hyperparameters)
copied_op.hyperparameters["base"] = copy(self.base)

return copied_op

Expand Down Expand Up @@ -92,16 +92,6 @@ def num_params(self):
def wires(self):
return self.base.wires

# pylint: disable=protected-access
@property
def _wires(self):
return self.base._wires

# pylint: disable=protected-access
@_wires.setter
def _wires(self, new_wires):
self.base._wires = new_wires

@property
def num_wires(self):
return len(self.wires)
Expand Down Expand Up @@ -136,3 +126,8 @@ def hash(self):
self.base.hash,
)
)

def map_wires(self, wire_map: dict):
new_op = copy(self)
new_op.hyperparameters["base"] = self.base.map_wires(wire_map=wire_map)
return new_op
17 changes: 8 additions & 9 deletions pennylane/transforms/commutation_dag.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
A transform to obtain the commutation DAG of a quantum circuit.
"""
import heapq
from functools import wraps
from collections import OrderedDict
from networkx.drawing.nx_pydot import to_pydot
from functools import wraps

import networkx as nx
from networkx.drawing.nx_pydot import to_pydot

import pennylane as qml
from pennylane.tape import QuantumScript
from pennylane.wires import Wires


Expand Down Expand Up @@ -99,7 +101,7 @@ def wrapper(*args, **kwargs):
circuit.construct(args, kwargs)
tape = circuit.qtape

elif isinstance(circuit, qml.tape.QuantumTape):
elif isinstance(circuit, QuantumScript):
# user passed a tape
tape = circuit

Expand Down Expand Up @@ -210,7 +212,7 @@ class CommutationDAG:
"""

def __init__(self, tape):
def __init__(self, tape: QuantumScript):

self.num_wires = len(tape.wires)
self.node_id = -1
Expand All @@ -220,15 +222,12 @@ def __init__(self, tape):
wires_map = OrderedDict(zip(tape.wires, consecutive_wires))

for operation in tape.operations:
operation._wires = Wires([wires_map[wire] for wire in operation.wires.tolist()])
operation = qml.map_wires(operation, wire_map=wires_map)
self.add_node(operation)

self._add_successors()

for obs in tape.observables:
obs._wires = Wires([wires_map[wire] for wire in obs.wires.tolist()])

self.observables = tape.observables if tape.observables is not None else []
self.observables = [qml.map_wires(obs, wire_map=wires_map) for obs in tape.observables]

def _add_node(self, node):
self.node_id += 1
Expand Down
36 changes: 15 additions & 21 deletions pennylane/transforms/optimization/pattern_matching.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,24 @@
"""Transform finding all maximal matches of a pattern in a quantum circuit and optimizing the circuit by
substitution."""

import itertools
import copy
import itertools
from collections import OrderedDict

import numpy as np

import pennylane as qml
from pennylane import apply, adjoint
from pennylane import adjoint
from pennylane.ops.qubit.attributes import symmetric_over_all_wires
from pennylane.tape import QuantumTape
from pennylane.transforms import qfunc_transform
from pennylane.transforms.commutation_dag import commutation_dag
from pennylane.wires import Wires
from pennylane.ops.qubit.attributes import (
symmetric_over_all_wires,
)


# pylint: disable=too-many-statements
@qfunc_transform
def pattern_matching_optimization(tape, pattern_tapes, custom_quantum_cost=None):
def pattern_matching_optimization(tape: QuantumTape, pattern_tapes, custom_quantum_cost=None):
r"""Quantum function transform to optimize a circuit given a list of patterns (templates).
Args:
Expand Down Expand Up @@ -172,14 +172,13 @@ def circuit():
# pylint: disable=protected-access, too-many-branches

measurements = tape.measurements
observables = tape.observables

consecutive_wires = Wires(range(len(tape.wires)))
inverse_wires_map = OrderedDict(zip(consecutive_wires, tape.wires))

for pattern in pattern_tapes:
# Check the validity of the pattern
if not isinstance(pattern, qml.tape.QuantumTape):
if not isinstance(pattern, QuantumTape):
raise qml.QuantumFunctionError("The pattern is not a valid quantum tape.")

# Check that it does not contain a measurement.
Expand Down Expand Up @@ -212,7 +211,7 @@ def circuit():
# If some substitutions are possible, we create an optimized circuit.
if substitution.substitution_list:
# Create a tape that does not affect the outside context.
with qml.tape.QuantumTape(do_queue=False) as tape_inside:
with QuantumTape(do_queue=False) as tape_inside:
# Loop over all possible substitutions
for group in substitution.substitution_list:

Expand All @@ -228,7 +227,7 @@ def circuit():
for elem in pred:
node = circuit_dag.get_node(elem)
inst = copy.deepcopy(node.op)
apply(inst)
qml.apply(inst)
already_sub.append(elem)

already_sub = already_sub + circuit_sub
Expand All @@ -244,28 +243,23 @@ def circuit():
node = group.template_dag.get_node(index)
inst = copy.deepcopy(node.op)

inst._wires = Wires(wires)

adjoint(apply, lazy=False)(inst)
inst = qml.map_wires(inst, wire_map=dict(zip(inst.wires, wires)))
adjoint(qml.apply, lazy=False)(inst)

# Add the unmatched gates.
for node_id in substitution.unmatched_list:
node = circuit_dag.get_node(node_id)
inst = copy.deepcopy(node.op)
apply(inst)
qml.apply(inst)

tape = tape_inside
tape = qml.map_wires(input=tape_inside, wire_map=inverse_wires_map)

for op in tape.operations:
op._wires = Wires([inverse_wires_map[wire] for wire in op.wires.tolist()])
apply(op)
qml.apply(op)

# After optimization, simply apply the measurements
for obs in observables:
obs._wires = Wires([inverse_wires_map[wire] for wire in obs.wires.tolist()])

for m in measurements:
apply(m)
qml.apply(m)


def pattern_matching(circuit_dag, pattern_dag):
Expand Down
34 changes: 12 additions & 22 deletions pennylane/transforms/qcut.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,12 +448,7 @@ def graph_to_tape(graph: MultiDiGraph) -> QuantumTape:

with QuantumTape() as tape:
for op in copy_ops:
new_wires = Wires([wire_map[w] for w in op.wires])

# TODO: find a better way to update operation wires
op._wires = new_wires
apply(op)

op = qml.map_wires(op, wire_map=wire_map, queue=True)
if isinstance(op, MeasureNode):
assert len(op.wires) == 1
measured_wire = op.wires[0]
Expand Down Expand Up @@ -481,8 +476,8 @@ def graph_to_tape(graph: MultiDiGraph) -> QuantumTape:
)

for meas in copy_meas:
meas = qml.map_wires(meas, wire_map=wire_map)
obs = meas.obs
obs._wires = Wires([wire_map[w] for w in obs.wires])
observables.append(obs)

if return_type is Sample:
Expand Down Expand Up @@ -1332,7 +1327,7 @@ def circuit(x):
replace_wire_cut_nodes(g)
fragments, communication_graph = fragment_graph(g)
fragment_tapes = [graph_to_tape(f) for f in fragments]
fragment_tapes = [remap_tape_wires(t, device_wires) for t in fragment_tapes]
fragment_tapes = [qml.map_wires(t, dict(zip(t.wires, device_wires))) for t in fragment_tapes]

configurations, settings = expand_fragment_tapes_mc(
fragment_tapes, communication_graph, shots=shots
Expand Down Expand Up @@ -2061,7 +2056,7 @@ def circuit(x):
replace_wire_cut_nodes(g)
fragments, communication_graph = fragment_graph(g)
fragment_tapes = [graph_to_tape(f) for f in fragments]
fragment_tapes = [remap_tape_wires(t, device_wires) for t in fragment_tapes]
fragment_tapes = [qml.map_wires(t, dict(zip(t.wires, device_wires))) for t in fragment_tapes]
expanded = [expand_fragment_tape(t) for t in fragment_tapes]

configurations = []
Expand Down Expand Up @@ -2194,6 +2189,12 @@ def remap_tape_wires(tape: QuantumTape, wires: Sequence) -> QuantumTape:
0: ──RX(0.5)──╭●──╭┤ ⟨Z ⊗ Z⟩
1: ──RY(0.6)──╰X──╰┤ ⟨Z ⊗ Z⟩
"""
warnings.warn(
"The method remap_tape_wires is deprecated. Use "
"qml.map_wires(tape, dict(zip(tape.wires, wires))) instead.",
UserWarning,
)

if len(tape.wires) > len(wires):
raise ValueError(
f"Attempting to run a {len(tape.wires)}-wire circuit on a "
Expand All @@ -2207,20 +2208,9 @@ def remap_tape_wires(tape: QuantumTape, wires: Sequence) -> QuantumTape:

with QuantumTape() as new_tape:
for op in copy_ops:
new_wires = Wires([wire_map[w] for w in op.wires])
op._wires = new_wires
apply(op)
qml.map_wires(op, wire_map=wire_map, queue=True)
for meas in copy_meas:
obs = meas.obs

if isinstance(obs, Tensor):
for obs in obs.obs:
new_wires = Wires([wire_map[w] for w in obs.wires])
obs._wires = new_wires
else:
new_wires = Wires([wire_map[w] for w in obs.wires])
obs._wires = new_wires
apply(meas)
qml.map_wires(meas, wire_map=wire_map, queue=True)

return new_tape

Expand Down
14 changes: 1 addition & 13 deletions tests/ops/op_math/test_adjoint_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,18 +285,6 @@ def test_queue_category_None(self):
op = Adjoint(qml.PauliX(0) @ qml.PauliY(1))
assert op._queue_category is None

def test_private_wires(self):
"""Test that we can get and set the wires via the private property `_wires`."""
wire0 = qml.wires.Wires("a")
base = qml.PauliZ(wire0)
op = Adjoint(base)

assert op._wires == base._wires == wire0

wire1 = qml.wires.Wires(0)
op._wires = wire1
assert op._wires == base._wires == wire1

@pytest.mark.parametrize("value", (True, False))
def test_is_hermitian(self, value):
"""Test `is_hermitian` property mirrors that of the base."""
Expand Down Expand Up @@ -684,7 +672,7 @@ def test_adj_hamiltonian(self):

def test_sparse_matrix():
"""Test that the spare_matrix method returns the adjoint of the base sparse matrix."""
from scipy.sparse import csr_matrix, coo_matrix
from scipy.sparse import coo_matrix, csr_matrix

H = np.array([[6 + 0j, 1 - 2j], [1 + 2j, -1]])
H = csr_matrix(H)
Expand Down
Loading

0 comments on commit f206330

Please sign in to comment.