Skip to content

Commit

Permalink
Add random_ordered_tree and forest_str
Browse files Browse the repository at this point in the history
  • Loading branch information
Erotemic committed Oct 28, 2020
1 parent d397b4d commit 63ca8f3
Show file tree
Hide file tree
Showing 4 changed files with 407 additions and 0 deletions.
54 changes: 54 additions & 0 deletions networkx/generators/random_graphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1293,3 +1293,57 @@ def my_function(b):
j = int(math.ceil(n * kernel_root(i / n, j / n, r)))
graph.add_edge(i - 1, j - 1)
return graph


@py_random_state(2)
def random_ordered_tree(n, seed=None, directed=False):
"""
Creates a random ordered tree
Parameters
----------
n : int
A positive integer representing the number of nodes in the tree.
seed : integer, random_state, or None (default)
Indicator of random number generation state.
See :ref:`Randomness<randomness>`.
directed : bool
if the edges are one-way
Returns
-------
networkx.OrderedDiGraph | networkx.OrderedGraph
Example
-------
>>> assert len(random_ordered_tree(n=1, seed=0).nodes) == 1
>>> assert len(random_ordered_tree(n=2, seed=0).nodes) == 2
>>> assert len(random_ordered_tree(n=3, seed=0).nodes) == 3
>>> from networkx.readwrite.text import forest_str
>>> otree = random_ordered_tree(n=5, seed=3, directed=True)
>>> print(forest_str(otree))
╙── 1
├─╼ 4
│   ├─╼ 3
│   └─╼ 2
└─╼ 0
"""
from networkx.utils import create_py_random_state

rng = create_py_random_state(seed)
# Create a random undirected tree
utree = nx.random_tree(n, seed=rng)
# Use a random root node and dfs to define edge directions
nodes = list(utree.nodes)
source = rng.choice(nodes)
edges = nx.dfs_edges(utree, source=source)
if directed:
otree = nx.OrderedDiGraph()
else:
otree = nx.OrderedGraph()
# Populate the ordered graph
otree.add_nodes_from(utree.nodes)
otree.add_edges_from(edges)
return otree
1 change: 1 addition & 0 deletions networkx/readwrite/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
from networkx.readwrite.gexf import *
from networkx.readwrite.nx_shp import *
from networkx.readwrite.json_graph import *
from networkx.readwrite.text import *
206 changes: 206 additions & 0 deletions networkx/readwrite/tests/test_text.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import networkx as nx
from textwrap import dedent


def test_directed_tree_str():
# Create a directed forest with labels
graph = nx.balanced_tree(r=2, h=2, create_using=nx.DiGraph)
for node in graph.nodes:
graph.nodes[node]["label"] = "node_" + chr(ord("a") + node)

node_target = dedent(
"""
╙── 0
├─╼ 2
│   ├─╼ 6
│   └─╼ 5
└─╼ 1
├─╼ 4
└─╼ 3
"""
).strip()

label_target = dedent(
"""
╙── node_a
├─╼ node_c
│   ├─╼ node_g
│   └─╼ node_f
└─╼ node_b
├─╼ node_e
└─╼ node_d
"""
).strip()

# Basic node case
ret = nx.forest_str(graph, use_labels=False)
print(ret)
assert ret == node_target

# Basic label case
ret = nx.forest_str(graph, use_labels=True)
print(ret)
assert ret == label_target

# Custom write function case
lines = []
ret = nx.forest_str(graph, write=lines.append, use_labels=False)
assert ret is None
assert lines == node_target.split("\n")

# Smoke test to ensure passing the print function works. To properly test
# this case we would need to capture stdout. (for potential reference
# implementation see :class:`ubelt.util_stream.CaptureStdout`)
ret = nx.forest_str(graph, write=print)
assert ret is None


def test_empty_graph():
assert nx.forest_str(nx.DiGraph()) == "╙"
assert nx.forest_str(nx.Graph()) == "╙"


def test_directed_multi_tree_forest():
tree1 = nx.balanced_tree(r=2, h=2, create_using=nx.DiGraph)
tree2 = nx.balanced_tree(r=2, h=2, create_using=nx.DiGraph)
tree2 = nx.relabel_nodes(tree2, {n: n + len(tree1) for n in tree2.nodes})
forest = nx.union(tree1, tree2)
ret = nx.forest_str(forest, sources=[0, 7])
print(ret)

target = dedent(
"""
╟── 7
╎   ├─╼ 9
╎   │   ├─╼ 13
╎   │   └─╼ 12
╎   └─╼ 8
╎   ├─╼ 11
╎   └─╼ 10
╙── 0
├─╼ 2
│   ├─╼ 6
│   └─╼ 5
└─╼ 1
├─╼ 4
└─╼ 3
"""
).strip()
assert ret == target

tree3 = nx.balanced_tree(r=2, h=2, create_using=nx.DiGraph)
tree3 = nx.relabel_nodes(tree3, {n: n + len(forest) for n in tree3.nodes})
forest = nx.union(forest, tree3)
ret = nx.forest_str(forest, sources=[0, 7, 14])
print(ret)

target = dedent(
"""
╟── 14
╎   ├─╼ 16
╎   │   ├─╼ 20
╎   │   └─╼ 19
╎   └─╼ 15
╎   ├─╼ 18
╎   └─╼ 17
╟── 7
╎   ├─╼ 9
╎   │   ├─╼ 13
╎   │   └─╼ 12
╎   └─╼ 8
╎   ├─╼ 11
╎   └─╼ 10
╙── 0
├─╼ 2
│   ├─╼ 6
│   └─╼ 5
└─╼ 1
├─╼ 4
└─╼ 3
"""
).strip()
assert ret == target


def test_undirected_multi_tree_forest():
tree1 = nx.balanced_tree(r=2, h=2, create_using=nx.Graph)
tree2 = nx.balanced_tree(r=2, h=2, create_using=nx.Graph)
tree2 = nx.relabel_nodes(tree2, {n: n + len(tree1) for n in tree2.nodes})
forest = nx.union(tree1, tree2)
ret = nx.forest_str(forest, sources=[0, 7])
print(ret)

target = dedent(
"""
╟── 7
╎   ├── 9
╎   │   ├── 13
╎   │   └── 12
╎   └── 8
╎   ├── 11
╎   └── 10
╙── 0
├── 2
│   ├── 6
│   └── 5
└── 1
├── 4
└── 3
"""
).strip()
assert ret == target


def test_undirected_tree_str():
# Create a directed forest with labels
graph = nx.balanced_tree(r=2, h=2, create_using=nx.Graph)

# arbitrary starting point
nx.forest_str(graph)

node_target0 = dedent(
"""
╙── 0
├── 2
│   ├── 6
│   └── 5
└── 1
├── 4
└── 3
"""
).strip()

# defined starting point
ret = nx.forest_str(graph, sources=[0])
print(ret)
assert ret == node_target0

# defined starting point
node_target2 = dedent(
"""
╙── 2
├── 6
├── 5
└── 0
└── 1
├── 4
└── 3
"""
).strip()
ret = nx.forest_str(graph, sources=[2])
print(ret)
assert ret == node_target2


def test_forest_str_errors():
import pytest

ugraph = nx.generators.complete_graph(3, create_using=nx.Graph)

with pytest.raises(nx.NetworkXNotImplemented):
nx.forest_str(ugraph)

dgraph = nx.generators.complete_graph(3, create_using=nx.DiGraph)

with pytest.raises(nx.NetworkXNotImplemented):
nx.forest_str(dgraph)

0 comments on commit 63ca8f3

Please sign in to comment.