def star_topology(n): """ Return a star (a.k.a hub-and-spoke) topology of :math:`n+1` nodes The root (hub) node has id 0 while all leaf (spoke) nodes have id :math:`(1, n+1)`. Each node has the attribute type which can either be *root* (for node 0) or *leaf* for all other nodes Parameters ---------- n : int The number of leaf nodes Returns ------- topology : A Topology object """ if not isinstance(n, int): raise TypeError('n argument must be of int type') if n < 1: raise ValueError('n argument must be a positive integer') G = Topology(nx.star_graph(n)) G.name = "star_topology(%d)" % (n) G.graph['type'] = 'star' G.node[0]['type'] = 'root' for v in range(1, n + 1): G.node[v]['type'] = 'leaf' return G
def parse_ashiip(path): """ Parse a topology from an output file generated by the aShiip topology generator Parameters ---------- path : str The path to the aShiip output file Returns ------- topology : Topology """ topology = Topology(type='ashiip') for line in open(path, "r").readlines(): # There is no documented aShiip format but we assume that if the line # does not start with a number it is not part of the topology if line[0].isdigit(): node_ids = re.findall("\d+", line) if len(node_ids) < 3: raise ValueError('Invalid input file. Parsing failed while '\ 'trying to parse a line') node = int(node_ids[0]) level = int(node_ids[1]) topology.add_node(node, level=level) for i in range(2, len(node_ids)): topology.add_edge(node, int(node_ids[i])) return topology
def from_mininet(topology): """Convert a Mininet topology to an FNSS one. Parameters ---------- topology : Mininet Topo A Mininet topology object Returns ------- topology : Topology An FNSS Topology object """ fnss_topo = Topology(capacity_unit='Mbps') for v in topology.switches(): fnss_topo.add_node(v, type='switch') for v in topology.hosts(): fnss_topo.add_node(v, type='host') for u, v in topology.links(): fnss_topo.add_edge(u, v) opts = topology.linkInfo(u, v) if 'bw' in opts: fnss_topo.edge[u][v]['capacity'] = opts['bw'] if 'delay' in opts: delay = opts['delay'] val = re.findall("\d+\.?\d*", delay)[0] unit = delay.strip(val).strip(' ') set_delays_constant(fnss_topo, val, unit, [(u,v)]) return fnss_topo
def k_ary_tree_topology(k, h): """ Return a balanced k-ary tree topology of with depth h Each node has two attributes: * type: which can either be *root*, *intermediate* or *leaf* * depth:math:`(0, h)` the height of the node in the tree, where 0 is the root and h are leaves. Parameters ---------- k : int The branching factor of the tree h : int The height or depth of the tree Returns ------- topology : A Topology object """ if not isinstance(k, int) or not isinstance(h, int): raise TypeError('k and h arguments must be of int type') if k <= 1: raise ValueError("Invalid k parameter. It should be > 1") if h < 1: raise ValueError("Invalid h parameter. It should be >=1") G = Topology(nx.balanced_tree(k, h)) G.name = "k_ary_tree_topology(%d,%d)" % (k, h) G.graph['type'] = 'tree' G.graph['k'] = k G.graph['h'] = h G.node[0]['type'] = 'root' G.node[0]['depth'] = 0 # Iterate through the tree to assign labels to nodes v = 1 for depth in range(1, h + 1): for _ in range(k**depth): G.node[v]['depth'] = depth if depth == h: G.node[v]['type'] = 'leaf' else: G.node[v]['type'] = 'intermediate' v += 1 return G
def k_ary_tree_topology(k, h): """ Return a balanced k-ary tree topology of with depth h Each node has two attributes: * type: which can either be *root*, *intermediate* or *leaf* * depth:math:`(0, h)` the height of the node in the tree, where 0 is the root and h are leaves. Parameters ---------- k : int The branching factor of the tree h : int The height or depth of the tree Returns ------- topology : A Topology object """ if not isinstance(k, int) or not isinstance(h, int): raise TypeError('k and h arguments must be of int type') if k <= 1: raise ValueError("Invalid k parameter. It should be > 1") if h < 1: raise ValueError("Invalid h parameter. It should be >=1") G = Topology(nx.balanced_tree(k, h)) G.name = "k_ary_tree_topology(%d,%d)" % (k, h) G.graph['type'] = 'tree' G.graph['k'] = k G.graph['h'] = h G.node[0]['type'] = 'root' G.node[0]['depth'] = 0 # Iterate through the tree to assign labels to nodes v = 1 for depth in range(1, h + 1): for _ in range(k**depth): G.node[v]['depth'] = depth G.node[v]['type'] = 'leaf' if (depth == h) else 'intermediate' v += 1 return G
def full_mesh_topology(n): """ Return a fully connected mesh topology of n nodes Parameters ---------- n : int The number of nodes Returns ------- topology : A Topology object """ if not isinstance(n, int): raise TypeError('n argument must be of int type') if n < 1: raise ValueError('n argument must be a positive integer') G = Topology(nx.complete_graph(n)) G.name = "full_mesh_topology(%d)" % (n) G.graph['type'] = 'full_mesh' return G
def line_topology(n): """ Return a line topology of n nodes Parameters ---------- n : int The number of nodes Returns ------- topology : A Topology object """ if not isinstance(n, int): raise TypeError('n argument must be of int type') if n < 1: raise ValueError('n argument must be a positive integer') G = Topology(nx.path_graph(n)) G.name = "line_topology(%d)" % (n) G.graph['type'] = 'line' return G
def erdos_renyi_topology(n, p, seed=None, fast=False): r"""Return a random graph :math:`G_{n,p}` (Erdos-Renyi graph, binomial graph). Chooses each of the possible edges with probability p. Parameters ---------- n : int The number of nodes. p : float Probability for edge creation. seed : int, optional Seed for random number generator (default=None). fast : boolean, optional Uses the algorithm proposed by [3]_, which is faster for small p References ---------- .. [1] P. Erdos and A. Renyi, On Random Graphs, Publ. Math. 6, 290 (1959). .. [2] E. N. Gilbert, Random Graphs, Ann. Math. Stat., 30, 1141 (1959). .. [3] Vladimir Batagelj and Ulrik Brandes, "Efficient generation of large random networks", Phys. Rev. E, 71, 036113, 2005. """ # validate input parameters if not isinstance(n, int) or n < 0: raise ValueError('n must be a positive integer') if p > 1 or p < 0: raise ValueError('p must be a value in (0,1)') if fast: G = Topology(nx.fast_gnp_random_graph(n, p, seed=seed)) else: G = Topology(nx.gnp_random_graph(n, p, seed=seed)) G.name = "erdos_renyi_topology(%s, %s)" % (n, p) G.graph['type'] = 'er' return G
def waxman_2_topology(n, alpha=0.4, beta=0.1, domain=(0, 0, 1, 1), distance_unit='Km', seed=None): r"""Return a Waxman-2 random topology. The Waxman-2 random topology models place n nodes uniformly at random in a rectangular domain. Two nodes u, v are connected with a link with probability .. math:: p = \alpha*exp(-d/(\beta*L)). where the distance *d* is the Euclidean distance between the nodes u and v. and *L* is the maximum distance between all nodes in the graph. Parameters ---------- n : int Number of nodes alpha : float Model parameter chosen in *(0,1]* (higher alpha increases link density) beta : float Model parameter chosen in *(0,1]* (higher beta increases difference between density of short and long links) domain : tuple of numbers, optional Domain size (xmin, ymin, xmax, ymax) seed : int, optional Seed for random number generator (default=None). Returns ------- G : Topology Notes ----- Each edge of G has the attribute *length* References ---------- .. [1] B. M. Waxman, Routing of multipoint connections. IEEE J. Select. Areas Commun. 6(9),(1988) 1617-1622. """ # validate input parameters if not isinstance(n, int) or n <= 0: raise ValueError('n must be a positive integer') if alpha > 1 or alpha <= 0 or beta > 1 or beta <= 0: raise ValueError('alpha and beta must be float values in (0,1]') if not isinstance(domain, tuple) or len(domain) != 4: raise ValueError('domain must be a tuple of 4 number') (xmin, ymin, xmax, ymax) = domain if xmin > xmax: raise ValueError('In domain, xmin cannot be greater than xmax') if ymin > ymax: raise ValueError('In domain, ymin cannot be greater than ymax') if seed is not None: random.seed(seed) G = Topology(type='waxman_2', distance_unit=distance_unit) G.name = "waxman_2_topology(%s, %s, %s)" % (n, alpha, beta) G.add_nodes_from(range(n)) for v in G.nodes_iter(): G.node[v]['latitude'] = (ymin + (ymax - ymin)) * random.random() G.node[v]['longitude'] = (xmin + (xmax - xmin)) * random.random() l = {} nodes = G.nodes() while nodes: u = nodes.pop() for v in nodes: x_u = G.node[u]['longitude'] x_v = G.node[v]['longitude'] y_u = G.node[u]['latitude'] y_v = G.node[v]['latitude'] l[(u, v)] = math.sqrt((x_u - x_v)**2 + (y_u - y_v)**2) L = max(l.values()) for (u, v), d in l.items(): if random.random() < alpha * math.exp(-d / (beta * L)): G.add_edge(u, v, length=d) return G
def dumbbell_topology(m1, m2): """ Return a dumbbell topology consisting of two star topologies connected by a path. More precisely, two star graphs :math:`K_{m1}` form the left and right bells, and are connected by a path :math:`P_{m2}`. The :math:`2*m1+m2` nodes are numbered as follows. * :math:`0,...,m1-1` for the left barbell, * :math:`m1,...,m1+m2-1` for the path, * :math:`m1+m2,...,2*m1+m2-1` for the right barbell. The 3 subgraphs are joined via the edges :math:`(m1-1,m1)` and :math:`(m1+m2-1,m1+m2)`. If m2 = 0, this is merely two star topologies joined together. Please notice that this dumbbell topology is different from the barbell graph generated by networkx's barbell_graph function. That barbell graph consists of two complete graphs connected by a path. This consists of two stars whose roots are connected by a path. This dumbbell topology is particularly useful for simulating transport layer protocols. All nodes and edges of this topology have an attribute *type* which can be either *right bell*, *core* or *left_bell* Parameters ---------- m1 : int The number of nodes in each bell m2 : int The number of nodes in the path Returns ------- topology : A Topology object """ if not isinstance(m1, int) or not isinstance(m2, int): raise TypeError('m1 and m2 arguments must be of int type') if m1 < 2: raise ValueError("Invalid graph description, m1 should be >= 2") if m2 < 1: raise ValueError("Invalid graph description, m2 should be >= 1") G = Topology(type='dumbbell') G.name = "dumbbell_topology(%d,%d)" % (m1, m2) # left bell G.add_node(m1) for v in range(m1): G.add_node(v, type='left_bell') G.add_edge(v, m1, type='left_bell') # right bell for v in range(m1): G.add_node(v + m1 + m2, type='right_bell') G.add_edge(v + m1 + m2, m1 + m2 - 1, type='right_bell') # connecting path for v in range(m1, m1 + m2 - 1): G.node[v]['type'] = 'core' G.add_edge(v, v + 1, type='core') G.node[m1 + m2 - 1]['type'] = 'core' return G
def parse_brite(path, capacity_unit='Mbps', delay_unit='ms', distance_unit='Km', directed=True): """ Parse a topology from an output file generated by the BRITE topology generator Parameters ---------- path : str The path to the BRITE output file capacity_unit : str, optional The unit in which link capacity values are expresses in the BRITE file delay_unit : str, optional The unit in which link delay values are expresses in the BRITE file distance_unit : str, optional The unit in which node coordinates are expresses in the BRITE file directed : bool, optional If True, the topology is parsed as directed topology. Returns ------- topology : Topology or DirectedTopology Notes ----- Each node of the returned topology object is labeled with *latitude* and *longitude* attributes. These attributes are not expressed in degrees but in *distance_unit*. """ # BRITE output format: # http://www.cs.bu.edu/brite/user_manual/node29.html topology = DirectedTopology() if directed else Topology() topology.graph = { 'type': 'brite', 'capacity_unit': capacity_unit, 'delay_unit': delay_unit, 'distance_unit': distance_unit } line_type = None for line in open(path, "r").readlines(): if line.startswith('Nodes:'): line_type = 'node' elif line.startswith('Edges:'): line_type = 'edge' elif line[0].isdigit(): elements = line.strip().split("\t") if line_type == 'node': # Parse node try: node_id = int(elements[0]) longitude = float(elements[1]) latitude = float(elements[2]) # indegree = int(elements[3]) # outdegree = int(elements[4]) as_id = int(elements[5]) # Node type can be: # AS-only: AS_NODE # Router-only: RT_NODE # Top-down: RT_NODE, RT_BORDER # Bottom-up: RT_NODE node_type = elements[6] except (ValueError, IndexError): raise ValueError('Invalid input file. Parsing failed '\ 'while trying to parse a node') topology.add_node(node_id, latitude=latitude, longitude=longitude, type=node_type) if as_id > 0: topology.node[node_id]['AS'] = as_id elif line_type == 'edge': # Parse link try: edge_id = int(elements[0]) from_node = int(elements[1]) to_node = int(elements[2]) length = float(elements[3]) delay = float(elements[4]) capacity = float(elements[5]) # from_as = elements[6] # to_as = elements[7] # Link type can be: # AS-only: E_AS # Router-only: E_RT # Top-down: E_AS, E_RT # bottom-up: E_RT link_type = elements[8] except (ValueError, IndexError): raise ValueError('Invalid input file. Parsing failed '\ 'while trying to parse a link') topology.add_edge(from_node, to_node, id=edge_id, length=length, delay=delay, capacity=capacity, type=link_type) else: continue return topology
def parse_topology_zoo(path): """ Parse a topology from the Topology Zoo dataset. Parameters ---------- path : str The path to the Topology Zoo file Returns ------- topology : Topology or DirectedTopology The parsed topology. Notes ----- If the parsed topology contains bundled links, i.e. multiple links between the same pair or nodes, the topology is parsed correctly but each bundle of links is represented as a single link whose capacity is the sum of the capacities of the links of the bundle (if capacity values were provided). The returned topology has a boolean attribute named *link_bundling* which is True if the topology contains at list one bundled link or False otherwise. If the topology contains bundled links, then each link has an additional boolean attribute named *bundle* which is True if that specific link was bundled in the original topology or False otherwise. """ def try_convert_int(value): """ Try to convert a string to an int. If not possible, returns the given value unchanged """ if type(value) != int: try: value = int(value) except ValueError: pass return value if path.endswith('.gml'): topo_zoo_graph = nx.read_gml(path) elif path.endswith('.graphml'): topo_zoo_graph = nx.read_graphml(path) else: raise ValueError('Invalid input file format. It must either be a GML '\ 'or GraphML file (with extensions .gml or .graphml)') topology = DirectedTopology() if topo_zoo_graph.is_directed() \ else Topology() topology.graph['type'] = 'topology_zoo' topology.graph['distance_unit'] = 'Km' topology.graph['link_bundling'] = True if topo_zoo_graph.is_multigraph() \ else False for tv in topo_zoo_graph.nodes_iter(): v = try_convert_int(tv) topology.add_node(v) if 'label' in topo_zoo_graph.node[tv]: topology.node[v]['label'] = topo_zoo_graph.node[tv]['label'] try: longitude = topo_zoo_graph.node[tv]['Longitude'] latitude = topo_zoo_graph.node[tv]['Latitude'] topology.node[v]['longitude'] = longitude topology.node[v]['latitude'] = latitude except KeyError: pass for tv, tu in topo_zoo_graph.edges_iter(): v = try_convert_int(tv) u = try_convert_int(tu) if u == v: continue topology.add_edge(v, u) if 'Latitude' in topo_zoo_graph.node[tv] and \ 'Longitude' in topo_zoo_graph.node[tv] and \ 'Latitude' in topo_zoo_graph.node[tu] and \ 'Longitude' in topo_zoo_graph.node[tu]: lat_v = topo_zoo_graph.node[tv]['Latitude'] lon_v = topo_zoo_graph.node[tv]['Longitude'] lat_u = topo_zoo_graph.node[tu]['Latitude'] lon_u = topo_zoo_graph.node[tu]['Longitude'] length = geographical_distance(lat_v, lon_v, lat_u, lon_u) topology.edge[v][u]['length'] = length if topo_zoo_graph.is_multigraph(): edge = topo_zoo_graph.edge[tv][tu] topology.edge[v][u]['bundle'] = True if len(edge) > 1 else False capacity = 0 for edge_attr in list(edge.values()): if 'LinkSpeedRaw' in edge_attr: capacity += edge_attr['LinkSpeedRaw'] if capacity > 0: topology.edge[v][u]['capacity'] = capacity else: if 'LinkSpeedRaw' in topo_zoo_graph.edge[tv][tu]: topology.edge[v][u]['capacity'] = \ topo_zoo_graph.edge[tv][tu]['LinkSpeedRaw'] if len(nx.get_edge_attributes(topology, 'capacity')) > 0: topology.graph['capacity_unit'] = 'bps' return topology
def waxman_2_topology(n, alpha=0.4, beta=0.1, domain=(0, 0, 1, 1), distance_unit='Km', seed=None): r"""Return a Waxman-2 random topology. The Waxman-2 random topology models place n nodes uniformly at random in a rectangular domain. Two nodes u, v are connected with a link with probability .. math:: p = \alpha*exp(-d/(\beta*L)). where the distance *d* is the Euclidean distance between the nodes u and v. and *L* is the maximum distance between all nodes in the graph. Parameters ---------- n : int Number of nodes alpha : float Model parameter chosen in *(0,1]* (higher alpha increases link density) beta : float Model parameter chosen in *(0,1]* (higher beta increases difference between density of short and long links) domain : tuple of numbers, optional Domain size (xmin, ymin, xmax, ymax) seed : int, optional Seed for random number generator (default=None). Returns ------- G : Topology Notes ----- Each edge of G has the attribute *length* References ---------- .. [1] B. M. Waxman, Routing of multipoint connections. IEEE J. Select. Areas Commun. 6(9),(1988) 1617-1622. """ # validate input parameters if not isinstance(n, int) or n <= 0: raise ValueError('n must be a positive integer') if alpha > 1 or alpha <= 0 or beta > 1 or beta <= 0: raise ValueError('alpha and beta must be float values in (0,1]') if not isinstance(domain, tuple) or len(domain) != 4: raise ValueError('domain must be a tuple of 4 number') (xmin, ymin, xmax, ymax) = domain if xmin > xmax: raise ValueError('In domain, xmin cannot be greater than xmax') if ymin > ymax: raise ValueError('In domain, ymin cannot be greater than ymax') if seed is not None: random.seed(seed) G = Topology(type='waxman_2', distance_unit=distance_unit) G.name = "waxman_2_topology(%s, %s, %s)" % (n, alpha, beta) G.add_nodes_from(range(n)) for v in G.nodes(): G.node[v]['latitude'] = (ymin + (ymax - ymin)) * random.random() G.node[v]['longitude'] = (xmin + (xmax - xmin)) * random.random() l = {} nodes = list(G.nodes()) while nodes: u = nodes.pop() for v in nodes: x_u = G.node[u]['longitude'] x_v = G.node[v]['longitude'] y_u = G.node[u]['latitude'] y_v = G.node[v]['latitude'] l[(u, v)] = math.sqrt((x_u - x_v) ** 2 + (y_u - y_v) ** 2) L = max(l.values()) for (u, v), d in l.items(): if random.random() < alpha * math.exp(-d / (beta * L)): G.add_edge(u, v, length=d) return G
def parse_inet(path): """ Parse a topology from an output file generated by the Inet topology generator Parameters ---------- path : str The path to the Inet output file Returns ------- topology : Topology Notes ----- Each node of the returned topology object is labeled with *latitude* and *longitude* attributes. These attributes are not expressed in degrees but in Kilometers. """ topology = Topology(type='inet', distance_unit='Km') lines = open(path, "r").readlines() sep = re.compile('[\s\t]') first_line = sep.split(lines[0].strip()) try: n_nodes = int(first_line[0]) n_links = int(first_line[1]) except (ValueError, IndexError): raise ValueError('Invalid input file. '\ 'Cannot parse the number of nodes and links') if len(lines) != 1 + n_nodes + n_links: raise ValueError('Invalid input file. '\ 'It does not have as many lines as expected') i = 0 for line in lines[1:]: entry = sep.split(line.strip()) if i < n_nodes: i += 1 try: node_id = int(entry[0]) longitude = int(entry[1]) latitude = int(entry[2]) except (ValueError, IndexError): raise ValueError('Invalid input file. Parsing failed while '\ 'trying to parse a node') topology.add_node(node_id, latitude=latitude, longitude=longitude) else: try: u = int(entry[0]) v = int(entry[1]) weight = int(entry[2]) x_u = topology.node[u]['longitude'] y_u = topology.node[u]['latitude'] x_v = topology.node[v]['longitude'] y_v = topology.node[v]['latitude'] length = float(math.sqrt((x_v - x_u)**2 + (y_v - y_u)**2)) except (ValueError, IndexError): raise ValueError('Invalid input file. Parsing failed while '\ 'trying to parse a link') topology.add_edge(u, v, weight=weight, length=length) return topology
def waxman_1_topology(n, alpha=0.4, beta=0.1, L=1.0, distance_unit='Km', seed=None): r""" Return a Waxman-1 random topology. The Waxman-1 random topology models assigns link between nodes with probability .. math:: p = \alpha*exp(-d/(\beta*L)). where the distance *d* is chosen randomly in *[0,L]*. Parameters ---------- n : int Number of nodes alpha : float Model parameter chosen in *(0,1]* (higher alpha increases link density) beta : float Model parameter chosen in *(0,1]* (higher beta increases difference between density of short and long links) L : float Maximum distance between nodes. seed : int, optional Seed for random number generator (default=None). Returns ------- G : Topology Notes ----- Each node of G has the attributes *latitude* and *longitude*. These attributes are not expressed in degrees but in *distance_unit*. Each edge of G has the attribute *length*, which is also expressed in *distance_unit*. References ---------- .. [1] B. M. Waxman, Routing of multipoint connections. IEEE J. Select. Areas Commun. 6(9),(1988) 1617-1622. """ # validate input parameters if not isinstance(n, int) or n <= 0: raise ValueError('n must be a positive integer') if alpha > 1 or alpha <= 0 or beta > 1 or beta <= 0: raise ValueError('alpha and beta must be float values in (0,1]') if L <= 0: raise ValueError('L must be a positive number') if seed is not None: random.seed(seed) G = Topology(type='waxman_1', distance_unit=distance_unit) G.name = "waxman_1_topology(%s, %s, %s, %s)" % (n, alpha, beta, L) G.add_nodes_from(range(n)) nodes = list(G.nodes()) while nodes: u = nodes.pop() for v in nodes: d = L * random.random() if random.random() < alpha * math.exp(-d / (beta * L)): G.add_edge(u, v, length=d) return G
def barabasi_albert_topology(n, m, m0, seed=None): r""" Return a random topology using Barabasi-Albert preferential attachment model. A topology of n nodes is grown by attaching new nodes each with m links that are preferentially attached to existing nodes with high degree. More precisely, the Barabasi-Albert topology is built as follows. First, a line topology with m0 nodes is created. Then at each step, one node is added and connected to m existing nodes. These nodes are selected randomly with probability .. math:: \Pi(i) = \frac{deg(i)}{sum_{v \in V} deg V}. Where i is the selected node and V is the set of nodes of the graph. Parameters ---------- n : int Number of nodes m : int Number of edges to attach from a new node to existing nodes m0 : int Number of nodes initially attached to the network seed : int, optional Seed for random number generator (default=None). Returns ------- G : Topology Notes ----- The initialization is a graph with with m nodes connected by :math:`m -1` edges. It does not use the Barabasi-Albert method provided by NetworkX because it does not allow to specify *m0* parameter. There are no disconnected subgraphs in the topology. References ---------- .. [1] A. L. Barabasi and R. Albert "Emergence of scaling in random networks", Science 286, pp 509-512, 1999. """ def calc_pi(G): """Calculate BA Pi function for all nodes of the graph""" degree = dict(G.degree()) den = float(sum(degree.values())) return {node: degree[node] / den for node in G.nodes()} # input parameters if n < 1 or m < 1 or m0 < 1: raise ValueError('n, m and m0 must be positive integers') if m >= m0: raise ValueError('m must be <= m0') if n < m0: raise ValueError('n must be > m0') if seed is not None: random.seed(seed) # Step 1: Add m0 nodes. These nodes are interconnected together # because otherwise they will end up isolated at the end G = Topology(nx.path_graph(m0)) G.name = "ba_topology(%d,%d,%d)" % (n, m, m0) G.graph['type'] = 'ba' # Step 2: Add one node and connect it with m links while G.number_of_nodes() < n: pi = calc_pi(G) u = G.number_of_nodes() G.add_node(u) new_links = 0 while new_links < m: v = random_from_pdf(pi) if not G.has_edge(u, v): G.add_edge(u, v) new_links += 1 return G
def glp_topology(n, m, m0, p, beta, seed=None): r""" Return a random topology using the Generalized Linear Preference (GLP) preferential attachment model. It differs from the extended Barabasi-Albert model in that there is link rewiring and a beta parameter is introduced to fine-tune preferential attachment. More precisely, the GLP topology is built as follows. First, a line topology with *m0* nodes is created. Then, at each step: with probability *p*, add *m* new links between existing nodes, selected with probability: .. math:: \Pi(i) = \frac{deg(i) - \beta 1}{\sum_{v \in V} (deg(v) - \beta)} with probability :math:`1-p`, add a new node and attach it to m nodes of the existing topology selected with probability :math:`\Pi(i)` Repeat the previous step until the topology comprises n nodes in total. Parameters ---------- n : int Number of nodes m : int Number of edges to attach from a new node to existing nodes m0 : int Number of edges initially attached to the network p : float The probability that new links are added beta : float Parameter to fine-tune preferntial attachment: beta < 1 seed : int, optional Seed for random number generator (default=None). Returns ------- G : Topology References ---------- .. [1] T. Bu and D. Towsey "On distinguishing between Internet power law topology generators", Proceeding od the 21st IEEE INFOCOM conference. IEEE, volume 2, pages 638-647, 2002. """ def calc_pi(G, beta): """Calculate GLP Pi function for all nodes of the graph""" # validate input parameter if beta >= 1: raise ValueError('beta must be < 1') degree = G.degree() den = float(sum(degree.values()) - (G.number_of_nodes() * beta)) return {node: (degree[node] - beta) / den for node in G.nodes_iter()} def add_m_links(G, pi): """Add m links between existing nodes to the graph""" n_nodes = G.number_of_nodes() n_edges = G.number_of_edges() max_n_edges = (n_nodes * (n_nodes - 1)) / 2 if n_edges + m > max_n_edges: # cannot add m links add_node(G, pi) # add a new node instead # return in any case because before doing another operation # (add node or links) we need to recalculate pi return new_links = 0 while new_links < m: u = random_from_pdf(pi) v = random_from_pdf(pi) if u != v and not G.has_edge(u, v): G.add_edge(u, v) new_links += 1 def add_node(G, pi): """Add one node to the graph and connect it to m existing nodes""" new_node = G.number_of_nodes() G.add_node(new_node) new_links = 0 while new_links < m: existing_node = random_from_pdf(pi) if not G.has_edge(new_node, existing_node): G.add_edge(new_node, existing_node) new_links += 1 # validate input parameters if n < 1 or m < 1 or m0 < 1: raise ValueError('n, m and m0 must be a positive integers') if beta >= 1: raise ValueError('beta must be < 1') if m >= m0: raise ValueError('m must be <= m0') if p > 1 or p < 0: raise ValueError('p must be included between 0 and 1') if seed is not None: random.seed(seed) # step 1: create a graph of m0 nodes connected by n-1 edges G = Topology(nx.path_graph(m0)) G.graph['type'] = 'glp' G.name = "glp_topology(%d, %d, %d, %f, %f)" % (n, m, m0, p, beta) # Add nodes and links now while G.number_of_nodes() < n: pi = calc_pi(G, beta) if random.random() < p: # add m new links with probability p add_m_links(G, pi) else: # add a new node with m new links with probability 1 - p add_node(G, pi) return G
def barabasi_albert_topology(n, m, m0, seed=None): r""" Return a random topology using Barabasi-Albert preferential attachment model. A topology of n nodes is grown by attaching new nodes each with m links that are preferentially attached to existing nodes with high degree. More precisely, the Barabasi-Albert topology is built as follows. First, a line topology with m0 nodes is created. Then at each step, one node is added and connected to m existing nodes. These nodes are selected randomly with probability .. math:: \Pi(i) = \frac{deg(i)}{sum_{v \in V} deg V}. Where i is the selected node and V is the set of nodes of the graph. Parameters ---------- n : int Number of nodes m : int Number of edges to attach from a new node to existing nodes m0 : int Number of nodes initially attached to the network seed : int, optional Seed for random number generator (default=None). Returns ------- G : Topology Notes ----- The initialization is a graph with with m nodes connected by :math:`m -1` edges. It does not use the Barabasi-Albert method provided by NetworkX because it does not allow to specify *m0* parameter. There are no disconnected subgraphs in the topology. References ---------- .. [1] A. L. Barabasi and R. Albert "Emergence of scaling in random networks", Science 286, pp 509-512, 1999. """ def calc_pi(G): """Calculate BA Pi function for all nodes of the graph""" degree = G.degree() den = float(sum(degree.values())) return {node: degree[node] / den for node in G.nodes_iter()} # input parameters if n < 1 or m < 1 or m0 < 1: raise ValueError('n, m and m0 must be positive integers') if m >= m0: raise ValueError('m must be <= m0') if n < m0: raise ValueError('n must be > m0') if seed is not None: random.seed(seed) # Step 1: Add m0 nodes. These nodes are interconnected together # because otherwise they will end up isolated at the end G = Topology(nx.path_graph(m0)) G.name = "ba_topology(%d,%d,%d)" % (n, m, m0) G.graph['type'] = 'ba' # Step 2: Add one node and connect it with m links while G.number_of_nodes() < n: pi = calc_pi(G) u = G.number_of_nodes() G.add_node(u) new_links = 0 while new_links < m: v = random_from_pdf(pi) if not G.has_edge(u, v): G.add_edge(u, v) new_links += 1 return G
def extended_barabasi_albert_topology(n, m, m0, p, q, seed=None): r""" Return a random topology using the extended Barabasi-Albert preferential attachment model. Differently from the original Barabasi-Albert model, this model takes into account the presence of local events, such as the addition of new links or the rewiring of existing links. More precisely, the Barabasi-Albert topology is built as follows. First, a topology with *m0* isolated nodes is created. Then, at each step: with probability *p* add *m* new links between existing nodes, selected with probability: .. math:: \Pi(i) = \frac{deg(i) + 1}{\sum_{v \in V} (deg(v) + 1)} with probability *q* rewire *m* links. Each link to be rewired is selected as follows: a node i is randomly selected and a link is randomly removed from it. The node i is then connected to a new node randomly selected with probability :math:`\Pi(i)`, with probability :math:`1-p-q` add a new node and attach it to m nodes of the existing topology selected with probability :math:`\Pi(i)` Repeat the previous step until the topology comprises n nodes in total. Parameters ---------- n : int Number of nodes m : int Number of edges to attach from a new node to existing nodes m0 : int Number of edges initially attached to the network p : float The probability that new links are added q : float The probability that existing links are rewired seed : int, optional Seed for random number generator (default=None). Returns ------- G : Topology References ---------- .. [1] A. L. Barabasi and R. Albert "Topology of evolving networks: local events and universality", Physical Review Letters 85(24), 2000. """ def calc_pi(G): """Calculate extended-BA Pi function for all nodes of the graph""" degree = G.degree() den = float(sum(degree.values()) + G.number_of_nodes()) return {node: (degree[node] + 1) / den for node in G.nodes_iter()} # input parameters if n < 1 or m < 1 or m0 < 1: raise ValueError('n, m and m0 must be a positive integer') if m >= m0: raise ValueError('m must be <= m0') if n < m0: raise ValueError('n must be > m0') if p > 1 or p < 0: raise ValueError('p must be included between 0 and 1') if q > 1 or q < 0: raise ValueError('q must be included between 0 and 1') if p + q > 1: raise ValueError('p + q must be <= 1') if seed is not None: random.seed(seed) G = Topology(type='extended_ba') G.name = "ext_ba_topology(%d, %d, %d, %f, %f)" % (n, m, m0, p, q) # Step 1: Add m0 isolated nodes G.add_nodes_from(range(m0)) while G.number_of_nodes() < n: pi = calc_pi(G) r = random.random() if r <= p: # add m new links with probability p n_nodes = G.number_of_nodes() n_edges = G.number_of_edges() max_n_edges = (n_nodes * (n_nodes - 1)) / 2 if n_edges + m > max_n_edges: # cannot add m links continue # rewire or add nodes new_links = 0 while new_links < m: u = random_from_pdf(pi) v = random_from_pdf(pi) if u is not v and not G.has_edge(u, v): G.add_edge(u, v) new_links += 1 elif r > p and r <= p + q: # rewire m links with probability q rewired_links = 0 while rewired_links < m: i = random.choice(G.nodes()) # pick up node randomly (uniform) if len(G.edge[i]) is 0: # if i has no edges, I cannot rewire break j = random.choice(list( G.edge[i].keys())) # node to be disconnected k = random_from_pdf(pi) # new node to be connected if i is not k and j is not k and not G.has_edge(i, k): G.remove_edge(i, j) G.add_edge(i, k) rewired_links += 1 else: # add a new node with probability 1 - p - q new_node = G.number_of_nodes() G.add_node(new_node) new_links = 0 while new_links < m: existing_node = random_from_pdf(pi) if not G.has_edge(new_node, existing_node): G.add_edge(new_node, existing_node) new_links += 1 return G
def extended_barabasi_albert_topology(n, m, m0, p, q, seed=None): r""" Return a random topology using the extended Barabasi-Albert preferential attachment model. Differently from the original Barabasi-Albert model, this model takes into account the presence of local events, such as the addition of new links or the rewiring of existing links. More precisely, the Barabasi-Albert topology is built as follows. First, a topology with *m0* isolated nodes is created. Then, at each step: with probability *p* add *m* new links between existing nodes, selected with probability: .. math:: \Pi(i) = \frac{deg(i) + 1}{\sum_{v \in V} (deg(v) + 1)} with probability *q* rewire *m* links. Each link to be rewired is selected as follows: a node i is randomly selected and a link is randomly removed from it. The node i is then connected to a new node randomly selected with probability :math:`\Pi(i)`, with probability :math:`1-p-q` add a new node and attach it to m nodes of the existing topology selected with probability :math:`\Pi(i)` Repeat the previous step until the topology comprises n nodes in total. Parameters ---------- n : int Number of nodes m : int Number of edges to attach from a new node to existing nodes m0 : int Number of edges initially attached to the network p : float The probability that new links are added q : float The probability that existing links are rewired seed : int, optional Seed for random number generator (default=None). Returns ------- G : Topology References ---------- .. [1] A. L. Barabasi and R. Albert "Topology of evolving networks: local events and universality", Physical Review Letters 85(24), 2000. """ def calc_pi(G): """Calculate extended-BA Pi function for all nodes of the graph""" degree = dict(G.degree()) den = float(sum(degree.values()) + G.number_of_nodes()) return {node: (degree[node] + 1) / den for node in G.nodes()} # input parameters if n < 1 or m < 1 or m0 < 1: raise ValueError('n, m and m0 must be a positive integer') if m >= m0: raise ValueError('m must be <= m0') if n < m0: raise ValueError('n must be > m0') if p > 1 or p < 0: raise ValueError('p must be included between 0 and 1') if q > 1 or q < 0: raise ValueError('q must be included between 0 and 1') if p + q > 1: raise ValueError('p + q must be <= 1') if seed is not None: random.seed(seed) G = Topology(type='extended_ba') G.name = "ext_ba_topology(%d, %d, %d, %f, %f)" % (n, m, m0, p, q) # Step 1: Add m0 isolated nodes G.add_nodes_from(range(m0)) while G.number_of_nodes() < n: pi = calc_pi(G) r = random.random() if r <= p: # add m new links with probability p n_nodes = G.number_of_nodes() n_edges = G.number_of_edges() max_n_edges = (n_nodes * (n_nodes - 1)) / 2 if n_edges + m > max_n_edges: # cannot add m links continue # rewire or add nodes new_links = 0 while new_links < m: u = random_from_pdf(pi) v = random_from_pdf(pi) if u is not v and not G.has_edge(u, v): G.add_edge(u, v) new_links += 1 elif r > p and r <= p + q: # rewire m links with probability q rewired_links = 0 while rewired_links < m: i = random.choice(list(G.nodes())) # pick up node randomly (uniform) if len(G.adj[i]) is 0: # if i has no edges, I cannot rewire break j = random.choice(list(G.adj[i].keys())) # node to be disconnected k = random_from_pdf(pi) # new node to be connected if i is not k and j is not k and not G.has_edge(i, k): G.remove_edge(i, j) G.add_edge(i, k) rewired_links += 1 else: # add a new node with probability 1 - p - q new_node = G.number_of_nodes() G.add_node(new_node) new_links = 0 while new_links < m: existing_node = random_from_pdf(pi) if not G.has_edge(new_node, existing_node): G.add_edge(new_node, existing_node) new_links += 1 return G
def waxman_1_topology(n, alpha=0.4, beta=0.1, L=1.0, distance_unit='Km', seed=None): r""" Return a Waxman-1 random topology. The Waxman-1 random topology models assigns link between nodes with probability .. math:: p = \alpha*exp(-d/(\beta*L)). where the distance *d* is chosen randomly in *[0,L]*. Parameters ---------- n : int Number of nodes alpha : float Model parameter chosen in *(0,1]* (higher alpha increases link density) beta : float Model parameter chosen in *(0,1]* (higher beta increases difference between density of short and long links) L : float Maximum distance between nodes. seed : int, optional Seed for random number generator (default=None). Returns ------- G : Topology Notes ----- Each node of G has the attributes *latitude* and *longitude*. These attributes are not expressed in degrees but in *distance_unit*. Each edge of G has the attribute *length*, which is also expressed in *distance_unit*. References ---------- .. [1] B. M. Waxman, Routing of multipoint connections. IEEE J. Select. Areas Commun. 6(9),(1988) 1617-1622. """ # validate input parameters if not isinstance(n, int) or n <= 0: raise ValueError('n must be a positive integer') if alpha > 1 or alpha <= 0 or beta > 1 or beta <= 0: raise ValueError('alpha and beta must be float values in (0,1]') if L <= 0: raise ValueError('L must be a positive number') if seed is not None: random.seed(seed) G = Topology(type='waxman_1', distance_unit=distance_unit) G.name = "waxman_1_topology(%s, %s, %s, %s)" % (n, alpha, beta, L) G.add_nodes_from(range(n)) nodes = G.nodes() while nodes: u = nodes.pop() for v in nodes: d = L * random.random() if random.random() < alpha * math.exp(-d / (beta * L)): G.add_edge(u, v, length=d) return G
def glp_topology(n, m, m0, p, beta, seed=None): r""" Return a random topology using the Generalized Linear Preference (GLP) preferential attachment model. It differs from the extended Barabasi-Albert model in that there is link rewiring and a beta parameter is introduced to fine-tune preferential attachment. More precisely, the GLP topology is built as follows. First, a line topology with *m0* nodes is created. Then, at each step: with probability *p*, add *m* new links between existing nodes, selected with probability: .. math:: \Pi(i) = \frac{deg(i) - \beta 1}{\sum_{v \in V} (deg(v) - \beta)} with probability :math:`1-p`, add a new node and attach it to m nodes of the existing topology selected with probability :math:`\Pi(i)` Repeat the previous step until the topology comprises n nodes in total. Parameters ---------- n : int Number of nodes m : int Number of edges to attach from a new node to existing nodes m0 : int Number of edges initially attached to the network p : float The probability that new links are added beta : float Parameter to fine-tune preferntial attachment: beta < 1 seed : int, optional Seed for random number generator (default=None). Returns ------- G : Topology References ---------- .. [1] T. Bu and D. Towsey "On distinguishing between Internet power law topology generators", Proceeding od the 21st IEEE INFOCOM conference. IEEE, volume 2, pages 638-647, 2002. """ def calc_pi(G, beta): """Calculate GLP Pi function for all nodes of the graph""" # validate input parameter if beta >= 1: raise ValueError('beta must be < 1') degree = dict(G.degree()) den = float(sum(degree.values()) - (G.number_of_nodes() * beta)) return {node: (degree[node] - beta) / den for node in G.nodes()} def add_m_links(G, pi): """Add m links between existing nodes to the graph""" n_nodes = G.number_of_nodes() n_edges = G.number_of_edges() max_n_edges = (n_nodes * (n_nodes - 1)) / 2 if n_edges + m > max_n_edges: # cannot add m links add_node(G, pi) # add a new node instead # return in any case because before doing another operation # (add node or links) we need to recalculate pi return new_links = 0 while new_links < m: u = random_from_pdf(pi) v = random_from_pdf(pi) if u != v and not G.has_edge(u, v): G.add_edge(u, v) new_links += 1 def add_node(G, pi): """Add one node to the graph and connect it to m existing nodes""" new_node = G.number_of_nodes() G.add_node(new_node) new_links = 0 while new_links < m: existing_node = random_from_pdf(pi) if not G.has_edge(new_node, existing_node): G.add_edge(new_node, existing_node) new_links += 1 # validate input parameters if n < 1 or m < 1 or m0 < 1: raise ValueError('n, m and m0 must be a positive integers') if beta >= 1: raise ValueError('beta must be < 1') if m >= m0: raise ValueError('m must be <= m0') if p > 1 or p < 0: raise ValueError('p must be included between 0 and 1') if seed is not None: random.seed(seed) # step 1: create a graph of m0 nodes connected by n-1 edges G = Topology(nx.path_graph(m0)) G.graph['type'] = 'glp' G.name = "glp_topology(%d, %d, %d, %f, %f)" % (n, m, m0, p, beta) # Add nodes and links now while G.number_of_nodes() < n: pi = calc_pi(G, beta) if random.random() < p: # add m new links with probability p add_m_links(G, pi) else: # add a new node with m new links with probability 1 - p add_node(G, pi) return G