def preferentialAttachment(topo_list, scalar=1.0, **kwargs): """ Creates links between topologies in the topo_list. For each pair of topologies t1, t2 in the list a pair of nodes (a in t1), (b in t2) will get an edge with probability given by: (scalar/2) * (a.degree + b.degree) / (t1.numEdges + t2.numEdges) Each entry in topo_list should be a (name, topo, filter) tuple. The name will be used as the prefix for the nodes in that sub-topology. This is necessary because two nodes in different topologies may have the same name, and there must be no two nodes with the same name in the final generated topology. The filter will be used to select nodes which may be candidates for attachment in each graph. The filter should be a set of labels; any nodes with one or more of those labels may be used in attachment. If filter is None, it will be treated as the universal set. Any extra kwargs provided will be passed to the addEdge function when inter-topology links are created. """ # create deep copies of input topos because we will modify the nodes # we will also use this dict to map a topology to its prefix t = {x.copy(): (n,f) for n,x,f in topo_list} # create new empty topology, then copy nodes and edges from components tm = Topology() tm.nodeSet = set.union(*[x.nodeSet for x in t]) tm.edgeSet = set.union(*[x.edgeSet for x in t]) # add prefixes to node names for c in t: for a in c.nodeSet: a.name = t[c][0] + "_" + a.name # returns the probability that a, b should have an edge based on the # degrees of a and b and total edges in t1 and t2 def p(a, b, t1, t2): if t[t1][1] != None and t[t1][1] & a.labels == set(): return 0.0 if t[t2][1] != None and t[t2][1] & b.labels == set(): return 0.0 return scalar/2.0 * (a.getDegree() + b.getDegree()) / (len(t1.edgeSet) + len(t2.edgeSet)) # add edge with probability p for each pair of nodes (a, b) for t1, t2 in itertools.combinations(t, 2): for a, b in itertools.product(t1.nodeSet, t2.nodeSet): if random.random() < p(a, b, t1, t2): tm.addEdge(a, b, **kwargs) return tm