Skip to content

Commit

Permalink
Update Hamiltonian data storing and result of ExpvalCost for trivial …
Browse files Browse the repository at this point in the history
…Hamiltonians (#1082)

* convert coeffs and ops to lists internally, add edge cases with different typed args

* remove test arg

* comment update

* changelog

* zero Hamiltonian cases for subtraction and scalar mul

* Hamiltonian simplify to zero

* ExpvalCost zero Ham

* Comments, docstring

* zero Ham simplified is zero Ham

* coeffs

* revert modifying simplify()

* Hamiltonian with no coeffs or ops produces 0 expval with ExpvalCost

* changelog

* Update tests/test_vqe.py

* fix

Co-authored-by: Josh Izaac <josh146@gmail.com>
  • Loading branch information
antalszava and josh146 committed Feb 12, 2021
1 parent 6ecd027 commit 12ba516
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 5 deletions.
10 changes: 9 additions & 1 deletion .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@

<h3>Bug fixes</h3>

* 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 bug where inverse operations could not be differentiated
using backpropagation on `default.qubit`.
[(#1072)](https://github.com/PennyLaneAI/pennylane/pull/1072)
Expand All @@ -101,7 +109,7 @@

This release contains contributions from (in alphabetical order):

Thomas Bromley, Josh Izaac, Daniel Polatajko, Chase Roberts, Maria Schuld
Thomas Bromley, Josh Izaac, Daniel Polatajko, Chase Roberts, Maria Schuld, Antal Száva.

# Release 0.14.0 (current release)

Expand Down
8 changes: 6 additions & 2 deletions pennylane/vqe/vqe.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 @@ -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
110 changes: 108 additions & 2 deletions tests/test_vqe.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ def seed():
((0.5, 1.2), (qml.PauliZ(0), qml.PauliZ(1)), [0.5 * 1.0, 1.2 * 1.0]),
]



zero_hamiltonians_with_expvals = [
([], [], [0]),
((0, 0), (qml.PauliZ(0), qml.PauliZ(1)), [0]),
((0,0,0), (qml.PauliX(0) @ qml.Identity(1), qml.PauliX(0), qml.PauliX(1)), [0]),
]

simplify_hamiltonians = [
(
qml.Hamiltonian([1, 1, 1], [qml.PauliX(0) @ qml.Identity(1), qml.PauliX(0), qml.PauliX(1)]),
Expand Down Expand Up @@ -143,6 +151,20 @@ def seed():
[qml.Hermitian(np.array([[1, 0], [0, -1]]), "a"), qml.PauliX("b") @ qml.PauliY(1.3)],
),
),

# Simplifies to zero Hamiltonian
(
qml.Hamiltonian([1, -0.5, -0.5], [qml.PauliX(0) @ qml.Identity(1), qml.PauliX(0), qml.PauliX(0)]),
qml.Hamiltonian([], []),
),
(
qml.Hamiltonian([1, -1], [qml.PauliX(4) @ qml.Identity(0) @ qml.PauliX(1), qml.PauliX(4) @ qml.PauliX(1)]),
qml.Hamiltonian([], []),
),
(
qml.Hamiltonian([0], [qml.Identity(0)]),
qml.Hamiltonian([0], [qml.Identity(0)]),
),
]

equal_hamiltonians = [
Expand Down Expand Up @@ -231,6 +253,15 @@ def seed():
qml.PauliX("b") @ qml.Identity(5),
qml.Hamiltonian([2, 1.2, 0.1], [qml.PauliX("b"), qml.PauliZ(3.1), qml.PauliX(1.6)]),
),

# Case where arguments coeffs and ops to the Hamiltonian are iterables other than lists
(
qml.Hamiltonian((1, 1.2, 0.1), (qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2))),
qml.Hamiltonian(np.array([0.5, 0.3, 1]), np.array([qml.PauliX(0), qml.PauliX(1), qml.PauliX(2)])),
qml.Hamiltonian(
(1.5, 1.2, 1.1, 0.3), np.array([qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2), qml.PauliX(1)])
),
),
]

sub_hamiltonians = [
Expand Down Expand Up @@ -276,6 +307,28 @@ def seed():
qml.PauliX("b") @ qml.Identity(1),
qml.Hamiltonian([1.2, 0.1], [qml.PauliZ(3.1), qml.PauliX(1.6)]),
),

# The result is the zero Hamiltonian
(
qml.Hamiltonian([1, 1.2, 0.1], [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2)]),
qml.Hamiltonian([1, 1.2, 0.1], [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2)]),
qml.Hamiltonian([], []),
),
(
qml.Hamiltonian([1, 2], [qml.PauliX(4), qml.PauliZ(2)]),
qml.Hamiltonian([1, 2], [qml.PauliX(4), qml.PauliZ(2)]),
qml.Hamiltonian([], []),
),


# Case where arguments coeffs and ops to the Hamiltonian are iterables other than lists
(
qml.Hamiltonian((1, 1.2, 0.1), (qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2))),
qml.Hamiltonian(np.array([0.5, 0.3, 1.6]), np.array([qml.PauliX(0), qml.PauliX(1), qml.PauliX(2)])),
qml.Hamiltonian(
(0.5, 1.2, -1.5, -0.3), np.array([qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2), qml.PauliX(1)])
),
),
]

mul_hamiltonians = [
Expand All @@ -300,6 +353,25 @@ def seed():
[qml.Hermitian(np.array([[1, 0], [0, -1]]), "b"), qml.PauliZ(23) @ qml.PauliZ(0)],
),
),

# The result is the zero Hamiltonian
(
0,
qml.Hamiltonian([1], [qml.PauliX(0)]),
qml.Hamiltonian([0], [qml.PauliX(0)]),
),
(
0,
qml.Hamiltonian([1, 1.2, 0.1], [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2)]),
qml.Hamiltonian([0, 0, 0], [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2)]),
),

# Case where arguments coeffs and ops to the Hamiltonian are iterables other than lists
(
3,
qml.Hamiltonian((1.5, 0.5), (qml.PauliX(0), qml.PauliZ(1))),
qml.Hamiltonian(np.array([4.5, 1.5]), np.array([qml.PauliX(0), qml.PauliZ(1)])),
),
]

matmul_hamiltonians = [
Expand Down Expand Up @@ -347,6 +419,21 @@ def seed():
qml.PauliX(2),
qml.Hamiltonian([1, 1], [qml.PauliX(0) @ qml.PauliX(2), qml.PauliZ(1) @ qml.PauliX(2)]),
),

# Case where arguments coeffs and ops to the Hamiltonian are iterables other than lists
(
qml.Hamiltonian((1, 1), (qml.PauliX(0), qml.PauliZ(1))),
qml.Hamiltonian(np.array([0.5, 0.5]), np.array([qml.PauliZ(2), qml.PauliZ(3)])),
qml.Hamiltonian(
(0.5, 0.5, 0.5, 0.5),
np.array([
qml.PauliX(0) @ qml.PauliZ(2),
qml.PauliX(0) @ qml.PauliZ(3),
qml.PauliZ(1) @ qml.PauliZ(2),
qml.PauliZ(1) @ qml.PauliZ(3),
]),
),
),
]

big_hamiltonian_coeffs = np.array(
Expand Down Expand Up @@ -507,7 +594,7 @@ def test_hamiltonian_valid_init(self, coeffs, ops):
"""Tests that the Hamiltonian object is created with
the correct attributes"""
H = qml.vqe.Hamiltonian(coeffs, ops)
assert H.terms == (coeffs, ops)
assert H.terms == (list(coeffs), list(ops))

@pytest.mark.parametrize("coeffs, ops", invalid_hamiltonians)
def test_hamiltonian_invalid_init_exception(self, coeffs, ops):
Expand Down Expand Up @@ -709,7 +796,7 @@ def test_cost_evaluate(self, params, ansatz, coeffs, observables):
assert type(expval(params)) == np.float64
assert np.shape(expval(params)) == () # expval should be scalar

@pytest.mark.parametrize("coeffs, observables, expected", hamiltonians_with_expvals)
@pytest.mark.parametrize("coeffs, observables, expected", hamiltonians_with_expvals + zero_hamiltonians_with_expvals)
def test_cost_expvals(self, coeffs, observables, expected):
"""Tests that the cost function returns correct expectation values"""
dev = qml.device("default.qubit", wires=2)
Expand Down Expand Up @@ -824,6 +911,25 @@ def test_optimize_grad(self):
assert np.allclose(dc, big_hamiltonian_grad)
assert np.allclose(dc2, big_hamiltonian_grad)

@pytest.mark.parametrize('opt', [True, False])
def test_grad_zero_hamiltonian(self, opt):
"""Test that the gradient of ExpvalCost is accessible and correct when using observable
optimization and the autograd interface with a zero Hamiltonian."""
if not qml.tape_mode_active():
pytest.skip("This test is only intended for tape mode")

dev = qml.device("default.qubit", wires=4)
hamiltonian = qml.Hamiltonian([0], [qml.PauliX(0)])

cost = qml.ExpvalCost(
qml.templates.StronglyEntanglingLayers, hamiltonian, dev, optimize=opt, diff_method="parameter-shift"
)

w = qml.init.strong_ent_layers_uniform(2, 4, seed=1967)

dc = qml.grad(cost)(w)
assert np.allclose(dc, 0)

def test_optimize_grad_torch(self, torch_support):
"""Test that the gradient of ExpvalCost is accessible and correct when using observable
optimization and the Torch interface."""
Expand Down

0 comments on commit 12ba516

Please sign in to comment.