123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440 |
- """Generates graphs resembling the Internet Autonomous System network"""
- import networkx as nx
- from networkx.utils import py_random_state
- __all__ = ["random_internet_as_graph"]
- def uniform_int_from_avg(a, m, seed):
- """Pick a random integer with uniform probability.
- Returns a random integer uniformly taken from a distribution with
- minimum value 'a' and average value 'm', X~U(a,b), E[X]=m, X in N where
- b = 2*m - a.
- Notes
- -----
- p = (b-floor(b))/2
- X = X1 + X2; X1~U(a,floor(b)), X2~B(p)
- E[X] = E[X1] + E[X2] = (floor(b)+a)/2 + (b-floor(b))/2 = (b+a)/2 = m
- """
- from math import floor
- assert m >= a
- b = 2 * m - a
- p = (b - floor(b)) / 2
- X1 = round(seed.random() * (floor(b) - a) + a)
- if seed.random() < p:
- X2 = 1
- else:
- X2 = 0
- return X1 + X2
- def choose_pref_attach(degs, seed):
- """Pick a random value, with a probability given by its weight.
- Returns a random choice among degs keys, each of which has a
- probability proportional to the corresponding dictionary value.
- Parameters
- ----------
- degs: dictionary
- It contains the possible values (keys) and the corresponding
- probabilities (values)
- seed: random state
- Returns
- -------
- v: object
- A key of degs or None if degs is empty
- """
- if len(degs) == 0:
- return None
- s = sum(degs.values())
- if s == 0:
- return seed.choice(list(degs.keys()))
- v = seed.random() * s
- nodes = list(degs.keys())
- i = 0
- acc = degs[nodes[i]]
- while v > acc:
- i += 1
- acc += degs[nodes[i]]
- return nodes[i]
- class AS_graph_generator:
- """Generates random internet AS graphs."""
- def __init__(self, n, seed):
- """Initializes variables. Immediate numbers are taken from [1].
- Parameters
- ----------
- n: integer
- Number of graph nodes
- seed: random state
- Indicator of random number generation state.
- See :ref:`Randomness<randomness>`.
- Returns
- -------
- GG: AS_graph_generator object
- References
- ----------
- [1] A. Elmokashfi, A. Kvalbein and C. Dovrolis, "On the Scalability of
- BGP: The Role of Topology Growth," in IEEE Journal on Selected Areas
- in Communications, vol. 28, no. 8, pp. 1250-1261, October 2010.
- """
- self.seed = seed
- self.n_t = min(n, round(self.seed.random() * 2 + 4)) # num of T nodes
- self.n_m = round(0.15 * n) # number of M nodes
- self.n_cp = round(0.05 * n) # number of CP nodes
- self.n_c = max(0, n - self.n_t - self.n_m - self.n_cp) # number of C nodes
- self.d_m = 2 + (2.5 * n) / 10000 # average multihoming degree for M nodes
- self.d_cp = 2 + (1.5 * n) / 10000 # avg multihoming degree for CP nodes
- self.d_c = 1 + (5 * n) / 100000 # average multihoming degree for C nodes
- self.p_m_m = 1 + (2 * n) / 10000 # avg num of peer edges between M and M
- self.p_cp_m = 0.2 + (2 * n) / 10000 # avg num of peer edges between CP, M
- self.p_cp_cp = 0.05 + (2 * n) / 100000 # avg num of peer edges btwn CP, CP
- self.t_m = 0.375 # probability M's provider is T
- self.t_cp = 0.375 # probability CP's provider is T
- self.t_c = 0.125 # probability C's provider is T
- def t_graph(self):
- """Generates the core mesh network of tier one nodes of a AS graph.
- Returns
- -------
- G: Networkx Graph
- Core network
- """
- self.G = nx.Graph()
- for i in range(self.n_t):
- self.G.add_node(i, type="T")
- for r in self.regions:
- self.regions[r].add(i)
- for j in self.G.nodes():
- if i != j:
- self.add_edge(i, j, "peer")
- self.customers[i] = set()
- self.providers[i] = set()
- return self.G
- def add_edge(self, i, j, kind):
- if kind == "transit":
- customer = str(i)
- else:
- customer = "none"
- self.G.add_edge(i, j, type=kind, customer=customer)
- def choose_peer_pref_attach(self, node_list):
- """Pick a node with a probability weighted by its peer degree.
- Pick a node from node_list with preferential attachment
- computed only on their peer degree
- """
- d = {}
- for n in node_list:
- d[n] = self.G.nodes[n]["peers"]
- return choose_pref_attach(d, self.seed)
- def choose_node_pref_attach(self, node_list):
- """Pick a node with a probability weighted by its degree.
- Pick a node from node_list with preferential attachment
- computed on their degree
- """
- degs = dict(self.G.degree(node_list))
- return choose_pref_attach(degs, self.seed)
- def add_customer(self, i, j):
- """Keep the dictionaries 'customers' and 'providers' consistent."""
- self.customers[j].add(i)
- self.providers[i].add(j)
- for z in self.providers[j]:
- self.customers[z].add(i)
- self.providers[i].add(z)
- def add_node(self, i, kind, reg2prob, avg_deg, t_edge_prob):
- """Add a node and its customer transit edges to the graph.
- Parameters
- ----------
- i: object
- Identifier of the new node
- kind: string
- Type of the new node. Options are: 'M' for middle node, 'CP' for
- content provider and 'C' for customer.
- reg2prob: float
- Probability the new node can be in two different regions.
- avg_deg: float
- Average number of transit nodes of which node i is customer.
- t_edge_prob: float
- Probability node i establish a customer transit edge with a tier
- one (T) node
- Returns
- -------
- i: object
- Identifier of the new node
- """
- regs = 1 # regions in which node resides
- if self.seed.random() < reg2prob: # node is in two regions
- regs = 2
- node_options = set()
- self.G.add_node(i, type=kind, peers=0)
- self.customers[i] = set()
- self.providers[i] = set()
- self.nodes[kind].add(i)
- for r in self.seed.sample(list(self.regions), regs):
- node_options = node_options.union(self.regions[r])
- self.regions[r].add(i)
- edge_num = uniform_int_from_avg(1, avg_deg, self.seed)
- t_options = node_options.intersection(self.nodes["T"])
- m_options = node_options.intersection(self.nodes["M"])
- if i in m_options:
- m_options.remove(i)
- d = 0
- while d < edge_num and (len(t_options) > 0 or len(m_options) > 0):
- if len(m_options) == 0 or (
- len(t_options) > 0 and self.seed.random() < t_edge_prob
- ): # add edge to a T node
- j = self.choose_node_pref_attach(t_options)
- t_options.remove(j)
- else:
- j = self.choose_node_pref_attach(m_options)
- m_options.remove(j)
- self.add_edge(i, j, "transit")
- self.add_customer(i, j)
- d += 1
- return i
- def add_m_peering_link(self, m, to_kind):
- """Add a peering link between two middle tier (M) nodes.
- Target node j is drawn considering a preferential attachment based on
- other M node peering degree.
- Parameters
- ----------
- m: object
- Node identifier
- to_kind: string
- type for target node j (must be always M)
- Returns
- -------
- success: boolean
- """
- # candidates are of type 'M' and are not customers of m
- node_options = self.nodes["M"].difference(self.customers[m])
- # candidates are not providers of m
- node_options = node_options.difference(self.providers[m])
- # remove self
- if m in node_options:
- node_options.remove(m)
- # remove candidates we are already connected to
- for j in self.G.neighbors(m):
- if j in node_options:
- node_options.remove(j)
- if len(node_options) > 0:
- j = self.choose_peer_pref_attach(node_options)
- self.add_edge(m, j, "peer")
- self.G.nodes[m]["peers"] += 1
- self.G.nodes[j]["peers"] += 1
- return True
- else:
- return False
- def add_cp_peering_link(self, cp, to_kind):
- """Add a peering link to a content provider (CP) node.
- Target node j can be CP or M and it is drawn uniformly among the nodes
- belonging to the same region as cp.
- Parameters
- ----------
- cp: object
- Node identifier
- to_kind: string
- type for target node j (must be M or CP)
- Returns
- -------
- success: boolean
- """
- node_options = set()
- for r in self.regions: # options include nodes in the same region(s)
- if cp in self.regions[r]:
- node_options = node_options.union(self.regions[r])
- # options are restricted to the indicated kind ('M' or 'CP')
- node_options = self.nodes[to_kind].intersection(node_options)
- # remove self
- if cp in node_options:
- node_options.remove(cp)
- # remove nodes that are cp's providers
- node_options = node_options.difference(self.providers[cp])
- # remove nodes we are already connected to
- for j in self.G.neighbors(cp):
- if j in node_options:
- node_options.remove(j)
- if len(node_options) > 0:
- j = self.seed.sample(list(node_options), 1)[0]
- self.add_edge(cp, j, "peer")
- self.G.nodes[cp]["peers"] += 1
- self.G.nodes[j]["peers"] += 1
- return True
- else:
- return False
- def graph_regions(self, rn):
- """Initializes AS network regions.
- Parameters
- ----------
- rn: integer
- Number of regions
- """
- self.regions = {}
- for i in range(rn):
- self.regions["REG" + str(i)] = set()
- def add_peering_links(self, from_kind, to_kind):
- """Utility function to add peering links among node groups."""
- peer_link_method = None
- if from_kind == "M":
- peer_link_method = self.add_m_peering_link
- m = self.p_m_m
- if from_kind == "CP":
- peer_link_method = self.add_cp_peering_link
- if to_kind == "M":
- m = self.p_cp_m
- else:
- m = self.p_cp_cp
- for i in self.nodes[from_kind]:
- num = uniform_int_from_avg(0, m, self.seed)
- for _ in range(num):
- peer_link_method(i, to_kind)
- def generate(self):
- """Generates a random AS network graph as described in [1].
- Returns
- -------
- G: Graph object
- Notes
- -----
- The process steps are the following: first we create the core network
- of tier one nodes, then we add the middle tier (M), the content
- provider (CP) and the customer (C) nodes along with their transit edges
- (link i,j means i is customer of j). Finally we add peering links
- between M nodes, between M and CP nodes and between CP node couples.
- For a detailed description of the algorithm, please refer to [1].
- References
- ----------
- [1] A. Elmokashfi, A. Kvalbein and C. Dovrolis, "On the Scalability of
- BGP: The Role of Topology Growth," in IEEE Journal on Selected Areas
- in Communications, vol. 28, no. 8, pp. 1250-1261, October 2010.
- """
- self.graph_regions(5)
- self.customers = {}
- self.providers = {}
- self.nodes = {"T": set(), "M": set(), "CP": set(), "C": set()}
- self.t_graph()
- self.nodes["T"] = set(self.G.nodes())
- i = len(self.nodes["T"])
- for _ in range(self.n_m):
- self.nodes["M"].add(self.add_node(i, "M", 0.2, self.d_m, self.t_m))
- i += 1
- for _ in range(self.n_cp):
- self.nodes["CP"].add(self.add_node(i, "CP", 0.05, self.d_cp, self.t_cp))
- i += 1
- for _ in range(self.n_c):
- self.nodes["C"].add(self.add_node(i, "C", 0, self.d_c, self.t_c))
- i += 1
- self.add_peering_links("M", "M")
- self.add_peering_links("CP", "M")
- self.add_peering_links("CP", "CP")
- return self.G
- @py_random_state(1)
- def random_internet_as_graph(n, seed=None):
- """Generates a random undirected graph resembling the Internet AS network
- Parameters
- ----------
- n: integer in [1000, 10000]
- Number of graph nodes
- seed : integer, random_state, or None (default)
- Indicator of random number generation state.
- See :ref:`Randomness<randomness>`.
- Returns
- -------
- G: Networkx Graph object
- A randomly generated undirected graph
- Notes
- -----
- This algorithm returns an undirected graph resembling the Internet
- Autonomous System (AS) network, it uses the approach by Elmokashfi et al.
- [1]_ and it grants the properties described in the related paper [1]_.
- Each node models an autonomous system, with an attribute 'type' specifying
- its kind; tier-1 (T), mid-level (M), customer (C) or content-provider (CP).
- Each edge models an ADV communication link (hence, bidirectional) with
- attributes:
- - type: transit|peer, the kind of commercial agreement between nodes;
- - customer: <node id>, the identifier of the node acting as customer
- ('none' if type is peer).
- References
- ----------
- .. [1] A. Elmokashfi, A. Kvalbein and C. Dovrolis, "On the Scalability of
- BGP: The Role of Topology Growth," in IEEE Journal on Selected Areas
- in Communications, vol. 28, no. 8, pp. 1250-1261, October 2010.
- """
- GG = AS_graph_generator(n, seed)
- G = GG.generate()
- return G
|