Source code for pennylane.pauli.grouping.graph_colouring
# MIT License
#
# Copyright (c) 2020 Jakob S. Kottmann, Sumner Alperin-Lea, Alán Aspuru-Guzik
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""
A module for heuristic algorithms for colouring Pauli graphs.
A Pauli graph is a graph where vertices represent Pauli words and edges denote
if a specified symmetric binary relation (e.g., commutation) is satisfied for the
corresponding Pauli words. The graph-colouring problem is to assign a colour to
each vertex such that no vertices of the same colour are connected, using the
fewest number of colours (lowest "chromatic number") as possible.
"""
import numpy as np
[docs]def largest_first(binary_observables, adj):
"""Performs graph-colouring using the Largest Degree First heuristic. Runtime is quadratic in
number of vertices.
Args:
binary_observables (array[int]): the set of Pauli words represented by a column matrix
of the Pauli words in binary vector representation
adj (array[int]): the adjacency matrix of the Pauli graph
Returns:
dict(int, list[array[int]]): keys correspond to colours (labelled by integers) and values
are lists of Pauli words of the same colour in binary vector representation.
**Example**
>>> binary_observables = np.array([[1., 1., 0.],
... [1., 0., 0.],
... [0., 0., 1.],
... [1., 0., 1.]])
>>> adj = np.array([[0., 0., 1.],
... [0., 0., 1.],
... [1., 1., 0.]])
>>> largest_first(binary_observables, adj)
{1: [array([0., 0., 1.])], 2: [array([1., 0., 0.]), array([1., 1., 0.])]}
"""
n_terms = np.shape(adj)[0]
terms = [binary_observables[i] for i in range(n_terms)]
rows = adj.sum(axis=0)
ind = np.argsort(rows)[::-1]
m_array = adj[ind, :][:, ind]
colours = {}
c_vec = np.zeros(n_terms, dtype=int)
k = 0
for i in range(n_terms):
neighbours = np.argwhere(m_array[i, :])
colours_available = set(np.arange(1, k + 1)) - set(c_vec[[x[0] for x in neighbours]])
term = terms[ind[i]]
if not colours_available:
k += 1
c_vec[i] = k
colours[c_vec[i]] = [term]
else:
c_vec[i] = min(list(colours_available))
colours[c_vec[i]].append(term)
return colours
[docs]def recursive_largest_first(binary_observables, adj): # pylint:disable=too-many-locals
"""Performs graph-colouring using the Recursive Largest Degree First heuristic. Often yields a
lower chromatic number than Largest Degree First, but takes longer (runtime is cubic in number
of vertices).
Args:
binary_observables (array[int]): the set of Pauli words represented by a column matrix of
the Pauli words in binary vector representation
adj (array[int]): the adjacency matrix of the Pauli graph
Returns:
dict(int, list[array[int]]): keys correspond to colours (labelled by integers) and values
are lists of Pauli words of the same colour in binary vector representation
**Example**
>>> binary_observables = np.array([[1., 1., 0.],
... [1., 0., 0.],
... [0., 0., 1.],
... [1., 0., 1.]])
>>> adj = np.array([[0., 0., 1.],
... [0., 0., 1.],
... [1., 1., 0.]])
>>> recursive_largest_first(binary_observables, adj)
{1: [array([0., 0., 1.])], 2: [array([1., 1., 0.]), array([1., 0., 0.])]}
"""
def n_0(m_array, coloured):
m_coloured = m_array[list(coloured)]
l_val = m_coloured[-1]
for i in range(len(m_coloured) - 1):
l_val += m_coloured[i]
white_neighbours = np.argwhere(np.logical_not(l_val))
return {x[0] for x in white_neighbours} - coloured
n_terms = np.shape(adj)[0]
terms = [binary_observables[i] for i in range(n_terms)]
colours = {}
c_vec = np.zeros(n_terms, dtype=int)
uncoloured = set(np.arange(n_terms))
coloured = set()
k = 0
while uncoloured:
decode = np.array(list(uncoloured))
k += 1
m_array = adj[:, decode][decode, :]
v_indices = np.argmax(m_array.sum(axis=1))
coloured_sub = {v_indices}
uncoloured_sub = set(np.arange(len(decode))) - {v_indices}
n0_set = n_0(m_array, coloured_sub)
n1_set = uncoloured_sub - n0_set
while n0_set:
m_uncoloured = m_array[:, list(n1_set)][list(n0_set), :]
v_indices = list(n0_set)[np.argmax(m_uncoloured.sum(axis=1))]
coloured_sub.add(v_indices)
uncoloured_sub -= {v_indices}
n0_set = n_0(m_array, coloured_sub)
n1_set = uncoloured_sub - n0_set
indices = decode[list(coloured_sub)]
c_vec[indices] = k
colours[k] = [terms[i] for i in indices]
coloured |= set(indices)
uncoloured = set(np.arange(n_terms)) - coloured
return colours
_modules/pennylane/pauli/grouping/graph_colouring
Download Python script
Download Notebook
View on GitHub