# Source code for qubiter.device_specific.ChipCouplingsFitter

```import itertools as it

import matplotlib.pyplot as plt
import networkx as nx
import networkx.algorithms.isomorphism as iso

import qubiter.device_specific.utilities_ds as uds
from qubiter.CktEmbedder import *

[docs]class ChipCouplingsFitter:
"""
Recall that an undirected graph G is defined as a pair of sets (V, E),
where V is a set of vertices, and E, the edges, is a set of tuples (c,
t), where c and t are in V. For undirected graphs, the order of c and t
is ignored.

This class reads an English file in order to build a list of the CNots
used in the file. The CNots in the list are specified as (c, t) tuples.
Then the class builds an undirected graph, call it GE=(V_GE, E_GE) for
Graph_English, from that CNots list. Then the class builds another
graph, call it GP=(V_GP, E_GP) for Graph_Physical, from the c_to_tars
describing the couplings of a particular chip. Then the class uses the
python networkx function for finding "graph isomorphisms" between
undirected graphs. The class attempts to find a map phi() from the
vertices of GE to the vertices of GP, so that phi(x_GE) is a subset of
x_GP with x=V, E. If the search for phi() succeeds, then phi induces a
permutation map, call it bit_map: range(num_qbits)->range(num_qbits),
bit_ge->bit_gp, of the qubits of the circuit from which GE was
assembled. Under this permutation, the CNots of the permuted circuit are
all allowed** by the chip constraint c_to_tars. In that sense, the map
bit_map() is a "fit" to the chip couplings.

**except, some CNots may have to be reversed (control and target
swapped) using Hadamards. These reversals can be performed with the
class ForbiddenCNotExpander.

Attributes
----------
bit_map: list[int]
bit_map[bit_ge] = bit_gp defines a map from the bits bit_ge
of the graph GE to the bits bit_gp of the graph GP.

"""
[docs]    def __init__(self, file_prefix, num_qbits, c_to_tars, verbose=False):
"""
Constructor

Parameters
----------
file_prefix : str
file prefix of English file which is to be read to assemble
a list of CNots used in the file.
num_qbits : int
Number of qubits used in English file with file prefix
`file_prefix`. IMP: We assume that c_to_tars refers to a chip with
num_qbits too. Both the English file and the chip must have the
same number of qubits. This is no loss of generality. As long as
the English file doesn't mention qubit positions >= num_qbits,
all you have to do to conform is to change the name of the
English file so that it claims to pertain to num_qbits qubits.
c_to_tars : dict[int, list[int]]
a dictionary mapping j in range(num_qbits) to a list, possibly
empty, of the physically allowed targets of qubit j, when j is
the control of a CNOT.

verbose : bool

Returns
-------

"""

old_cnots = ChipCouplingsFitter.get_cnots_in_file(
file_prefix, num_qbits, verbose)
self.bit_map = ChipCouplingsFitter.get_bit_map_from_c_to_tars(
num_qbits, old_cnots, c_to_tars, verbose)
emb = CktEmbedder(num_qbits, num_qbits, self.bit_map)
wr = SEO_writer(out_file_prefix, emb)

[docs]    @staticmethod
def get_cnots_in_file(file_prefix, num_qbits, verbose=False):
"""
This function reads an English file with file prefix `file_prefix`
pertaining to a circuit with num_bit many qubits. It returns a tuple
of the CNots mentioned in the English file. The CNots are specified
as tuples (c, t), where c is the control bit position and t is the
target bit position.

Parameters
----------
file_prefix : str
num_qbits : int
verbose : bool

Returns
-------
tuple[tuple[int, int]]

"""
old_cnots = []
english_in = open(utg.preface(
file_prefix + '_' + str(num_qbits) + '_eng.txt'), 'rt')

while not english_in.closed:
if not line:
english_in.close()
break
split_line = line.split()
line_name = split_line[0]
if line_name == "SIGX":
# example:
# SIGX AT 1 IF 3F 2T
tar_bit_pos = int(split_line[2])
if len(split_line) > 3:
if verbose:
print(line[:-1])
assert len(split_line) == 5, \
"Only SIGX with <= 1 controls are allowed."
trol_bit_pos = int(split_line[4][:-1])
old_cnots.append((trol_bit_pos, tar_bit_pos))
if verbose:
print("old_cnots=", old_cnots)
return tuple(old_cnots)

[docs]    @staticmethod
def draw_phys_and_eng_graphs(file_prefix, num_qbits, c_to_tars):
"""
Draws the Physical and English undirected graphs. This is useful in
case you want to try to use human pattern recognition to embed the
English graph inside the Physical graph.

Parameters
----------
file_prefix : str
num_qbits : int
c_to_tars : dict[int, list[int]]

Returns
-------
None

"""

plt.figure(1)
GP = nx.Graph()
dir_edges = uds.get_dir_edges_from_c_to_tars(c_to_tars)

plt.title('Physical graph')
nx.draw(GP, with_labels=True, node_color='white')

plt.figure(2)
old_cnots = ChipCouplingsFitter.get_cnots_in_file(file_prefix,
num_qbits)
GE = nx.Graph()
plt.title('English graph')
nx.draw(GE, with_labels=True, node_color='white')

plt.show()

[docs]    @staticmethod
def get_bit_map_from_c_to_tars(
num_qbits, old_cnots, c_to_tars, verbose=False):
"""
This function has as inputs `old_cnot` describing the CNots in an
English file for a circuit with num_qbits qubits and `c_to_tars`
describing the couplings of a chip with num_qbits qubits. The
function returns a list `bit_map` of num_bit many ints that
describes a permutation map mapping range(num_qbits)->range(
num_qbits), bit_ge->bit_gp. Under this permutation, the CNots of the
circuit described by the input English file are mapped into new
CNots which are all allowed** by the chip couplings constraint
c_to_tars.

** except their targets and controls may have to be reversed.

Parameters
----------
num_qbits : int
old_cnots : tuple[tuple[int, int]]
c_to_tars : dict[int, list[int]]
verbose : bool

Returns
-------
list[int]

"""
GP = nx.Graph()
dir_edges = uds.get_dir_edges_from_c_to_tars(c_to_tars)
if verbose:
print("GP=", GP.edges())

GE = nx.Graph()
if verbose:
print("GE=", GE.edges())

ge_num_edges = len(GE.edges())
g_match = None
is_iso = False
for edge_sublist in it.combinations(GP.edges(), ge_num_edges):
subgraph = nx.Graph()
if verbose:
print("subgraph=", subgraph.edges())
g_match = iso.GraphMatcher(subgraph, GE)
if g_match.is_isomorphic():
is_iso = True
break
assert is_iso, "Could not find node mapping."
gp_bit_to_ge_bit = g_match.mapping
if verbose:
print('gp_bit_to_ge_bit=', gp_bit_to_ge_bit)
bit_map = [-1]*num_qbits
gp_bits, ge_bits = zip(*gp_bit_to_ge_bit.items())
for k, ge_bit in enumerate(ge_bits):
bit_map[ge_bit] = gp_bits[k]
undefined_domain = [k for k in range(num_qbits) if bit_map[k] == -1]
complement_of_range = [k for k in range(num_qbits) if k not in bit_map]
for y, x in enumerate(undefined_domain):
bit_map[x] = complement_of_range[y]
if verbose:
print("bit_map=", bit_map)
return bit_map

if __name__ == "__main__":
def main():
import qubiter.device_specific.chip_couplings_ibm as ibm
c_to_tars = ibm.ibmq5YorktownTenerife_c_to_tars
num_qbits = 5
file_prefix = "couplings_fitter"

print("control_to_targets=", c_to_tars)

verbose = True
fitter = ChipCouplingsFitter(
file_prefix, num_qbits, c_to_tars, verbose=verbose)

print("Must close or save/close all matplotlib "
"windows in order to finish execution of script.")
ChipCouplingsFitter.draw_phys_and_eng_graphs(file_prefix,
num_qbits,
c_to_tars)
main()

```