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

Commit

Permalink
Migrate topological codes fitters to use retworkx (#552)
Browse files Browse the repository at this point in the history
This commit migrates the networkx usage in the topological codes fitters
module away from networkx to use retworkx instead. This should provide
a speedup as retworkx is typically much faster than networkx. But, more
importantly this module is the last usage of networkx in qiksit and that
causes noticeable import overhead as networkx is slow to import. Moving
to retworkx like the rest of Qiskit will remove this overhead.
  • Loading branch information
mtreinish authored Mar 3, 2021
1 parent 6103f99 commit 1537c75
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 38 deletions.
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

0 comments on commit 1537c75

Please sign in to comment.