diff --git a/doc/code/qml_qaoa.rst b/doc/code/qml_qaoa.rst new file mode 100644 index 00000000000..3a0c7f8776e --- /dev/null +++ b/doc/code/qml_qaoa.rst @@ -0,0 +1,9 @@ +qml.qaoa +======== + +.. currentmodule:: pennylane.qaoa + +.. automodapi:: pennylane.qaoa + :no-heading: + :no-inheritance-diagram: + :no-inherited-members: \ No newline at end of file diff --git a/doc/index.rst b/doc/index.rst index 60691f9ce28..619bae2e9a3 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -206,6 +206,7 @@ PennyLane is **free** and **open source**, released under the Apache License, Ve code/qml_interfaces code/qml_operation code/qml_plugins + code/qml_qaoa code/qml_qchem code/qml_qnn code/qml_qnodes diff --git a/pennylane/__init__.py b/pennylane/__init__.py index 171eea42465..118de917d8e 100644 --- a/pennylane/__init__.py +++ b/pennylane/__init__.py @@ -30,6 +30,7 @@ import pennylane.init import pennylane.templates import pennylane.qnn +import pennylane.qaoa as qaoa from pennylane.templates import template, broadcast from pennylane.about import about from pennylane.vqe import Hamiltonian, VQECost @@ -47,7 +48,6 @@ from ._version import __version__ from .io import * - # Look for an existing configuration file default_config = Configuration("config.toml") diff --git a/pennylane/qaoa/__init__.py b/pennylane/qaoa/__init__.py new file mode 100644 index 00000000000..6a6bfd0c371 --- /dev/null +++ b/pennylane/qaoa/__init__.py @@ -0,0 +1,18 @@ +# 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. +r""" +This module contains functionality to construct QAOA workflows in PennyLane. +""" + +from .mixers import * diff --git a/pennylane/qaoa/mixers.py b/pennylane/qaoa/mixers.py new file mode 100644 index 00000000000..49219ccaec2 --- /dev/null +++ b/pennylane/qaoa/mixers.py @@ -0,0 +1,114 @@ +# 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. +r""" +This file contains built-in functions for constructing QAOA mixer Hamiltonians. +""" +import networkx as nx +import pennylane as qml +from pennylane.wires import Wires + + +def x_mixer(wires): + r"""Creates a basic Pauli-X mixer Hamiltonian. + + This Hamiltonian is defined as: + + .. math:: H_M \ = \ \displaystyle\sum_{i} X_{i}, + + where :math:`i` ranges over all wires, and :math:`X_i` + denotes the Pauli-X operator on the :math:`i`-th wire. + + This is mixer is used in *A Quantum Approximate Optimization Algorithm* + by Edward Farhi, Jeffrey Goldstone, Sam Gutmann [`arXiv:1411.4028 `__]. + + + Args: + wires (Iterable or Wires): The wires on which the Hamiltonian is applied + + Returns: + Hamiltonian: Mixer Hamiltonian + + .. UsageDetails:: + + The mixer Hamiltonian can be called as follows: + + .. code-block:: python + + from pennylane import qaoa + + wires = range(3) + mixer_h = qaoa.x_mixer(wires) + + >>> print(mixer_h) + (1.0) [X0] + (1.0) [X1] + (1.0) [X2] + """ + + wires = Wires(wires) + + coeffs = [1 for w in wires] + obs = [qml.PauliX(w) for w in wires] + + return qml.Hamiltonian(coeffs, obs) + + +def xy_mixer(graph): + r"""Creates a generalized SWAP/XY mixer Hamiltonian. + + This mixer Hamiltonian is defined as: + + .. math:: H_M \ = \ \frac{1}{2} \displaystyle\sum_{(i, j) \in E(G)} X_i X_j \ + \ Y_i Y_j, + + for some graph :math:`G`. :math:`X_i` and :math:`Y_i` denote the Pauli-X and Pauli-Y operators on the :math:`i`-th + wire respectively. + + This mixer was introduced in *From the Quantum Approximate Optimization Algorithm + to a Quantum Alternating Operator Ansatz* by Stuart Hadfield, Zhihui Wang, Bryan O'Gorman, + Eleanor G. Rieffel, Davide Venturelli, and Rupak Biswas [`arXiv:1709.03489 `__]. + + Args: + graph (nx.Graph): A graph defining the pairs of wires on which each term of the Hamiltonian acts. + + Returns: + Hamiltonian: Mixer Hamiltonian + + .. UsageDetails:: + + The mixer Hamiltonian can be called as follows: + + .. code-block:: python + + from pennylane import qaoa + from networkx import Graph + + graph = Graph([(0, 1), (1, 2)]) + mixer_h = qaoa.xy_mixer(graph) + + >>> print(mixer_h) + (0.5) [X0 X1] + (0.5) [Y0 Y1] + (0.5) [X1 X2] + (0.5) [Y1 Y2] + """ + + if not isinstance(graph, nx.Graph): + raise ValueError( + "Input graph must be a nx.Graph object, got {}".format(type(graph).__name__) + ) + + edges = graph.edges + coeffs = 2 * [0.5 for e in edges] + + obs = [] + for node1, node2 in edges: + obs.append(qml.PauliX(node1) @ qml.PauliX(node2)) + obs.append(qml.PauliY(node1) @ qml.PauliY(node2)) + + return qml.Hamiltonian(coeffs, obs) diff --git a/tests/test_qaoa.py b/tests/test_qaoa.py new file mode 100644 index 00000000000..7b1168c1124 --- /dev/null +++ b/tests/test_qaoa.py @@ -0,0 +1,136 @@ +# 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. +""" +Unit tests for the :mod:`pennylane.qaoa` submodule. +""" +import pytest +import numpy as np +import pennylane as qml +from pennylane import qaoa +from networkx import Graph + + +##################################################### + +graph = Graph() +graph.add_nodes_from([0, 1, 2]) +graph.add_edges_from([(0, 1), (1, 2)]) + +non_consecutive_graph = Graph([(0, 4), (3, 4), (2, 1), (2, 0)]) + + +class TestMixerHamiltonians: + """Tests that the mixer Hamiltonians are being generated correctly""" + + def test_x_mixer_output(self): + """Tests that the output of the Pauli-X mixer is correct""" + + wires = range(4) + mixer_hamiltonian = qaoa.x_mixer(wires) + + mixer_coeffs = mixer_hamiltonian.coeffs + mixer_ops = [i.name for i in mixer_hamiltonian.ops] + mixer_wires = [i.wires[0] for i in mixer_hamiltonian.ops] + + assert mixer_coeffs == [1, 1, 1, 1] + assert mixer_ops == ["PauliX", "PauliX", "PauliX", "PauliX"] + assert mixer_wires == [0, 1, 2, 3] + + def test_xy_mixer_type_error(self): + """Tests that the XY mixer throws the correct error""" + + graph = [(0, 1), (1, 2)] + + with pytest.raises(ValueError, match=r"Input graph must be a nx.Graph object, got list"): + output = qaoa.xy_mixer(graph) + + @pytest.mark.parametrize( + ("graph", "target_hamiltonian"), + [ + ( + Graph([(0, 1), (1, 2), (2, 3)]), + qml.Hamiltonian( + [0.5, 0.5, 0.5, 0.5, 0.5, 0.5], + [ + qml.PauliX(0) @ qml.PauliX(1), + qml.PauliY(0) @ qml.PauliY(1), + qml.PauliX(1) @ qml.PauliX(2), + qml.PauliY(1) @ qml.PauliY(2), + qml.PauliX(2) @ qml.PauliX(3), + qml.PauliY(2) @ qml.PauliY(3), + ], + ), + ), + ( + Graph((np.array([0, 1]), np.array([1, 2]), np.array([2, 0]))), + qml.Hamiltonian( + [0.5, 0.5, 0.5, 0.5, 0.5, 0.5], + [ + qml.PauliX(0) @ qml.PauliX(1), + qml.PauliY(0) @ qml.PauliY(1), + qml.PauliX(0) @ qml.PauliX(2), + qml.PauliY(0) @ qml.PauliY(2), + qml.PauliX(1) @ qml.PauliX(2), + qml.PauliY(1) @ qml.PauliY(2), + ], + ), + ), + ( + graph, + qml.Hamiltonian( + [0.5, 0.5, 0.5, 0.5], + [ + qml.PauliX(0) @ qml.PauliX(1), + qml.PauliY(0) @ qml.PauliY(1), + qml.PauliX(1) @ qml.PauliX(2), + qml.PauliY(1) @ qml.PauliY(2), + ], + ), + ), + ( + non_consecutive_graph, + qml.Hamiltonian( + [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5], + [ + qml.PauliX(0) @ qml.PauliX(4), + qml.PauliY(0) @ qml.PauliY(4), + qml.PauliX(0) @ qml.PauliX(2), + qml.PauliY(0) @ qml.PauliY(2), + qml.PauliX(4) @ qml.PauliX(3), + qml.PauliY(4) @ qml.PauliY(3), + qml.PauliX(2) @ qml.PauliX(1), + qml.PauliY(2) @ qml.PauliY(1), + ], + ), + ), + ], + ) + def test_xy_mixer_output(self, graph, target_hamiltonian): + """Tests that the output of the XY mixer is correct""" + + print(graph.edges) + + mixer_hamiltonian = qaoa.xy_mixer(graph) + + mixer_coeffs = mixer_hamiltonian.coeffs + mixer_ops = [i.name for i in mixer_hamiltonian.ops] + mixer_wires = [i.wires for i in mixer_hamiltonian.ops] + + target_coeffs = target_hamiltonian.coeffs + target_ops = [i.name for i in target_hamiltonian.ops] + target_wires = [i.wires for i in target_hamiltonian.ops] + + assert mixer_coeffs == target_coeffs + assert mixer_ops == target_ops + assert mixer_wires == target_wires