3

I have a NetworkX MultiDiGraph that contains selfloops. According to the documentation, this is a valid property of a MultiDiGraph.

A MultiDiGraph holds directed edges. Self loops are allowed.

But when I try to remove the selfloops from the MultiDiGraph using MG.remove_edges_from(MG.selfloop_edges()), the following warning in generated:

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-13-ff3391f2296f> in <module>()
      1 # remove selfloop edges from the graph
----> 2 MG.remove_edges_from(MG.selfloop_edges())

~/Program_Files/miniconda3/envs/py36/lib/python3.6/site-packages/networkx/classes/multigraph.py in remove_edges_from(self, ebunch)
    603         []
    604         """
--> 605         for e in ebunch:
    606             try:
    607                 self.remove_edge(*e[:3])

~/Program_Files/miniconda3/envs/py36/lib/python3.6/site-packages/networkx/classes/function.py in <genexpr>(.0)
   1154                 return ((n, n)
   1155                         for n, nbrs in G.adj.items()
-> 1156                         if n in nbrs for d in nbrs[n].values())
   1157         else:
   1158             return ((n, n) for n, nbrs in G.adj.items() if n in nbrs)

~/Program_Files/miniconda3/envs/py36/lib/python3.6/_collections_abc.py in __iter__(self)
    759 
    760     def __iter__(self):
--> 761         for key in self._mapping:
    762             yield self._mapping[key]
    763 

RuntimeError: dictionary changed size during iteration

Am I missing something in the way you remove a selfloop from a MultliDiGraph or is this a bug with NetworkX?

Reproducible example demonstrating the unexpected error:

import networkx as nx

# create an empty MultiDiGraph
MG = nx.MultiDiGraph()

# add some edges to the graph
MG.add_edges_from([(1, 2), (2, 3), (3, 1), (1, 2), (2, 1), (2, 2)])

# check the edges in the graph
MG.edges()

# remove selfloop edges from the graph
MG.remove_edges_from(MG.selfloop_edges())

This method works as expected with a DiGraph, as seen below:

# create an empty MultiDiGraph
G = nx.DiGraph()

# add some edges to the graph
G.add_edges_from([(1, 2), (2, 3), (3, 1), (1, 2), (2, 1), (2, 2)])

# check the edges in the graph
G.edges
OutEdgeView([(1, 2), (2, 3), (2, 1), (2, 2), (3, 1)])

# remove selfloop edges from the graph
G.remove_edges_from(G.selfloop_edges())

# check the edges in the graph
G.edges()
OutEdgeView([(1, 2), (2, 3), (2, 1), (3, 1)])
CurtLH
  • 2,329
  • 4
  • 41
  • 64

2 Answers2

3

The problem is that MG.selfloop_edges() is an iterator (more precisely generator) over the selfloop edges in the graph, and by removing the edges you are changing the iterator's edges during the iteration.

According to the documentation:

Parameters: ebunch (list or container of edge tuples) - ...

That means that the ebunch parameter should be a container, while MG.selfloop_edges() returns a generator. You can read more about the difference between the two here.

The issue can be solved by passing list(MG.selfloop_edges()) to MG.remove_edges_from (instead of passing MG.selfloop_edges() directly).

zohar.kom
  • 1,765
  • 3
  • 12
  • 28
  • 1
    Since you'll always put a graph in there I suspect a bug unless OP is misusing the library. It must have worked in python 2 so perhaps it wasn't caught on the port to 3. – Andras Deak -- Слава Україні Sep 27 '18 at 07:37
  • 1
    Documentation states the `ebunch` should be a list or container, and here a generator is passed. So at least according to the documentation it's ok that it fails. However, it's counterintuitive that the original code doesn't work, and can be easily fixed internally. – zohar.kom Sep 27 '18 at 07:48
  • 1
    Thanks. I'd add that (preferably with doc link) to your answer, because that would change the meaning from "here's a workaround: ..." to "you're using it wrong" ;) – Andras Deak -- Слава Україні Sep 27 '18 at 09:36
  • 1
    Thanks @AndrasDeak, added – zohar.kom Sep 27 '18 at 10:51
  • The [documentation](https://networkx.github.io/documentation/networkx-2.2/reference/classes/generated/networkx.DiGraph.remove_edges_from.html#networkx.DiGraph.remove_edges_from) also states that the argument for `DiGraph.remove_edges_from()` is also an `ebunch`. But for some reason, you can pass `G.selfloop_edges()` directly into that function and it doesn't complain. – CurtLH Sep 28 '18 at 01:53
  • 1
    I've added an answer below, explaining this. But basically this is a bug that should go away once you update networkx. – Joel Sep 08 '20 at 03:41
1

I address this issue in another answer here: https://stackoverflow.com/a/49428652/2966723

Right now you can do MG.remove_edges_from(list(MG.selfloops_edges())). However, this is actually a bug and will be fixed in an upcoming release: https://github.com/networkx/networkx/pull/4080. Then the code you wanted to write will work.

Joel
  • 22,598
  • 6
  • 69
  • 93