Skip to content

Commit

Permalink
Make Operator.data a tuple instead of a list (#4222)
Browse files Browse the repository at this point in the history
* wip

* wip

* wip

* fix adaptive optimizer tests

* fix legacy tests except ham expand

* fix ham expand tests

* remove bind new parameters

* Changelog and cleanup

* Update tests/transforms/test_hamiltonian_expand.py

Co-authored-by: Mudit Pandey <mudit.pandey@xanadu.ai>

* Apply suggestions from code review

Co-authored-by: Mudit Pandey <mudit.pandey@xanadu.ai>

* Revert back to op_idx=0 for measurements

* Update pennylane/operation.py

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

* hamiltonian data to list

---------

Co-authored-by: Mudit Pandey <mudit.pandey@xanadu.ai>
Co-authored-by: Christina Lee <christina@xanadu.ai>
  • Loading branch information
3 people committed Jun 15, 2023
1 parent 1c5e44b commit 58bdaea
Show file tree
Hide file tree
Showing 34 changed files with 165 additions and 150 deletions.
3 changes: 3 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,9 @@

* `pennylane.collections`, `pennylane.op_sum`, and `pennylane.utils.sparse_hamiltonian` are removed.

* `Operator.data` now returns a `tuple` instead of a `list`.
[(#4222)](https://github.com/PennyLaneAI/pennylane/pull/4222)

<h3>Deprecations 👋</h3>

* `LieAlgebraOptimizer` is renamed. Please use `RiemannianGradientOptimizer` instead.
Expand Down
22 changes: 14 additions & 8 deletions pennylane/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,7 @@ def compute_decomposition(theta, wires):
def __copy__(self):
cls = self.__class__
copied_op = cls.__new__(cls)
copied_op.data = self.data.copy()
copied_op.data = copy.copy(self.data)
for attr, value in vars(self).items():
if attr != "data":
setattr(copied_op, attr, value)
Expand All @@ -680,7 +680,7 @@ def __deepcopy__(self, memo):
# Shallow copy the list of parameters. We avoid a deep copy
# here, since PyTorch does not support deep copying of tensors
# within a differentiable computation.
copied_op.data = value.copy()
copied_op.data = copy.copy(value)
else:
# Deep copy everything else.
setattr(copied_op, attribute, copy.deepcopy(value, memo))
Expand Down Expand Up @@ -1024,7 +1024,7 @@ def __init__(self, *params, wires=None, do_queue=None, id=None):

self._check_batching(params)

self.data = [np.array(p) if isinstance(p, (list, tuple)) else p for p in params]
self.data = tuple(np.array(p) if isinstance(p, (list, tuple)) else p for p in params)

if do_queue is not None:
do_queue_deprecation_warning = (
Expand Down Expand Up @@ -1174,7 +1174,7 @@ def wires(self):
@property
def parameters(self):
"""Trainable parameters that the operator depends on."""
return self.data.copy()
return list(self.data)

@property
def hyperparameters(self):
Expand Down Expand Up @@ -2058,9 +2058,9 @@ def data(self):
"""Raw parameters of all constituent observables in the tensor product.
Returns:
list[Any]: flattened list containing all dependent parameters
tuple[Any]: flattened list containing all dependent parameters
"""
return sum((o.data for o in self.obs), [])
return tuple(d for op in self.obs for d in op.data)

@data.setter
def data(self, new_data):
Expand All @@ -2081,8 +2081,14 @@ def data(self, new_data):
[array([[5., 0.],
[0., 5.]])]
"""
for new_entry, op in zip(new_data, self.obs):
op.data = new_entry
if isinstance(new_data, tuple):
start = 0
for op in self.obs:
op.data = new_data[start : start + len(op.data)]
start += len(op.data)
else:
for new_entry, op in zip(new_data, self.obs):
op.data = tuple(new_entry)

@property
def num_params(self):
Expand Down
2 changes: 1 addition & 1 deletion pennylane/ops/op_math/adjoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ class Adjoint(SymbolicOp):
>>> qml.generator(Adjoint(qml.RX(1.0, wires=0)))
(PauliX(wires=[0]), 0.5)
>>> Adjoint(qml.RX(1.234, wires=0)).data
[1.234]
(1.234,)
.. details::
:title: Developer Details
Expand Down
5 changes: 3 additions & 2 deletions pennylane/ops/op_math/composite.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import abc
from typing import Callable, List
import warnings
import copy

import numpy as np

Expand Down Expand Up @@ -122,7 +123,7 @@ def _op_symbol(self) -> str:
@property
def data(self):
"""Create data property"""
return [d for op in self for d in op.data]
return tuple(d for op in self for d in op.data)

@data.setter
def data(self, new_data):
Expand Down Expand Up @@ -351,7 +352,7 @@ def map_wires(self, wire_map: dict):
new_op = cls.__new__(cls)
new_op.operands = tuple(op.map_wires(wire_map=wire_map) for op in self)
new_op._wires = Wires([wire_map.get(wire, wire) for wire in self.wires])
new_op.data = self.data.copy()
new_op.data = copy.copy(self.data)
for attr, value in vars(self).items():
if attr not in {"data", "operands", "_wires"}:
setattr(new_op, attr, value)
Expand Down
2 changes: 1 addition & 1 deletion pennylane/ops/op_math/controlled.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ class Controlled(SymbolicOp):
>>> op.base
RX(1.234, wires=[1])
>>> op.data
[1.234]
(1.234,)
>>> op.wires
<Wires = [0, 1]>
>>> op.control_wires
Expand Down
2 changes: 1 addition & 1 deletion pennylane/ops/op_math/evolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def __init__(self, generator, param=1, num_steps=None, do_queue=None, id=None):
super().__init__(
generator, coeff=-1j * param, num_steps=num_steps, do_queue=do_queue, id=id
)
self._data = [param]
self._data = (param,)

def __repr__(self):
return (
Expand Down
2 changes: 1 addition & 1 deletion pennylane/ops/op_math/symbolicop.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def batch_size(self):

@property
def data(self):
return [self.scalar, *self.base.data]
return (self.scalar, *self.base.data)

@data.setter
def data(self, new_data):
Expand Down
6 changes: 3 additions & 3 deletions pennylane/ops/qubit/hamiltonian.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ def terms(self):
>>> t[0]
[<tf.Tensor: shape=(), dtype=float32, numpy=1.0>, <tf.Tensor: shape=(), dtype=float32, numpy=2.0>]
"""
return self.data, self.ops
return self.parameters, self.ops

@property
def wires(self):
Expand Down Expand Up @@ -475,7 +475,7 @@ def simplify(self):

# hotfix: We `self.data`, since `self.parameters` returns a copy of the data and is now returned in
# self.terms(). To be improved soon.
self.data = new_coeffs
self.data = tuple(new_coeffs)
# hotfix: We overwrite the hyperparameter entry, which is now returned in self.terms().
# To be improved soon.
self.hyperparameters["ops"] = new_ops
Expand Down Expand Up @@ -752,7 +752,7 @@ def map_wires(self, wire_map: dict):
"""
cls = self.__class__
new_op = cls.__new__(cls)
new_op.data = self.data.copy()
new_op.data = copy(self.data)
new_op._wires = Wires( # pylint: disable=protected-access
[wire_map.get(wire, wire) for wire in self.wires]
)
Expand Down
3 changes: 2 additions & 1 deletion pennylane/optimize/adaptive.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ def append_gate(tape, params, gates):

for i, g in enumerate(gates):
g = copy.copy(g)
g.data[0] = params[i]
new_params = (params[i], *g.data[1:])
g.data = new_params
qml.apply(g)

for m in tape.measurements:
Expand Down
14 changes: 12 additions & 2 deletions pennylane/tape/qscript.py
Original file line number Diff line number Diff line change
Expand Up @@ -729,10 +729,20 @@ def set_parameters(self, params, trainable_only=True):
if len(params) != required_length:
raise ValueError("Number of provided parameters does not match.")

op_data = []
for pinfo in self._par_info:
if pinfo["p_idx"] == 0:
op_data.append((pinfo["op"], list(pinfo["op"].data)))
else:
op_data.append(op_data[-1])

for idx, p in iterator:
op = self._par_info[idx]["op"]
op.data[self._par_info[idx]["p_idx"]] = p
op_data[idx][1][self._par_info[idx]["p_idx"]] = p

for op, d in op_data:
op.data = tuple(d)
op._check_batching(op.data)

self._update_batch_size()
self._update_output_dim()

Expand Down
2 changes: 1 addition & 1 deletion tests/gradients/parameter_shift/test_parameter_shift_cv.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ def _mock_transform_observable(obs, Z, device_wires): # pylint: disable=unused-
transformed_obs.parameters[0]`` the condition ``len(A.nonzero()[0])
== 1 and A.ndim == 2 and A[0, 0] != 0`` is ``True``."""
iden = qml.Identity(0)
iden.data = [np.array([[1, 0], [0, 0]])]
iden.data = (np.array([[1, 0], [0, 0]]),)
return iden

monkeypatch.setattr(
Expand Down
8 changes: 4 additions & 4 deletions tests/legacy/test_hamiltonian_expand_old.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,8 +473,8 @@ def test_sum_dif_autograd(self, tol):
3.50307411e-01,
-3.41123470e-01,
0.0,
0.0,
0.0,
-4.36578753e-01,
6.41233474e-01,
]

with AnnotatedQueue() as q:
Expand Down Expand Up @@ -567,8 +567,8 @@ def test_sum_dif_jax(self, tol):
3.50307411e-01,
-3.41123470e-01,
0.0,
0.0,
0.0,
-4.36578753e-01,
6.41233474e-01,
]

with AnnotatedQueue() as q:
Expand Down
6 changes: 3 additions & 3 deletions tests/legacy/test_metric_tensor_old.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_rot_decomposition(self, diff_method):
# Second parameter subcircuit
assert len(tapes[1].operations) == 4
assert isinstance(tapes[1].operations[0], qml.RZ)
assert tapes[1].operations[0].data == [1]
assert tapes[1].operations[0].data == (1,)
# PauliY decomp
assert isinstance(tapes[1].operations[1], qml.PauliZ)
assert isinstance(tapes[1].operations[2], qml.S)
Expand All @@ -56,8 +56,8 @@ def test_rot_decomposition(self, diff_method):
assert len(tapes[2].operations) == 2
assert isinstance(tapes[2].operations[0], qml.RZ)
assert isinstance(tapes[2].operations[1], qml.RY)
assert tapes[2].operations[0].data == [1]
assert tapes[2].operations[1].data == [2]
assert tapes[2].operations[0].data == (1,)
assert tapes[2].operations[1].data == (2,)

@pytest.mark.parametrize("diff_method", ["parameter-shift", "backprop"])
def test_multirz_decomposition(self, diff_method):
Expand Down
1 change: 0 additions & 1 deletion tests/legacy/test_qscript_old.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,6 @@ def test_shallow_copy_with_operations(self, copy_fn):
# however, the underlying operation data *is still shared*
assert copied_qs.operations[0].wires is qs.operations[0].wires
# the data list is copied, but the elements of the list remain th same
assert copied_qs.operations[0].data is not qs.operations[0].data
assert copied_qs.operations[0].data[0] is qs.operations[0].data[0]

assert qs.get_parameters() == copied_qs.get_parameters()
Expand Down
34 changes: 17 additions & 17 deletions tests/ops/op_math/test_adjoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def test_nonparametric_ops(self):

assert op.num_params == 0
assert op.parameters == []
assert op.data == []
assert op.data == ()

assert op.wires == qml.wires.Wires("a")

Expand Down Expand Up @@ -176,18 +176,18 @@ def test_data(self):
base = qml.RX(x, wires="a")
adj = Adjoint(base)

assert adj.data == [x]
assert adj.data == (x,)

# update parameters through adjoint
x_new = np.array(2.3456)
adj.data = [x_new]
assert base.data == [x_new]
assert adj.data == [x_new]
adj.data = (x_new,)
assert base.data == (x_new,)
assert adj.data == (x_new,)

# update base data updates Adjoint data
x_new2 = np.array(3.456)
base.data = [x_new2]
assert adj.data == [x_new2]
base.data = (x_new2,)
assert adj.data == (x_new2,)

def test_has_matrix_true(self):
"""Test `has_matrix` property carries over when base op defines matrix."""
Expand Down Expand Up @@ -866,7 +866,7 @@ def test_adjoint_single_op_function(self):
assert out == tape[0]
assert isinstance(out, Adjoint)
assert out.base.__class__ is qml.RX
assert out.data == [1.234]
assert out.data == (1.234,)
assert out.wires == qml.wires.Wires("a")

def test_adjoint_template(self):
Expand Down Expand Up @@ -908,9 +908,9 @@ def func(x, y, z):
assert tape[2].base.__class__ is qml.RX

# check parameters assigned correctly
assert tape[0].data == [z]
assert tape[1].data == [y]
assert tape[2].data == [x]
assert tape[0].data == (z,)
assert tape[1].data == (y,)
assert tape[2].data == (x,)

def test_nested_adjoint(self):
"""Test the adjoint transform on an adjoint transform."""
Expand All @@ -923,7 +923,7 @@ def test_nested_adjoint(self):
assert isinstance(out, Adjoint)
assert isinstance(out.base, Adjoint)
assert out.base.base.__class__ is qml.RX
assert out.data == [x]
assert out.data == (x,)
assert out.wires == qml.wires.Wires("b")


Expand All @@ -942,7 +942,7 @@ def test_single_decomposeable_op(self):
assert q.queue[0] is out

assert isinstance(out, qml.RX)
assert out.data == [-1.23]
assert out.data == (-1.23,)

def test_single_nondecomposable_op(self):
"""Test lazy=false for a single op that can't be decomposed."""
Expand All @@ -966,7 +966,7 @@ def test_single_decomposable_op_function(self):
assert out is tape[0]
assert not isinstance(out, Adjoint)
assert isinstance(out, qml.RX)
assert out.data == [-x]
assert out.data == (-x,)

def test_single_nondecomposable_op_function(self):
"""Test lazy=False for a single op function that can't be decomposed."""
Expand Down Expand Up @@ -1009,7 +1009,7 @@ def test_single_op(self):

assert isinstance(out, Adjoint)
assert out.base.__class__ is qml.RZ
assert out.data == [1.234]
assert out.data == (1.234,)
assert out.wires == qml.wires.Wires(0)

def test_single_op_eager(self):
Expand All @@ -1020,7 +1020,7 @@ def test_single_op_eager(self):
out = adjoint(base, lazy=False)

assert isinstance(out, qml.RX)
assert out.data == [-x]
assert out.data == (-x,)

def test_observable(self):
"""Test providing a preconstructed Observable outside of a queuing context."""
Expand All @@ -1039,7 +1039,7 @@ def test_single_op_function(self):

assert isinstance(out, Adjoint)
assert out.base.__class__ is qml.IsingXX
assert out.data == [1.234]
assert out.data == (1.234,)
assert out.wires == qml.wires.Wires((0, 1))

def test_function(self):
Expand Down
6 changes: 3 additions & 3 deletions tests/ops/op_math/test_composite.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,14 @@ def test_parameters(self):
def test_data(self):
"""Test that data is initialized correctly."""
op = ValidOp(qml.RX(9.87, wires=0), qml.Rot(1.23, 4.0, 5.67, wires=1), qml.PauliX(0))
assert op.data == [9.87, 1.23, 4.0, 5.67]
assert op.data == (9.87, 1.23, 4.0, 5.67)

def test_data_setter(self):
"""Test the setter method for data"""
op = ValidOp(qml.RX(9.87, wires=0), qml.Rot(1.23, 4.0, 5.67, wires=1), qml.PauliX(0))
assert op.data == [9.87, 1.23, 4.0, 5.67]
assert op.data == (9.87, 1.23, 4.0, 5.67)

new_data = [1.23, 0.0, -1.0, -2.0]
new_data = (1.23, 0.0, -1.0, -2.0)
op.data = new_data # pylint:disable=attribute-defined-outside-init
assert op.data == new_data

Expand Down
Loading

0 comments on commit 58bdaea

Please sign in to comment.