about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/networkx/generators/internet_as_graphs.py
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/networkx/generators/internet_as_graphs.py
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/networkx/generators/internet_as_graphs.py')
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/generators/internet_as_graphs.py441
1 files changed, 441 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/networkx/generators/internet_as_graphs.py b/.venv/lib/python3.12/site-packages/networkx/generators/internet_as_graphs.py
new file mode 100644
index 00000000..449d5437
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/generators/internet_as_graphs.py
@@ -0,0 +1,441 @@
+"""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)
+@nx._dispatchable(graphs=None, returns_graph=True)
+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