summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Schult <dschult@colgate.edu>2021-05-27 16:54:53 -0400
committerGitHub <noreply@github.com>2021-05-27 16:54:53 -0400
commitfc032996017983aca9b1910b9a51947d74def0f9 (patch)
tree138ce91ffd2d25d2061fefd6938dd999c2e50106
parent8933105b29ba92225879bc4dcd28f68e3d5c9604 (diff)
downloadnetworkx-fc032996017983aca9b1910b9a51947d74def0f9.tar.gz
add special processing of `multigraph_input` upon graph init (#4823)
* add special processing of `multigraph_input` upon graph init Fixes: #4720 Adding a keyword argument `multigraph_input=True` to a graph construction call should treat any incoming input data for the graph as a dict-of-dict-of-dict multigraph data structure. Previously the multigraph_input argument would be added to the graph attribute dict and ignored when processing the input data. * Change default and add tests * make default try mgi=True, and if fails try mgi=False * copy parameter docstring to main class
-rw-r--r--networkx/classes/multidigraph.py46
-rw-r--r--networkx/classes/multigraph.py46
-rw-r--r--networkx/classes/tests/test_graph.py3
-rw-r--r--networkx/classes/tests/test_multigraph.py89
-rw-r--r--networkx/convert.py17
-rw-r--r--networkx/tests/test_convert.py5
6 files changed, 190 insertions, 16 deletions
diff --git a/networkx/classes/multidigraph.py b/networkx/classes/multidigraph.py
index d8903606..51e590fe 100644
--- a/networkx/classes/multidigraph.py
+++ b/networkx/classes/multidigraph.py
@@ -13,6 +13,7 @@ from networkx.classes.reportviews import (
InMultiDegreeView,
)
from networkx.exception import NetworkXError
+import networkx.convert as convert
__all__ = ["MultiDiGraph"]
@@ -40,6 +41,19 @@ class MultiDiGraph(MultiGraph, DiGraph):
dict of dicts, dict of lists, NetworkX graph, NumPy matrix
or 2d ndarray, SciPy sparse matrix, or PyGraphviz graph.
+ multigraph_input : bool or None (default None)
+ Note: Only used when `incoming_graph_data` is a dict.
+ If True, `incoming_graph_data` is assumed to be a
+ dict-of-dict-of-dict-of-dict structure keyed by
+ node to neighbor to edge keys to edge data for multi-edges.
+ A NetworkXError is raised if this is not the case.
+ If False, :func:`to_networkx_graph` is used to try to determine
+ the dict's graph data structure as either a dict-of-dict-of-dict
+ keyed by node to neighbor to edge data, or a dict-of-iterable
+ keyed by node to neighbors.
+ If None, the treatment for True is tried, but if it fails,
+ the treatment for False is tried.
+
attr : keyword arguments, optional (default= no attributes)
Attributes to add to graph as key=value pairs.
@@ -265,7 +279,7 @@ class MultiDiGraph(MultiGraph, DiGraph):
edge_key_dict_factory = dict
# edge_attr_dict_factory = dict
- def __init__(self, incoming_graph_data=None, **attr):
+ def __init__(self, incoming_graph_data=None, multigraph_input=None, **attr):
"""Initialize a graph with edges, name, or graph attributes.
Parameters
@@ -277,6 +291,19 @@ class MultiDiGraph(MultiGraph, DiGraph):
packages are installed the data can also be a NumPy matrix
or 2d ndarray, a SciPy sparse matrix, or a PyGraphviz graph.
+ multigraph_input : bool or None (default None)
+ Note: Only used when `incoming_graph_data` is a dict.
+ If True, `incoming_graph_data` is assumed to be a
+ dict-of-dict-of-dict-of-dict structure keyed by
+ node to neighbor to edge keys to edge data for multi-edges.
+ A NetworkXError is raised if this is not the case.
+ If False, :func:`to_networkx_graph` is used to try to determine
+ the dict's graph data structure as either a dict-of-dict-of-dict
+ keyed by node to neighbor to edge data, or a dict-of-iterable
+ keyed by node to neighbors.
+ If None, the treatment for True is tried, but if it fails,
+ the treatment for False is tried.
+
attr : keyword arguments, optional (default= no attributes)
Attributes to add to graph as key=value pairs.
@@ -299,7 +326,22 @@ class MultiDiGraph(MultiGraph, DiGraph):
"""
self.edge_key_dict_factory = self.edge_key_dict_factory
- DiGraph.__init__(self, incoming_graph_data, **attr)
+ # multigraph_input can be None/True/False. So check "is not False"
+ if isinstance(incoming_graph_data, dict) and multigraph_input is not False:
+ DiGraph.__init__(self)
+ try:
+ convert.from_dict_of_dicts(
+ incoming_graph_data, create_using=self, multigraph_input=True
+ )
+ self.graph.update(attr)
+ except Exception as e:
+ if multigraph_input is True:
+ raise nx.NetworkXError(
+ f"converting multigraph_input raised:\n{type(e)}: {e}"
+ )
+ DiGraph.__init__(self, incoming_graph_data, **attr)
+ else:
+ DiGraph.__init__(self, incoming_graph_data, **attr)
@property
def adj(self):
diff --git a/networkx/classes/multigraph.py b/networkx/classes/multigraph.py
index f8044c62..b2e69561 100644
--- a/networkx/classes/multigraph.py
+++ b/networkx/classes/multigraph.py
@@ -6,6 +6,7 @@ from networkx.classes.graph import Graph
from networkx.classes.coreviews import MultiAdjacencyView
from networkx.classes.reportviews import MultiEdgeView, MultiDegreeView
from networkx import NetworkXError
+import networkx.convert as convert
__all__ = ["MultiGraph"]
@@ -34,6 +35,19 @@ class MultiGraph(Graph):
dict of dicts, dict of lists, NetworkX graph, NumPy matrix
or 2d ndarray, SciPy sparse matrix, or PyGraphviz graph.
+ multigraph_input : bool or None (default None)
+ Note: Only used when `incoming_graph_data` is a dict.
+ If True, `incoming_graph_data` is assumed to be a
+ dict-of-dict-of-dict-of-dict structure keyed by
+ node to neighbor to edge keys to edge data for multi-edges.
+ A NetworkXError is raised if this is not the case.
+ If False, :func:`to_networkx_graph` is used to try to determine
+ the dict's graph data structure as either a dict-of-dict-of-dict
+ keyed by node to neighbor to edge data, or a dict-of-iterable
+ keyed by node to neighbors.
+ If None, the treatment for True is tried, but if it fails,
+ the treatment for False is tried.
+
attr : keyword arguments, optional (default= no attributes)
Attributes to add to graph as key=value pairs.
@@ -274,7 +288,7 @@ class MultiGraph(Graph):
"""
return MultiGraph
- def __init__(self, incoming_graph_data=None, **attr):
+ def __init__(self, incoming_graph_data=None, multigraph_input=None, **attr):
"""Initialize a graph with edges, name, or graph attributes.
Parameters
@@ -286,6 +300,19 @@ class MultiGraph(Graph):
packages are installed the data can also be a NumPy matrix
or 2d ndarray, a SciPy sparse matrix, or a PyGraphviz graph.
+ multigraph_input : bool or None (default None)
+ Note: Only used when `incoming_graph_data` is a dict.
+ If True, `incoming_graph_data` is assumed to be a
+ dict-of-dict-of-dict-of-dict structure keyed by
+ node to neighbor to edge keys to edge data for multi-edges.
+ A NetworkXError is raised if this is not the case.
+ If False, :func:`to_networkx_graph` is used to try to determine
+ the dict's graph data structure as either a dict-of-dict-of-dict
+ keyed by node to neighbor to edge data, or a dict-of-iterable
+ keyed by node to neighbors.
+ If None, the treatment for True is tried, but if it fails,
+ the treatment for False is tried.
+
attr : keyword arguments, optional (default= no attributes)
Attributes to add to graph as key=value pairs.
@@ -308,7 +335,22 @@ class MultiGraph(Graph):
"""
self.edge_key_dict_factory = self.edge_key_dict_factory
- Graph.__init__(self, incoming_graph_data, **attr)
+ # multigraph_input can be None/True/False. So check "is not False"
+ if isinstance(incoming_graph_data, dict) and multigraph_input is not False:
+ Graph.__init__(self)
+ try:
+ convert.from_dict_of_dicts(
+ incoming_graph_data, create_using=self, multigraph_input=True
+ )
+ self.graph.update(attr)
+ except Exception as e:
+ if multigraph_input is True:
+ raise nx.NetworkXError(
+ f"converting multigraph_input raised:\n{type(e)}: {e}"
+ )
+ Graph.__init__(self, incoming_graph_data, **attr)
+ else:
+ Graph.__init__(self, incoming_graph_data, **attr)
@property
def adj(self):
diff --git a/networkx/classes/tests/test_graph.py b/networkx/classes/tests/test_graph.py
index 9b31cd5f..86744403 100644
--- a/networkx/classes/tests/test_graph.py
+++ b/networkx/classes/tests/test_graph.py
@@ -505,9 +505,6 @@ class TestGraph(BaseAttrGraphTester):
G = self.Graph({1: [2], 2: [1]}, name="test")
assert G.name == "test"
assert sorted(G.adj.items()) == [(1, {2: {}}), (2, {1: {}})]
- G = self.Graph({1: [2], 2: [1]}, name="test")
- assert G.name == "test"
- assert sorted(G.adj.items()) == [(1, {2: {}}), (2, {1: {}})]
def test_adjacency(self):
G = self.K3
diff --git a/networkx/classes/tests/test_multigraph.py b/networkx/classes/tests/test_multigraph.py
index bf2e98e0..8428acf2 100644
--- a/networkx/classes/tests/test_multigraph.py
+++ b/networkx/classes/tests/test_multigraph.py
@@ -182,6 +182,95 @@ class TestMultiGraph(BaseMultiGraphTester, _TestGraph):
expected = [(1, {2: {0: {}}}), (2, {1: {0: {}}})]
assert sorted(G.adj.items()) == expected
+ def test_data_multigraph_input(self):
+ # standard case with edge keys and edge data
+ edata0 = dict(w=200, s="foo")
+ edata1 = dict(w=201, s="bar")
+ keydict = {0: edata0, 1: edata1}
+ dododod = {"a": {"b": keydict}}
+
+ multiple_edge = [("a", "b", 0, edata0), ("a", "b", 1, edata1)]
+ single_edge = [("a", "b", 0, keydict)]
+
+ G = self.Graph(dododod, multigraph_input=True)
+ assert list(G.edges(keys=True, data=True)) == multiple_edge
+ G = self.Graph(dododod, multigraph_input=None)
+ assert list(G.edges(keys=True, data=True)) == multiple_edge
+ G = self.Graph(dododod, multigraph_input=False)
+ assert list(G.edges(keys=True, data=True)) == single_edge
+
+ # test round-trip to_dict_of_dict and MultiGraph constructor
+ G = self.Graph(dododod, multigraph_input=True)
+ H = self.Graph(nx.to_dict_of_dicts(G))
+ assert nx.is_isomorphic(G, H) is True # test that default is True
+ for mgi in [True, False]:
+ H = self.Graph(nx.to_dict_of_dicts(G), multigraph_input=mgi)
+ assert nx.is_isomorphic(G, H) == mgi
+
+ # Set up cases for when incoming_graph_data is not multigraph_input
+ etraits = {"w": 200, "s": "foo"}
+ egraphics = {"color": "blue", "shape": "box"}
+ edata = {"traits": etraits, "graphics": egraphics}
+ dodod1 = {"a": {"b": edata}}
+ dodod2 = {"a": {"b": etraits}}
+ dodod3 = {"a": {"b": {"traits": etraits, "s": "foo"}}}
+ dol = {"a": ["b"]}
+
+ multiple_edge = [
+ ("a", "b", "traits", etraits),
+ ("a", "b", "graphics", egraphics),
+ ]
+ single_edge = [("a", "b", 0, {})]
+ single_edge1 = [("a", "b", 0, edata)]
+ single_edge2 = [("a", "b", 0, etraits)]
+ single_edge3 = [("a", "b", 0, {"traits": etraits, "s": "foo"})]
+
+ cases = [ # (dod, mgi, edges)
+ (dodod1, True, multiple_edge),
+ (dodod1, False, single_edge1),
+ (dodod2, False, single_edge2),
+ (dodod3, False, single_edge3),
+ (dol, False, single_edge),
+ ]
+
+ @pytest.mark.parametrize("dod, mgi, edges", cases)
+ def test_non_multigraph_input(self, dod, mgi, edges):
+ G = self.Graph(dod, multigraph_input=mgi)
+ assert list(G.edges(keys=True, data=True)) == edges
+ G = nx.to_networkx_graph(dod, create_using=self.Graph, multigraph_input=mgi)
+ assert list(G.edges(keys=True, data=True)) == edges
+
+ mgi_none_cases = [
+ (dodod1, multiple_edge),
+ (dodod2, single_edge2),
+ (dodod3, single_edge3),
+ ]
+
+ @pytest.mark.parametrize("dod, edges", mgi_none_cases)
+ def test_non_multigraph_input_mgi_none(self, dod, edges):
+ # test constructor without to_networkx_graph for mgi=None
+ G = self.Graph(dod)
+ assert list(G.edges(keys=True, data=True)) == edges
+
+ raise_cases = [dodod2, dodod3, dol]
+
+ @pytest.mark.parametrize("dod", raise_cases)
+ def test_non_multigraph_input_raise(self, dod):
+ # cases where NetworkXError is raised
+ pytest.raises(
+ nx.NetworkXError,
+ self.Graph,
+ dod,
+ multigraph_input=True,
+ )
+ pytest.raises(
+ nx.NetworkXError,
+ nx.to_networkx_graph,
+ dod,
+ create_using=self.Graph,
+ multigraph_input=True,
+ )
+
def test_getitem(self):
G = self.K3
assert G[0] == {1: {0: {}}, 2: {0: {}}}
diff --git a/networkx/convert.py b/networkx/convert.py
index 3cc80e3c..1db67976 100644
--- a/networkx/convert.py
+++ b/networkx/convert.py
@@ -103,7 +103,11 @@ def to_networkx_graph(data, create_using=None, multigraph_input=False):
return from_dict_of_dicts(
data, create_using=create_using, multigraph_input=multigraph_input
)
- except:
+ except Exception as e:
+ if multigraph_input is True:
+ raise nx.NetworkXError(
+ f"converting multigraph_input raised:\n{type(e)}: {e}"
+ )
try:
return from_dict_of_lists(data, create_using=create_using)
except Exception as e:
@@ -370,9 +374,11 @@ def from_dict_of_dicts(d, create_using=None, multigraph_input=False):
Graph type to create. If graph instance, then cleared before populated.
multigraph_input : bool (default False)
- When True, the values of the inner dict are assumed
- to be containers of edge data for multiple edges.
- Otherwise this routine assumes the edge data are singletons.
+ When True, the dict `d` is assumed
+ to be a dict-of-dict-of-dict-of-dict structure keyed by
+ node to neighbor to edge keys to edge data for multi-edges.
+ Otherwise this routine assumes dict-of-dict-of-dict keyed by
+ node to neighbor to edge data.
Examples
--------
@@ -386,9 +392,8 @@ def from_dict_of_dicts(d, create_using=None, multigraph_input=False):
"""
G = nx.empty_graph(0, create_using)
G.add_nodes_from(d)
- # is dict a MultiGraph or MultiDiGraph?
+ # does dict d represent a MultiGraph or MultiDiGraph?
if multigraph_input:
- # make a copy of the list of edge data (but not the edge data)
if G.is_directed():
if G.is_multigraph():
G.add_edges_from(
diff --git a/networkx/tests/test_convert.py b/networkx/tests/test_convert.py
index a99b6001..62a5b05d 100644
--- a/networkx/tests/test_convert.py
+++ b/networkx/tests/test_convert.py
@@ -182,10 +182,9 @@ class TestConvert:
GW = to_networkx_graph(dod, create_using=nx.MultiGraph, multigraph_input=True)
assert nodes_equal(sorted(XGM.nodes()), sorted(GW.nodes()))
assert edges_equal(sorted(XGM.edges()), sorted(GW.edges()))
- GI = nx.MultiGraph(dod) # convert can't tell whether to duplicate edges!
+ GI = nx.MultiGraph(dod)
assert nodes_equal(sorted(XGM.nodes()), sorted(GI.nodes()))
- # assert_not_equal(sorted(XGM.edges()), sorted(GI.edges()))
- assert not sorted(XGM.edges()) == sorted(GI.edges())
+ assert sorted(XGM.edges()) == sorted(GI.edges())
GE = from_dict_of_dicts(dod, create_using=nx.MultiGraph, multigraph_input=False)
assert nodes_equal(sorted(XGM.nodes()), sorted(GE.nodes()))
assert sorted(XGM.edges()) != sorted(GE.edges())