Skip to content

Commit

Permalink
V0.14.1 bump (#1083)
Browse files Browse the repository at this point in the history
* PR 1082

* PR 1072

* Update vqe.py (#1077)

update `genearate_hamiltonian` refs

* PR 1074

* Add jax skip (#1066)

Co-authored-by: Josh Izaac <josh146@gmail.com>

* update changelog

* update version number

* Apply suggestions from code review

Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>
Co-authored-by: antalszava <antalszava@gmail.com>

Co-authored-by: antalszava <antalszava@gmail.com>
Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>
  • Loading branch information
3 people authored Feb 12, 2021
1 parent 4fffb1b commit 149a785
Show file tree
Hide file tree
Showing 13 changed files with 275 additions and 23 deletions.
40 changes: 39 additions & 1 deletion .github/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,42 @@
# Release 0.14.0 (current release)
# Release 0.14.1 (current release)

<h3>Bug fixes</h3>

* Fixes a bug where inverse operations could not be differentiated
using backpropagation on `default.qubit`.
[(#1072)](https://github.com/PennyLaneAI/pennylane/pull/1072)

* The QNode has a new keyword argument, `max_expansion`, that determines the maximum number of times
the internal circuit should be expanded when executed on a device. In addition, the default number
of max expansions has been increased from 2 to 10, allowing devices that require more than two
operator decompositions to be supported.
[(#1074)](https://github.com/PennyLaneAI/pennylane/pull/1074)

* Fixes a bug where `Hamiltonian` objects created with non-list arguments
raised an error for arithmetic operations.
[(#1082)](https://github.com/PennyLaneAI/pennylane/pull/1082)

* Fixes a bug where `Hamiltonian` objects with no coefficients or operations
would return a faulty result when used with `ExpvalCost`.
[(#1082)](https://github.com/PennyLaneAI/pennylane/pull/1082)

* Fixes a testing bug where tests that required JAX would fail if JAX was not installed.
The tests will now instead be skipped if JAX cannot be imported.
[(#1066)](https://github.com/PennyLaneAI/pennylane/pull/1066)

<h3>Documentation</h3>

* Updates mentions of `generate_hamiltonian` to `molecular_hamiltonian` in the
docstrings of the `ExpvalCost` and `Hamiltonian` classes.
[(#1077)](https://github.com/PennyLaneAI/pennylane/pull/1077)

<h3>Contributors</h3>

This release contains contributions from (in alphabetical order):

Thomas Bromley, Josh Izaac, Antal Száva.

# Release 0.14.0

<h3>New features since last release</h3>

Expand Down
2 changes: 1 addition & 1 deletion pennylane/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@
Version number (major.minor.patch[-label])
"""

__version__ = "0.14.0"
__version__ = "0.14.1"
13 changes: 10 additions & 3 deletions pennylane/devices/default_qubit_autograd.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,18 @@ def _get_unitary_matrix(self, unitary):
the unitary in the computational basis, or, in the case of a diagonal unitary,
a 1D array representing the matrix diagonal.
"""
op_name = unitary.name
op_name = unitary.name.split(".inv")[0]

if op_name in self.parametric_ops:
if op_name == "MultiRZ":
return self.parametric_ops[unitary.name](*unitary.parameters, len(unitary.wires))
return self.parametric_ops[unitary.name](*unitary.parameters)
mat = self.parametric_ops[op_name](*unitary.parameters, len(unitary.wires))
else:
mat = self.parametric_ops[op_name](*unitary.parameters)

if unitary.inverse:
mat = self._transpose(self._conj(mat))

return mat

if isinstance(unitary, DiagonalOperation):
return unitary.eigvals
Expand Down
13 changes: 10 additions & 3 deletions pennylane/devices/default_qubit_jax.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,18 @@ def _get_unitary_matrix(self, unitary):
the unitary in the computational basis, or, in the case of a diagonal unitary,
a 1D array representing the matrix diagonal.
"""
op_name = unitary.name
op_name = unitary.name.split(".inv")[0]

if op_name in self.parametric_ops:
if op_name == "MultiRZ":
return self.parametric_ops[unitary.name](*unitary.parameters, len(unitary.wires))
return self.parametric_ops[unitary.name](*unitary.parameters)
mat = self.parametric_ops[op_name](*unitary.parameters, len(unitary.wires))
else:
mat = self.parametric_ops[op_name](*unitary.parameters)

if unitary.inverse:
mat = self._transpose(self._conj(mat))

return mat

if isinstance(unitary, DiagonalOperation):
return unitary.eigvals
Expand Down
16 changes: 12 additions & 4 deletions pennylane/devices/default_qubit_tf.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,18 @@ def _get_unitary_matrix(self, unitary):
the return type will be a ``np.ndarray``. For parametric unitaries, a ``tf.Tensor``
object will be returned.
"""
if unitary.name in self.parametric_ops:
if unitary.name == "MultiRZ":
return self.parametric_ops[unitary.name](unitary.parameters, len(unitary.wires))
return self.parametric_ops[unitary.name](*unitary.parameters)
op_name = unitary.name.split(".inv")[0]

if op_name in self.parametric_ops:
if op_name == "MultiRZ":
mat = self.parametric_ops[op_name](*unitary.parameters, len(unitary.wires))
else:
mat = self.parametric_ops[op_name](*unitary.parameters)

if unitary.inverse:
mat = self._transpose(self._conj(mat))

return mat

if isinstance(unitary, DiagonalOperation):
return unitary.eigvals
Expand Down
26 changes: 23 additions & 3 deletions pennylane/tape/qnode.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ class QNode:
and is stored and re-used for further quantum evaluations. Only set this to False
if it is known that the underlying quantum structure is **independent of QNode input**.
max_expansion (int): The number of times the internal circuit should be expanded when
executed on a device. Expansion occurs when an operation or measurement is not
supported, and results in a gate decomposition. If any operations in the decomposition
remain unsupported by the device, another expansion occurs.
Keyword Args:
h=1e-7 (float): step size for the finite difference method
order=1 (int): The order of the finite difference method to use. ``1`` corresponds
Expand All @@ -125,7 +130,14 @@ class QNode:
# pylint:disable=too-many-instance-attributes,too-many-arguments

def __init__(
self, func, device, interface="autograd", diff_method="best", mutable=True, **diff_options
self,
func,
device,
interface="autograd",
diff_method="best",
mutable=True,
max_expansion=10,
**diff_options,
):

if interface is not None and interface not in self.INTERFACE_MAP:
Expand Down Expand Up @@ -156,7 +168,7 @@ def __init__(
self.diff_options.update(tape_diff_options)

self.dtype = np.float64
self.max_expansion = 2
self.max_expansion = max_expansion

# pylint: disable=too-many-return-statements
@staticmethod
Expand Down Expand Up @@ -765,7 +777,9 @@ def to_jax(self):
INTERFACE_MAP = {"autograd": to_autograd, "torch": to_torch, "tf": to_tf, "jax": to_jax}


def qnode(device, interface="autograd", diff_method="best", mutable=True, **diff_options):
def qnode(
device, interface="autograd", diff_method="best", mutable=True, max_expansion=10, **diff_options
):
"""Decorator for creating QNodes.
This decorator is used to indicate to PennyLane that the decorated function contains a
Expand Down Expand Up @@ -842,6 +856,11 @@ def qnode(device, interface="autograd", diff_method="best", mutable=True, **diff
and is stored and re-used for further quantum evaluations. Only set this to False
if it is known that the underlying quantum structure is **independent of QNode input**.
max_expansion (int): The number of times the internal circuit should be expanded when
executed on a device. Expansion occurs when an operation or measurement is not
supported, and results in a gate decomposition. If any operations in the decomposition
remain unsupported by the device, another expansion occurs.
Keyword Args:
h=1e-7 (float): Step size for the finite difference method.
order=1 (int): The order of the finite difference method to use. ``1`` corresponds
Expand All @@ -865,6 +884,7 @@ def qfunc_decorator(func):
interface=interface,
diff_method=diff_method,
mutable=mutable,
max_expansion=max_expansion,
**diff_options,
)
return update_wrapper(qn, func)
Expand Down
14 changes: 9 additions & 5 deletions pennylane/vqe/vqe.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class Hamiltonian:
simplify (bool): Specifies whether the Hamiltonian is simplified upon initialization
(like-terms are combined). The default value is `False`.
.. seealso:: :class:`~.ExpvalCost`, :func:`~.generate_hamiltonian`
.. seealso:: :class:`~.ExpvalCost`, :func:`~.molecular_hamiltonian`
**Example:**
Expand All @@ -66,7 +66,7 @@ class Hamiltonian:
>>> print(H)
(0.8) [Hermitian0'1]
Alternatively, the :func:`~.generate_hamiltonian` function from the
Alternatively, the :func:`~.molecular_hamiltonian` function from the
:doc:`/introduction/chemistry` module can be used to generate a molecular
Hamiltonian.
"""
Expand All @@ -90,8 +90,8 @@ def __init__(self, coeffs, observables, simplify=False):
"Could not create circuits. Some or all observables are not valid."
)

self._coeffs = coeffs
self._ops = observables
self._coeffs = list(coeffs)
self._ops = list(observables)

if simplify:
self.simplify()
Expand Down Expand Up @@ -396,7 +396,7 @@ class ExpvalCost:
callable: a cost function with signature ``cost_fn(params, **kwargs)`` that evaluates
the expectation of the Hamiltonian on the provided device(s)
.. seealso:: :class:`~.Hamiltonian`, :func:`~.generate_hamiltonian`, :func:`~.map`, :func:`~.dot`
.. seealso:: :class:`~.Hamiltonian`, :func:`~.molecular_hamiltonian`, :func:`~.map`, :func:`~.dot`
**Example:**
Expand Down Expand Up @@ -486,6 +486,10 @@ def __init__(
self._multiple_devices = isinstance(device, Sequence)
"""Bool: Records if multiple devices are input"""

if all(c == 0 for c in coeffs) or not coeffs:
self.cost_fn = lambda *args, **kwargs: np.array(0)
return

tape_mode = qml.tape_mode_active()
self._optimize = optimize

Expand Down
5 changes: 5 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ def skip_if_no_tf_support(tf_support):
pytest.skip("Skipped, no tf support")


@pytest.fixture
def skip_if_no_jax_support():
pytest.importorskip("jax")


@pytest.fixture(scope="module",
params=[1, 2, 3])
def seed(request):
Expand Down
17 changes: 17 additions & 0 deletions tests/devices/test_default_qubit_autograd.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,23 @@ def circuit(param):
res = qml.jacobian(circuit)(param)
assert np.allclose(res, np.zeros(wires **2))

def test_inverse_operation_jacobian_backprop(self, tol):
"""Test that inverse operations work in backprop
mode"""
dev = qml.device('default.qubit.autograd', wires=1)

@qml.qnode(dev, diff_method="backprop")
def circuit(param):
qml.RY(param, wires=0).inv()
return qml.expval(qml.PauliX(0))

x = 0.3
res = circuit(x)
assert np.allclose(res, -np.sin(x), atol=tol, rtol=0)

grad = qml.grad(circuit)(x)
assert np.allclose(grad, -np.cos(x), atol=tol, rtol=0)

def test_full_subsystem(self, mocker):
"""Test applying a state vector to the full subsystem"""
dev = DefaultQubitAutograd(wires=['a', 'b', 'c'])
Expand Down
18 changes: 18 additions & 0 deletions tests/devices/test_default_qubit_jax.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,24 @@ def circuit(param):
res = jacobian_transform(circuit)(param)
assert jnp.allclose(res, jnp.zeros(wires ** 2))

def test_inverse_operation_jacobian_backprop(self, tol):
"""Test that inverse operations work in backprop
mode"""
dev = qml.device('default.qubit.jax', wires=1)

@qml.qnode(dev, diff_method="backprop", interface="jax")
def circuit(param):
qml.RY(param, wires=0).inv()
return qml.expval(qml.PauliX(0))

x = 0.3
res = circuit(x)
assert np.allclose(res, -np.sin(x), atol=tol, rtol=0)

# Adjust grad func to be compatible when tested with both old and new cores of PennyLane
grad = jax.grad(lambda a: circuit(a).reshape(()))(x)
assert np.allclose(grad, -np.cos(x), atol=tol, rtol=0)

def test_full_subsystem(self, mocker):
"""Test applying a state vector to the full subsystem"""
dev = DefaultQubitJax(wires=["a", "b", "c"])
Expand Down
20 changes: 20 additions & 0 deletions tests/devices/test_default_qubit_tf.py
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,26 @@ def cost(params):
)
assert np.allclose(res.numpy(), expected_grad, atol=tol, rtol=0)

def test_inverse_operation_jacobian_backprop(self, tol):
"""Test that inverse operations work in backprop
mode"""
dev = qml.device('default.qubit.tf', wires=1)

@qml.qnode(dev, diff_method="backprop", interface="tf")
def circuit(param):
qml.RY(param, wires=0).inv()
return qml.expval(qml.PauliX(0))

x = tf.Variable(0.3)

with tf.GradientTape() as tape:
res = circuit(x)

assert np.allclose(res, -tf.sin(x), atol=tol, rtol=0)

grad = tape.gradient(res, x)
assert np.allclose(grad, -tf.cos(x), atol=tol, rtol=0)

@pytest.mark.parametrize("interface", ["autograd", "torch"])
def test_error_backprop_wrong_interface(self, interface, tol):
"""Tests that an error is raised if diff_method='backprop' but not using
Expand Down
4 changes: 3 additions & 1 deletion tests/qnn/test_cost.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ def skip_if_no_torch_support():


@pytest.mark.parametrize("interface", ALLOWED_INTERFACES)
@pytest.mark.usefixtures("skip_if_no_torch_support", "skip_if_no_tf_support")
@pytest.mark.usefixtures(
"skip_if_no_torch_support", "skip_if_no_tf_support", "skip_if_no_jax_support"
)
class TestSquaredErrorLoss:
def test_no_target(self, interface):
with pytest.raises(ValueError, match="The target cannot be None"):
Expand Down
Loading

0 comments on commit 149a785

Please sign in to comment.