Spaces:
Running
Running
# This file contains utilities for testing the dispatching feature | |
# A full test of all dispatchable algorithms is performed by | |
# modifying the pytest invocation and setting an environment variable | |
# NETWORKX_TEST_BACKEND=nx-loopback pytest | |
# This is comprehensive, but only tests the `test_override_dispatch` | |
# function in networkx.classes.backends. | |
# To test the `_dispatch` function directly, several tests scattered throughout | |
# NetworkX have been augmented to test normal and dispatch mode. | |
# Searching for `dispatch_interface` should locate the specific tests. | |
import networkx as nx | |
from networkx import DiGraph, Graph, MultiDiGraph, MultiGraph, PlanarEmbedding | |
from networkx.classes.reportviews import NodeView | |
class LoopbackGraph(Graph): | |
__networkx_backend__ = "nx-loopback" | |
class LoopbackDiGraph(DiGraph): | |
__networkx_backend__ = "nx-loopback" | |
class LoopbackMultiGraph(MultiGraph): | |
__networkx_backend__ = "nx-loopback" | |
class LoopbackMultiDiGraph(MultiDiGraph): | |
__networkx_backend__ = "nx-loopback" | |
class LoopbackPlanarEmbedding(PlanarEmbedding): | |
__networkx_backend__ = "nx-loopback" | |
def convert(graph): | |
if isinstance(graph, PlanarEmbedding): | |
return LoopbackPlanarEmbedding(graph) | |
if isinstance(graph, MultiDiGraph): | |
return LoopbackMultiDiGraph(graph) | |
if isinstance(graph, MultiGraph): | |
return LoopbackMultiGraph(graph) | |
if isinstance(graph, DiGraph): | |
return LoopbackDiGraph(graph) | |
if isinstance(graph, Graph): | |
return LoopbackGraph(graph) | |
raise TypeError(f"Unsupported type of graph: {type(graph)}") | |
class LoopbackDispatcher: | |
def __getattr__(self, item): | |
try: | |
return nx.utils.backends._registered_algorithms[item].orig_func | |
except KeyError: | |
raise AttributeError(item) from None | |
def convert_from_nx( | |
graph, | |
*, | |
edge_attrs=None, | |
node_attrs=None, | |
preserve_edge_attrs=None, | |
preserve_node_attrs=None, | |
preserve_graph_attrs=None, | |
name=None, | |
graph_name=None, | |
): | |
if name in { | |
# Raise if input graph changes | |
"lexicographical_topological_sort", | |
"topological_generations", | |
"topological_sort", | |
# Sensitive tests (iteration order matters) | |
"dfs_labeled_edges", | |
}: | |
return graph | |
if isinstance(graph, NodeView): | |
# Convert to a Graph with only nodes (no edges) | |
new_graph = Graph() | |
new_graph.add_nodes_from(graph.items()) | |
graph = new_graph | |
G = LoopbackGraph() | |
elif not isinstance(graph, Graph): | |
raise TypeError( | |
f"Bad type for graph argument {graph_name} in {name}: {type(graph)}" | |
) | |
elif graph.__class__ in {Graph, LoopbackGraph}: | |
G = LoopbackGraph() | |
elif graph.__class__ in {DiGraph, LoopbackDiGraph}: | |
G = LoopbackDiGraph() | |
elif graph.__class__ in {MultiGraph, LoopbackMultiGraph}: | |
G = LoopbackMultiGraph() | |
elif graph.__class__ in {MultiDiGraph, LoopbackMultiDiGraph}: | |
G = LoopbackMultiDiGraph() | |
elif graph.__class__ in {PlanarEmbedding, LoopbackPlanarEmbedding}: | |
G = LoopbackDiGraph() # or LoopbackPlanarEmbedding | |
else: | |
# It would be nice to be able to convert _AntiGraph to a regular Graph | |
# nx.algorithms.approximation.kcomponents._AntiGraph | |
# nx.algorithms.tree.branchings.MultiDiGraph_EdgeKey | |
# nx.classes.tests.test_multidigraph.MultiDiGraphSubClass | |
# nx.classes.tests.test_multigraph.MultiGraphSubClass | |
G = graph.__class__() | |
if preserve_graph_attrs: | |
G.graph.update(graph.graph) | |
if preserve_node_attrs: | |
G.add_nodes_from(graph.nodes(data=True)) | |
elif node_attrs: | |
G.add_nodes_from( | |
( | |
node, | |
{ | |
k: datadict.get(k, default) | |
for k, default in node_attrs.items() | |
if default is not None or k in datadict | |
}, | |
) | |
for node, datadict in graph.nodes(data=True) | |
) | |
else: | |
G.add_nodes_from(graph) | |
if graph.is_multigraph(): | |
if preserve_edge_attrs: | |
G.add_edges_from( | |
(u, v, key, datadict) | |
for u, nbrs in graph._adj.items() | |
for v, keydict in nbrs.items() | |
for key, datadict in keydict.items() | |
) | |
elif edge_attrs: | |
G.add_edges_from( | |
( | |
u, | |
v, | |
key, | |
{ | |
k: datadict.get(k, default) | |
for k, default in edge_attrs.items() | |
if default is not None or k in datadict | |
}, | |
) | |
for u, nbrs in graph._adj.items() | |
for v, keydict in nbrs.items() | |
for key, datadict in keydict.items() | |
) | |
else: | |
G.add_edges_from( | |
(u, v, key, {}) | |
for u, nbrs in graph._adj.items() | |
for v, keydict in nbrs.items() | |
for key, datadict in keydict.items() | |
) | |
elif preserve_edge_attrs: | |
G.add_edges_from(graph.edges(data=True)) | |
elif edge_attrs: | |
G.add_edges_from( | |
( | |
u, | |
v, | |
{ | |
k: datadict.get(k, default) | |
for k, default in edge_attrs.items() | |
if default is not None or k in datadict | |
}, | |
) | |
for u, v, datadict in graph.edges(data=True) | |
) | |
else: | |
G.add_edges_from(graph.edges) | |
return G | |
def convert_to_nx(obj, *, name=None): | |
return obj | |
def on_start_tests(items): | |
# Verify that items can be xfailed | |
for item in items: | |
assert hasattr(item, "add_marker") | |
def can_run(self, name, args, kwargs): | |
# It is unnecessary to define this function if algorithms are fully supported. | |
# We include it for illustration purposes. | |
return hasattr(self, name) | |
dispatcher = LoopbackDispatcher() | |