Skip to content

Commit

Permalink
Rename VQECost to ExpvalCost (PennyLaneAI#913)
Browse files Browse the repository at this point in the history
* Rename VQECost to ExpvalCost

* Add VQECost as deprecated

* Add to changelog

* Update PR number

* Revert changes to changelog

* Change a to an

* Add pylint exception

* Remove VQECost from docs

* Reintroduce docstring
  • Loading branch information
trbromley authored and alejomonbar committed Dec 1, 2020
1 parent 32fd803 commit 02f106c
Show file tree
Hide file tree
Showing 15 changed files with 95 additions and 60 deletions.
12 changes: 8 additions & 4 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

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

* The ``VQECost`` class now provides observable optimization using the ``optimize`` argument,
resulting in potentially fewer device executions.
* The ``ExpvalCost`` class (previously ``VQECost``) now provides observable optimization using the
``optimize`` argument, resulting in potentially fewer device executions.
[(#902)](https://github.com/PennyLaneAI/pennylane/pull/902)

This is achieved by separating the observables composing the Hamiltonian into qubit-wise
Expand All @@ -18,8 +18,8 @@
dev = qml.device("default.qubit", wires=2)
ansatz = qml.templates.StronglyEntanglingLayers

cost_opt = qml.VQECost(ansatz, H, dev, optimize=True)
cost_no_opt = qml.VQECost(ansatz, H, dev, optimize=False)
cost_opt = qml.ExpvalCost(ansatz, H, dev, optimize=True)
cost_no_opt = qml.ExpvalCost(ansatz, H, dev, optimize=False)

params = qml.init.strong_ent_layers_uniform(3, 2)
```
Expand Down Expand Up @@ -276,6 +276,10 @@

<h3>Breaking changes</h3>

- The ``VQECost`` class has been renamed to ``ExpvalCost`` to reflect its general applicability
beyond VQE. Use of ``VQECost`` is still possible but will result in a deprecation warning.
[(#913)](https://github.com/PennyLaneAI/pennylane/pull/913)

<h3>Documentation</h3>

<h3>Bug fixes</h3>
Expand Down
4 changes: 2 additions & 2 deletions doc/code/qml_qaoa.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@ computational basis states, and then repeatedly apply QAOA layers with the
qml.layer(qaoa_layer, 2, params[0], params[1])
With the circuit defined, we call the device on which QAOA will be executed, as well as the ``qml.VQECost``, which
With the circuit defined, we call the device on which QAOA will be executed, as well as the ``qml.ExpvalCost``, which
creates the QAOA cost function: the expected value of the cost Hamiltonian with respect to the parametrized output
of the QAOA circuit.

.. code-block:: python3
# Defines the device and the QAOA cost function
dev = qml.device('default.qubit', wires=len(wires))
cost_function = qml.VQECost(circuit, cost_h, dev)
cost_function = qml.ExpvalCost(circuit, cost_h, dev)
>>> print(cost_function([[1, 1], [1, 1]]))
-1.8260274380964299
Expand Down
4 changes: 2 additions & 2 deletions doc/introduction/chemistry.rst
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ where a quantum computer is used to prepare the trial wave function of a molecul
the expectation value of the *electronic Hamiltonian*, while a classical optimizer is used to
find its ground state.

We can use :class:`~.VQECost` to automatically create the required PennyLane QNodes and define
We can use :class:`~.ExpvalCost` to automatically create the required PennyLane QNodes and define
the cost function:

.. code-block:: python
Expand All @@ -175,7 +175,7 @@ the cost function:
qml.CNOT(wires=[2, 0])
qml.CNOT(wires=[3, 1])
cost = qml.VQECost(circuit, hamiltonian, dev, interface="torch")
cost = qml.ExpvalCost(circuit, hamiltonian, dev, interface="torch")
params = torch.rand([4, 3])
cost(params)
Expand Down
2 changes: 1 addition & 1 deletion pennylane/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
import pennylane.qaoa as qaoa
from pennylane.templates import template, broadcast, layer
from pennylane.about import about
from pennylane.vqe import Hamiltonian, VQECost
from pennylane.vqe import Hamiltonian, ExpvalCost, VQECost

from .circuit_graph import CircuitGraph
from .configuration import Configuration
Expand Down
10 changes: 5 additions & 5 deletions pennylane/optimize/qng.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class QNGOptimizer(GradientDescentOptimizer):
.. note::
The QNG optimizer supports single QNodes or :class:`~.VQECost` objects as objective functions.
The QNG optimizer supports single QNodes or :class:`~.ExpvalCost` objects as objective functions.
Alternatively, the metric tensor can directly be provided to the :func:`step` method of the optimizer,
using the ``metric_tensor_fn`` argument.
Expand All @@ -91,7 +91,7 @@ class QNGOptimizer(GradientDescentOptimizer):
If the objective function is VQE/VQE-like, i.e., a function of a group
of QNodes that share an ansatz, there are two ways to use the optimizer:
* Realize the objective function as a :class:`~.VQECost` object, which has
* Realize the objective function as an :class:`~.ExpvalCost` object, which has
a ``metric_tensor`` method.
* Manually provide the ``metric_tensor_fn`` corresponding to the metric tensor of
Expand All @@ -100,7 +100,7 @@ class QNGOptimizer(GradientDescentOptimizer):
**Examples:**
For VQE/VQE-like problems, the objective function for the optimizer can be
realized as a VQECost object.
realized as an ExpvalCost object.
>>> dev = qml.device("default.qubit", wires=1)
>>> def circuit(params, wires=0):
Expand All @@ -109,7 +109,7 @@ class QNGOptimizer(GradientDescentOptimizer):
>>> coeffs = [1, 1]
>>> obs = [qml.PauliX(0), qml.PauliZ(0)]
>>> H = qml.Hamiltonian(coeffs, obs)
>>> cost_fn = qml.VQECost(circuit, H, dev)
>>> cost_fn = qml.ExpvalCost(circuit, H, dev)
Once constructed, the cost function can be passed directly to the
optimizer's ``step`` function:
Expand Down Expand Up @@ -174,7 +174,7 @@ def step(self, qnode, x, recompute_tensor=True, metric_tensor_fn=None):
if not hasattr(qnode, "metric_tensor") and not metric_tensor_fn:
raise ValueError(
"The objective function must either be encoded as a single QNode or "
"a VQECost object for the natural gradient to be automatically computed. "
"an ExpvalCost object for the natural gradient to be automatically computed. "
"Otherwise, metric_tensor_fn must be explicitly provided to the optimizer."
)

Expand Down
2 changes: 1 addition & 1 deletion pennylane/templates/layers/particle_conserving_u1.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ def ParticleConservingU1(weights, wires, init_state=None):
ansatz = partial(ParticleConservingU1, init_state=ref_state)
# Define the cost function
cost_fn = qml.VQECost(ansatz, h, dev)
cost_fn = qml.ExpvalCost(ansatz, h, dev)
# Compute the expectation value of 'h'
layers = 2
Expand Down
2 changes: 1 addition & 1 deletion pennylane/templates/layers/particle_conserving_u2.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def ParticleConservingU2(weights, wires, init_state=None):
ansatz = partial(ParticleConservingU2, init_state=ref_state)
# Define the cost function
cost_fn = qml.VQECost(ansatz, h, dev)
cost_fn = qml.ExpvalCost(ansatz, h, dev)
# Compute the expectation value of 'h' for a given set of parameters
layers = 1
Expand Down
2 changes: 1 addition & 1 deletion pennylane/templates/subroutines/uccsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def UCCSD(weights, wires, s_wires=None, d_wires=None, init_state=None):
ansatz = partial(UCCSD, init_state=ref_state, s_wires=s_wires, d_wires=d_wires)
# Define the cost function
cost_fn = qml.VQECost(ansatz, h, dev)
cost_fn = qml.ExpvalCost(ansatz, h, dev)
# Compute the expectation value of 'h' for given set of parameters 'params'
params = np.random.normal(0, np.pi, len(singles) + len(doubles))
Expand Down
2 changes: 1 addition & 1 deletion pennylane/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def decompose_hamiltonian(H, hide_identity=False):
+ (-0.5) [Z0 X1]
+ (-0.5) [Z0 Y1]
This Hamiltonian can then be used in defining VQE problems using :class:`~.VQECost`.
This Hamiltonian can then be used in defining VQE problems using :class:`~.ExpvalCost`.
"""
n = int(np.log2(len(H)))
N = 2 ** n
Expand Down
2 changes: 1 addition & 1 deletion pennylane/vqe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
This package contains functionality for running Variational Quantum Eigensolver (VQE)
computations using PennyLane.
"""
from .vqe import Hamiltonian, VQECost
from .vqe import Hamiltonian, ExpvalCost, VQECost
38 changes: 28 additions & 10 deletions pennylane/vqe/vqe.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"""
# pylint: disable=too-many-arguments, too-few-public-methods
import itertools
import warnings

import pennylane as qml
from pennylane import numpy as np
Expand All @@ -40,7 +41,7 @@ class Hamiltonian:
simplify (bool): Specifies whether the Hamiltonian is simplified upon initialization
(like-terms are combined). The default value is `False`.
.. seealso:: :class:`~.VQECost`, :func:`~.generate_hamiltonian`
.. seealso:: :class:`~.ExpvalCost`, :func:`~.generate_hamiltonian`
**Example:**
Expand Down Expand Up @@ -346,9 +347,10 @@ def __isub__(self, H):
raise ValueError(f"Cannot subtract {type(H)} from Hamiltonian")


class VQECost:
"""Create a VQE cost function, i.e., a cost function returning the
expectation value of a Hamiltonian.
class ExpvalCost:
"""Create a cost function that gives the expectation value of an input Hamiltonian.
This cost function is useful for a range of problems including VQE and QAOA.
Args:
ansatz (callable): The ansatz for the circuit before the final measurement step.
Expand Down Expand Up @@ -382,7 +384,7 @@ class VQECost:
**Example:**
To construct a ``VQECost`` cost function, we require a Hamiltonian to measure, and an ansatz
To construct an ``ExpvalCost`` cost function, we require a Hamiltonian to measure, and an ansatz
for our variational circuit.
We can construct a Hamiltonian manually,
Expand All @@ -404,7 +406,7 @@ class VQECost:
>>> ansatz = qml.templates.StronglyEntanglingLayers
>>> dev = qml.device("default.qubit", wires=4)
>>> cost = qml.VQECost(ansatz, H, dev, interface="torch")
>>> cost = qml.ExpvalCost(ansatz, H, dev, interface="torch")
>>> params = torch.rand([2, 4, 3])
>>> cost(params)
tensor(-0.2316, dtype=torch.float64)
Expand All @@ -430,8 +432,8 @@ class VQECost:
dev = qml.device("default.qubit", wires=2)
ansatz = qml.templates.StronglyEntanglingLayers
cost_opt = qml.VQECost(ansatz, H, dev, optimize=True)
cost_no_opt = qml.VQECost(ansatz, H, dev, optimize=False)
cost_opt = qml.ExpvalCost(ansatz, H, dev, optimize=True)
cost_no_opt = qml.ExpvalCost(ansatz, H, dev, optimize=False)
params = qml.init.strong_ent_layers_uniform(3, 2)
Expand Down Expand Up @@ -462,7 +464,7 @@ def __init__(
coeffs, observables = hamiltonian.terms

self.hamiltonian = hamiltonian
"""Hamiltonian: the hamiltonian defining the VQE problem."""
"""Hamiltonian: the input Hamiltonian."""

self.qnodes = None
"""QNodeCollection: The QNodes to be evaluated. Each QNode corresponds to the expectation
Expand Down Expand Up @@ -527,7 +529,23 @@ def metric_tensor(self, args, kwargs=None, diag_approx=False, only_construct=Fal
"optimized observables. Set the argument optimize=False to obtain "
"the metric tensor."
)
# We know that for VQE, all the qnodes share the same ansatz so we select the first
# all the qnodes share the same ansatz so we select the first
return self.qnodes.qnodes[0].metric_tensor(
args=args, kwargs=kwargs, diag_approx=diag_approx, only_construct=only_construct
)


class VQECost(ExpvalCost):
"""Create a cost function that gives the expectation value of an input Hamiltonian.
.. warning::
Use of :class:`~.VQECost` is deprecated and should be replaced with
:class:`~.ExpvalCost`.
"""

def __init__(self, *args, **kwargs):
warnings.warn(
"Use of VQECost is deprecated and should be replaced with ExpvalCost",
DeprecationWarning,
)
super().__init__(*args, **kwargs)
8 changes: 4 additions & 4 deletions qchem/tests/test_convert_observable.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ def test_not_xyz_terms_to_qubit_operator():
def test_integration_observable_to_vqe_cost(
monkeypatch, mol_name, terms_ref, expected_cost, custom_wires, tol
):
r"""Test if `convert_observable()` in qchem integrates with `VQECost()` in pennylane"""
r"""Test if `convert_observable()` in qchem integrates with `ExpvalCost()` in pennylane"""

qOp = QubitOperator()
if terms_ref is not None:
Expand All @@ -375,7 +375,7 @@ def dummy_ansatz(phis, wires):
for phi, w in zip(phis, wires):
qml.RX(phi, wires=w)

dummy_cost = qml.VQECost(dummy_ansatz, vqe_observable, dev)
dummy_cost = qml.ExpvalCost(dummy_ansatz, vqe_observable, dev)
params = [0.1 * i for i in range(num_qubits)]
res = dummy_cost(params)

Expand All @@ -397,7 +397,7 @@ def test_integration_mol_file_to_vqe_cost(
name, core, active, mapping, expected_cost, custom_wires, tol
):
r"""Test if the output of `decompose()` works with `convert_observable()`
to generate `VQECost()`"""
to generate `ExpvalCost()`"""

ref_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test_ref_files")
hf_file = os.path.join(ref_dir, name)
Expand All @@ -424,7 +424,7 @@ def dummy_ansatz(phis, wires):

phis = np.load(os.path.join(ref_dir, "dummy_ansatz_parameters.npy"))

dummy_cost = qml.VQECost(dummy_ansatz, vqe_hamiltonian, dev)
dummy_cost = qml.ExpvalCost(dummy_ansatz, vqe_hamiltonian, dev)
res = dummy_cost(phis)

assert np.abs(res - expected_cost) < tol["atol"]
Expand Down
6 changes: 3 additions & 3 deletions tests/test_optimize_qng.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def cost(a):
params = 0.5

with pytest.raises(
ValueError, match="The objective function must either be encoded as a single QNode or a VQECost object"
ValueError, match="The objective function must either be encoded as a single QNode or an ExpvalCost object"
):
opt.step(cost, params)

Expand Down Expand Up @@ -143,7 +143,7 @@ def gradient(params):
assert np.allclose(cost_fn(theta), -1.41421356, atol=tol, rtol=0)

def test_single_qubit_vqe_using_vqecost(self, tol):
"""Test single-qubit VQE using VQECost
"""Test single-qubit VQE using ExpvalCost
has the correct QNG value every step, the correct parameter updates,
and correct cost after 200 steps"""
dev = qml.device("default.qubit", wires=1)
Expand All @@ -160,7 +160,7 @@ def circuit(params, wires=0):

h = qml.Hamiltonian(coeffs=coeffs, observables=obs_list)

cost_fn = qml.VQECost(ansatz=circuit, hamiltonian=h, device=dev)
cost_fn = qml.ExpvalCost(ansatz=circuit, hamiltonian=h, device=dev)

def gradient(params):
"""Returns the gradient"""
Expand Down
2 changes: 1 addition & 1 deletion tests/test_qaoa.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ def circuit(params, **kwargs):

# Defines the device and the QAOA cost function
dev = qml.device('default.qubit', wires=len(wires))
cost_function = qml.VQECost(circuit, cost_h, dev)
cost_function = qml.ExpvalCost(circuit, cost_h, dev)

res = cost_function([[1, 1], [1, 1]])
expected = -1.8260274380964299
Expand Down
Loading

0 comments on commit 02f106c

Please sign in to comment.