# Source code for dwave_networkx.generators.pegasus

# Copyright 2018 D-Wave Systems Inc.
#
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#
#    Unless required by applicable law or agreed to in writing, software
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#
# ================================================================================================
"""
Generators for some graphs derived from the D-Wave System.

"""
import re

import networkx as nx

from dwave_networkx import _PY2
from dwave_networkx.exceptions import DWaveNetworkXException
import warnings

__all__ = ['pegasus_graph',
'pegasus_coordinates',
]

# compatibility for python 2/3
if _PY2:
range = xrange

[docs]def pegasus_graph(m, create_using=None, node_list=None, edge_list=None, data=True,
offset_lists=None, offsets_index=None, coordinates=False, fabric_only=True,
nice_coordinates=False):
"""
Creates a Pegasus graph with size parameter m.

Parameters
----------
m : int
Size parameter for the Pegasus lattice.
create_using : Graph, optional (default None)
If provided, this graph is cleared of nodes and edges and filled
with the new graph. Usually used to set the type of the graph.
node_list : iterable, optional (default None)
Iterable of nodes in the graph. If None, calculated from m.
Note that this list is used to remove nodes, so any nodes specified
not in range(24 * m * (m-1)) are not added.
edge_list : iterable, optional (default None)
Iterable of edges in the graph. If None, edges are generated as
described below. The nodes in each edge must be integer-labeled in
range(24 * m * (m-1)).
data : bool, optional (default True)
If True, each node has a Pegasus_index attribute. The attribute
is a 4-tuple Pegasus index as defined below. If the coordinates parameter
is True, a linear_index, which is an integer, is used.
coordinates : bool, optional (default False)
If True, node labels are 4-tuple Pegasus indices. Ignored if the
nice_coordinates parameter is True.
offset_lists : pair of lists, optional (default None)
Directly controls the offsets. Each list in the pair must have length 12
and contain even ints.  If offset_lists is not None, the offsets_index
parameter must be None.
offsets_index : int, optional (default None)
A number between 0 and 7, inclusive, that selects a preconfigured
set of topological parameters. If both the offsets_index and
offset_lists parameters are None, the offsets_index parameters is set
to zero. At least one of these two parameters must be None.
fabric_only: bool, optional (default True)
The Pegasus graph, by definition, has some disconnected
components.  If True, the generator only constructs nodes from the
largest component. If False, the full disconnected graph is
constructed. Ignored if the edge_lists parameter is not None or
nice_coordinates is True
nice_coordinates: bool, optional (default False)
If the offsets_index parameter is 0, the graph uses a "nicer"
coordinate system, more compatible with Chimera addressing.
These coordinates are 5-tuples taking the form :math:(t, y, x, u, k) where
:math:0 <= x < M-1, :math:0 <= y < M-1, :math:0 <= u < 2,
:math:0 <= k < 4, and :math:0 <= t < 3.
For any given :math:0 <= t0 < 3, the subgraph of nodes with :math:t = t0
has the structure of chimera(M-1, M-1, 4) with the addition of odd couplers.
Supercedes both the fabric_only and coordinates parameters.

Returns
-------
G : NetworkX Graph
A Pegasus lattice for size parameter m.

The maximum degree of this graph is 15. The number of nodes depends on multiple
parameters; for example,

* pegasus_graph(1): zero nodes
* pegasus_graph(m, fabric_only=False): :math:24m(m-1) nodes
* pegasus_graph(m, fabric_only=True): :math:24m(m-1)-8(m-1) nodes
* pegasus_graph(m, nice_coordinates=True): :math:24(m-1)^2 nodes

Counting formulas for edges have a complicated dependency on parameter settings.
Some example upper bounds are:

* pegasus_graph(1, fabric_only=False): zero edges
* pegasus_graph(m, fabric_only=False): :math:12*(15*(m-1)^2 + m - 3)
edges if m > 1

Note that the formulas above are valid for default offset parameters.

A Pegasus lattice is a graph minor of a lattice similar
to Chimera, where unit tiles are completely connected. In its most general
definition, prelattice :math:Q(N0,N1) contains nodes of the form

* vertical nodes: :math:(i, j, 0, k) with :math:0 <= k < 2
* horizontal nodes: :math:(i, j, 1, k) with :math:0 <= k < 2

for :math:0 <= i <= N0 and :math:0 <= j < N1, and edges of the form

* external: :math:(i, j, u, k) ~ :math:(i+u, j+1-u, u, k)
* internal: :math:(i, j, 0, k) ~ :math:(i, j, 1, k)
* odd: :math:(i, j, u, 0) ~ :math:(i, j, u, 1)

Given two lists of offsets, :math:S0 and :math:S1, of length
:math:L0 and :math:L1, where both lengths and values must be divisible by
2, the minor---a Pegasus lattice---is constructed by contracting the complete
intervals of external edges::

I(0, w, k, z) = [(L1*w + k, L0*z + S0[k] + r, 0, k % 2) for 0 <= r < L0]
I(1, w, k, z) = [(L1*z + S1[k] + r, L0*w + k, 1, k % 2) for 0 <= r < L1]

and deleting the prelattice nodes of any interval not fully contained in
:math:Q(N0, N1).

This generator, 'pegasus_graph()', is specialized for the minor constructed by
prelattice and offset parameters :math:L0 = L1 = 12 and :math:N0 = N1 = 12m.

The *Pegasus index* of a node in a Pegasus lattice, :math:(u, w, k, z), can be
interpreted as:

* :math:u: qubit orientation (0 = vertical, 1 = horizontal)
* :math:w: orthogonal major offset
* :math:k: orthogonal minor offset
* :math:z: parallel offset

Edges in the minor have the form

* external: :math:(u, w, k, z) ~ :math:(u, w, k, z+1)
* internal: :math:(0, w0, k0, z0) ~ :math:(1, w1, k1, z1)
* odd: :math:(u, w, 2k, z) ~ :math:(u, w, 2k+1, z)

where internal edges only exist when

1. w1 = z0 + (1 if k1 < S0[k0] else 0)
2. z1 = w0 - (1 if k0 < S1[k1] else 0)

Linear indices are computed from Pegasus indices by the formula::

q = ((u * m + w) * 12 + k) * (m - 1) + z

Examples
========
>>> G = dnx.pegasus_graph(2, nice_coordinates=True)
>>> G.nodes(data=True)[(0, 0, 0, 0, 0)]    # doctest: +SKIP
{'linear_index': 4, 'pegasus_index': (0, 0, 4, 0)}

"""
if offset_lists is None:
offsets_descriptor = offsets_index = offsets_index or 0
offset_lists = [
[(2, 2, 2, 2, 10, 10, 10, 10, 6, 6, 6, 6,), (6, 6, 6, 6, 2, 2, 2, 2, 10, 10, 10, 10,)],
[(2, 2, 2, 2, 10, 10, 10, 10, 6, 6, 6, 6,), (2, 2, 2, 2, 10, 10, 10, 10, 6, 6, 6, 6,)],
[(2, 2, 2, 2, 10, 10, 10, 10, 6, 6, 6, 6,), (10, 10, 10, 10, 6, 6, 6, 6, 2, 2, 2, 2,)],
[(10, 10, 10, 10, 6, 6, 6, 6, 2, 2, 2, 2,), (10, 10, 10, 10, 6, 6, 6, 6, 2, 2, 2, 2,)],
[(10, 10, 10, 10, 6, 6, 6, 6, 2, 2, 2, 2,), (2, 2, 2, 2, 6, 6, 6, 6, 10, 10, 10, 10,)],
[(6, 6, 2, 2, 2, 2, 10, 10, 10, 10, 6, 6,), (6, 6, 2, 2, 2, 2, 10, 10, 10, 10, 6, 6,)],
[(6, 6, 2, 2, 2, 2, 10, 10, 10, 10, 6, 6,), (6, 6, 10, 10, 10, 10, 2, 2, 2, 2, 6, 6,)],
[(6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,), (6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,)],
][offsets_index]
elif offsets_index is not None:
raise DWaveNetworkXException("provide at most one of offsets_index and offset_lists")
else:
for ori in 0, 1:
for x, y in zip(offset_lists[ori][::2], offset_lists[ori][1::2]):
if x != y:
warnings.warn("The offets list you've provided is possibly non-physical.  Odd-coupled qubits should have the same value.")
offsets_descriptor = offset_lists

G = nx.empty_graph(0, create_using)

G.name = "pegasus_graph(%s, %s)" % (m, offsets_descriptor)

m1 = m - 1
if nice_coordinates:
if offsets_index != 0:
raise NotImplementedError("nice coordinate system is only implemented for offsets_index 0")
labels = 'nice'
p2n = pegasus_coordinates.pegasus_to_nice
c2i = lambda *q: p2n(q)
elif coordinates:
c2i = lambda *q: q
labels = 'coordinate'
else:
labels = 'int'
def c2i(u, w, k, z): return u * 12 * m * m1 + w * 12 * m1 + k * m1 + z

construction = (("family", "pegasus"), ("rows", m), ("columns", m),
("tile", 12), ("vertical_offsets", offset_lists[0]),
("horizontal_offsets", offset_lists[1]), ("data", data),
("labels", labels))

G.graph.update(construction)

max_size = m * (m - 1) * 24  # max number of nodes G can have

if edge_list is None:
if nice_coordinates:
fabric_start = 4,8
fabric_end = 8, 4
elif fabric_only:
fabric_start = min(s for s in offset_lists[1]), min(s for s in offset_lists[0])
fabric_end = 12 - max(s for s in offset_lists[1]), 12 - max(s for s in offset_lists[0])
else:
fabric_end = fabric_start = 0, 0

G.add_edges_from((c2i(u, w, k, z), c2i(u, w, k, z + 1))
for u in (0, 1)
for w in range(m)
for k in range(fabric_start[u] if w == 0 else 0, 12 - (fabric_end[u] if w == m1 else 0))
for z in range(m1 - 1))

G.add_edges_from((c2i(u, w, k, z), c2i(u, w, k + 1, z))
for u in (0, 1)
for w in range(m)
for k in range(fabric_start[u] if w == 0 else 0, 12 - (fabric_end[u] if w == m1 else 0), 2)
for z in range(m1))

off0, off1 = offset_lists
def qfilter(u, w, k, z):
if w == 0: return k >= fabric_start[u]
if w == m1: return k < 12-fabric_end[u]
return True
def efilter(e): return qfilter(*e[0]) and qfilter(*e[1])

internal_couplers = (((0, w, k, z), (1, z + (kk < off0[k]), kk, w - (k < off1[kk])))
for w in range(m)
for kk in range(12)
for k in range(0 if w else off1[kk], 12 if w < m1 else off1[kk])
for z in range(m1))
G.add_edges_from((c2i(*e[0]), c2i(*e[1])) for e in internal_couplers if efilter(e))

else:

if node_list is not None:
nodes = set(node_list)
G.remove_nodes_from(set(G) - nodes)

if data:
v = 0
for u in range(2):
for w in range(m):
for k in range(12):
for z in range(m1):
q = u, w, k, z
if nice_coordinates:
p = c2i(*q)
if p in G:
G.nodes[p]['linear_index'] = v
G.nodes[p]['pegasus_index'] = q
elif coordinates:
if q in G:
G.nodes[q]['linear_index'] = v
else:
if v in G:
G.nodes[v]['pegasus_index'] = q
v += 1

return G

def get_tuple_fragmentation_fn(pegasus_graph):
"""
Returns a fragmentation function that is specific to pegasus_graph. This fragmentation function,
fragment_tuple(..), takes in a list of Pegasus qubit coordinates and returns their corresponding
K2,2 Chimera fragment coordinates.

Details on the returned function, fragment_tuple(list_of_pegasus_coordinates):
Each Pegasus qubit is split into six fragments. If edges are drawn between adjacent
fragments and drawn between fragments that are connected by an existing Pegasus coupler, we
can see that a K2,2 Chimera graph is formed.

The K2,2 Chimera graph uses a coordinate system with an origin at the upper left corner of
the graph.
y: number of vertical fragments from the top-most row
x: number of horizontal fragments from the left-most column
u: 1 if it belongs to a horizontal qubit, 0 otherwise
r: fragment index on the K2,2 shore

Parameters
----------
pegasus_graph: networkx.graph
A pegasus graph

Returns
-------
fragment_tuple(pegasus_coordinates): a function
A function that accepts a list of pegasus coordinates and returns a list of their
corresponding K2,2 Chimera coordinates.
"""
horizontal_offsets = pegasus_graph.graph['horizontal_offsets']
vertical_offsets = pegasus_graph.graph['vertical_offsets']

# Note: we are returning a fragmentation function rather than fragmenting the pegasus
# coordinates ourselves because:
#   (1) We don't want the user to have to deal with Pegasus horizontal/vertical offsets directly.
#       (i.e. Don't want fragment_tuple(pegasus_coord, vertical_offset, horizontal_offset))
#   (2) We don't want the user to have to pass entire Pegasus graph each time they want to
#       fragment some pegasus coordinates.
#       (i.e. Don't want fragment_tuple(pegasus_coord, pegasus_graph))
def fragment_tuple(pegasus_coords):
fragments = []
for u, w, k, z in pegasus_coords:
# Determine offset
offset = horizontal_offsets if u else vertical_offsets
offset = offset[k]

# Find the base (i.e. zeroth) Chimera fragment of this pegasus coordinate
fz0 = (z*12 + offset) // 2 #first fragment's z-coordinate
fw = (w*12 + k) // 2 #fragment w-coordinate
fk = k&1 #fragment k-index
base = [fw, 0, u, fk] if u else [0, fw, u, fk]

# Generate the six fragments associated with this pegasus coordinate
for fz in range(fz0, fz0 + 6):
base[u] = fz
fragments.append(tuple(base))

return fragments

return fragment_tuple

def get_tuple_defragmentation_fn(pegasus_graph):
"""
Returns a de-fragmentation function that is specific to pegasus_graph. The returned
de-fragmentation function, defragment_tuple(..), takes in a list of K2,2 Chimera coordinates and
returns the corresponding list of unique pegasus coordinates.

Details on the returned function, defragment_tuple(list_of_chimera_fragment_coordinates):
Each Pegasus qubit is split into six fragments. If edges are drawn between adjacent
fragments and drawn between fragments that are connected by an existing Pegasus coupler, we
can see that a K2,2 Chimera graph is formed.

The K2,2 Chimera graph uses a coordinate system with an origin at the upper left corner of
the graph.
y: number of vertical fragments from the top-most row
x: number of horizontal fragments from the left-most column
u: 1 if it belongs to a horizontal qubit, 0 otherwise
r: fragment index on the K2,2 shore

The defragment_tuple(..) takes in the list of Chimera fragments and returns a list of their
corresponding Pegasus qubit coordinates. Note that the returned list has a unique set of
Pegasus coordinates.

Parameters
----------
pegasus_graph: networkx.graph
A Pegasus graph

Returns
-------
defragment_tuple(chimera_coordinates): a function
A function that accepts a list of chimera coordinates and returns a set of their
corresponding Pegasus coordinates.
"""
horizontal_offsets = pegasus_graph.graph['horizontal_offsets']
vertical_offsets = pegasus_graph.graph['vertical_offsets']

# Note: we are returning a defragmentation function rather than defragmenting the chimera
# fragments ourselves because:
#   (1) We don't want the user to have to deal with Pegasus horizontal/vertical offsets directly.
#       (i.e. Don't want defragment_tuple(chimera_coord, vertical_offset, horizontal_offset))
#   (2) We don't want the user to have to pass entire Pegasus graph each time they want to
#       defragment some chimera coordinates.
#       (i.e. Don't want defragment_tuple(chimera_coord, pegasus_graph))
def defragment_tuple(chimera_coords):
pegasus_coords = []
for y, x, u, r in chimera_coords:
# Set up shifts and offsets
shifts = [x, y]
offsets = horizontal_offsets if u else vertical_offsets

# Determine number of tiles and track number
w, k = divmod(2 * shifts[u] + r, 12)

# Determine qubit index on track
z = (shifts[1-u] * 2 - offsets[k]) // 12

pegasus_coords.append((u, w, k, z))

# Several chimera coordinates may map to the same pegasus coordinate, hence, apply set(..)
return list(set(pegasus_coords))

return defragment_tuple

def fragmented_edges(pegasus_graph):
"""
Generator for the edges contained in a Chimera graph obtained by splitting each Pegasus node into
six Chimera nodes.  If the Pegasus graph has size parameter m, then the derived graph will be a
subgraph of Chimera(6m, 6m, 2) -- that is, a Chimera graph with K2,2 unit tiles.

The K2,2 Chimera graph uses a coordinate system with an origin at the upper left corner of
the graph.
y: number of vertical fragments from the top-most row
x: number of horizontal fragments from the left-most column
u: 1 if it belongs to a horizontal qubit, 0 otherwise
r: fragment index on the K2,2 shore

Parameters
----------
pegasus_graph: networkx.graph
A pegasus graph

Returns
-------
(coord0, coord1), ... : an iterator of tuples
Yields the edges contained in the Chimera graph derived from the "fragmentation" construction
"""
offsetlist = [pegasus_graph.graph['vertical_offsets'], pegasus_graph.graph['horizontal_offsets']]
offsets = {(u, k): offsetlist[u][k] for u in (0, 1) for k in range(12)}

if pegasus_graph.graph['labels'] == 'nice':
coords = pegasus_coordinates.nice_to_pegasus
elif pegasus_graph.graph['labels'] == 'int':
coords = pegasus_coordinates(pegasus_graph.graph['rows']).linear_to_pegasus
else:
coords = lambda z: z

#first, we generate the edges internal to the fragments corresponding to a node
for q in pegasus_graph.nodes():
u, w, k, z = coords(q)
#copied from get_tuple_fragmentation_fn and slightly optimized
offset = offsets[u, k]
fz0 = (z*12 + offset) // 2 #first fragment z-coordinate
fw = (w*12 + k) // 2 #fragment w-coordinate
base = [fw, fz0, u, k&1] if u else [fz0, fw, u, k&1]
prev = tuple(base)
for fz in range(fz0+1, fz0+6):
base[u] = fz
curr = tuple(base)
yield (prev, curr)
prev = curr

#now for the thinky part: for each Pegasus edge, generate the corresponding Chimera edge
#we skip the "odd-coupler" edges because they don't exist in Chimera
for q0, q1 in pegasus_graph.edges():
u0, w0, k0, z0 = coords(q0)
u1, w1, k1, z1 = coords(q1)
if u0 == u1:
if k0 == k1:
#this is an external edge -- we could probably do some hijinks to fold this in
#with the nodes loop, but cost/benefit doesn't support it right now
offset = offsets[u0, k0]
fz = (min(z0, z1)*12 + offset) // 2 #first fragment z-coordinate in the pair
fw = (w0*12 + k0) // 2 #fragment w-coordinate for both qubits
fk = k0&1 #fragment k-index
if u0:
yield ((fw, fz+5, u0, fk), (fw, fz+6, u0, fk))
else:
yield ((fz+5, fw, u0, fk), (fz+6, fw, u0, fk))

#else: this is an odd edge; yield nothing
else:
#this may look a little magical -- we're looking for an edge of the form
# (fy, fx, u0, fk0), (fy, fx, u1, fk1)
#where (fy, fx) are the first two coordinates of both the fragments of (u0, w0, k0,z0),
# (fy, fx) in [(fz0+0, fw0), (fz0+1, fw0), ..., (fz0+5, fw0)]
#and also the the fragments of (u1, w1, k1, z1):
# (fy, fx) in [(fw1, fz1+0), (fw1, fz1+1), ..., (fw1, fz1+5)]
#(see get_tuple_fragmentation_fn to see the fragment generator)
#with the assumption that an intersection exists: it can only be located at (fw0, fw1)
#since those coordinates are constant in the respective intervals.  Thus, we get to
#skip looking up the offsets.  Magic?  No, Math!
fw0 = (w0*12 + k0) // 2
fw1 = (w1*12 + k1) // 2
if u0:
yield ((fw0, fw1, u0, k0&1), (fw0, fw1, u1, k1&1))
else:
yield ((fw1, fw0, u0, k0&1), (fw1, fw0, u1, k1&1))

# Developer note: we could implement a function that creates the iter_*_to_* and
# iter_*_to_*_pairs methods just-in-time, but there are a small enough number
# that for now it makes sense to do them by hand.
class pegasus_coordinates(object):
def __init__(self, m):
"""Provides coordinate converters for the Pegasus indexing schemes.

Parameters
----------
m : int
Size parameter for the Pegasus lattice.

--------
:func:.pegasus_graph for a description of the various coordinate
conventions.

"""

self.args = m, m - 1

[docs]    def pegasus_to_linear(self, q):
"""Convert a 4-term Pegasus coordinate into a linear index.

Parameters
----------
q : 4-tuple
Pegasus indices.

Examples
--------
>>> dnx.pegasus_coordinates(2).pegasus_to_linear((0, 0, 4, 0))
4
"""
u, w, k, z = q
m, m1 = self.args
return ((m * u + w) * 12 + k) * m1 + z

[docs]    def linear_to_pegasus(self, r):
"""Convert a linear index into a 4-term Pegasus coordinate.

Parameters
----------
r : int
Linear index.

Examples
--------
>>> dnx.pegasus_coordinates(2).linear_to_pegasus(4)
(0, 0, 4, 0)

"""
m, m1 = self.args
r, z = divmod(r, m1)
r, k = divmod(r, 12)
u, w = divmod(r, m)
return u, w, k, z

[docs]    @staticmethod
def nice_to_pegasus(n):
"""Convert a 5-term nice coordinate into a 4-term Pegasus coordinate.

Parameters
----------
n : 5-tuple
Nice coordinate.

Examples
--------
>>> dnx.pegasus_coordinates.nice_to_pegasus((0, 0, 0, 0, 0))
(0, 0, 4, 0)

Note that this method does not depend on the size of the Pegasus
lattice.
"""
t, y, x, u, k = n

if t == 0:
return (u, y+1 if u else x, 4+k if u else 4+k, x if u else y)
elif t == 1:
return (u, y+1 if u else x, k if u else 8+k, x if u else y)
elif t == 2:
return (u, y if u else x + 1, 8+k if u else k, x if u else y)

# can happen when t is a float for instance
raise ValueError("invalid Nice coordinate: {}".format(n))

[docs]    @staticmethod
def pegasus_to_nice(p):
"""Convert a 4-term Pegasus coordinate to a 5-term nice coordinate.

Parameters
----------
p : 4-tuple
Pegasus coordinate.

Examples
--------
>>> dnx.pegasus_coordinates.pegasus_to_nice((0, 0, 4, 0))
(0, 0, 0, 0, 0)

Note that this method does not depend on the size of the Pegasus
lattice.
"""
u, w, k, z = p

t = (2-u-(2*u-1)*(k//4)) % 3

if t == 0:
return (0, w-1 if u else z, z if u else w, u, k-4 if u else k-4)
elif t == 1:
return (1, w-1 if u else z, z if u else w, u, k if u else k-8)
elif t == 2:
return (2, w if u else z, z if u else w-1, u, k-8 if u else k)

# can happen when given floats for instance
raise ValueError('invalid Pegasus coordinates')

[docs]    def linear_to_nice(self, r):
"""Convert a linear index into a 5-term nice coordinate.

Parameters
----------
r : int
Linear index.

Examples
--------
>>> dnx.pegasus_coordinates(2).linear_to_nice(4)
(0, 0, 0, 0, 0)
"""
return self.pegasus_to_nice(self.linear_to_pegasus(r))

[docs]    def nice_to_linear(self, n):
"""Convert a 5-term nice coordinate into a linear index.

Parameters
----------
n : 5-tuple
Nice coordinate.

Examples
--------
>>> dnx.pegasus_coordinates(2).nice_to_linear((0, 0, 0, 0, 0))
4
"""
return self.pegasus_to_linear(self.nice_to_pegasus(n))

def iter_pegasus_to_linear(self, qlist):
"""Return an iterator converting a sequence of 4-term Pegasus
coordinates to linear indices.
"""
m, m1 = self.args
for (u, w, k, z) in qlist:
yield ((m * u + w) * 12 + k) * m1 + z

def iter_linear_to_pegasus(self, rlist):
"""Return an iterator converting a sequence of linear indices to 4-term
Pegasus coordinates.
"""
m, m1 = self.args
for r in rlist:
r, z = divmod(r, m1)
r, k = divmod(r, 12)
u, w = divmod(r, m)
yield u, w, k, z

@classmethod
def iter_nice_to_pegasus(cls, nlist):
"""Return an iterator converting a sequence of 5-term nice coordinates
to 4-term Pegasus coordinates.

Note that this method does not depend on the size of the Pegasus
lattice.
"""
for n in nlist:
yield cls.nice_to_pegasus(n)

@classmethod
def iter_pegasus_to_nice(cls, plist):
"""Return an iterator converting a sequence of 4-term Pegasus
coordinates to 5-term nice coordinates.

Note that this method does not depend on the size of the Pegasus
lattice.
"""
for p in plist:
yield cls.pegasus_to_nice(p)

def iter_linear_to_nice(self, rlist):
"""Return an iterator converting a sequence of linear indices to 5-term
nice coordinates.
"""
for r in rlist:
yield self.linear_to_nice(r)

def iter_nice_to_linear(self, nlist):
"""Return an iterator converting a sequence of 5-term nice coordinates
to linear indices.
"""
for n in nlist:
yield self.nice_to_linear(n)

@staticmethod
def _pair_repack(f, plist):
"""Flattens a sequence of pairs to pass through f, and then
re-pairs the result.
"""
ulist = f(u for p in plist for u in p)
for u in ulist:
v = next(ulist)
yield u, v

def iter_pegasus_to_linear_pairs(self, plist):
"""Return an iterator converting a sequence of pairs of 4-term Pegasus
coordinates to pairs of linear indices.
"""
return self._pair_repack(self.iter_pegasus_to_linear, plist)

def iter_linear_to_pegasus_pairs(self, plist):
"""Return an iterator converting a sequence of pairs of linear indices
to pairs of 4-term Pegasus coordinates.
"""
return self._pair_repack(self.iter_linear_to_pegasus, plist)

@classmethod
def iter_nice_to_pegasus_pairs(cls, nlist):
"""Return an iterator converting a sequence of pairs of 5-term nice
coordinates to pairs of 4-term Pegasus coordinates.

Note that this method does not depend on the size of the Pegasus
lattice.
"""
return cls._pair_repack(cls.iter_nice_to_pegasus, nlist)

@classmethod
def iter_pegasus_to_nice_pairs(cls, plist):
"""Return an iterator converting a sequence of pairs of 4-term Pegasus
coordinates to pairs of 5-term nice coordinates.

Note that this method does not depend on the size of the Pegasus
lattice.
"""
return cls._pair_repack(cls.iter_pegasus_to_nice, plist)

def iter_linear_to_nice_pairs(self, rlist):
"""Return an iterator converting a sequence of pairs of linear indices
to pairs of 5-term nice coordinates.
"""
return self._pair_repack(self.iter_linear_to_nice, rlist)

def iter_nice_to_linear_pairs(self, nlist):
"""Return an iterator converting a sequence of pairs of 5-term nice
coordinates to pairs of linear indices.
"""
return self._pair_repack(self.iter_nice_to_linear, nlist)

def int(self, q):
"""Deprecated alias of pegasus_to_linear."""
msg = ('pegasus_coordinates.int is deprecated and will be removed in '
warnings.warn(msg, DeprecationWarning)
return self.pegasus_to_linear(q)

def tuple(self, r):
"""Deprecated alias for linear_to_pegasus."""
msg = ('pegasus_coordinates.tuple is deprecated and will be removed in '
warnings.warn(msg, DeprecationWarning)
return self.linear_to_pegasus(r)

def ints(self, qlist):
"""Deprecated alias for iter_pegasus_to_linear."""
msg = ('pegasus_coordinates.ints is deprecated and will be removed in '
warnings.warn(msg, DeprecationWarning)
return self.iter_pegasus_to_linear(qlist)

def tuples(self, rlist):
"""Deprecated alias for iter_linear_to_pegasus."""
msg = ('pegasus_coordinates.tuples is deprecated and will be removed in '
warnings.warn(msg, DeprecationWarning)
return self.iter_linear_to_pegasus(rlist)

def int_pairs(self, plist):
"""Deprecated alias for iter_pegasus_to_linear_pairs."""
msg = ('pegasus_coordinates.int_pairs is deprecated and will be removed'
' in dwave-networkx 0.9.0, please use '
warnings.warn(msg, DeprecationWarning)
return self.iter_pegasus_to_linear_pairs(plist)

def tuple_pairs(self, plist):
"""Deprecated alias for iter_linear_to_pegasus_pairs."""
msg = ('pegasus_coordinates.tuple_pairs is deprecated and will be removed'
' in dwave-networkx 0.9.0, please use '
warnings.warn(msg, DeprecationWarning)
return self.iter_linear_to_pegasus_pairs(plist)

# maintained for backwards compatibility
def get_pegasus_to_nice_fn(*args, **kwargs):
"""
Returns a coordinate translation function from the 4-term pegasus_index
coordinates to the 5-term "nice" coordinates.

Details on the returned function, pegasus_to_nice(u,w,k,z)
Inputs are 4-tuples of ints, return is a 5-tuple of ints.  See
pegasus_graph for description of the pegasus_index and "nice"
coordinate systems.

Returns
-------
pegasus_to_nice_fn(chimera_coordinates): a function
A function that accepts augmented chimera coordinates and returns corresponding
Pegasus coordinates.
"""
if args or kwargs:
msg = "get_pegasus_to_nice_fn does not need / use parameters anymore"
warnings.warn(msg, DeprecationWarning)

msg = ('get_pegasus_to_nice_fn is deprecated and will be removed in '
warnings.warn(msg, DeprecationWarning)

return lambda *args: pegasus_coordinates.pegasus_to_nice(args)

# maintained for backwards compatibility
def get_nice_to_pegasus_fn(*args, **kwargs):
"""
Returns a coordinate translation function from the 5-term "nice"
coordinates to the 4-term pegasus_index coordinates.

Details on the returned function, nice_to_pegasus(t, y, x, u, k)
Inputs are 5-tuples of ints, return is a 4-tuple of ints.  See
pegasus_graph for description of the pegasus_index and "nice"
coordinate systems.

Returns
-------
nice_to_pegasus_fn(pegasus_coordinates): a function
A function that accepts Pegasus coordinates and returns the corresponding
augmented chimera coordinates
"""
if args or kwargs:
msg = "get_pegasus_to_nice_fn does not need / use parameters anymore"
warnings.warn(msg, DeprecationWarning)

msg = ('get_nice_to_pegasus_fn is deprecated and will be removed in '