def getTreeAsNet(self,thinning=True): if thinning: newNet=pynet.SymmNet() for edge in self.net.edges: newNet[edge[0],edge[1]]=1#edge[2] for node in self.net: if newNet[node].deg()==2: l=list(newNet[node]) n1=self._getNodeByName(l[0]) n2=self._getNodeByName(l[1]) me=self._getNodeByName(node) if n1[0]<me[0]: parent=n1 child=n2 else: parent=n2 child=n1 if newNet[self._getNameByNode(parent)].deg()==2: newNet[node,l[0]]=0 newNet[node,l[1]]=0 newNet[self._getNameByNode(parent),self._getNameByNode(child)]=1 deletedNodes=[] for node in newNet: if newNet[node].deg()==0: deletedNodes.append(node) for node in deletedNodes: del newNet[node] return newNet else: return self.net
def dist_to_weights(net, epsilon=0.001): '''Transforms a distance matrix / network to a weight matrix / network using the formula W = 1 - D / max(D). Returns a matrix/network''' N = len(net._nodes) if (isinstance(net, pynet.SymmFullNet)): newmat = pynet.SymmFullNet(N) else: newmat = pynet.SymmNet() edges = list(net.edges) maxd = 0.0 for edge in edges: if edge[2] > maxd: maxd = edge[2] # epsilon trick; lowest weight will be almost but # not entirely zero maxd = maxd + epsilon for edge in edges: if not (edge[2] == maxd): newmat[edge[0]][edge[1]] = 1 - edge[2] / maxd netext.copyNodeProperties(net, newmat) return newmat
def __iter__(self): if self.buildNet: if self.symmetricNet: net = pynet.SymmNet() else: net = pynet.Net() if self.nodes == None: ktree = Ktree() else: ktree = Ktree(size=len(self.nodes), nodeNames=self.nodes) for edge in self.edges: if isinstance(edge, EvaluationEvent): if self.returnKtree: ktree.threshold = edge.threshold ktree.addedEdges = edge.addedElements yield ktree else: cs = ktree.getCommStruct() cs.threshold = edge.threshold cs.addedEdges = edge.addedElements if self.buildNet: cs.net = net yield cs else: ktree.addEdge(edge) if self.buildNet: net[edge[0], edge[1]] = edge[2]
def __init__(self,cslist): cslist.reverse() self.tree=[] self.cslist=cslist self.tree.append([]) self.net=pynet.SymmNet() #self.multiplier=10**ceil(np.log10(len(cslist))) #add roots: for c in cslist[0]: self.tree[0].append(None) #go through each level and add links: for thisLevel in range(1,len(cslist)): self.tree.append([]) for communityIndex in range(0,len(cslist[thisLevel])): self.tree[thisLevel].append(None) for fatherIndex in range(0,len(cslist[thisLevel-1])): if cslist[thisLevel][communityIndex]<=cslist[thisLevel-1][fatherIndex]: bestFatherIndex=fatherIndex #there might be multiple candidates for father, choose the smallest one self.tree[thisLevel][communityIndex]=bestFatherIndex self.net[self._getNameByNode((thisLevel,communityIndex)),self._getNameByNode((thisLevel-1,bestFatherIndex))]=len(cslist)+1-thisLevel cslist.reverse()
def _getOverlapNetwork(self, other): """Create a bipartite network from overlapping nodes. Nodes correspond to communities and edge weight is the number of common nodes between the two communities. Nodes [0 ... len(self)-1] correspond to communities in self, and nodes [len(self) ... len(self)+len(other)] correspond to communities in `other`. """ # Find out the number of nodes. N = max(self.N_nodes, other.N_nodes) # Construct community ID dictionaries. cID_A = self.getCommIDs() cID_B = other.getCommIDs() # Construct the bipartite community net. Nc_A = len(self.comm) commNet = pynet.SymmNet() for node in xrange(N): try: for cj in cID_B[node]: cj += Nc_A for ci in cID_A[node]: commNet[ci, cj] += 1 except AttributeError: # `node` does not occur in one of the node # covers. Skip this node. continue return commNet
def getCommunityNetwork(self, net): """ Builds a network where each node is a node set in this node cover, and there is an edge between two nodes if there is an edge in the network given as a parameter between two nodes in the two node sets. The weight of each edge is the sum of weights of such edges. Todo: tests """ newNet = pynet.SymmNet() commID = self.getCommIDs() #First add the nodes for node in range(len(self)): #communities are named from 0 to n-1 newNet.addNode(node) #Then add the edges by going through the edges in the underlying net for edge in net.edges: if edge[0] in commID and edge[ 1] in commID: #node might not belong to a community for newNode1 in commID[edge[0]]: for newNode2 in commID[edge[1]]: if newNode1 != newNode2: #no self-edges newNet[newNode1, newNode2] += edge[2] return newNet
def getBetweennessCentrality(net, edgeBC=False): """ Returns a map from each node to its unweighted betweenness centrality. Note: this function needs some more testing. """ #Implementation of the algorithm found in this paper: #www.inf.uni-konstanz.de/algo/publications/b-fabc-01.pdf cb = {} for node in net: cb[node] = 0 if edgeBC: bcNet = pynet.SymmNet() for node in net: st = [] p = {} sigma = {} d = {} delta = {} for t in net: p[t] = [] sigma[t] = 0 d[t] = -1 delta[t] = 0 sigma[node] = 1 d[node] = 0 q = [node] while len(q) > 0: v = q.pop(0) st.append(v) for w in net[v]: if d[w] < 0: q.append(w) d[w] = d[v] + 1 if d[w] == d[v] + 1: sigma[w] += sigma[v] p[w].append(v) while len(st) > 0: w = st.pop() for v in p[w]: partialDelta = float(sigma[v]) / float( sigma[w]) * (1 + delta[w]) delta[v] += partialDelta if edgeBC: bcNet[v, w] = bcNet[v, w] + partialDelta if w != node: cb[w] += delta[w] for node in cb: cb[node] = cb[node] / 2.0 if edgeBC: for e in bcNet.edges: bcNet[e[0], e[1]] = e[2] / 2.0 return cb, bcNet else: return cb
def kcliquesByEdges(edges, k): """Generate k-cliques from edges. Generator function that generates a list of cliques of size k in the order they are formed when edges are added in the order defined by the `edges` argument. If many cliques are formed by adding one edge, the order of the cliques is arbitrary. This generator will pass through any EvaluationEvent objects that are passed to it in the `edges`. Parameters ---------- edges : iterable with elements (node_1, node_2, weight) The edges that form the network. `edges` may also contain EvaluationEvent objects, which are simply passed through. k : int, >= 3 The function returns `k`-cliques, that is, induced full subnets of `k` nodes. Yield ----- kclique : KClique object When a new k-clique is formed, it is returned as a KClique object. Notes ----- If an edge is included in `edges` multiple times, all k-cliques in the network constructed so far will be returned every time. Most of the time this is not what is wanted, so take care not the supply multiple edges. (LK 31.7.2009) """ newNet = pynet.SymmNet() # Edges are added to an empty network one by one for edge in edges: if isinstance(edge, EvaluationEvent): yield edge else: # First we find all new triangles that are born when the new edge is added triangleEnds = set( ) # We keep track of the tip nodes of the new triangles for adjacentNode in newNet[ edge[0]]: # Neighbor of one node of the edge ... if newNet[adjacentNode, edge[ 1]] != 0: #...is a neighbor of the other node of the edge... triangleEnds.add( adjacentNode ) #...then the neighbor is a tip of a new triangle # New k-cliques are now (k-2)-cliques at the triangle end points plus # the two nodes at the tips of the edge we are adding to the network for kclique in kcliquesAtSubnet(triangleEnds, newNet, k - 2): yield kclique + KClique([edge[0], edge[1]]) # Finally we add the new edge to the network. newNet[edge[0], edge[1]] = edge[2]
def makeER(n, p): """ Make a realisation of Erdos-Renyi network * fast for non-sparse networks * the sparce version should be implemented """ net = pynet.SymmNet() for i in range(0, n): for j in range(0, i): if p > np.random.ranf(): net[i, j] = 1 return net
def linearLattice(n, r): """Linear lattice with periodic boundary conditions. Two nodes are connected if they are at most r steps away from each other in the lattice. """ net = pynet.SymmNet() if r >= n: r = n - 1 for i in range(n): for ri in range(r): net[i, (i + ri + 1) % n] = 1 net[i, (i - 1 - ri) % n] = 1 return net
def getKRCliqueNet(net, k): """ Returns a network of (k-1)-cliques, which are subcliques of some k-clique in the network given as a parameter. Two (k-1)-cliques are adjacent if they are subcliques of the same k-clique. """ krnet = pynet.SymmNet() for kclique in kcliquesByEdges(net.edges, k): krcliques = list(kclique.getSubcliques()) for krclique in krcliques: for krclique2 in krcliques: krnet[krclique][krclique2] = 1 return krnet
def collapseBipartiteNet(net, nodesToRemove): """ Returns an unipartite projection of a bipartite network. """ newNet = pynet.SymmNet() for node in nodesToRemove: degree = float(net[node].deg()) for node1 in net[node]: for node2 in net[node]: if node1.__hash__() > node2.__hash__(): newNet[node1, node2] = newNet[node1, node2] + 1.0 / degree netext.copyNodeProperties(net, newNet) return newNet
def sumNets(nets): """Aggregates networks defined in nets (list of SymmNets) by summing up edge weights between nodes in all nets""" newNet = pynet.SymmNet() for currnet in nets: curr_edges = list(currnet.edges) for edge in curr_edges: newNet[edge[0], edge[1]] += edge[2] return newNet
def _getOverlapNetwork(self, other): """Create a bipartite network from overlapping nodes. In the overlap network the nodes correspond to communities and edge weight is the number of common nodes between two communities. Nodes [0 ... len(self)-1] correspond to communities in self, and nodes [len(self) ... len(self)+len(otherCover)] correspond to communities in `other`. """ commNet = pynet.SymmNet() for node in self._commIDs: ci = self._commIDs[node] cj = len(self) + other._commIDs[node] commNet[ci, cj] += 1 return commNet
def mst_kruskal(net, randomize=True, maximum=False): """Find a minimum/maximum spanning tree using Kruskal's algorithm If random is set to true and the mst is not unique, a random mst is chosen. >>> t=pynet.SymmNet() >>> t[1,2]=1 >>> t[2,3]=2 >>> t[3,1]=3 >>> m=mst_kruskal(t) >>> print m.edges [[1, 2, 1], [2, 3, 2]] """ edges = list(net.edges) if randomize: random.shuffle( edges) #the sort has been stable since python version 2.3 edges.sort(lambda x, y: cmp(x[2], y[2]), reverse=maximum) mst = pynet.SymmNet() numberOfNodes = len(net) #ktree=percolator.Ktree(numberOfNodes) ktree = percolator.Ktree() #just use dict addedEdges = 0 for edge in edges: if ktree.getParent(edge[0]) != ktree.getParent(edge[1]): mst[edge[0], edge[1]] = edge[2] ktree.setParent(edge[0], edge[1]) addedEdges += 1 if addedEdges == numberOfNodes - 1: #the mst is a tree netext.copyNodeProperties(net, mst) return mst # else it is a forest netext.copyNodeProperties(net, mst) return mst
def getKCliqueBipartiteNet(net, k): """ Returns a bipartite network where to partitions are k-cliques and (k-1)-cliques in the net that is given as a parameter. There is a link between a k-clique and (k-1)-clique if the (k-1)-clique is a subclique of the k-clique. """ kcliques = set() krcliques = set() kbinet = pynet.SymmNet() for kclique in kcliquesByEdges(net.edges, k): kcliques.add(kclique) for krclique in kclique.getSubcliques(): krcliques.add(krclique) kbinet[kclique, krclique] = 1 return kbinet, kcliques, krcliques
def local_threshold_by_value(net, threshold): '''Generates a new network by thresholding the input network. Inputs: net = network, threshold = threshold value, mode = 0 (accept weights < threshold), 1 (accept weights > threshold) Returns a network. Note! threshold is really alpha which is defined in "Extracting the multiscale backbone of complex weighted networks" http://www.pnas.org/content/106/16/6483.full.pdf''' newnet = pynet.SymmNet() for node in net: s = net[node].strength() k = net[node].deg() for neigh in net[node]: w = net[node, neigh] if (1 - w / s)**(k - 1) < threshold: newnet[node, neigh] = w netext.copyNodeProperties(net, newnet) return newnet
def threshold_by_value(net, threshold, accept="<", keepIsolatedNodes=False): '''Generates a new network by thresholding the input network. If using option keepIsolatedNodes=True, all nodes in the original network will be included in the thresholded network; otherwise only those nodes which have links will remain (this is the default). Inputs: net = network, threshold = threshold value, accept = "foobar": accept weights foobar threshold (e.g accept = "<": accept weights < threshold) Returns a network.''' newnet = pynet.SymmNet() edges = list(net.edges) if accept == "<": for edge in edges: if (edge[2] < threshold): newnet[edge[0], edge[1]] = edge[2] elif accept == ">": for edge in edges: if (edge[2] > threshold): newnet[edge[0], edge[1]] = edge[2] elif accept == ">=": for edge in edges: if (edge[2] >= threshold): newnet[edge[0], edge[1]] = edge[2] elif accept == "<=": for edge in edges: if (edge[2] <= threshold): newnet[edge[0], edge[1]] = edge[2] else: raise Exception( "Parameter 'accept' must be either '<', '>', '<=' or '>='.") # Add isolated nodes to the network. if keepIsolatedNodes == True: for node in net: if not newnet.__contains__(node): newnet.addNode(node) netext.copyNodeProperties(net, newnet) return newnet
def loadNet_gml(input): """ Reads a networks data from input in gml format. Note: This is not a complete gml-parser, because gml format can hold almost anykind of data. Instead this parser tries to find edges of one graph from the given input. Use at you own risk with complicated gml-files. """ source = None target = None value = None for line in input: line = line.strip() if line.startswith("directed"): if line[9:10] == "0": net = pynet.SymmNet() elif line[9:10] == "1": net = pynet.Net() elif line.startswith("source"): source = line.split()[1] elif line.startswith("target"): target = line.split()[1] elif line.startswith("value"): value = line.split()[1] elif line.startswith("edge"): if source != None and target != None: if value != None: net[source][target] = float(value) else: net[source][target] = 1 source = None target = None value = None if source != None and target != None: if value != None: net[source][target] = float(value) else: net[source][target] = 1 return net
def makeSparseER(n, p): """ Make a realisation of Erdos-Renyi network * fast for sparse networks * 0 < p < 1 * Algorithm: Efficient generation of large random networks Phys. Rev. E 71, 036113 (2005) """ net = pynet.SymmNet() v = 1 w = -1 while (v < n): r = np.random.ranf() w = w + 1 + int(np.floor(np.log(1 - r) / np.log(1 - p))) while ((w >= v) and (v < n)): w = w - v v = v + 1 if (v < n): net[v, w] = 1 return net
def girvanNewman(communitySize, numberOfCommunities, kIn, kOut): """ A network model producing equally sized communities with equal expected link density inside the communities and between the communities. The model was first defined in the article: M. Girvan and M.E.J. Newman: Community structure in social and biological networks, PNAS 99, 7821 (2002) Parameters ---------- communitySize : int Size of a single community in nodes. numberOfCommunities : int Number of communities kIn : int Expected value for inside community node degrees. That is, the expected number of links from each node going to other nodes in the same community. This parameter is used to calculate the probability of links inside communities. If kIn > communitySize-1, then kIn is set to communitySize-1. kOut : int Expected value for outside community node degrees. That is, the expected number of links from each node going to nodes in other communities. This parameter is used to calculate the probability of links between communities. If kOut > (numberOfCommunities-1)*communitySize, then kOut is set to (numberOfCommunities-1)*communitySize. Return ------ net : SymmNet A realisation of the model network. Complexity ---------- For a network with N nodes: Time complexity: O(N**2) Memory complexity: Memory used by the returned network object. Time complexity can be improved for sparse networks. """ #Calculate pIn and pOut from kIn and kOut if (communitySize - 1) < kIn: kIn = communitySize - 1 if (numberOfCommunities - 1) * communitySize < kOut: kOut = (numberOfCommunities - 1) * communitySize pIn = float(kIn) / float(communitySize - 1) if numberOfCommunities > 1: pOut = float(kOut) / float((numberOfCommunities - 1) * communitySize) else: pOut = 0.0 net = pynet.SymmNet() #the net object to be returned #First, put the internal edges: for communityIndex in range(numberOfCommunities): for node1Index in range(communitySize): for node2Index in range(node1Index + 1, communitySize): if pIn > np.random.ranf(): net[communityIndex * communitySize + node1Index, communityIndex * communitySize + node2Index] = 1 #Second, put the external edges: for community1Index in range(numberOfCommunities): for community2Index in range(community1Index + 1, numberOfCommunities): for node1Index in range(communitySize): for node2Index in range(communitySize): if pOut > np.random.ranf(): net[community1Index * communitySize + node1Index, community2Index * communitySize + node2Index] = 1 return net
def loadNet_edg(input, mutualEdges=False, splitterChar=None, symmetricNet=True, numerical=None, allowSelfEdges=True, hasHeaderLine=False): """Read network data from input in edg format. If `mutualEdges` is set to True, an edge is added between nodes i and j only if both edges (i,j) and (j,i) are listed. The weight of the edge is the sum of the weights of the original edges. If the same edge is encountered multiple times, the edge weight will be the sum of all weights. If 'allowSelfEdges', the self edges are translated as nodes with no edges. Otherwise those edges are just thrown out. If hasHeaderLine is True the first line is skipped, if it is False the first line is read normally, and if it is None the first line is skanned for "from to weight" string to decide if it is a header line. """ def isNumerical(input): try: for line in input: int(line.split(splitterChar)[0]) int(line.split(splitterChar)[1]) except ValueError: input.seek(0) return False input.seek(0) return True if numerical is None: numerical = isNumerical(input) if symmetricNet: newNet = pynet.SymmNet() else: newNet = pynet.Net() #Check for headers possibleHeaders = [["from", "to", "weight"], ["head", "tail", "weight"]] if hasHeaderLine != False: firstLine = input.readline().strip().lower() fields = firstLine.split(splitterChar) if hasHeaderLine == None and fields not in possibleHeaders: input.seek(0) if hasHeaderLine == True: input.seek(0) nodeMap = {} # Used only if mutualEdges = True. for line in input: fields = line.split(splitterChar) if len(fields) > 1: if len(fields) == 2: #if weight is missing: fields[1] = fields[1].strip( '\n') #strip the endline from the node name fields.append(1) #add one as the weight if fields[0] != fields[1] or allowSelfEdges: if numerical: fields[0] = int(fields[0]) fields[1] = int(fields[1]) if fields[0] != fields[1]: if mutualEdges: if nodeMap.has_key((fields[1], fields[0])): newNet[fields[0]][fields[1]] += nodeMap[( fields[1], fields[0])] newNet[fields[0]][fields[1]] += float(fields[2]) nodeMap[(fields[1], fields[0])] = 0 nodeMap[(fields[0], fields[1])] = 0 else: nodeMap[(fields[0], fields[1])] = nodeMap.get( (fields[0], fields[1]), 0) + float(fields[2]) else: newNet[fields[0]][fields[1]] += float(fields[2]) else: newNet.addNode(fields[0]) return newNet
def fullNet(nodes,weight=1): net=pynet.SymmNet() for node1 in nodes: for node2 in nodes: net[node1,node2]=weight return net
def netConfiguration(net, keepsOrigNet=False, seed=None): """Generate configuration network This function generates a configuration network from any arbitrary net. It retains the degree of each node but randomize the edges between them. Parameters ---------- net : pynet.SymmNet object The network to be used as the basis for the configuration model. keepsOrigNet : bool (default: False) If False, the input network, `net`, will be overwritten by the configuration network. seed : int (default: None) A seed for the random number generator. If None, the RNG is not be re-initialized but the current state is used. Return ------ configuration_net : pynet.SymmNet object The shuffled network. Note that if `keepsOrigNet` is False, the returned value will be identical to `net`. """ if seed is not None: random.seed(int(seed)) newNet = pynet.SymmNet() if keepsOrigNet: testNet = pynet.SymmNet() for edge in net.edges: testNet[edge[0], edge[1]] = edge[2] else: testNet = net edgeList = list(net.edges) for i in range(len(edgeList)): j = i while j == i: j = random.randint(0, len(edgeList) - 1) if ((edgeList[i][1] == edgeList[j][0]) or (edgeList[j][1] == edgeList[i][0])): continue if ((edgeList[i][1] == edgeList[j][0]) and (edgeList[j][1] == edgeList[i][0])): continue if ((edgeList[i][0] == edgeList[j][0]) or (edgeList[i][1] == edgeList[j][1])): continue if ((newNet[edgeList[i][0], edgeList[j][1]] > 0.0) or (newNet[edgeList[j][0], edgeList[i][1]] > 0.0)): continue if ((testNet[edgeList[i][0], edgeList[j][1]] > 0.0) or (testNet[edgeList[j][0], edgeList[i][1]] > 0.0)): continue edgeList[i][1] += edgeList[j][1] edgeList[j][1] = edgeList[i][1] - edgeList[j][1] edgeList[i][1] = edgeList[i][1] - edgeList[j][1] newNet[edgeList[i][0], edgeList[j][1]] = 0.0 newNet[edgeList[j][0], edgeList[i][1]] = 0.0 testNet[edgeList[i][0], edgeList[j][1]] = 0.0 testNet[edgeList[j][0], edgeList[i][1]] = 0.0 newNet[edgeList[i][0], edgeList[i][1]] = 1.0 newNet[edgeList[j][0], edgeList[j][1]] = 1.0 testNet[edgeList[i][0], edgeList[i][1]] = 1.0 testNet[edgeList[j][0], edgeList[j][1]] = 1.0 return newNet
def getLineGraph(net, useWeights=False, output=None, format='edg'): """Return a line graph constructed from `net`. The nodes in the line graph correspond to edges in the original graph, and there is an edge between two nodes if they have a common incident node in the original graph. If weights are not used (`useWeights = False`), the resulting network will be undirected and the weight of each new edge will be 1/(k_i-1), where k_i is the degree of the common node in `net`. If weights are used (`useWeights = True`), the resulting network will be directed and the weight of edge (e_ij, e_jk) will be w_jk/sum_{x != i} w_jx, where the indices i, j and k refer to nodes in `net`. Parameters ---------- net : pynet.SymmNet object The original graph that is used for constructing the line graph. useWeights : boolean If True, the edge weights will be used when constructing the line graph. output : file object If given, the edges will be written to output in edg-format instead of returning a pynet.Net() or pynet.SymmNet() object. format : str, 'edg' or 'net' If `output` is specified, `format` specifies how the output is written. 'edg' is the standard edge format (FROM TO WEIGHT) and 'net' gives the Pajek format. Return ------ IF `output` is None: linegraph : pynet.SymmNet or pynet.Net object The weighted line graph. id_array : numpy.array with shape (len(net.edges), 2) Array for converting the nodes in the line graph back into the edges of the original graph. id_array[EDGE_ID] contains the two end nodes of given edge, where EDGE_ID is the same as used in `linegraph`. """ if output is None: if useWeights: linegraph = pynet.Net() else: linegraph = pynet.SymmNet() edge_map = dict() # edge_map[sorted([n_i, n_j])] = new_node_ID if output is not None and format == 'net': # Print Pajek file header. N_edges = len(list(net.edges)) output.write("*Vertices %d\n" % N_edges) for i in range(N_edges): output.write('%d "%d"\n' % (i, i)) N_edge_links = 0 for n in net: degree = len(list(net[n])) N_edge_links += (degree * (degree - 1)) / 2 if useWeights: output.write("*Arcs %d\n" % (2 * N_edge_links, )) else: output.write("*Edges %d\n" % N_edge_links) # Go through all nodes (n_c = center node), and for each node, go # through all pairs of neighbours (n_i and n_j). The edges # e_i=(n_c,n_i) and e_j=(n_c,n_j) are nodes in the line graph, so # we add a link between them. for n_c in net: strength = net[n_c].strength() nb = list(net[n_c]) # List of neighbours for i, n_i in enumerate(nb): e_i = edge_map.setdefault(tuple(sorted([n_c, n_i])), len(edge_map)) other_nb = (nb[:i] + nb[i + 1:] if useWeights else nb[i + 1:]) for n_j in other_nb: e_j = edge_map.setdefault(tuple(sorted([n_c, n_j])), len(edge_map)) if useWeights: w = net[n_c][n_j] / (strength - net[n_c][n_i]) else: w = 1.0 / (len(nb) - 1) if output is None: linegraph[e_i][e_j] = w else: output.write(" ".join(map(str, [e_i, e_j, w])) + "\n") # Construct id_array from edge_map id_array = np.zeros((len(edge_map), 2), int) for node_pair, edgeID in edge_map.iteritems(): id_array[edgeID] = list(node_pair) if output is None: return linegraph, id_array else: return id_array