Source code for pennylane.qaoa.mixers
# Copyright 2018-2021 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"""
Methods for constructing QAOA mixer Hamiltonians.
"""
import functools
# pylint: disable=unnecessary-lambda-assignment
import itertools
from collections.abc import Iterable
from typing import Union
import networkx as nx
import rustworkx as rx
from pennylane.ops import Identity, LinearCombination, X, Y, Z, prod
from pennylane.wires import Wires
[docs]
def x_mixer(wires: Union[Iterable, 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 <https://arxiv.org/abs/1411.4028>`__].
Args:
wires (Iterable or Wires): The wires on which the Hamiltonian is applied
Returns:
Hamiltonian: Mixer Hamiltonian
**Example**
The mixer Hamiltonian can be called as follows:
>>> from pennylane import qaoa
>>> wires = range(3)
>>> mixer_h = qaoa.x_mixer(wires)
>>> print(mixer_h)
1 * X(0) + 1 * X(1) + 1 * X(2)
"""
wires = Wires(wires)
coeffs = [1 for w in wires]
obs = [X(w) for w in wires]
H = LinearCombination(coeffs, obs)
# store the valuable information that all observables are in one commuting group
H.grouping_indices = [list(range(len(H.ops)))]
return H
[docs]
def xy_mixer(graph: Union[nx.Graph, rx.PyGraph]):
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 `Algorithms 12.2 (2019) <https://doi.org/10.3390/a12020034>`__.
Args:
graph (nx.Graph or rx.PyGraph): A graph defining the collections of wires on which the Hamiltonian acts.
Returns:
Hamiltonian: Mixer Hamiltonian
**Example**
The mixer Hamiltonian can be called as follows:
>>> 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]
>>> import rustworkx as rx
>>> graph = rx.PyGraph()
>>> graph.add_nodes_from([0, 1, 2])
>>> graph.add_edges_from([(0, 1, ""), (1, 2, "")])
>>> mixer_h = 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, rx.PyGraph)):
raise ValueError(
f"Input graph must be a nx.Graph or rx.PyGraph object, got {type(graph).__name__}"
)
is_rx = isinstance(graph, rx.PyGraph)
edges = graph.edge_list() if is_rx else graph.edges
# In RX each node is assigned to an integer index starting from 0;
# thus, we use the following lambda function to get node-values.
get_nvalue = lambda i: graph.nodes()[i] if is_rx else i
coeffs = 2 * [0.5 for e in edges]
obs = []
for node1, node2 in edges:
obs.append(X(get_nvalue(node1)) @ X(get_nvalue(node2)))
obs.append(Y(get_nvalue(node1)) @ Y(get_nvalue(node2)))
return LinearCombination(coeffs, obs)
[docs]
def bit_flip_mixer(graph: Union[nx.Graph, rx.PyGraph], b: int):
r"""Creates a bit-flip mixer Hamiltonian.
This mixer is defined as:
.. math:: H_M \ = \ \displaystyle\sum_{v \in V(G)} \frac{1}{2^{d(v)}} X_{v}
\displaystyle\prod_{w \in N(v)} (\mathbb{I} \ + \ (-1)^b Z_w)
where :math:`V(G)` is the set of vertices of some graph :math:`G`, :math:`d(v)` is the
`degree <https://en.wikipedia.org/wiki/Degree_(graph_theory)>`__ of vertex :math:`v`, and
:math:`N(v)` is the `neighbourhood <https://en.wikipedia.org/wiki/Neighbourhood_(graph_theory)>`__
of vertex :math:`v`. In addition, :math:`Z_v` and :math:`X_v`
are the Pauli-Z and Pauli-X operators on vertex :math:`v`, respectively,
and :math:`\mathbb{I}` is the identity operator.
This mixer was introduced in `Hadfield et al. (2019) <https://doi.org/10.3390/a12020034>`__.
Args:
graph (nx.Graph or rx.PyGraph): A graph defining the collections of wires on which the Hamiltonian acts.
b (int): Either :math:`0` or :math:`1`. When :math:`b=0`, a bit flip is performed on
vertex :math:`v` only when all neighbouring nodes are in state :math:`|0\rangle`.
Alternatively, for :math:`b=1`, a bit flip is performed only when all the neighbours of
:math:`v` are in the state :math:`|1\rangle`.
Returns:
Hamiltonian: Mixer Hamiltonian
**Example**
The mixer Hamiltonian can be called as follows:
>>> from pennylane import qaoa
>>> from networkx import Graph
>>> graph = Graph([(0, 1), (1, 2)])
>>> mixer_h = qaoa.bit_flip_mixer(graph, 0)
>>> mixer_h
(
0.5 * X(0)
+ 0.5 * (X(0) @ Z(1))
+ 0.25 * X(1)
+ 0.25 * (X(1) @ Z(2))
+ 0.25 * (X(1) @ Z(0))
+ 0.25 * (X(1) @ Z(0) @ Z(2))
+ 0.5 * X(2)
+ 0.5 * (X(2) @ Z(1))
)
>>> import rustworkx as rx
>>> graph = rx.PyGraph()
>>> graph.add_nodes_from([0, 1, 2])
>>> graph.add_edges_from([(0, 1, ""), (1, 2, "")])
>>> mixer_h = qaoa.bit_flip_mixer(graph, 0)
>>> print(mixer_h)
(
0.5 * X(0)
+ 0.5 * (X(0) @ Z(1))
+ 0.25 * X(1)
+ 0.25 * (X(1) @ Z(2))
+ 0.25 * (X(1) @ Z(0))
+ 0.25 * (X(1) @ Z(0) @ Z(2))
+ 0.5 * X(2)
+ 0.5 * (X(2) @ Z(1))
)
"""
if not isinstance(graph, (nx.Graph, rx.PyGraph)):
raise ValueError(
f"Input graph must be a nx.Graph or rx.PyGraph object, got {type(graph).__name__}"
)
if b not in [0, 1]:
raise ValueError(f"'b' must be either 0 or 1, got {b}")
sign = 1 if b == 0 else -1
coeffs = []
terms = []
is_rx = isinstance(graph, rx.PyGraph)
graph_nodes = graph.node_indexes() if is_rx else graph.nodes
# In RX each node is assigned to an integer index starting from 0;
# thus, we use the following lambda function to get node-values.
get_nvalue = lambda i: graph.nodes()[i] if is_rx else i
for i in graph_nodes:
neighbours = sorted(graph.neighbors(i)) if is_rx else list(graph.neighbors(i))
degree = len(neighbours)
n_terms = [[X(get_nvalue(i))]] + [
[Identity(get_nvalue(n)), Z(get_nvalue(n))] for n in neighbours
]
n_coeffs = [[1, sign] for _ in neighbours]
final_terms = [prod(*list(m)).simplify() for m in itertools.product(*n_terms)]
final_coeffs = [
(0.5**degree) * functools.reduce(lambda x, y: x * y, list(m), 1)
for m in itertools.product(*n_coeffs)
]
coeffs.extend(final_coeffs)
terms.extend(final_terms)
return LinearCombination(coeffs, terms)
_modules/pennylane/qaoa/mixers
Download Python script
Download Notebook
View on GitHub