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

[QAOA] Adds Cost and Mixer Layers #720

Merged
merged 39 commits into from
Aug 7, 2020
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
fef9b5e
maxcut
Lucaman99 Jul 22, 2020
984f4e4
edits
Lucaman99 Jul 22, 2020
acccb5c
stuff
Lucaman99 Jul 22, 2020
03e1543
changes
Lucaman99 Jul 22, 2020
069d379
stuff
Lucaman99 Jul 24, 2020
932c561
changes
Lucaman99 Jul 24, 2020
dedba09
edits
Lucaman99 Jul 24, 2020
8886d60
edit
Lucaman99 Jul 24, 2020
8df2bca
changes
Lucaman99 Jul 27, 2020
620eb6d
edit
Lucaman99 Jul 27, 2020
9446b64
changes
Lucaman99 Jul 27, 2020
9f0f90c
more changes
Lucaman99 Jul 27, 2020
c2dc97b
edit
Lucaman99 Jul 27, 2020
8b164c5
wires
Lucaman99 Jul 30, 2020
f792997
Merge branch 'master' into layers
Lucaman99 Jul 30, 2020
92ce647
small change
Lucaman99 Jul 30, 2020
c4643bb
changes
Lucaman99 Jul 30, 2020
11c21da
final
Lucaman99 Jul 30, 2020
2992383
edits
Lucaman99 Jul 30, 2020
7fb6d18
changes
Lucaman99 Jul 30, 2020
729d9a8
changes
Lucaman99 Jul 31, 2020
8127663
stuff
Lucaman99 Jul 31, 2020
b5d590a
change
Lucaman99 Jul 31, 2020
a2ceaec
remove time evolution
Lucaman99 Aug 5, 2020
443530f
Merge branch 'master' into layers
Lucaman99 Aug 5, 2020
5cc316b
Apply suggestions from code review
Lucaman99 Aug 5, 2020
033f036
Merge branch 'master' into layers
Lucaman99 Aug 5, 2020
ef1b32d
Apply suggestions from code review
Lucaman99 Aug 6, 2020
470c3c3
code review
Lucaman99 Aug 6, 2020
caa80cd
test changes
Lucaman99 Aug 6, 2020
6387e7e
Merge branch 'master' into layers
Lucaman99 Aug 6, 2020
68678fc
changes
Lucaman99 Aug 6, 2020
48a1f1d
Merge branch 'master' into layers
Lucaman99 Aug 6, 2020
fe8da5c
test changes
Lucaman99 Aug 6, 2020
b34f76a
Update pennylane/qaoa/layers.py
Lucaman99 Aug 6, 2020
2f8b013
Update pennylane/qaoa/layers.py
Lucaman99 Aug 6, 2020
f90226b
Merge branch 'master' into layers
Lucaman99 Aug 7, 2020
97cc6b9
small change
Lucaman99 Aug 7, 2020
09a1efb
changes
Lucaman99 Aug 7, 2020
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
1 change: 1 addition & 0 deletions pennylane/qaoa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@

from .mixers import *
from .cost import *
from .layers import *
166 changes: 166 additions & 0 deletions pennylane/qaoa/layers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Copyright 2018-2020 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Methods that define cost and mixer layers for use in QAOA workflows.
"""
import pennylane as qml
from pennylane.operation import Tensor


def _diagonal_terms(hamiltonian):
r"""Checks if all terms in a Hamiltonian are products of diagonal Pauli gates (PauliZ and Identity).
Lucaman99 marked this conversation as resolved.
Show resolved Hide resolved

Args:
hamiltonian (.Hamiltonian): The Hamiltonian being checked

Returns:
bool: ``True`` if all terms are products of diagonal Pauli gates, ``False`` otherwise
"""
val = True

for i in hamiltonian.ops:
i = Tensor(i) if isinstance(i.name, str) else i
Lucaman99 marked this conversation as resolved.
Show resolved Hide resolved
for j in i.obs:
if j.name != "PauliZ" and j.name != "Identity":
Lucaman99 marked this conversation as resolved.
Show resolved Hide resolved
val = False
Lucaman99 marked this conversation as resolved.
Show resolved Hide resolved

return val


def cost_layer(hamiltonian):
Copy link
Contributor

@ixfoduap ixfoduap Aug 5, 2020

Choose a reason for hiding this comment

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

I find it a bit strange at first sight that the layer does not take gamma as an input. I understand that we call ApproxTimeEvolution as a lambda function, but I'm wondering if this is the best choice.

Alternatively we could use def cost_layer(hamiltonian, gamma): and then use

from pennylane.qaoa import cost_layer
@qml.qnode(dev)
def circuit(gamma):
    cost_layer(cost_h, gamma)

I'm not sure which approach is best. Curious what others think?

Copy link
Member

Choose a reason for hiding this comment

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

I am ambivalent 🤔

I see pros and cons for both approaches:

  • In the cost_layer(cost_h, gamma) approach, cost_layer is essentially a template, and must always be called within a QNode.

  • In the cost = cost_layer(cosh_h) approach, cost_layer is more like a 'template generating function'. The returned cost function can only be used within a QNode, like a template.

Copy link
Member

Choose a reason for hiding this comment

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

Probably lean more towards cost_layer(cost_h, gamma), unless you see a use-case for having the 'template generating' behaviour.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ixfoduap @josh146 Sounds good, I'll make the change!

r"""Returns the QAOA cost layer corresponding to a cost Hamiltonian.
Lucaman99 marked this conversation as resolved.
Show resolved Hide resolved

For the cost Hamiltonian :math:`H_C`, this is defined as the following unitary:

.. math:: U_C \ = \ e^{-i \gamma H_C}

where :math:`\gamma` is a variational parameter.

Args:
hamiltonian (.Hamiltonian): The cost Hamiltonian

Raises:
ValueError: if the terms of the supplied cost Hamiltonian are not exclusively products of diagonal Pauli gates

.. UsageDetails::

To define a cost layer, one must define a cost Hamiltonian
Lucaman99 marked this conversation as resolved.
Show resolved Hide resolved
and pass it into ``cost_layer``:

.. code-block:: python
Lucaman99 marked this conversation as resolved.
Show resolved Hide resolved

from pennylane import qaoa
import pennylane as qml

cost_h = qml.Hamiltonian([1, 1], [qml.PauliZ(0), qml.PauliZ(0) @ qml.PauliZ(1)])
cost_layer = qaoa.cost_layer(cost_h)

We can then use the cost layer within a quantum circuit:

.. code-block:: python

dev = qml.device('default.qubit', wires=2)

@qml.qnode(dev)
def circuit(gamma):

for i in range(2):
qml.Hadamard(wires=i)

cost_layer(gamma)

return [qml.expval(qml.PauliZ(wires=i)) for i in range(2)]

which gives us a circuit of the form:

>>> circuit(0.5)

.. code-block:: none

0: ──H──RZ(-1.0)──╭RZ(-1.0)──┤ ⟨Z⟩
1: ──H────────────╰RZ(-1.0)──┤ ⟨Z⟩
Lucaman99 marked this conversation as resolved.
Show resolved Hide resolved

"""
if not isinstance(hamiltonian, qml.Hamiltonian):
raise ValueError(
"hamiltonian must be of type pennylane.Hamiltonian, got {}".format(
type(hamiltonian).__name__
)
)

if not _diagonal_terms(hamiltonian):
raise ValueError("hamiltonian must be written only in terms of PauliZ and Identity gates")
Lucaman99 marked this conversation as resolved.
Show resolved Hide resolved

return lambda gamma: qml.templates.ApproxTimeEvolution(hamiltonian, gamma, 1)


def mixer_layer(hamiltonian):
r"""Returns the QAOA cost layer corresponding to a mixer Hamiltonian.
Lucaman99 marked this conversation as resolved.
Show resolved Hide resolved

For a mixer Hamiltonian :math:`H_M`, this is defined as the following unitary:

.. math:: U_M \ = \ e^{-i \alpha H_M}

where :math:`\alpha` is a variational parameter.

Args:
hamiltonian (.Hamiltonian): The mixer Hamiltonian

.. UsageDetails::

To define a mixer layer, one must define a mixer Hamiltonian
and pass it into ``mixer_layer``:
Lucaman99 marked this conversation as resolved.
Show resolved Hide resolved

.. code-block:: python
Lucaman99 marked this conversation as resolved.
Show resolved Hide resolved

from pennylane import qaoa
import pennylane as qml

mixer_h = qml.Hamiltonian([1, 1], [qml.PauliX(0), qml.PauliX(0) @ qml.PauliX(1)])
mixer_layer = qaoa.mixer_layer(mixer_h)

We can then use the cost layer within a quantum circuit:

.. code-block:: python

dev = qml.device('default.qubit', wires=2)

@qml.qnode(dev)
def circuit(alpha):

for i in range(2):
qml.Hadamard(wires=i)

mixer_layer(alpha)

return [qml.expval(qml.PauliZ(wires=i)) for i in range(2)]

which gives us a circuit of the form:

>>> circuit(0.5)

.. code-block:: none

Lucaman99 marked this conversation as resolved.
Show resolved Hide resolved
0: ──H──RZ(-1.0)──H──H──╭RZ(-1.0)──H──┤ ⟨Z⟩
1: ──H──────────────────╰RZ(-1.0)──H──┤ ⟨Z⟩
Lucaman99 marked this conversation as resolved.
Show resolved Hide resolved

"""
if not isinstance(hamiltonian, qml.Hamiltonian):
raise ValueError(
"hamiltonian must be of type pennylane.Hamiltonian, got {}".format(
type(hamiltonian).__name__
)
)

return lambda alpha: qml.templates.ApproxTimeEvolution(hamiltonian, alpha, 1)
89 changes: 89 additions & 0 deletions tests/test_qaoa.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import pennylane as qml
from pennylane import qaoa
from networkx import Graph
from pennylane.templates import ApproxTimeEvolution
from pennylane.wires import Wires


Expand Down Expand Up @@ -199,3 +200,91 @@ def test_maxcut_output(self, graph, target_hamiltonian):
assert cost_coeffs == target_coeffs
assert cost_ops == target_ops
assert cost_wires == target_wires


class TestUtils:
"""Tests that the utility functions are working properly"""

@pytest.mark.parametrize(
("hamiltonian", "value"),
(
(qml.Hamiltonian([1, 1], [qml.PauliZ(0), qml.PauliZ(1)]), True),
(qml.Hamiltonian([1, 1], [qml.PauliX(0), qml.PauliZ(1)]), False),
(qml.Hamiltonian([1, 1], [qml.PauliZ(0) @ qml.Identity(1), qml.PauliZ(1)]), True),
(qml.Hamiltonian([1, 1], [qml.PauliZ(0), qml.PauliX(0) @ qml.PauliZ(1)]), False),
),
)
def test_diagonal_terms(self, hamiltonian, value):
assert qaoa.layers._diagonal_terms(hamiltonian) == value


class TestLayers:
"""Tests that the cost and mixer layers are being constructed properly"""

def test_mixer_layer_errors(self):
"""Tests that the mixer layer is throwing the correct errors"""

hamiltonian = [[1, 1], [1, 1]]

with pytest.raises(ValueError) as info:
output = qaoa.mixer_layer(hamiltonian)

assert "hamiltonian must be of type pennylane.Hamiltonian, got list" in str(info.value)

def test_cost_layer_errors(self):
"""Tests that the cost layer is throwing the correct errors"""

hamiltonian = [[1, 1], [1, 1]]

with pytest.raises(ValueError) as info:
output = qaoa.cost_layer(hamiltonian)

assert "hamiltonian must be of type pennylane.Hamiltonian, got list" in str(info.value)

hamiltonian = qml.Hamiltonian([1, 1], [qml.PauliZ(0), qml.PauliX(1)])

with pytest.raises(ValueError) as info:
output = qaoa.cost_layer(hamiltonian)

assert "hamiltonian must be written only in terms of PauliZ and Identity gates" in str(
info.value
)

def test_mixer_layer_output(self):
"""Tests that the gates of the mixer layer is correct"""
Lucaman99 marked this conversation as resolved.
Show resolved Hide resolved

alpha = 1
hamiltonian = qml.Hamiltonian([1, 1], [qml.PauliX(0), qml.PauliX(1)])
Copy link
Contributor

Choose a reason for hiding this comment

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

As far as I understand, this is testing that the layers are implementing the same sequence of gates as ApproxTimeEvolution, which I'm not sure is the best way to test since ApproxTimeEvolution may be giving the wrong answers, i.e., these tests don't add much more than the tests for ApproxTimeEvolution. Not sure if this is ok...

Maybe it's good to test an output circuit explicitly, without relying on ApproxTimeEvolution

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Made these changes 🙂

mixer = qaoa.mixer_layer(hamiltonian)

with qml._queuing.OperationRecorder() as rec1:
mixer(alpha)

with qml._queuing.OperationRecorder() as rec2:
ApproxTimeEvolution(hamiltonian, 1, 1)

for i, j in zip(rec1.operations, rec2.operations):

prep = [i.name, i.parameters, i.wires]
target = [j.name, j.parameters, j.wires]

assert prep == target

def test_cost_layer_output(self):
"""Tests that the gates of the cost layer is correct"""

gamma = 1
hamiltonian = qml.Hamiltonian([1, 1], [qml.PauliZ(0), qml.PauliZ(1)])
cost = qaoa.cost_layer(hamiltonian)

with qml._queuing.OperationRecorder() as rec1:
cost(gamma)

with qml._queuing.OperationRecorder() as rec2:
ApproxTimeEvolution(hamiltonian, 1, 1)

for i, j in zip(rec1.operations, rec2.operations):
prep = [i.name, i.parameters, i.wires]
target = [j.name, j.parameters, j.wires]

assert prep == target