Repository: networkx/networkx

Issue:
Wrong random_lobster implementation?
Hi, it seems that [networkx.random_lobster's implementation logic](https://github.com/networkx/networkx/blob/4e9771f04192e94a5cbdd71249a983d124a56593/networkx/generators/random_graphs.py#L1009) is not aligned with the common definition as given in [wolfram mathworld](http://mathworld.wolfram.com/LobsterGraph.html) or [Wikipedia](https://en.wikipedia.org/wiki/List_of_graphs#Lobster).
For example, it can not produce the simplest lobster graph examples such as ![lobster_example](https://www.researchgate.net/profile/Dr_Nasreen_Khan/publication/256292297/figure/fig5/AS:669566174756876@1536648424468/A-diameter-2-lobster-graph.png).


Discussion:
I believe it can produce such monster lobsters :)
What makes you think that it can't?
It creates random lobsters -- you can't request a specific lobster.
Hi, in my understanding as well as my experiments, the implementation can generate at most one first-leaf and one second-leaf. It doesn't support multiple leaves~
Here is my simple beta-version implementation if you are interested in. The definitions of p1 and p2 are different from yours.
```py
def random_lobster(n, p1, p2):
    path_edges = [list(range(n-1)), list(range(1,n))]
    first_leaf_num = np.random.binomial(n, p1)
    first_leaf_edges = [np.random.randint(low=0, high=n, size=first_leaf_num).tolist(),
                        np.arange(n, n+first_leaf_num).tolist()]
    second_leaf_num = np.random.binomial(first_leaf_num, p2)
    second_leaf_edges = [np.random.randint(low=n, high=n+first_leaf_num, size=second_leaf_num).tolist(),
                         np.arange(n+first_leaf_num, n+first_leaf_num+second_leaf_num).tolist()]
    rol = path_edges[0]+first_leaf_edges[0]+second_leaf_edges[0]
    col = path_edges[1]+first_leaf_edges[1]+second_leaf_edges[1]
    edges = [rol, col]
    return edges
```
So, you agree that it creates lobsters -- just not the lobsters you would like to see?

None of the references you point to specify anything specific about creating lobsters other than it is a tree where when you remove leaves twice you end up with a path.

It looks like you are proposing a new random model of lobster generation. Would you mind describing your model, where you got the algorithm from, why you think it is good, how it might be included in NetworkX, etc?
It seems that you agree that the current implementation is not able to generate multi-leaves lobster graphs... And as a random generator of a specific set of graphs, it should be able to generate any element in the set. Can you imagine that `np.random.choice(10)` will never generate 5? If you can't do that, maybe you should not include this function in the package, or use another clearer name. 

My codes just resulted from my one-minute thinking. It's super easy but can generate a much larger subset of lobster graphs than yours. It's not ideal of-course. The correct implementation logic given your function name should be able to generate any element with equal probability in the set of lobster graphs of some specific properties.

I appreciate your efforts on sharing the codes and networkx is probably one of the most popular python libraries about graphs. But please remind that influence implies responsibility. The more users use your codes, the more influential a mistake will be. The stable version should be stable and reliable.

BTW, I don't care if my codes will be included; no responsibility as well. Just wanna remind of this issue and if the team cares about the users, they should do something to fix this. 

The current implementation can/does implement multi-leaf lobsters. But it only generates at most one second level leaf for each first level leaf.  As is true for many of our random graph algorithms, we don't claim uniformly likely outcomes from the set. That is often too difficult, though it is the ideal we'd all appreciate. Perhaps we should be more clear in the documentation.

I would like to replace/improve the lobster generator if there is interest. And it would be nice to use a method established in the literature. As you say, influence implies responsibility. Since you are suggesting the change, would you be willing to search for established methods of creating random lobsters? Thanks either way -- it's good to have this on our radar.
Hi, feel free to do whatever you want. This is just a suggestion of common misunderstanding.
I was using your function for building datasets for training and evaluation methods. I have to say that the distribution of generated samples does matter in this case. As for the established methods, I don't think of me as an expert in the algorithm area, and I don't have enough time for learning everything from scratch.
And sorry for being harsh these days. I was freak out for wasting several days on a small-variant dataset. I took networkx's reliability as granted and spent lots of time debugging other pieces of my codes. You can at least mention something in the documents to avoid such situations. Thanks.

Resolving patch:
diff --git a/networkx/generators/random_graphs.py b/networkx/generators/random_graphs.py
--- 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<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!
 
 


Test patch:
diff --git a/networkx/generators/tests/test_random_graphs.py b/networkx/generators/tests/test_random_graphs.py
--- 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)