ok, you are right this needs more care. I think the following will work:

Note that for directed graphs, we are sampling from a maximum of E=n*(n-1) edges and for mutual graphs, we are sampling from a maximum of E=n*(n-1)/2 edges. In both cases, we can think of this as picking a random subset from indices (0..E-1). A chosen index i for directed graph can be thought of as belonging to between 'from' and 'to' nodes such that 'from' = floor(i/n) and 'to' = i mod n (and a '+1' adjustment so that from != to) . How we implemented this in the directed case was to have an outer loop that loops through the 'from' node and inner loop that chooses the 'to' node.

For the undirected case, we just need to have a different way to interpret a chosen 'i'. For example, we can have the outer loop to be looping (n-1)/2 times and inner loop still chooses from (0..n-1) and therefore uses only one Bin(n,p). Then we need to interpret the chosen 'i' as an edge between a 'from' and a 'to' node.

Oh and we should also simply use scala 'par' to parallelize the outer loop.

Let me know if this makes sense -- a refactoring to have an outer and inner loop formulation and an interpretation of chosen 'i' that is different for different types of graphs should be cleaner code.