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 all 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 *
162 changes: 162 additions & 0 deletions pennylane/qaoa/layers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# 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
(:class:`~.PauliZ` and :class:`~.Identity`).

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 not isinstance(i, Tensor) else i
for j in i.obs:
if j.name not in ("PauliZ", "Identity"):
val = False
break

return val


def cost_layer(gamma, hamiltonian):
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 it is already an abuse of style, but the other templates use the "CapitalizedWords" class-like case style (even though they are also functions). Is there a motivation for breaking this tradition here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a good point! @ixfoduap @josh146 what do you think?

Copy link
Contributor Author

@Lucaman99 Lucaman99 Aug 7, 2020

Choose a reason for hiding this comment

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

@trbromley I talked to Josh, and he is Ok with either. @ixfoduap do you have a preference?

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the suggestion Tom! I view this more as a function inside qaoa than a template, so I prefer not using capitalized naming here

r"""Applies the QAOA cost layer corresponding to a cost Hamiltonian.

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:
gamma (int or float): The variational parameter passed into the cost layer
hamiltonian (.Hamiltonian): The cost Hamiltonian

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

.. UsageDetails::

We first define a cost Hamiltonian:

.. code-block:: python3

from pennylane import qaoa
import pennylane as qml

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

We can then pass it into ``qaoa.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, cost_h)

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

which gives us a circuit of the form:

>>> circuit(0.5)
>>> print(circuit.draw())
0: ──H──RZ(-1.0)──╭RZ(-1.0)──┤ ⟨Z⟩
1: ──H────────────╰RZ(-1.0)──┤ ⟨Z⟩

"""
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

qml.templates.ApproxTimeEvolution(hamiltonian, gamma, 1)
Lucaman99 marked this conversation as resolved.
Show resolved Hide resolved


def mixer_layer(alpha, hamiltonian):
r"""Applies the QAOA mixer layer corresponding to a mixer Hamiltonian.

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:
alpha (int or float): The variational parameter passed into the mixer layer
hamiltonian (.Hamiltonian): The mixer Hamiltonian

.. UsageDetails::

We first define a mixer Hamiltonian:

.. code-block:: python3

from pennylane import qaoa
import pennylane as qml

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

We can then pass it into ``qaoa.mixer_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)

qaoa.mixer_layer(alpha, mixer_h)

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

which gives us a circuit of the form:

>>> circuit(0.5)
>>> print(circuit.draw())
0: ──H──RZ(-1.0)──H──H──╭RZ(-1.0)──H──┤ ⟨Z⟩
1: ──H──────────────────╰RZ(-1.0)──H──┤ ⟨Z⟩

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

qml.templates.ApproxTimeEvolution(hamiltonian, alpha, 1)
2 changes: 1 addition & 1 deletion pennylane/templates/subroutines/approx_time_evolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
r"""
Contains the ``TimeEvolution`` template.
Contains the ``ApproxTimeEvolution`` template.
"""
# pylint: disable-msg=too-many-branches,too-many-arguments,protected-access
import pennylane as qml
Expand Down
101 changes: 101 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 @@ -208,3 +209,103 @@ def test_maxcut_output(self, graph, cost_hamiltonian, mixer_hamiltonian):

assert decompose_hamiltonian(cost_hamiltonian) == decompose_hamiltonian(cost_h)
assert decompose_hamiltonian(mixer_hamiltonian) == decompose_hamiltonian(mixer_h)


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, match=r"hamiltonian must be of type pennylane.Hamiltonian"):
qaoa.mixer_layer(0.1, hamiltonian)

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, match=r"hamiltonian must be of type pennylane.Hamiltonian"):
qaoa.cost_layer(0.1, hamiltonian)

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

with pytest.raises(ValueError, match=r"hamiltonian must be written only in terms of PauliZ and Identity gates"):
qaoa.cost_layer(0.1, hamiltonian)

@pytest.mark.parametrize(
("mixer", "gates"),
[
[
qml.Hamiltonian([1, 1], [qml.PauliX(0), qml.PauliX(1)]),
[qml.PauliRot(2, "X", wires=[0]), qml.PauliRot(2, "X", wires=[1])]
],
[
qaoa.xy_mixer(Graph([(0, 1), (1, 2), (2, 0)])),
[qml.PauliRot(1, "XX", wires=[0, 1]), qml.PauliRot(1, "YY", wires=[0, 1]),
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice, I never saw this class before! Maybe we need to add it to https://pennylane.readthedocs.io/en/latest/introduction/operations.html

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, definitely! MultiRZ should probably be added as well

Copy link
Member

Choose a reason for hiding this comment

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

@trbromley @Lucaman99 Let's make sure that the documentation is fully up to date next week during the feature freeze :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@josh146 Sounds good!

qml.PauliRot(1, "XX", wires=[0, 2]), qml.PauliRot(1, "YY", wires=[0, 2]),
qml.PauliRot(1, "XX", wires=[1, 2]), qml.PauliRot(1, "YY", wires=[1, 2])]
]
]
)
def test_mixer_layer_output(self, mixer, gates):
"""Tests that the gates of the mixer layer are correct"""

alpha = 1

with qml._queuing.OperationRecorder() as rec:
qaoa.mixer_layer(alpha, mixer)

for i, j in zip(rec.operations, gates):

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

assert prep == target

@pytest.mark.parametrize(
("cost", "gates"),
[
[
qml.Hamiltonian([1, 1], [qml.PauliZ(0), qml.PauliZ(1)]),
[qml.PauliRot(2, "Z", wires=[0]), qml.PauliRot(2, "Z", wires=[1])]
],
[
qaoa.maxcut(Graph([(0, 1), (1, 2), (2, 0)]))[0],
[qml.PauliRot(1, "ZZ", wires=[0, 1]),
qml.PauliRot(1, "ZZ", wires=[0, 2]),
qml.PauliRot(1, "ZZ", wires=[1, 2])]
]
]
)
def test_cost_layer_output(self, cost, gates):
"""Tests that the gates of the cost layer is correct"""

gamma = 1

with qml._queuing.OperationRecorder() as rec:
qaoa.cost_layer(gamma, cost)

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

assert prep == target