Repository: networkx/networkx

Issue:
Problem with nx.partial_duplication_graph
Hi everyone,
the operations on the partial_duplication_graph algorithm are out of order. One should first select a node to replicate and then include a new node. 

Also, the edge connecting the original node and the replica should be added after the other edge. If you do it before, the replica is considered a neighbor of the original one and you won't have the desired result.


Discussion:
You are correct that the new node should not get self-loop edges and by adding an edge to ```u``` before duplicating the neighbor edges, we make it possible to create a self-loop edge ```v-v```.  This doesn't align with the doc_string order either.  That should be fixed... Probably good to check the article to check whether the code aligns with the paper, or the doc_string aligns with the paper (are self-loops created for the new node or not).

I pretty sure the order of selecting ```u``` and then adding ```v``` doesn't impact the results.  But we might as well make it agree with the doc_string description.
In fact, the deal about selecting "u" and adding "v" is that we choose a node "u" randomly to be duplicated and then add a node "v" to be "u"'s replica. If you add "v" before selecting "u", there is a slight chance that you select the recently added node "v" to be replicated, and that would make no sense for the model and does not agree with the doc_string description. But it's certainly good to check the article.

thank you for the attention. 


Yes -- I agree with you that you can't choose u to be v.  But the code doesn't allow that because it doesn't choose from all nodes, only from ```range(0,newN)``` which excludes the new node.
Here is the part of the code that selects the node to be replicated:

- src_node = random.randint(0, new_node)

In fact, the node to be duplicated have to be chosen from range(0, newN). But the function random.randint(), that is used to select the node to be replicated, takes into account "newN" to the selectable nodes. Maybe the alternative should be:

- src_node = random.choice(range(0, new_node))

or

- src_node = random.randint(0, new_node-1)
Ahh -- thank you for persisting -- now I understand where my confusion is coming...

The code actually doesn't use the ```random.randint``` function.  It uses ```seed.randint``` where ```seed``` is created by the decorator ```@py_random_state```. The function ```numpy.Random.randint``` does not include the upper endpoint, while the ```random.randint``` does. I got those two confused...

So... better to switch the order to what the docs say, and also to make that code clearer... Otherwise we could (as the current code allows) choose u to be ```new node``` which is not good.

Thanks again...


Resolving patch:
diff --git a/networkx/generators/duplication.py b/networkx/generators/duplication.py
--- a/networkx/generators/duplication.py
+++ b/networkx/generators/duplication.py
@@ -80,21 +80,21 @@ def partial_duplication_graph(N, n, p, q, seed=None):
 
     G = nx.complete_graph(n)
     for new_node in range(n, N):
-        # Add a new vertex, v, to the graph.
-        G.add_node(new_node)
-
         # Pick a random vertex, u, already in the graph.
-        src_node = seed.randint(0, new_node)
+        src_node = seed.randint(0, new_node - 1)
 
-        # Join v and u with probability q.
-        if seed.random() < q:
-            G.add_edge(new_node, src_node)
+        # Add a new vertex, v, to the graph.
+        G.add_node(new_node)
 
         # For each neighbor of u...
         for neighbor_node in list(nx.all_neighbors(G, src_node)):
             # Add the neighbor to v with probability p.
             if seed.random() < p:
                 G.add_edge(new_node, neighbor_node)
+
+        # Join v and u with probability q.
+        if seed.random() < q:
+            G.add_edge(new_node, src_node)
     return G
 
 


Test patch: