Skip to content
This repository has been archived by the owner on Jun 12, 2023. It is now read-only.

Migrate topological codes fitters to use retworkx #552

Merged
93 changes: 56 additions & 37 deletions qiskit/ignis/verification/topological_codes/fitters.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

import copy
import warnings
import networkx as nx
import retworkx as rx
import numpy as np

from qiskit import QuantumCircuit, execute
Expand All @@ -45,7 +45,7 @@ def __init__(self, code, S=None):
Args:
code (RepitionCode): The QEC Code object for which this decoder
will be used.
S (networkx.Graph): Graph describing connectivity between syndrome
S (retworkx.PyGraph): Graph describing connectivity between syndrome
elements. Will be generated automatically if not supplied.
Additional information:
Expand Down Expand Up @@ -97,7 +97,7 @@ def _make_syndrome_graph(self):
elements that can be created by the same error.
"""

S = nx.Graph()
S = rx.PyGraph(multigraph=False)

qc = self.code.circuit['0']

Expand Down Expand Up @@ -129,11 +129,11 @@ def _make_syndrome_graph(self):

job = execute(list(error_circuit.values()), simulator)

node_map = {}
for j in range(depth):
qubits = qc.data[j][1]
for qubit in qubits:
for error in ['x', 'y', 'z']:

raw_results = {}
raw_results['0'] = job.result().get_counts(
str((j, qubit, error)))
Expand All @@ -148,13 +148,14 @@ def _make_syndrome_graph(self):
" at depth " + str(j) + " creates " + \
str(len(nodes)) + \
" nodes in syndrome graph, instead of 2."

for node in nodes:
S.add_node(node)
if node not in node_map:
node_map[node] = S.add_node(node)
for source in nodes:
for target in nodes:
if source != target:
S.add_edge(source, target, distance=1)
if target != source:
S.add_edge(node_map[source],
node_map[target], 1)

return S

Expand All @@ -180,7 +181,7 @@ def weight_syndrome_graph(self, results):

nodes = self._string2nodes(string)

for edge in self.S.edges:
for edge in self.S.edge_list():
element = ''
for j in range(2):
if edge[j] in nodes:
Expand All @@ -189,15 +190,15 @@ def weight_syndrome_graph(self, results):
element += '0'
count[element][edge] += results[string]

for edge in self.S.edges:
edge_data = self.S.get_edge_data(edge[0], edge[1])
for edge in self.S.edge_list():
ratios = []
for elements in [('00', '11'), ('11', '00'),
('01', '10'), ('10', '01')]:
if count[elements[1]][edge] > 0:
ratio = count[elements[0]][edge]/count[elements[1]][edge]
ratios.append(ratio)
edge_data['distance'] = -np.log(min(ratios))
self.S.remove_edge(edge[0], edge[1])
self.S.add_edge(edge[0], edge[1], -np.log(min(ratios)))

def make_error_graph(self, string, subgraphs=None):
"""
Expand All @@ -219,31 +220,39 @@ def make_error_graph(self, string, subgraphs=None):
set_subgraphs = [
subgraph for subs4type in subgraphs for subgraph in subs4type]

E = {subgraph: nx.Graph() for subgraph in set_subgraphs}
E = {}
node_sets = {}
for subgraph in set_subgraphs:
E[subgraph] = rx.PyGraph(multigraph=False)
node_sets[subgraph] = set()

E = {subgraph: rx.PyGraph(multigraph=False) for subgraph in set_subgraphs}
separated_string = self._separate_string(string)

for syndrome_type, _ in enumerate(separated_string):
for syndrome_round in range(len(separated_string[syndrome_type])):
elements = separated_string[syndrome_type][syndrome_round]
for elem_num, element in enumerate(elements):
if element == '1' or syndrome_type == 0:
for subgraph in subgraphs[syndrome_type]:
E[subgraph].add_node(
(syndrome_type,
syndrome_round,
elem_num))
node_data = (syndrome_type, syndrome_round, elem_num)
if node_data not in node_sets[subgraph]:
E[subgraph].add_node(node_data)
node_sets[subgraph].add(node_data)

# for each pair of nodes in error create an edge and weight with the
# distance
for subgraph in set_subgraphs:
for source in E[subgraph]:
for target in E[subgraph]:
if target != (source):
distance = int(nx.shortest_path_length(
self.S, source, target, weight='distance'))
E[subgraph].add_edge(source, target, weight=-distance)
distance_matrix = rx.graph_floyd_warshall_numpy(self.S, weight_fn=float)
s_node_map = {self.S[index]: index for index in self.S.node_indexes()}

for subgraph in set_subgraphs:
for source_index in E[subgraph].node_indexes():
for target_index in E[subgraph].node_indexes():
source = E[subgraph][source_index]
target = E[subgraph][target_index]
if target != source:
distance = int(distance_matrix[s_node_map[source]][s_node_map[target]])
E[subgraph].add_edge(source_index, target_index,
-distance)
return E

def matching(self, string):
Expand All @@ -265,11 +274,13 @@ def matching(self, string):

# set up graph that is like E, but each syndrome node is connected to a
# separate copy of the nearest logical node
E_matching = nx.Graph()
E_matching = rx.PyGraph(multigraph=False)
syndrome_nodes = []
logical_nodes = []
logical_neighbours = []
for node in E:
node_map = {}
for node in E.nodes():
node_map[node] = E_matching.add_node(node)
if node[0] == 0:
logical_nodes.append(node)
else:
Expand All @@ -278,25 +289,33 @@ def matching(self, string):
for target in syndrome_nodes:
if target != (source):
E_matching.add_edge(
source, target, weight=E[source][target]['weight'])
node_map[source],
node_map[target],
E.get_edge_data(node_map[source],
node_map[target]))

potential_logical = {}
for target in logical_nodes:
potential_logical[target] = E[source][target]['weight']
potential_logical[target] = E.get_edge_data(node_map[source],
node_map[target])
nearest_logical = max(potential_logical, key=potential_logical.get)
nl_target = nearest_logical + source
if nl_target not in node_map:
node_map[nl_target] = E_matching.add_node(nl_target)
E_matching.add_edge(
source,
nearest_logical + source,
weight=potential_logical[nearest_logical])
logical_neighbours.append(nearest_logical + source)
node_map[source],
node_map[nl_target],
potential_logical[nearest_logical])
logical_neighbours.append(nl_target)
for source in logical_neighbours:
for target in logical_neighbours:
if target != (source):
E_matching.add_edge(source, target, weight=0)

E_matching.add_edge(node_map[source], node_map[target], 0)
# do the matching on this
matches = nx.max_weight_matching(E_matching, maxcardinality=True)

matches = {
(E_matching[x[0]],
E_matching[x[1]]) for x in rx.max_weight_matching(
E_matching, max_cardinality=True, weight_fn=lambda x: x)}
# use it to construct and return a corrected logical string
logicals = self._separate_string(string)[0]
for (source, target) in matches:
Expand Down
6 changes: 6 additions & 0 deletions releasenotes/notes/retworkx-requirement-f99e8b468f0d6ca3.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
upgrade:
- |
The python package ``retworkx`` is now a requirement for installing
qiskit-ignis. It replaces the previous usage of ``networkx`` (which is
no longer a requirement) to get better performance.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
requirements = [
"numpy>=1.13",
"qiskit-terra>=0.13.0",
"networkx>=2.2",
"retworkx>=0.8.0",
"scipy>=0.19,!=0.19.1",
"setuptools>=40.1.0",
"scikit-learn>=0.17",
Expand Down