# -*- coding: utf-8 -*-
# This code is part of Qiskit.
#
# (C) Copyright IBM 2018, 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""
For coloring Pauli Graph for transforming paulis into grouped Paulis
"""
import copy
import numpy as np
[docs]class PauliGraph:
"""Pauli Graph."""
def __init__(self, paulis, mode="largest-degree"):
self.nodes, self.weights = self._create_nodes(paulis) # must be pauli list
self._nqbits = self._get_nqbits()
self.edges = self._create_edges()
self._grouped_paulis = self._coloring(mode)
def _create_nodes(self, paulis):
"""
Check the validity of the pauli list and return immutable list as list of nodes.
Args:
paulis (list): list of [weight, Pauli object]
Returns:
Pauli: object as immutable list
"""
pauli_operators = [x[1] for x in paulis]
pauli_weights = [x[0] for x in paulis]
return tuple(pauli_operators), tuple(pauli_weights) # fix their ordering
def _get_nqbits(self):
nqbits = self.nodes[0].num_qubits
for i in range(1, len(self.nodes)):
assert nqbits == self.nodes[i].num_qubits, "different number of qubits"
return nqbits
def _create_edges(self):
"""
Create edges (i,j) if i and j is not commutable under Paulis.
Returns:
dict: dictionary of graph connectivity with node index as key and
list of neighbor as values
"""
# pylint: disable=invalid-name
conv = {
'I': 0,
'X': 1,
'Y': 2,
'Z': 3
}
a = np.array([[conv[e] for e in reversed(n.to_label())] for n in self.nodes], dtype=np.int8)
b = a[:, None]
# i and j are commutable with TPB if c[i, j] is True
c = (((a * b) * (a - b)) == 0).all(axis=2)
# pylint: disable=singleton-comparison
edges = {i: np.where(c[i] == False)[0] for i in range(len(self.nodes))} # noqa
return edges
def _coloring(self, mode="largest-degree"):
# pylint: disable=invalid-name
if mode == "largest-degree":
nodes = sorted(self.edges.keys(), key=lambda x: len(self.edges[x]), reverse=True)
# -1 means not colored; 0 ... len(self.nodes)-1 is valid colored
max_node = max(nodes)
color = np.array([-1] * (max_node + 1))
all_colors = np.arange(len(nodes))
for i in nodes:
neighbors = self.edges[i]
color_neighbors = color[neighbors]
color_neighbors = color_neighbors[color_neighbors >= 0]
mask = np.ones(len(nodes), dtype=bool)
mask[color_neighbors] = False
color[i] = np.min(all_colors[mask])
assert np.min(color[nodes]) >= 0, "Uncolored node exists!"
# post-processing to grouped_paulis
max_color = np.max(color[nodes]) # the color used is 0, 1, 2, ..., max_color
temp_gp = [] # list of indices of grouped paulis
for c in range(max_color + 1): # max_color is included
temp_gp.append([i for i, icolor in enumerate(color) if icolor == c])
# create _grouped_paulis as dictated in the operator.py
gp = []
for c in range(max_color + 1): # max_color is included
# add all paulis
gp.append([[self.weights[i], self.nodes[i]] for i in temp_gp[c]])
# create header (measurement basis)
for i, groupi in enumerate(gp):
header = [0.0, copy.deepcopy(groupi[0][1])]
for _, p in groupi:
for k in range(self._nqbits):
if p.z[k] or p.x[k]:
header[1].update_z(p.z[k], k)
header[1].update_x(p.x[k], k)
gp[i].insert(0, header)
return gp
else:
return self._coloring("largest-degree") # this is the default implementation
@property
def grouped_paulis(self):
"""Getter of grouped Pauli list."""
return self._grouped_paulis