diff --git a/networkx/generators/random_graphs.py b/networkx/generators/random_graphs.py index e4f2c569dd0..745f64e4da4 100644 --- a/networkx/generators/random_graphs.py +++ b/networkx/generators/random_graphs.py @@ -1000,6 +1000,11 @@ def random_lobster(n, p1, p2, seed=None): leaf nodes. A caterpillar is a tree that reduces to a path graph when pruning all leaf nodes; setting `p2` to zero produces a caterpillar. + This implementation iterates on the probabilities `p1` and `p2` to add + edges at levels 1 and 2, respectively. Graphs are therefore constructed + iteratively with uniform randomness at each level rather than being selected + uniformly at random from the set of all possible lobsters. + Parameters ---------- n : int @@ -1011,19 +1016,29 @@ def random_lobster(n, p1, p2, seed=None): seed : integer, random_state, or None (default) Indicator of random number generation state. See :ref:`Randomness`. + + Raises + ------ + NetworkXError + If `p1` or `p2` parameters are >= 1 because the while loops would never finish. """ + p1, p2 = abs(p1), abs(p2) + if any([p >= 1 for p in [p1, p2]]): + raise nx.NetworkXError("Probability values for `p1` and `p2` must both be < 1.") + # a necessary ingredient in any self-respecting graph library llen = int(2 * seed.random() * n + 0.5) L = path_graph(llen) # build caterpillar: add edges to path graph with probability p1 current_node = llen - 1 for n in range(llen): - if seed.random() < p1: # add fuzzy caterpillar parts + while seed.random() < p1: # add fuzzy caterpillar parts current_node += 1 L.add_edge(n, current_node) - if seed.random() < p2: # add crunchy lobster bits + cat_node = current_node + while seed.random() < p2: # add crunchy lobster bits current_node += 1 - L.add_edge(current_node - 1, current_node) + L.add_edge(cat_node, current_node) return L # voila, un lobster! diff --git a/networkx/generators/tests/test_random_graphs.py b/networkx/generators/tests/test_random_graphs.py index f958dceab7e..8f2d6841545 100644 --- a/networkx/generators/tests/test_random_graphs.py +++ b/networkx/generators/tests/test_random_graphs.py @@ -91,7 +91,38 @@ def test_random_graph(self): constructor = [(10, 20, 0.8), (20, 40, 0.8)] G = random_shell_graph(constructor, seed) + def is_caterpillar(g): + """ + A tree is a caterpillar iff all nodes of degree >=3 are surrounded + by at most two nodes of degree two or greater. + ref: http://mathworld.wolfram.com/CaterpillarGraph.html + """ + deg_over_3 = [n for n in g if g.degree(n) >= 3] + for n in deg_over_3: + nbh_deg_over_2 = [nbh for nbh in g.neighbors(n) if g.degree(nbh) >= 2] + if not len(nbh_deg_over_2) <= 2: + return False + return True + + def is_lobster(g): + """ + A tree is a lobster if it has the property that the removal of leaf + nodes leaves a caterpillar graph (Gallian 2007) + ref: http://mathworld.wolfram.com/LobsterGraph.html + """ + non_leafs = [n for n in g if g.degree(n) > 1] + return is_caterpillar(g.subgraph(non_leafs)) + G = random_lobster(10, 0.1, 0.5, seed) + assert max([G.degree(n) for n in G.nodes()]) > 3 + assert is_lobster(G) + pytest.raises(NetworkXError, random_lobster, 10, 0.1, 1, seed) + pytest.raises(NetworkXError, random_lobster, 10, 1, 1, seed) + pytest.raises(NetworkXError, random_lobster, 10, 1, 0.5, seed) + + # docstring says this should be a caterpillar + G = random_lobster(10, 0.1, 0.0, seed) + assert is_caterpillar(G) # difficult to find seed that requires few tries seq = random_powerlaw_tree_sequence(10, 3, seed=14, tries=1)