def test_overlapping_K5(): G = nx.Graph() G.add_edges_from(combinations(range(5), 2)) # Add a five clique G.add_edges_from(combinations(range(2, 7), 2)) # Add another five clique c = list(k_clique_communities(G, 4)) assert_equal(c, [frozenset(range(7))]) c = set(k_clique_communities(G, 5)) assert_equal(c, set([frozenset(range(5)), frozenset(range(2, 7))]))
def _dispersion(G_u, u, v): """dispersion for all nodes 'v' in a ego network G_u of node 'u'""" u_nbrs = set(G_u[u]) ST = set(n for n in G_u[v] if n in u_nbrs) set_uv = set([u, v]) # all possible ties of connections that u and b share possib = combinations(ST, 2) total = 0 for (s, t) in possib: # neighbors of s that are in G_u, not including u and v nbrs_s = u_nbrs.intersection(G_u[s]) - set_uv # s and t are not directly connected if t not in nbrs_s: # s and t do not share a connection if nbrs_s.isdisjoint(G_u[t]): # tick for disp(u, v) total += 1 # neighbors that u and v share embededness = len(ST) if normalized: if embededness + c != 0: norm_disp = ((total + b)**alpha) / (embededness + c) else: norm_disp = (total + b)**alpha dispersion = norm_disp else: dispersion = total return dispersion
def random_tournament(n, seed=None): r"""Returns a random tournament graph on `n` nodes. Parameters ---------- n : int The number of nodes in the returned graph. seed : integer, random_state, or None (default) Indicator of random number generation state. See :ref:`Randomness<randomness>`. Returns ------- bool Whether the given graph is a tournament graph. Notes ----- This algorithm adds, for each pair of distinct nodes, an edge with uniformly random orientation. In other words, `\binom{n}{2}` flips of an unbiased coin decide the orientations of the edges in the graph. """ # Flip an unbiased coin for each pair of distinct nodes. coins = (seed.random() for i in range((n * (n - 1)) // 2)) pairs = combinations(range(n), 2) edges = ((u, v) if r < 0.5 else (v, u) for (u, v), r in zip(pairs, coins)) return nx.DiGraph(edges)
def is_matching(G, matching): """Decides whether the given set or dictionary represents a valid matching in ``G``. A *matching* in a graph is a set of edges in which no two distinct edges share a common endpoint. Parameters ---------- G : NetworkX graph matching : dict or set A dictionary or set representing a matching. If a dictionary, it must have ``matching[u] == v`` and ``matching[v] == u`` for each edge ``(u, v)`` in the matching. If a set, it must have elements of the form ``(u, v)``, where ``(u, v)`` is an edge in the matching. Returns ------- bool Whether the given set or dictionary represents a valid matching in the graph. """ if isinstance(matching, dict): matching = matching_dict_to_set(matching) # TODO This is parallelizable. return all(len(set(e1) & set(e2)) == 0 for e1, e2 in combinations(matching, 2))
def is_tournament(G): """Returns True if and only if `G` is a tournament. A tournament is a directed graph, with neither self-loops nor multi-edges, in which there is exactly one directed edge joining each pair of distinct nodes. Parameters ---------- G : NetworkX graph A directed graph representing a tournament. Returns ------- bool Whether the given graph is a tournament graph. Notes ----- Some definitions require a self-loop on each node, but that is not the convention used here. """ # In a tournament, there is exactly one directed edge joining each pair. return (all((v in G[u]) ^ (u in G[v]) for u, v in combinations(G, 2)) and nx.number_of_selfloops(G) == 0)
def test_tree_all_pairs_lowest_common_ancestor3(self): """Specifying no pairs same as specifying all.""" all_pairs = chain(combinations(self.DG, 2), ((node, node) for node in self.DG)) ans = dict(tree_all_pairs_lca(self.DG, 0, all_pairs)) self.assert_has_same_pairs(ans, self.ans)
def test_several_communities(self): # This graph is the disjoint union of five triangles. ground_truth = set( [frozenset(range(3 * i, 3 * (i + 1))) for i in range(5)]) edges = chain.from_iterable(combinations(c, 2) for c in ground_truth) G = nx.Graph(edges) self._check_communities(G, ground_truth)
def test_default_flow_function_karate_club_graph(self): G = nx.karate_club_graph() nx.set_edge_attributes(G, 1, 'capacity') T = nx.gomory_hu_tree(G) assert_true(nx.is_tree(T)) for u, v in combinations(G, 2): cut_value, edge = self.minimum_edge_weight(T, u, v) assert_equal(nx.minimum_cut_value(G, u, v), cut_value)
def _find_partition(G, starting_cell): """ Find a partition of the vertices of G into cells of complete graphs Parameters ---------- G : NetworkX Graph starting_cell : tuple of vertices in G which form a cell Returns ------- List of tuples of vertices of G Raises ------ NetworkXError If a cell is not a complete subgraph then G is not a line graph """ G_partition = G.copy() P = [starting_cell] # partition set G_partition.remove_edges_from(list(combinations(starting_cell, 2))) # keep list of partitioned nodes which might have an edge in G_partition partitioned_vertices = list(starting_cell) while G_partition.number_of_edges() > 0: # there are still edges left and so more cells to be made u = partitioned_vertices[-1] deg_u = len(G_partition[u]) if deg_u == 0: # if u has no edges left in G_partition then we have found # all of its cells so we do not need to keep looking partitioned_vertices.pop() else: # if u still has edges then we need to find its other cell # this other cell must be a complete subgraph or else G is # not a line graph new_cell = [u] + list(G_partition.neighbors(u)) for u in new_cell: for v in new_cell: if (u != v) and (v not in G.neighbors(u)): msg = "G is not a line graph" \ "(partition cell not a complete subgraph)" raise nx.NetworkXError(msg) P.append(tuple(new_cell)) G_partition.remove_edges_from(list(combinations(new_cell, 2))) partitioned_vertices += new_cell return P
def test_florentine_families_graph(self): G = nx.florentine_families_graph() nx.set_edge_attributes(G, 1, 'capacity') for flow_func in flow_funcs: T = nx.gomory_hu_tree(G, flow_func=flow_func) assert_true(nx.is_tree(T)) for u, v in combinations(G, 2): cut_value, edge = self.minimum_edge_weight(T, u, v) assert_equal(nx.minimum_cut_value(G, u, v), cut_value)
def test_theta(self): """Tests that pairs of vertices adjacent if and only if their sum weights exceeds the threshold parameter theta. """ G = nx.thresholded_random_geometric_graph(50, 0.25, 0.1) for u, v in combinations(G, 2): # Adjacent vertices must be within the given distance. if v in G[u]: assert_true( (G.nodes[u]['weight'] + G.nodes[v]['weight']) >= 0.1)
def _slow_edges(G, radius, p): """Returns edge list of node pairs within `radius` of each other using Minkowski distance metric `p` Works without scipy, but in `O(n^2)` time. """ # TODO This can be parallelized. edges = [] for (u, pu), (v, pv) in combinations(G.nodes(data='pos'), 2): if sum(abs(a - b) ** p for a, b in zip(pu, pv)) <= radius ** p: edges.append((u, v)) return edges
def test_metric(self): """Tests for providing an alternate distance metric to the generator. """ # Use the L1 metric. dist = l1dist G = nx.geographical_threshold_graph(50, 10, metric=dist) for u, v in combinations(G, 2): # Adjacent vertices must exceed the threshold. if v in G[u]: assert_true(join(G, u, v, 10, -2, dist)) # Nonadjacent vertices must not exceed the threshold. else: assert_false(join(G, u, v, 10, -2, dist))
def test_p(self): """Tests for providing an alternate distance metric to the generator. """ # Use the L1 metric. def dist(x, y): return sum(abs(a - b) for a, b in zip(x, y)) G = nx.thresholded_random_geometric_graph(50, 0.25, 0.1, p=1) for u, v in combinations(G, 2): # Adjacent vertices must be within the given distance. if v in G[u]: assert_true(dist(G.nodes[u]['pos'], G.nodes[v]['pos']) <= 0.25)
def test_distances(self): """Tests that pairs of vertices adjacent if and only if they are within the prescribed radius. """ # Use the Euclidean metric, the default according to the # documentation. def dist(x, y): return sqrt(sum((a - b)**2 for a, b in zip(x, y))) G = nx.soft_random_geometric_graph(50, 0.25) for u, v in combinations(G, 2): # Adjacent vertices must be within the given distance. if v in G[u]: assert_true(dist(G.nodes[u]['pos'], G.nodes[v]['pos']) <= 0.25)
def test_distances(self): """Tests that pairs of vertices adjacent if and only if their distances meet the given threshold. """ # Use the Euclidean metric and alpha = -2 # the default according to the documentation. dist = euclidean G = nx.geographical_threshold_graph(50, 10) for u, v in combinations(G, 2): # Adjacent vertices must exceed the threshold. if v in G[u]: assert_true(join(G, u, v, 10, -2, dist)) # Nonadjacent vertices must not exceed the threshold. else: assert_false(join(G, u, v, 10, -2, dist))
def test_p(self): """Tests for providing an alternate distance metric to the generator. """ # Use the L1 metric. dist = l1dist G = nx.random_geometric_graph(50, 0.25, p=1) for u, v in combinations(G, 2): # Adjacent vertices must be within the given distance. if v in G[u]: assert_true(dist(G.nodes[u]['pos'], G.nodes[v]['pos']) <= 0.25) # Nonadjacent vertices must be at greater distance. else: assert_false( dist(G.nodes[u]['pos'], G.nodes[v]['pos']) <= 0.25)
def test_node_names(self): """Tests using values other than sequential numbers as node IDs. """ import string nodes = list(string.ascii_lowercase) G = nx.thresholded_random_geometric_graph(nodes, 0.25, 0.1) assert_equal(len(G), len(nodes)) def dist(x, y): return sqrt(sum((a - b)**2 for a, b in zip(x, y))) for u, v in combinations(G, 2): # Adjacent vertices must be within the given distance. if v in G[u]: assert_true(dist(G.nodes[u]['pos'], G.nodes[v]['pos']) <= 0.25)
def _node_redundancy(G, v): """Returns the redundancy of the node `v` in the bipartite graph `G`. If `G` is a graph with `n` nodes, the redundancy of a node is the ratio of the "overlap" of `v` to the maximum possible overlap of `v` according to its degree. The overlap of `v` is the number of pairs of neighbors that have mutual neighbors themselves, other than `v`. `v` must have at least two neighbors in `G`. """ n = len(G[v]) # TODO On Python 3, we could just use `G[u].keys() & G[w].keys()` instead # of instantiating the entire sets. overlap = sum(1 for (u, w) in combinations(G[v], 2) if (set(G[u]) & set(G[w])) - set([v])) return (2 * overlap) / (n * (n - 1))
def test_distances(self): """Tests that pairs of vertices adjacent if and only if they are within the prescribed radius. """ # Use the Euclidean metric, the default according to the # documentation. dist = euclidean G = nx.random_geometric_graph(50, 0.25) for u, v in combinations(G, 2): # Adjacent vertices must be within the given distance. if v in G[u]: assert_true(dist(G.nodes[u]['pos'], G.nodes[v]['pos']) <= 0.25) # Nonadjacent vertices must be at greater distance. else: assert_false( dist(G.nodes[u]['pos'], G.nodes[v]['pos']) <= 0.25)
def phase3(self): # build potential remaining edges and choose with rejection sampling potential_edges = combinations(self.remaining_degree, 2) # build auxiliary graph of potential edges not already in graph H = nx.Graph([(u, v) for (u, v) in potential_edges if not self.graph.has_edge(u, v)]) rng = self.rng while self.remaining_degree: if not self.suitable_edge(): raise nx.NetworkXUnfeasible('no suitable edges left') while True: u, v = sorted(rng.choice(list(H.edges()))) if rng.random() < self.q(u, v): break if rng.random() < self.p(u, v): # accept edge self.graph.add_edge(u, v) self.update_remaining(u, v, aux_graph=H)
def test_node_names(self): """Tests using values other than sequential numbers as node IDs. """ import string nodes = list(string.ascii_lowercase) G = nx.random_geometric_graph(nodes, 0.25) assert_equal(len(G), len(nodes)) dist = euclidean for u, v in combinations(G, 2): # Adjacent vertices must be within the given distance. if v in G[u]: assert_true(dist(G.nodes[u]['pos'], G.nodes[v]['pos']) <= 0.25) # Nonadjacent vertices must be at greater distance. else: assert_false( dist(G.nodes[u]['pos'], G.nodes[v]['pos']) <= 0.25)
def make_max_clique_graph(G, create_using=None): """Returns the maximal clique graph of the given graph. The nodes of the maximal clique graph of `G` are the cliques of `G` and an edge joins two cliques if the cliques are not disjoint. Parameters ---------- G : NetworkX graph create_using : NetworkX graph constructor, optional (default=nx.Graph) Graph type to create. If graph instance, then cleared before populated. Returns ------- NetworkX graph A graph whose nodes are the cliques of `G` and whose edges join two cliques if they are not disjoint. Notes ----- This function behaves like the following code:: import networkx as nx G = nx.make_clique_bipartite(G) cliques = [v for v in G.nodes() if G.nodes[v]['bipartite'] == 0] G = nx.bipartite.project(G, cliques) G = nx.relabel_nodes(G, {-v: v - 1 for v in G}) It should be faster, though, since it skips all the intermediate steps. """ if create_using is None: B = G.__class__() else: B = nx.empty_graph(0, create_using) cliques = list(enumerate(set(c) for c in find_cliques(G))) # Add a numbered node for each clique. B.add_nodes_from(i for i, c in cliques) # Join cliques by an edge if they share a node. clique_pairs = combinations(cliques, 2) B.add_edges_from((i, j) for (i, c1), (j, c2) in clique_pairs if c1 & c2) return B
def _consolidate(sets, k): """Merge sets that share k or more elements. See: http://rosettacode.org/wiki/Set_consolidation The iterative python implementation posted there is faster than this because of the overhead of building a Graph and calling nx.connected_components, but it's not clear for us if we can use it in NetworkX because there is no licence for the code. """ G = nx.Graph() nodes = dict((i, s) for i, s in enumerate(sets)) G.add_nodes_from(nodes) G.add_edges_from((u, v) for u, v in combinations(nodes, 2) if len(nodes[u] & nodes[v]) >= k) for component in nx.connected_components(G): yield set.union(*[nodes[n] for n in component])
def _odd_triangle(G, T): """ Test whether T is an odd triangle in G Parameters ---------- G : NetworkX Graph T : 3-tuple of vertices forming triangle in G Returns ------- True is T is an odd triangle False otherwise Raises ------ NetworkXError T is not a triangle in G Notes ----- An odd triangle is one in which there exists another vertex in G which is adjacent to either exactly one or exactly all three of the vertices in the triangle. """ for u in T: if u not in G.nodes(): raise nx.NetworkXError("Vertex %s not in graph" % u) for e in list(combinations(T, 2)): if e[0] not in G.neighbors(e[1]): raise nx.NetworkXError("Edge (%s, %s) not in graph" % (e[0], e[1])) T_neighbors = defaultdict(int) for t in T: for v in G.neighbors(t): if v not in T: T_neighbors[v] += 1 for v in T_neighbors: if T_neighbors[v] in [1, 3]: return True return False
def test_wikipedia_example(self): # Example from https://en.wikipedia.org/wiki/Gomory%E2%80%93Hu_tree G = nx.Graph() G.add_weighted_edges_from(( (0, 1, 1), (0, 2, 7), (1, 2, 1), (1, 3, 3), (1, 4, 2), (2, 4, 4), (3, 4, 1), (3, 5, 6), (4, 5, 2), )) for flow_func in flow_funcs: T = nx.gomory_hu_tree(G, capacity='weight', flow_func=flow_func) assert_true(nx.is_tree(T)) for u, v in combinations(G, 2): cut_value, edge = self.minimum_edge_weight(T, u, v) assert_equal(nx.minimum_cut_value(G, u, v, capacity='weight'), cut_value)
def thresholded_random_geometric_graph(n, radius, theta, dim=2, pos=None, weight=None, p=2, seed=None): """Returns a thresholded random geometric graph in the unit cube. The thresholded random geometric graph [1] model places `n` nodes uniformly at random in the unit cube of dimensions `dim`. Each node `u` is assigned a weight :math:`w_u`. Two nodes `u` and `v` are joined by an edge if they are within the maximum connection distance, `radius` computed by the `p`-Minkowski distance and the summation of weights :math:`w_u` + :math:`w_v` is greater than or equal to the threshold parameter `theta`. Edges within `radius` of each other are determined using a KDTree when SciPy is available. This reduces the time complexity from :math:`O(n^2)` to :math:`O(n)`. Parameters ---------- n : int or iterable Number of nodes or iterable of nodes radius: float Distance threshold value theta: float Threshold value dim : int, optional Dimension of graph pos : dict, optional A dictionary keyed by node with node positions as values. weight : dict, optional Node weights as a dictionary of numbers keyed by node. p : float, optional Which Minkowski distance metric to use. `p` has to meet the condition ``1 <= p <= infinity``. If this argument is not specified, the :math:`L^2` metric (the Euclidean distance metric), p = 2 is used. This should not be confused with the `p` of an Erdős-Rényi random graph, which represents probability. seed : integer, random_state, or None (default) Indicator of random number generation state. See :ref:`Randomness<randomness>`. Returns ------- Graph A thresholded random geographic graph, undirected and without self-loops. Each node has a node attribute ``'pos'`` that stores the position of that node in Euclidean space as provided by the ``pos`` keyword argument or, if ``pos`` was not provided, as generated by this function. Similarly, each node has a nodethre attribute ``'weight'`` that stores the weight of that node as provided or as generated. Examples -------- Default Graph: G = nx.thresholded_random_geometric_graph(50, 0.2, 0.1) Custom Graph: Create a thresholded random geometric graph on 50 uniformly distributed nodes where nodes are joined by an edge if their sum weights drawn from a exponential distribution with rate = 5 are >= theta = 0.1 and their Euclidean distance is at most 0.2. Notes ----- This uses a *k*-d tree to build the graph. The `pos` keyword argument can be used to specify node positions so you can create an arbitrary distribution and domain for positions. For example, to use a 2D Gaussian distribution of node positions with mean (0, 0) and standard deviation 2 If weights are not specified they are assigned to nodes by drawing randomly from the exponential distribution with rate parameter :math:`\lambda=1`. To specify weights from a different distribution, use the `weight` keyword argument:: :: >>> import random >>> import math >>> n = 50 >>> pos = {i: (random.gauss(0, 2), random.gauss(0, 2)) for i in range(n)} >>> w = {i: random.expovariate(5.0) for i in range(n)} >>> G = nx.thresholded_random_geometric_graph(n, 0.2, 0.1, 2, pos, w) References ---------- .. [1] http://cole-maclean.github.io/blog/files/thesis.pdf """ n_name, nodes = n G = nx.Graph() namestr = 'thresholded_random_geometric_graph({}, {}, {}, {})' G.name = namestr % (n, radius, theta, dim,) G.add_nodes_from(nodes) # If no weights are provided, choose them from an exponential # distribution. if weight is None: weight = dict((v, seed.expovariate(1)) for v in G) # If no positions are provided, choose uniformly random vectors in # Euclidean space of the specified dimension. if pos is None: pos = dict((v, [seed.random() for i in range(dim)]) for v in nodes) # If no distance metric is provided, use Euclidean distance. nx.set_node_attributes(G, weight, 'weight') nx.set_node_attributes(G, pos, 'pos') # Returns ``True`` if and only if the nodes whose attributes are # ``du`` and ``dv`` should be joined, according to the threshold # condition and node pairs are within the maximum connection # distance, ``radius``. def should_join(pair): u, v = pair u_weight, v_weight = weight[u], weight[v] u_pos, v_pos = pos[u], pos[v] dist = (sum(abs(a - b) ** p for a, b in zip(u_pos, v_pos)))**(1 / p) # Check if dist is <= radius parameter. This check is redundant if # scipy is available and _fast_edges routine is used, but provides # the check in case scipy is not available and all edge combinations # need to be checked if dist <= radius: return theta <= u_weight + v_weight else: return False if _is_scipy_available: edges = _fast_edges(G, radius, p) G.add_edges_from(filter(should_join, edges)) else: G.add_edges_from(filter(should_join, combinations(G, 2))) return G
def waxman_graph(n, beta=0.4, alpha=0.1, L=None, domain=(0, 0, 1, 1), metric=None, seed=None): r"""Return a Waxman random graph. The Waxman random graph model places `n` nodes uniformly at random in a rectangular domain. Each pair of nodes at distance `d` is joined by an edge with probability .. math:: p = \beta \exp(-d / \alpha L). This function implements both Waxman models, using the `L` keyword argument. * Waxman-1: if `L` is not specified, it is set to be the maximum distance between any pair of nodes. * Waxman-2: if `L` is specified, the distance between a pair of nodes is chosen uniformly at random from the interval `[0, L]`. Parameters ---------- n : int or iterable Number of nodes or iterable of nodes beta: float Model parameter alpha: float Model parameter L : float, optional Maximum distance between nodes. If not specified, the actual distance is calculated. domain : four-tuple of numbers, optional Domain size, given as a tuple of the form `(x_min, y_min, x_max, y_max)`. metric : function A metric on vectors of numbers (represented as lists or tuples). This must be a function that accepts two lists (or tuples) as input and yields a number as output. The function must also satisfy the four requirements of a `metric`_. Specifically, if $d$ is the function and $x$, $y$, and $z$ are vectors in the graph, then $d$ must satisfy 1. $d(x, y) \ge 0$, 2. $d(x, y) = 0$ if and only if $x = y$, 3. $d(x, y) = d(y, x)$, 4. $d(x, z) \le d(x, y) + d(y, z)$. If this argument is not specified, the Euclidean distance metric is used. .. _metric: https://en.wikipedia.org/wiki/Metric_%28mathematics%29 seed : integer, random_state, or None (default) Indicator of random number generation state. See :ref:`Randomness<randomness>`. Returns ------- Graph A random Waxman graph, undirected and without self-loops. Each node has a node attribute ``'pos'`` that stores the position of that node in Euclidean space as generated by this function. Examples -------- Specify an alternate distance metric using the ``metric`` keyword argument. For example, to use the "`taxicab metric`_" instead of the default `Euclidean metric`_:: >>> dist = lambda x, y: sum(abs(a - b) for a, b in zip(x, y)) >>> G = nx.waxman_graph(10, 0.5, 0.1, metric=dist) .. _taxicab metric: https://en.wikipedia.org/wiki/Taxicab_geometry .. _Euclidean metric: https://en.wikipedia.org/wiki/Euclidean_distance Notes ----- Starting in NetworkX 2.0 the parameters alpha and beta align with their usual roles in the probability distribution. In earlier versions their positions in the expression were reversed. Their position in the calling sequence reversed as well to minimize backward incompatibility. References ---------- .. [1] B. M. Waxman, *Routing of multipoint connections*. IEEE J. Select. Areas Commun. 6(9),(1988) 1617--1622. """ n_name, nodes = n G = nx.Graph() G.add_nodes_from(nodes) (xmin, ymin, xmax, ymax) = domain # Each node gets a uniformly random position in the given rectangle. pos = dict((v, (seed.uniform(xmin, xmax), seed.uniform(ymin, ymax))) for v in G) nx.set_node_attributes(G, pos, 'pos') # If no distance metric is provided, use Euclidean distance. if metric is None: metric = euclidean # If the maximum distance L is not specified (that is, we are in the # Waxman-1 model), then find the maximum distance between any pair # of nodes. # # In the Waxman-1 model, join nodes randomly based on distance. In # the Waxman-2 model, join randomly based on random l. if L is None: L = max(metric(x, y) for x, y in combinations(pos.values(), 2)) def dist(u, v): return metric(pos[u], pos[v]) else: def dist(u, v): return seed.random() * L # `pair` is the pair of nodes to decide whether to join. def should_join(pair): return seed.random() < beta * math.exp(-dist(*pair) / (alpha * L)) G.add_edges_from(filter(should_join, combinations(G, 2))) return G
def eulerize(G): """ Transforms a graph into an Eulerian graph Parameters ---------- G : NetworkX graph An undirected graph Returns ------- G : NetworkX multigraph Raises ------ NetworkXError If the graph is not connected. See Also -------- is_eulerian, eulerian_circuit References ---------- .. [1] J. Edmonds, E. L. Johnson. Matching, Euler tours and the Chinese postman. Mathematical programming, Volume 5, Issue 1 (1973), 111-114. [2] https://en.wikipedia.org/wiki/Eulerian_path .. [3] http://web.math.princeton.edu/math_alive/5/Notes1.pdf Examples -------- >>> G = nx.complete_graph(10) >>> H = nx.eulerize(G) >>> nx.is_eulerian(H) True """ if G.order() == 0: raise nx.NetworkXPointlessConcept("Cannot Eulerize null graph") if not nx.is_connected(G): raise nx.NetworkXError("G is not connected") odd_degree_nodes = [n for n, d in G.degree() if d % 2 == 1] G = nx.MultiGraph(G) if len(odd_degree_nodes) == 0: return G # get all shortest paths between vertices of odd degree odd_deg_pairs_paths = [(m, { n: nx.shortest_path(G, source=m, target=n) }) for m, n in combinations(odd_degree_nodes, 2)] # use inverse path lengths as edge-weights in a new graph # store the paths in the graph for easy indexing later Gp = nx.Graph() for n, Ps in odd_deg_pairs_paths: for m, P in Ps.items(): if n != m: Gp.add_edge(m, n, weight=1 / len(P), path=P) # find the minimum weight matching of edges in the weighted graph best_matching = nx.Graph(list(nx.max_weight_matching(Gp))) # duplicate each edge along each path in the set of paths in Gp for m, n in best_matching.edges(): path = Gp[m][n]["path"] G.add_edges_from(nx.utils.pairwise(path)) return G
def geographical_threshold_graph(n, theta, dim=2, pos=None, weight=None, metric=None, p_dist=None, seed=None): r"""Returns a geographical threshold graph. The geographical threshold graph model places $n$ nodes uniformly at random in a rectangular domain. Each node $u$ is assigned a weight $w_u$. Two nodes $u$ and $v$ are joined by an edge if .. math:: (w_u + w_v)h(r) \ge \theta where `r` is the distance between `u` and `v`, h(r) is a probability of connection as a function of `r`, and :math:`\theta` as the threshold parameter. h(r) corresponds to the p_dist parameter. Parameters ---------- n : int or iterable Number of nodes or iterable of nodes theta: float Threshold value dim : int, optional Dimension of graph pos : dict Node positions as a dictionary of tuples keyed by node. weight : dict Node weights as a dictionary of numbers keyed by node. metric : function A metric on vectors of numbers (represented as lists or tuples). This must be a function that accepts two lists (or tuples) as input and yields a number as output. The function must also satisfy the four requirements of a `metric`_. Specifically, if $d$ is the function and $x$, $y$, and $z$ are vectors in the graph, then $d$ must satisfy 1. $d(x, y) \ge 0$, 2. $d(x, y) = 0$ if and only if $x = y$, 3. $d(x, y) = d(y, x)$, 4. $d(x, z) \le d(x, y) + d(y, z)$. If this argument is not specified, the Euclidean distance metric is used. .. _metric: https://en.wikipedia.org/wiki/Metric_%28mathematics%29 p_dist : function, optional A probability density function computing the probability of connecting two nodes that are of distance, r, computed by metric. The probability density function, `p_dist`, must be any function that takes the metric value as input and outputs a single probability value between 0-1. The scipy.stats package has many probability distribution functions implemented and tools for custom probability distribution definitions [2], and passing the .pdf method of scipy.stats distributions can be used here. If the probability function, `p_dist`, is not supplied, the default exponential function :math: `r^{-2}` is used. seed : integer, random_state, or None (default) Indicator of random number generation state. See :ref:`Randomness<randomness>`. Returns ------- Graph A random geographic threshold graph, undirected and without self-loops. Each node has a node attribute ``pos`` that stores the position of that node in Euclidean space as provided by the ``pos`` keyword argument or, if ``pos`` was not provided, as generated by this function. Similarly, each node has a node attribute ``weight`` that stores the weight of that node as provided or as generated. Examples -------- Specify an alternate distance metric using the ``metric`` keyword argument. For example, to use the `taxicab metric`_ instead of the default `Euclidean metric`_:: >>> dist = lambda x, y: sum(abs(a - b) for a, b in zip(x, y)) >>> G = nx.geographical_threshold_graph(10, 0.1, metric=dist) .. _taxicab metric: https://en.wikipedia.org/wiki/Taxicab_geometry .. _Euclidean metric: https://en.wikipedia.org/wiki/Euclidean_distance Notes ----- If weights are not specified they are assigned to nodes by drawing randomly from the exponential distribution with rate parameter $\lambda=1$. To specify weights from a different distribution, use the `weight` keyword argument:: >>> import random >>> n = 20 >>> w = {i: random.expovariate(5.0) for i in range(n)} >>> G = nx.geographical_threshold_graph(20, 50, weight=w) If node positions are not specified they are randomly assigned from the uniform distribution. Starting in NetworkX 2.1 the parameter ``alpha`` is deprecated and replaced with the customizable ``p_dist`` function parameter, which defaults to r^-2 if ``p_dist`` is not supplied. To reproduce networks of earlier NetworkX versions, a custom function needs to be defined and passed as the ``p_dist`` parameter. For example, if the parameter ``alpha`` = 2 was used in NetworkX 2.0, the custom function def custom_dist(r): r**-2 can be passed in versions >=2.1 as the parameter p_dist = custom_dist to produce an equivalent network. Note the change in sign from +2 to -2 in this parameter change. References ---------- .. [1] Masuda, N., Miwa, H., Konno, N.: Geographical threshold graphs with small-world and scale-free properties. Physical Review E 71, 036108 (2005) .. [2] Milan Bradonjić, Aric Hagberg and Allon G. Percus, Giant component and connectivity in geographical threshold graphs, in Algorithms and Models for the Web-Graph (WAW 2007), Antony Bonato and Fan Chung (Eds), pp. 209--216, 2007 """ n_name, nodes = n G = nx.Graph() G.add_nodes_from(nodes) # If no weights are provided, choose them from an exponential # distribution. if weight is None: weight = dict((v, seed.expovariate(1)) for v in G) # If no positions are provided, choose uniformly random vectors in # Euclidean space of the specified dimension. if pos is None: pos = dict((v, [seed.random() for i in range(dim)]) for v in nodes) # If no distance metric is provided, use Euclidean distance. if metric is None: metric = euclidean nx.set_node_attributes(G, weight, 'weight') nx.set_node_attributes(G, pos, 'pos') # if p_dist is not supplied, use default r^-2 if p_dist is None: def p_dist(r): return r**-2 # Returns ``True`` if and only if the nodes whose attributes are # ``du`` and ``dv`` should be joined, according to the threshold # condition. def should_join(pair): u, v = pair u_pos, v_pos = pos[u], pos[v] u_weight, v_weight = weight[u], weight[v] return (u_weight + v_weight) * p_dist(metric(u_pos, v_pos)) >= theta G.add_edges_from(filter(should_join, combinations(G, 2))) return G