Skip to content

Commit

Permalink
Merge branch 'master' into fix/torch_cuda_backward
Browse files Browse the repository at this point in the history
  • Loading branch information
trbromley committed Nov 16, 2020
2 parents 3e7fd6a + 47b1dff commit 9e469fa
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 9 deletions.
8 changes: 6 additions & 2 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,11 @@

<h3>Bug fixes</h3>

* PennyLane tensor objects are now unwrapped in BaseQNode when passed as a
keyword argument to the quantum function.
[(#903)](https://github.com/PennyLaneAI/pennylane/pull/903)
[(#893)](https://github.com/PennyLaneAI/pennylane/pull/893)

* The new tape mode now prevents multiple observables from being evaluated on the same wire
if the observables are not qubit-wise commuting Pauli words.
[(#882)](https://github.com/PennyLaneAI/pennylane/pull/882)
Expand All @@ -255,8 +260,7 @@
This release contains contributions from (in alphabetical order):

Thomas Bromley, Christina Lee, Olivia Di Matteo, Anthony Hayes, Josh Izaac, Nathan Killoran, Shumpei Kobayashi,
Romain Moyard, Maria Schuld

Romain Moyard, Maria Schuld, Antal Száva.

# Release 0.12.0 (current release)

Expand Down
21 changes: 21 additions & 0 deletions pennylane/qnodes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,8 @@ def qfunc(a, w):
Each positional argument is replaced with a :class:`~.variable.Variable` instance.
kwargs (dict[str, Any]): Auxiliary arguments passed to the quantum function.
"""
kwargs = self.unwrap_tensor_kwargs(kwargs)

# Get the name of the qfunc's arguments
full_argspec = inspect.getfullargspec(self.func)

Expand Down Expand Up @@ -525,6 +527,25 @@ def qfunc(a, w):

return arg_vars, kwarg_vars

@staticmethod
def unwrap_tensor_kwargs(kwargs):
"""Unwraps the pennylane.numpy.tensor objects that were passed as
keyword arguments so that they can be handled as gate parameters by
arbitrary devices.
Args:
kwargs (dict[str, Any]): Auxiliary arguments passed to the quantum function.
Returns:
dict[str, Any]: Auxiliary arguments passed to the quantum function
in an unwrapped form (if applicable).
"""
for k, v in kwargs.items():
if isinstance(v, qml.numpy.tensor):
kwargs[k] = v.unwrap()

return kwargs

def _construct(self, args, kwargs):
"""Construct the quantum circuit graph by calling the quantum function.
Expand Down
7 changes: 6 additions & 1 deletion pennylane/tape/tapes/jacobian_tape.py
Original file line number Diff line number Diff line change
Expand Up @@ -490,8 +490,13 @@ def jacobian(self, device, params=None, **options):
all_tapes = []
reshape_info = []
processing_fns = []
nonzero_grad_idx = []

for trainable_idx, param_method in enumerate(diff_methods):
if param_method == "0":
continue

nonzero_grad_idx.append(trainable_idx)

if (method == "best" and param_method[0] == "F") or (method == "numeric"):
# numeric method
Expand All @@ -516,7 +521,7 @@ def jacobian(self, device, params=None, **options):
jac = None
start = 0

for i, (processing_fn, res_len) in enumerate(zip(processing_fns, reshape_info)):
for i, processing_fn, res_len in zip(nonzero_grad_idx, processing_fns, reshape_info):

# extract the correct results from the flat list
res = results[start : start + res_len]
Expand Down
6 changes: 1 addition & 5 deletions pennylane/templates/layers/random.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
get_shape,
)
from pennylane.wires import Wires
from pennylane.numpy import tensor


def random_layer(weights, wires, ratio_imprim, imprimitive, rotations, seed):
Expand All @@ -51,10 +50,7 @@ def random_layer(weights, wires, ratio_imprim, imprimitive, rotations, seed):
gate = np.random.choice(rotations)
rnd_wire = wires.select_random(1)

if isinstance(weights[i], tensor):
gate(weights[i].unwrap(), wires=rnd_wire)
else:
gate(weights[i], wires=rnd_wire)
gate(weights[i], wires=rnd_wire)

i += 1
else:
Expand Down
27 changes: 27 additions & 0 deletions tests/tape/tapes/test_jacobian_tape.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,33 @@ def test_numeric_unknown_order(self):
with pytest.raises(ValueError, match="Order must be 1 or 2"):
tape.jacobian(dev, order=3)

def test_independent_parameters(self):
"""Test the case where expectation values are independent of some parameters. For those
parameters, the gradient should be evaluated to zero without executing the device."""
dev = qml.device("default.qubit", wires=2)

with JacobianTape() as tape1:
qml.RX(1, wires=[0])
qml.RX(1, wires=[1])
qml.expval(qml.PauliZ(0))

with JacobianTape() as tape2:
qml.RX(1, wires=[0])
qml.RX(1, wires=[1])
qml.expval(qml.PauliZ(1))

j1 = tape1.jacobian(dev)

# We should only be executing the device to differentiate 1 parameter (2 executions)
assert dev.num_executions == 2

j2 = tape2.jacobian(dev)

exp = - np.sin(1)

assert np.allclose(j1, [exp, 0])
assert np.allclose(j2, [0, exp])


class TestJacobianIntegration:
"""Integration tests for the Jacobian method"""
Expand Down
2 changes: 1 addition & 1 deletion tests/templates/test_subroutines.py
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,7 @@ def test_uccsd_operations(self, s_wires, d_wires, weights, ref_gates):
[[0, 1, 2]],
[],
np.array([1.2, 1, 0, 0]),
"BasisState parameter must consist of 0 or 1 integers",
"Elements of 'init_state' must be integers",
),
(
np.array([-2.8]),
Expand Down
59 changes: 59 additions & 0 deletions tests/test_numpy_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,3 +497,62 @@ def test_convert_array(self):
assert np.all(res == data)
assert isinstance(res, np.ndarray)
assert not isinstance(res, np.tensor)

def test_single_gate_parameter(self, monkeypatch):
"""Test that when supplied a PennyLane tensor, a QNode passes an
unwrapped tensor as the argument to a gate taking a single parameter"""
dev = qml.device("default.qubit", wires=4)

@qml.qnode(dev)
def circuit(phi=None):
for y in phi:
for idx, x in enumerate(y):
qml.RX(x, wires=idx)
return qml.expval(qml.PauliZ(0))

phi = np.tensor([[0.04439891, 0.14490549, 3.29725643, 2.51240058]])

with qml._queuing.OperationRecorder() as rec:
circuit(phi=phi)

for i in range(phi.shape[1]):
# Test each rotation applied
assert rec.queue[0].name == "RX"
assert len(rec.queue[0].parameters) == 1

# Test that the gate parameter is not a PennyLane tensor, but a
# float
assert not isinstance(rec.queue[0].parameters[0], np.tensor)
assert isinstance(rec.queue[0].parameters[0], float)

def test_multiple_gate_parameter(self):
"""Test that when supplied a PennyLane tensor, a QNode passes arguments
as unwrapped tensors to a gate taking multiple parameters"""
dev = qml.device("default.qubit", wires=1)

@qml.qnode(dev)
def circuit(phi=None):
for idx, x in enumerate(phi):
qml.Rot(*x, wires=idx)
return qml.expval(qml.PauliZ(0))

phi = np.tensor([[0.04439891, 0.14490549, 3.29725643]])


with qml._queuing.OperationRecorder() as rec:
circuit(phi=phi)

# Test the rotation applied
assert rec.queue[0].name == "Rot"
assert len(rec.queue[0].parameters) == 3

# Test that the gate parameters are not PennyLane tensors, but a
# floats
assert not isinstance(rec.queue[0].parameters[0], np.tensor)
assert isinstance(rec.queue[0].parameters[0], float)

assert not isinstance(rec.queue[0].parameters[1], np.tensor)
assert isinstance(rec.queue[0].parameters[1], float)

assert not isinstance(rec.queue[0].parameters[2], np.tensor)
assert isinstance(rec.queue[0].parameters[2], float)

0 comments on commit 9e469fa

Please sign in to comment.