-
Notifications
You must be signed in to change notification settings - Fork 575
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
Changes from all commits
fef9b5e
984f4e4
acccb5c
03e1543
069d379
932c561
dedba09
8886d60
8df2bca
620eb6d
9446b64
9f0f90c
c2dc97b
8b164c5
f792997
92ce647
c4643bb
11c21da
2992383
7fb6d18
729d9a8
8127663
b5d590a
a2ceaec
443530f
5cc316b
033f036
ef1b32d
470c3c3
caa80cd
6387e7e
68678fc
48a1f1d
fe8da5c
b34f76a
2f8b013
f90226b
97cc6b9
09a1efb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,3 +17,4 @@ | |
|
||
from .mixers import * | ||
from .cost import * | ||
from .layers import * |
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): | ||
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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
||
|
||
|
@@ -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]), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, definitely! There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 :) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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