|
""" |
|
Generators for some directed graphs, including growing network (GN) graphs and |
|
scale-free graphs. |
|
|
|
""" |
|
|
|
import numbers |
|
from collections import Counter |
|
|
|
import networkx as nx |
|
from networkx.generators.classic import empty_graph |
|
from networkx.utils import discrete_sequence, py_random_state, weighted_choice |
|
|
|
__all__ = [ |
|
"gn_graph", |
|
"gnc_graph", |
|
"gnr_graph", |
|
"random_k_out_graph", |
|
"scale_free_graph", |
|
] |
|
|
|
|
|
@py_random_state(3) |
|
@nx._dispatchable(graphs=None, returns_graph=True) |
|
def gn_graph(n, kernel=None, create_using=None, seed=None): |
|
"""Returns the growing network (GN) digraph with `n` nodes. |
|
|
|
The GN graph is built by adding nodes one at a time with a link to one |
|
previously added node. The target node for the link is chosen with |
|
probability based on degree. The default attachment kernel is a linear |
|
function of the degree of a node. |
|
|
|
The graph is always a (directed) tree. |
|
|
|
Parameters |
|
---------- |
|
n : int |
|
The number of nodes for the generated graph. |
|
kernel : function |
|
The attachment kernel. |
|
create_using : NetworkX graph constructor, optional (default DiGraph) |
|
Graph type to create. If graph instance, then cleared before populated. |
|
seed : integer, random_state, or None (default) |
|
Indicator of random number generation state. |
|
See :ref:`Randomness<randomness>`. |
|
|
|
Examples |
|
-------- |
|
To create the undirected GN graph, use the :meth:`~DiGraph.to_directed` |
|
method:: |
|
|
|
>>> D = nx.gn_graph(10) # the GN graph |
|
>>> G = D.to_undirected() # the undirected version |
|
|
|
To specify an attachment kernel, use the `kernel` keyword argument:: |
|
|
|
>>> D = nx.gn_graph(10, kernel=lambda x: x**1.5) # A_k = k^1.5 |
|
|
|
References |
|
---------- |
|
.. [1] P. L. Krapivsky and S. Redner, |
|
Organization of Growing Random Networks, |
|
Phys. Rev. E, 63, 066123, 2001. |
|
""" |
|
G = empty_graph(1, create_using, default=nx.DiGraph) |
|
if not G.is_directed(): |
|
raise nx.NetworkXError("create_using must indicate a Directed Graph") |
|
|
|
if kernel is None: |
|
|
|
def kernel(x): |
|
return x |
|
|
|
if n == 1: |
|
return G |
|
|
|
G.add_edge(1, 0) |
|
ds = [1, 1] |
|
|
|
for source in range(2, n): |
|
|
|
dist = [kernel(d) for d in ds] |
|
|
|
target = discrete_sequence(1, distribution=dist, seed=seed)[0] |
|
G.add_edge(source, target) |
|
ds.append(1) |
|
ds[target] += 1 |
|
return G |
|
|
|
|
|
@py_random_state(3) |
|
@nx._dispatchable(graphs=None, returns_graph=True) |
|
def gnr_graph(n, p, create_using=None, seed=None): |
|
"""Returns the growing network with redirection (GNR) digraph with `n` |
|
nodes and redirection probability `p`. |
|
|
|
The GNR graph is built by adding nodes one at a time with a link to one |
|
previously added node. The previous target node is chosen uniformly at |
|
random. With probability `p` the link is instead "redirected" to the |
|
successor node of the target. |
|
|
|
The graph is always a (directed) tree. |
|
|
|
Parameters |
|
---------- |
|
n : int |
|
The number of nodes for the generated graph. |
|
p : float |
|
The redirection probability. |
|
create_using : NetworkX graph constructor, optional (default DiGraph) |
|
Graph type to create. If graph instance, then cleared before populated. |
|
seed : integer, random_state, or None (default) |
|
Indicator of random number generation state. |
|
See :ref:`Randomness<randomness>`. |
|
|
|
Examples |
|
-------- |
|
To create the undirected GNR graph, use the :meth:`~DiGraph.to_directed` |
|
method:: |
|
|
|
>>> D = nx.gnr_graph(10, 0.5) # the GNR graph |
|
>>> G = D.to_undirected() # the undirected version |
|
|
|
References |
|
---------- |
|
.. [1] P. L. Krapivsky and S. Redner, |
|
Organization of Growing Random Networks, |
|
Phys. Rev. E, 63, 066123, 2001. |
|
""" |
|
G = empty_graph(1, create_using, default=nx.DiGraph) |
|
if not G.is_directed(): |
|
raise nx.NetworkXError("create_using must indicate a Directed Graph") |
|
|
|
if n == 1: |
|
return G |
|
|
|
for source in range(1, n): |
|
target = seed.randrange(0, source) |
|
if seed.random() < p and target != 0: |
|
target = next(G.successors(target)) |
|
G.add_edge(source, target) |
|
return G |
|
|
|
|
|
@py_random_state(2) |
|
@nx._dispatchable(graphs=None, returns_graph=True) |
|
def gnc_graph(n, create_using=None, seed=None): |
|
"""Returns the growing network with copying (GNC) digraph with `n` nodes. |
|
|
|
The GNC graph is built by adding nodes one at a time with a link to one |
|
previously added node (chosen uniformly at random) and to all of that |
|
node's successors. |
|
|
|
Parameters |
|
---------- |
|
n : int |
|
The number of nodes for the generated graph. |
|
create_using : NetworkX graph constructor, optional (default DiGraph) |
|
Graph type to create. If graph instance, then cleared before populated. |
|
seed : integer, random_state, or None (default) |
|
Indicator of random number generation state. |
|
See :ref:`Randomness<randomness>`. |
|
|
|
References |
|
---------- |
|
.. [1] P. L. Krapivsky and S. Redner, |
|
Network Growth by Copying, |
|
Phys. Rev. E, 71, 036118, 2005k.}, |
|
""" |
|
G = empty_graph(1, create_using, default=nx.DiGraph) |
|
if not G.is_directed(): |
|
raise nx.NetworkXError("create_using must indicate a Directed Graph") |
|
|
|
if n == 1: |
|
return G |
|
|
|
for source in range(1, n): |
|
target = seed.randrange(0, source) |
|
for succ in G.successors(target): |
|
G.add_edge(source, succ) |
|
G.add_edge(source, target) |
|
return G |
|
|
|
|
|
@py_random_state(6) |
|
@nx._dispatchable(graphs=None, returns_graph=True) |
|
def scale_free_graph( |
|
n, |
|
alpha=0.41, |
|
beta=0.54, |
|
gamma=0.05, |
|
delta_in=0.2, |
|
delta_out=0, |
|
seed=None, |
|
initial_graph=None, |
|
): |
|
"""Returns a scale-free directed graph. |
|
|
|
Parameters |
|
---------- |
|
n : integer |
|
Number of nodes in graph |
|
alpha : float |
|
Probability for adding a new node connected to an existing node |
|
chosen randomly according to the in-degree distribution. |
|
beta : float |
|
Probability for adding an edge between two existing nodes. |
|
One existing node is chosen randomly according the in-degree |
|
distribution and the other chosen randomly according to the out-degree |
|
distribution. |
|
gamma : float |
|
Probability for adding a new node connected to an existing node |
|
chosen randomly according to the out-degree distribution. |
|
delta_in : float |
|
Bias for choosing nodes from in-degree distribution. |
|
delta_out : float |
|
Bias for choosing nodes from out-degree distribution. |
|
seed : integer, random_state, or None (default) |
|
Indicator of random number generation state. |
|
See :ref:`Randomness<randomness>`. |
|
initial_graph : MultiDiGraph instance, optional |
|
Build the scale-free graph starting from this initial MultiDiGraph, |
|
if provided. |
|
|
|
Returns |
|
------- |
|
MultiDiGraph |
|
|
|
Examples |
|
-------- |
|
Create a scale-free graph on one hundred nodes:: |
|
|
|
>>> G = nx.scale_free_graph(100) |
|
|
|
Notes |
|
----- |
|
The sum of `alpha`, `beta`, and `gamma` must be 1. |
|
|
|
References |
|
---------- |
|
.. [1] B. Bollobás, C. Borgs, J. Chayes, and O. Riordan, |
|
Directed scale-free graphs, |
|
Proceedings of the fourteenth annual ACM-SIAM Symposium on |
|
Discrete Algorithms, 132--139, 2003. |
|
""" |
|
|
|
def _choose_node(candidates, node_list, delta): |
|
if delta > 0: |
|
bias_sum = len(node_list) * delta |
|
p_delta = bias_sum / (bias_sum + len(candidates)) |
|
if seed.random() < p_delta: |
|
return seed.choice(node_list) |
|
return seed.choice(candidates) |
|
|
|
if initial_graph is not None and hasattr(initial_graph, "_adj"): |
|
if not isinstance(initial_graph, nx.MultiDiGraph): |
|
raise nx.NetworkXError("initial_graph must be a MultiDiGraph.") |
|
G = initial_graph |
|
else: |
|
|
|
G = nx.MultiDiGraph([(0, 1), (1, 2), (2, 0)]) |
|
|
|
if alpha <= 0: |
|
raise ValueError("alpha must be > 0.") |
|
if beta <= 0: |
|
raise ValueError("beta must be > 0.") |
|
if gamma <= 0: |
|
raise ValueError("gamma must be > 0.") |
|
|
|
if abs(alpha + beta + gamma - 1.0) >= 1e-9: |
|
raise ValueError("alpha+beta+gamma must equal 1.") |
|
|
|
if delta_in < 0: |
|
raise ValueError("delta_in must be >= 0.") |
|
|
|
if delta_out < 0: |
|
raise ValueError("delta_out must be >= 0.") |
|
|
|
|
|
vs = sum((count * [idx] for idx, count in G.out_degree()), []) |
|
ws = sum((count * [idx] for idx, count in G.in_degree()), []) |
|
|
|
|
|
node_list = list(G.nodes()) |
|
|
|
|
|
numeric_nodes = [n for n in node_list if isinstance(n, numbers.Number)] |
|
if len(numeric_nodes) > 0: |
|
|
|
cursor = max(int(n.real) for n in numeric_nodes) + 1 |
|
else: |
|
|
|
cursor = 0 |
|
|
|
while len(G) < n: |
|
r = seed.random() |
|
|
|
|
|
if r < alpha: |
|
|
|
|
|
v = cursor |
|
cursor += 1 |
|
|
|
node_list.append(v) |
|
|
|
w = _choose_node(ws, node_list, delta_in) |
|
|
|
elif r < alpha + beta: |
|
|
|
|
|
v = _choose_node(vs, node_list, delta_out) |
|
|
|
w = _choose_node(ws, node_list, delta_in) |
|
|
|
else: |
|
|
|
|
|
v = _choose_node(vs, node_list, delta_out) |
|
|
|
w = cursor |
|
cursor += 1 |
|
|
|
node_list.append(w) |
|
|
|
|
|
G.add_edge(v, w) |
|
|
|
|
|
vs.append(v) |
|
ws.append(w) |
|
|
|
return G |
|
|
|
|
|
@py_random_state(4) |
|
@nx._dispatchable(graphs=None, returns_graph=True) |
|
def random_uniform_k_out_graph(n, k, self_loops=True, with_replacement=True, seed=None): |
|
"""Returns a random `k`-out graph with uniform attachment. |
|
|
|
A random `k`-out graph with uniform attachment is a multidigraph |
|
generated by the following algorithm. For each node *u*, choose |
|
`k` nodes *v* uniformly at random (with replacement). Add a |
|
directed edge joining *u* to *v*. |
|
|
|
Parameters |
|
---------- |
|
n : int |
|
The number of nodes in the returned graph. |
|
|
|
k : int |
|
The out-degree of each node in the returned graph. |
|
|
|
self_loops : bool |
|
If True, self-loops are allowed when generating the graph. |
|
|
|
with_replacement : bool |
|
If True, neighbors are chosen with replacement and the |
|
returned graph will be a directed multigraph. Otherwise, |
|
neighbors are chosen without replacement and the returned graph |
|
will be a directed graph. |
|
|
|
seed : integer, random_state, or None (default) |
|
Indicator of random number generation state. |
|
See :ref:`Randomness<randomness>`. |
|
|
|
Returns |
|
------- |
|
NetworkX graph |
|
A `k`-out-regular directed graph generated according to the |
|
above algorithm. It will be a multigraph if and only if |
|
`with_replacement` is True. |
|
|
|
Raises |
|
------ |
|
ValueError |
|
If `with_replacement` is False and `k` is greater than |
|
`n`. |
|
|
|
See also |
|
-------- |
|
random_k_out_graph |
|
|
|
Notes |
|
----- |
|
The return digraph or multidigraph may not be strongly connected, or |
|
even weakly connected. |
|
|
|
If `with_replacement` is True, this function is similar to |
|
:func:`random_k_out_graph`, if that function had parameter `alpha` |
|
set to positive infinity. |
|
|
|
""" |
|
if with_replacement: |
|
create_using = nx.MultiDiGraph() |
|
|
|
def sample(v, nodes): |
|
if not self_loops: |
|
nodes = nodes - {v} |
|
return (seed.choice(list(nodes)) for i in range(k)) |
|
|
|
else: |
|
create_using = nx.DiGraph() |
|
|
|
def sample(v, nodes): |
|
if not self_loops: |
|
nodes = nodes - {v} |
|
return seed.sample(list(nodes), k) |
|
|
|
G = nx.empty_graph(n, create_using) |
|
nodes = set(G) |
|
for u in G: |
|
G.add_edges_from((u, v) for v in sample(u, nodes)) |
|
return G |
|
|
|
|
|
@py_random_state(4) |
|
@nx._dispatchable(graphs=None, returns_graph=True) |
|
def random_k_out_graph(n, k, alpha, self_loops=True, seed=None): |
|
"""Returns a random `k`-out graph with preferential attachment. |
|
|
|
A random `k`-out graph with preferential attachment is a |
|
multidigraph generated by the following algorithm. |
|
|
|
1. Begin with an empty digraph, and initially set each node to have |
|
weight `alpha`. |
|
2. Choose a node `u` with out-degree less than `k` uniformly at |
|
random. |
|
3. Choose a node `v` from with probability proportional to its |
|
weight. |
|
4. Add a directed edge from `u` to `v`, and increase the weight |
|
of `v` by one. |
|
5. If each node has out-degree `k`, halt, otherwise repeat from |
|
step 2. |
|
|
|
For more information on this model of random graph, see [1]. |
|
|
|
Parameters |
|
---------- |
|
n : int |
|
The number of nodes in the returned graph. |
|
|
|
k : int |
|
The out-degree of each node in the returned graph. |
|
|
|
alpha : float |
|
A positive :class:`float` representing the initial weight of |
|
each vertex. A higher number means that in step 3 above, nodes |
|
will be chosen more like a true uniformly random sample, and a |
|
lower number means that nodes are more likely to be chosen as |
|
their in-degree increases. If this parameter is not positive, a |
|
:exc:`ValueError` is raised. |
|
|
|
self_loops : bool |
|
If True, self-loops are allowed when generating the graph. |
|
|
|
seed : integer, random_state, or None (default) |
|
Indicator of random number generation state. |
|
See :ref:`Randomness<randomness>`. |
|
|
|
Returns |
|
------- |
|
:class:`~networkx.classes.MultiDiGraph` |
|
A `k`-out-regular multidigraph generated according to the above |
|
algorithm. |
|
|
|
Raises |
|
------ |
|
ValueError |
|
If `alpha` is not positive. |
|
|
|
Notes |
|
----- |
|
The returned multidigraph may not be strongly connected, or even |
|
weakly connected. |
|
|
|
References |
|
---------- |
|
[1]: Peterson, Nicholas R., and Boris Pittel. |
|
"Distance between two random `k`-out digraphs, with and without |
|
preferential attachment." |
|
arXiv preprint arXiv:1311.5961 (2013). |
|
<https://arxiv.org/abs/1311.5961> |
|
|
|
""" |
|
if alpha < 0: |
|
raise ValueError("alpha must be positive") |
|
G = nx.empty_graph(n, create_using=nx.MultiDiGraph) |
|
weights = Counter({v: alpha for v in G}) |
|
for i in range(k * n): |
|
u = seed.choice([v for v, d in G.out_degree() if d < k]) |
|
|
|
|
|
if not self_loops: |
|
adjustment = Counter({u: weights[u]}) |
|
else: |
|
adjustment = Counter() |
|
v = weighted_choice(weights - adjustment, seed=seed) |
|
G.add_edge(u, v) |
|
weights[v] += 1 |
|
return G |
|
|