# -*- coding: utf-8 -*- """ Generators for random graphs """ # Copyright (C) 2004,2005,2006 by # Aric Hagberg # Dan Schult # Pieter Swart # Distributed under the terms of the GNU Lesser General Public License # http://www.gnu.org/copyleft/lesser.html __author__ = """Aric Hagberg (hagberg@lanl.gov)\nPieter Swart (swart@lanl.gov)\nDan Schult(dschult@colgate.edu)""" __date__ = "$Date: 2005-06-17 08:06:22 -0600 (Fri, 17 Jun 2005) $" __credits__ = """""" __revision__ = "$Revision: 1049 $" import random import math import networkx from networkx.generators.classic import empty_graph, path_graph, complete_graph #------------------------------------------------------------------------- # Some Famous Random Graphs #------------------------------------------------------------------------- def fast_gnp_random_graph(n, p, seed=None): """ Return a random graph G_{n,p}. The G_{n,p} graph choses each of the possible [n(n-1)]/2 edges with probability p. Sometimes called Erdős-Rényi graph, or binomial graph. :Parameters: - `n`: the number of nodes - `p`: probability for edge creation - `seed`: seed for random number generator (default=None) This algorithm is O(n+m) where m is the expected number of edges m=p*n*(n-1)/2. It should be faster than gnp_random_graph when p is small, and the expected number of edges is small, (sparse graph). See: Batagelj and Brandes, "Efficient generation of large random networks", Phys. Rev. E, 71, 036113, 2005. """ G=empty_graph(n) G.name="fast_gnp_random_graph(%s,%s)"%(n,p) if not seed is None: random.seed(seed) v=1 # Nodes in graph are from 0,n-1 (this is the second node index). w=-1 lp=math.log(1.0-p) while v=v and v=mmax: G=complete_graph(n) else: G=empty_graph(n) G.name="dense_gnm_random_graph(%s,%s)"%(n,m) if n==1 or m>=mmax: return G if seed is not None: random.seed(seed) u=0 v=1 t=0 k=0 while True: if random.randrange(mmax-t)=n*(n-1)/2: return complete_graph(n) nlist=G.nodes() edge_count=0 while edge_count < m: # generate random edge,u,v u = random.choice(nlist) v = random.choice(nlist) if u==v or G.has_edge(u,v): continue # is this faster? # (u,v)=random.sample(nlist,2) # if G.has_edge(u,v): # continue else: G.add_edge(u,v) edge_count=edge_count+1 return G def newman_watts_strogatz_graph(n, k, p, seed=None): """ Return a Newman-Watts-Strogatz small world graph. First create a ring over n nodes. Then each node in the ring is connected with its k nearest neighbors. Then shortcuts are created by adding new edges as follows: for each edge u-v in the underlying "n-ring with k nearest neighbors"; with probability p add a new edge u-w with randomly-chosen existing node w. In contrast with watts_strogatz_graph(), no edges are removed. :Parameters: - `n`: the number of nodes - `k`: each node is connected to k nearest neighbors in ring topology - `p`: the probability of adding a new edge for each edge - `seed`: seed for random number generator (default=None) """ if seed is not None: random.seed(seed) G=empty_graph(n) G.name="newman_watts_strogatz_graph(%s,%s,%s)"%(n,k,p) nlist = G.nodes() fromv = nlist # connect the k/2 neighbors for n in range(1, k/2+1): tov = fromv[n:] + fromv[0:n] # the first n are now last for i in range(len(fromv)): G.add_edge(fromv[i], tov[i]) # for each edge u-v, with probability p, randomly select existing # node w and add new edge u-w e = G.edges() for (u, v) in e: if random.random() < p: w = random.choice(nlist) # no self-loops and reject if edge u-w exists # is that the correct NWS model? while w == u or G.has_edge(u, w): w = random.choice(nlist) G.add_edge(u,w) return G def watts_strogatz_graph(n, k, p, seed=None): """ Return a Watts-Strogatz small world graph. First create a ring over n nodes. Then each node in the ring is connected with its k nearest neighbors. Then shortcuts are created by rewiring existing edges as follows: for each edge u-v in the underlying "n-ring with k nearest neighbors"; with probability p replace u-v with a new edge u-w with randomly-chosen existing node w. In contrast with newman_watts_strogatz_graph(), the random rewiring does not increase the number of edges. :Parameters: - `n`: the number of nodes - `k`: each node is connected to k neighbors in the ring topology - `p`: the probability of rewiring an edge - `seed`: seed for random number generator (default=None) """ if seed is not None: random.seed(seed) G=empty_graph(n) G.name="watts_strogatz_graph(%s,%s,%s)"%(n,k,p) nlist = G.nodes() fromv = nlist # connect the k/2 neighbors for n in range(1, k/2+1): tov = fromv[n:] + fromv[0:n] # the first n are now last for i in range(len(fromv)): G.add_edge(fromv[i], tov[i]) # for each edge u-v, with probability p, randomly replace with # edge u-w e = G.edges() for (u, v) in e: if random.random() < p: newv = random.choice(nlist) # avoid self-loops and reject if edge u-newv exists # is that the correct WS model? while newv == u or G.has_edge(u, newv): newv = random.choice(nlist) G.delete_edge(u,v) # conserve number of edges G.add_edge(u,newv) return G def random_regular_graph(d, n, seed=None): """Return a random regular graph of n nodes each with degree d, G_{n,d}. Return False if unsuccessful. n*d must be even Nodes are numbered 0...n-1. To get a uniform sample from the space of random graphs you should chose d 0: source=random.choice(stubs) target=random.choice(stubs) if source!=target and not seen_edges[source].has_key(target): stubs.remove(source) stubs.remove(target) seen_edges[source][target]=1 seen_edges[target][source]=1 G.add_edge(source,target) else: # further check to see if suitable s=_suitable(stubs) if not s: return False return G def barabasi_albert_graph(n , m, seed=None): """Return random graph using Barabási-Albert preferential attachment model. A graph of n nodes is grown by attaching new nodes each with m edges that are preferentially attached to existing nodes with high degree. :Parameters: - `n`: the number of nodes - `m`: number of edges to attach from a new node to existing nodes - `seed`: seed for random number generator (default=None) The initialization is a graph with with m nodes and no edges. Reference:: @article{barabasi-1999-emergence, title = {Emergence of scaling in random networks}, author = {A. L. Barabási and R. Albert}, journal = {Science}, volume = {286}, number = {5439}, pages = {509 -- 512}, year = {1999}, } """ if m < 1 or n < m: raise networkx.NetworkXError,\ "NetworkXError must have m>1 and m1 and m 1 or p < 0: raise networkx.NetworkXError,\ "NetworkXError p must be in [0,1], p=%f"%(p) if seed is not None: random.seed(seed) G=empty_graph(m) # add m initial nodes (m0 in barabasi-speak) G.name="Powerlaw-Cluster Graph" repeated_nodes=G.nodes() # list of existing nodes to sample from # with nodes repeated once for each adjacent edge source=m # next node is m while source>> constructor=[(10,20,0.8),(20,40,0.8)] >>> G=random_shell_graph(constructor) """ G=empty_graph(0) G.name="random_shell_graph(constructor)" if seed is not None: random.seed(seed) glist=[] intra_edges=[] nnodes=0 # create gnm graphs for each shell for (n,m,d) in constructor: inter_edges=int(m*d) intra_edges.append(m-inter_edges) g=networkx.operators.convert_node_labels_to_integers( gnm_random_graph(n,inter_edges), first_label=nnodes) glist.append(g) nnodes+=n G=networkx.operators.union(G,g) # connect the shells randomly for gi in range(len(glist)-1): nlist1=glist[gi].nodes() nlist2=glist[gi+1].nodes() total_edges=intra_edges[gi] edge_count=0 while edge_count < total_edges: u = random.choice(nlist1) v = random.choice(nlist2) if u==v or G.has_edge(u,v): continue else: G.add_edge(u,v) edge_count=edge_count+1 return G def random_powerlaw_tree(n, gamma=3, seed=None, tries=100): """ Return a tree with a powerlaw degree distribution. A trial powerlaw degree sequence is chosen and then elements are swapped with new elements from a powerlaw distribution until the sequence makes a tree (#edges=#nodes-1). :Parameters: - `n`: the number of nodes - `gamma`: exponent of power law is gamma - `tries`: number of attempts to adjust sequence to make a tree - `seed`: seed for random number generator (default=None) """ from networkx.generators.degree_seq import degree_sequence_tree try: s=random_powerlaw_tree_sequence(n, gamma=gamma, seed=seed, tries=tries) except: raise networkx.NetworkXError,\ "Exceeded max (%d) attempts for a valid tree sequence."%tries G=degree_sequence_tree(s) G.name="random_powerlaw_tree(%s,%s)"%(n,gamma) return G def random_powerlaw_tree_sequence(n, gamma=3, seed=None, tries=100): """ Return a degree sequence for a tree with a powerlaw distribution. A trial powerlaw degree sequence is chosen and then elements are swapped with new elements from a powerlaw distribution until the sequence makes a tree (#edges=#nodes-1). :Parameters: - `n`: the number of nodes - `gamma`: exponent of power law is gamma - `tries`: number of attempts to adjust sequence to make a tree - `seed`: seed for random number generator (default=None) """ if seed is not None: random.seed(seed) # get trial sequence z=networkx.utils.powerlaw_sequence(n,exponent=gamma) # round to integer values in the range [0,n] zseq=[min(n, max( int(round(s)),0 )) for s in z] # another sequence to swap values from z=networkx.utils.powerlaw_sequence(tries,exponent=gamma) # round to integer values in the range [0,n] swap=[min(n, max( int(round(s)),0 )) for s in z] for deg in swap: if n-sum(zseq)/2.0 == 1.0: # is a tree, return sequence return zseq index=random.randint(0,n-1) zseq[index]=swap.pop() raise networkx.NetworkXError, \ "Exceeded max (%d) attempts for a valid tree sequence."%tries return False def _test_suite(): import doctest suite = doctest.DocFileSuite('tests/generators/random_graphs.txt',package='networkx') return suite if __name__ == "__main__": import os import sys import unittest if sys.version_info[:2] < (2, 4): print "Python version 2.4 or later required for tests (%d.%d detected)." % sys.version_info[:2] sys.exit(-1) # directory of networkx package (relative to this) nxbase=sys.path[0]+os.sep+os.pardir sys.path.insert(0,nxbase) # prepend to search path unittest.TextTestRunner().run(_test_suite())