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

V0.14.1 bump #1083

Merged
merged 8 commits into from
Feb 12, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 can not be imported.
josh146 marked this conversation as resolved.
Show resolved Hide resolved
[(#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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this has already been code reviewed, but for my understanding - we have added the max_expansion attribute but have we implemented the effects of changing max_expansion?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep! The change is on line 781, max_expansion=10.

**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
Comment on lines +489 to +491
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for relocating this! 🚀


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
17 changes: 17 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,23 @@ 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)

grad = jax.grad(lambda a: circuit(a).reshape(()))(x)
josh146 marked this conversation as resolved.
Show resolved Hide resolved
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