Repository: networkx/networkx

Issue:
Should `is_distance_regular` and `intersection_array` have @not_implemented_for?
Something I noticed while reviewing #5849 :

The docstrings for [`is_distance_regular`](https://github.com/networkx/networkx/blob/d68caf64b057f3c5f0adf4f09ce3843e4f2395c4/networkx/algorithms/distance_regular.py#L21) and [`intersection_array`](https://github.com/networkx/networkx/blob/d68caf64b057f3c5f0adf4f09ce3843e4f2395c4/networkx/algorithms/distance_regular.py#L114) state that they are only defined for undirected graphs; however, the implementations do not check the type of the graph. The functions and definitions should be reviewed to determine whether these should get the `not_implemented_for` decorator.

Discussion:
rossbar: I went ahead and pushed up the fixes for the other instances listed in #6904 as well!

Patch:
diff --git a/doc/conf.py b/doc/conf.py
index 1d8ea3987..f54a02289 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -248,9 +248,11 @@ numpydoc_show_class_members = False
 
 plot_pre_code = """
 import networkx as nx
+import numpy as np
+np.random.seed(42)
 """
 
-plot_formats = [("png", 100), "pdf"]
+plot_formats = [("png", 100)]
 
 
 def setup(app):
diff --git a/networkx/algorithms/approximation/maxcut.py b/networkx/algorithms/approximation/maxcut.py
index ec62b346b..31ccdc9fd 100644
--- a/networkx/algorithms/approximation/maxcut.py
+++ b/networkx/algorithms/approximation/maxcut.py
@@ -4,7 +4,8 @@ from networkx.utils.decorators import not_implemented_for, py_random_state
 __all__ = ["randomized_partitioning", "one_exchange"]
 
 
-@not_implemented_for("directed", "multigraph")
+@not_implemented_for("directed")
+@not_implemented_for("multigraph")
 @py_random_state(1)
 @nx._dispatch(edge_attrs="weight")
 def randomized_partitioning(G, seed=None, p=0.5, weight=None):
@@ -49,7 +50,8 @@ def _swap_node_partition(cut, node):
     return cut - {node} if node in cut else cut.union({node})
 
 
-@not_implemented_for("directed", "multigraph")
+@not_implemented_for("directed")
+@not_implemented_for("multigraph")
 @py_random_state(2)
 @nx._dispatch(edge_attrs="weight")
 def one_exchange(G, initial_cut=None, seed=None, weight=None):
diff --git a/networkx/algorithms/community/asyn_fluid.py b/networkx/algorithms/community/asyn_fluid.py
index 4c2223e63..490bb046e 100644
--- a/networkx/algorithms/community/asyn_fluid.py
+++ b/networkx/algorithms/community/asyn_fluid.py
@@ -10,7 +10,8 @@ from networkx.utils import groups, not_implemented_for, py_random_state
 __all__ = ["asyn_fluidc"]
 
 
-@not_implemented_for("directed", "multigraph")
+@not_implemented_for("directed")
+@not_implemented_for("multigraph")
 @py_random_state(3)
 @nx._dispatch
 def asyn_fluidc(G, k, max_iter=100, seed=None):
diff --git a/networkx/algorithms/distance_regular.py b/networkx/algorithms/distance_regular.py
index da3754330..936fd5a22 100644
--- a/networkx/algorithms/distance_regular.py
+++ b/networkx/algorithms/distance_regular.py
@@ -109,7 +109,8 @@ def global_parameters(b, c):
     return ((y, b[0] - x - y, x) for x, y in zip(b + [0], [0] + c))
 
 
-@not_implemented_for("directed", "multigraph")
+@not_implemented_for("directed")
+@not_implemented_for("multigraph")
 @nx._dispatch
 def intersection_array(G):
     """Returns the intersection array of a distance-regular graph.
@@ -181,7 +182,8 @@ def intersection_array(G):
 
 
 # TODO There is a definition for directed strongly regular graphs.
-@not_implemented_for("directed", "multigraph")
+@not_implemented_for("directed")
+@not_implemented_for("multigraph")
 @nx._dispatch
 def is_strongly_regular(G):
     """Returns True if and only if the given graph is strongly
diff --git a/networkx/algorithms/isomorphism/tree_isomorphism.py b/networkx/algorithms/isomorphism/tree_isomorphism.py
index 6e9350633..c0ef821c1 100644
--- a/networkx/algorithms/isomorphism/tree_isomorphism.py
+++ b/networkx/algorithms/isomorphism/tree_isomorphism.py
@@ -209,7 +209,8 @@ def rooted_tree_isomorphism(t1, root1, t2, root2):
     return isomorphism
 
 
-@not_implemented_for("directed", "multigraph")
+@not_implemented_for("directed")
+@not_implemented_for("multigraph")
 @nx._dispatch(graphs={"t1": 0, "t2": 1})
 def tree_isomorphism(t1, t2):
     """
diff --git a/networkx/generators/classic.py b/networkx/generators/classic.py
index 09d5d9938..4bb660b5a 100644
--- a/networkx/generators/classic.py
+++ b/networkx/generators/classic.py
@@ -75,6 +75,10 @@ def full_rary_tree(r, n, create_using=None):
     (if a leaf at the bottom level is missing, then so are all of the
     leaves to its right." [1]_
 
+    .. plot::
+
+        >>> nx.draw(nx.full_rary_tree(2, 10))
+
     Parameters
     ----------
     r : int
@@ -103,6 +107,10 @@ def full_rary_tree(r, n, create_using=None):
 def balanced_tree(r, h, create_using=None):
     """Returns the perfectly balanced `r`-ary tree of height `h`.
 
+    .. plot::
+
+        >>> nx.draw(nx.balanced_tree(2, 3))
+
     Parameters
     ----------
     r : int
@@ -149,6 +157,10 @@ def balanced_tree(r, h, create_using=None):
 def barbell_graph(m1, m2, create_using=None):
     """Returns the Barbell Graph: two complete graphs connected by a path.
 
+    .. plot::
+
+        >>> nx.draw(nx.barbell_graph(4, 2))
+
     Parameters
     ----------
     m1 : int
@@ -222,6 +234,10 @@ def binomial_tree(n, create_using=None):
     is defined recursively by linking two binomial trees of order k-1: the root of one is
     the leftmost child of the root of the other.
 
+    .. plot::
+
+        >>> nx.draw(nx.binomial_tree(3))
+
     Parameters
     ----------
     n : int
@@ -305,6 +321,10 @@ def circular_ladder_graph(n, create_using=None):
 
     Node labels are the integers 0 to n-1
 
+    .. plot::
+
+        >>> nx.draw(nx.circular_ladder_graph(5))
+
     """
     G = ladder_graph(n, create_using)
     G.add_edge(0, n - 1)
@@ -320,6 +340,10 @@ def circulant_graph(n, offsets, create_using=None):
     such that node $i$ is connected to nodes $(i + x) \mod n$ and $(i - x) \mod n$
     for all $x$ in $x_1, ..., x_m$. Thus $Ci_n(1)$ is a cycle graph.
 
+    .. plot::
+
+        >>> nx.draw(nx.circulant_graph(10, [1]))
+
     Parameters
     ----------
     n : integer
@@ -392,6 +416,10 @@ def cycle_graph(n, create_using=None):
 
     $C_n$ is a path with its two end-nodes connected.
 
+    .. plot::
+
+        >>> nx.draw(nx.cycle_graph(5))
+
     Parameters
     ----------
     n : int or iterable container of nodes
@@ -422,6 +450,10 @@ def dorogovtsev_goltsev_mendes_graph(n, create_using=None):
     - Total number of nodes = ``3 * (3**n + 1) / 2``
     - Total number of edges = ``3 ** (n + 1)``
 
+    .. plot::
+
+        >>> nx.draw(nx.dorogovtsev_goltsev_mendes_graph(3))
+
     Parameters
     ----------
     n : integer
@@ -476,6 +508,10 @@ def dorogovtsev_goltsev_mendes_graph(n, create_using=None):
 def empty_graph(n=0, create_using=None, default=Graph):
     """Returns the empty graph with n nodes and zero edges.
 
+    .. plot::
+
+        >>> nx.draw(nx.empty_graph(5))
+
     Parameters
     ----------
     n : int or iterable container of nodes (default = 0)
@@ -575,6 +611,10 @@ def ladder_graph(n, create_using=None):
 
     Node labels are the integers 0 to 2*n - 1.
 
+    .. plot::
+
+        >>> nx.draw(nx.ladder_graph(5))
+
     """
     G = empty_graph(2 * n, create_using)
     if G.is_directed():
@@ -592,6 +632,10 @@ def lollipop_graph(m, n, create_using=None):
 
     This is the Barbell Graph without the right barbell.
 
+    .. plot::
+
+        >>> nx.draw(nx.lollipop_graph(3, 4))
+
     Parameters
     ----------
     m, n : int or iterable container of nodes
@@ -664,6 +708,10 @@ def null_graph(create_using=None):
 def path_graph(n, create_using=None):
     """Returns the Path graph `P_n` of linearly connected nodes.
 
+    .. plot::
+
+        >>> nx.draw(nx.path_graph(5))
+
     Parameters
     ----------
     n : int or iterable
@@ -688,6 +736,10 @@ def star_graph(n, create_using=None):
 
     The star graph consists of one center node connected to n outer nodes.
 
+    .. plot::
+
+        >>> nx.draw(nx.star_graph(6))
+
     Parameters
     ----------
     n : int or iterable
@@ -724,6 +776,10 @@ def tadpole_graph(m, n, create_using=None):
     This graph on m+n nodes connects a cycle of size `m` to a path of length `n`.
     It looks like a tadpole. It is also called a kite graph or a dragon graph.
 
+    .. plot::
+
+        >>> nx.draw(nx.tadpole_graph(3, 5))
+
     Parameters
     ----------
     m, n : int or iterable container of nodes
@@ -776,7 +832,13 @@ def tadpole_graph(m, n, create_using=None):
 
 @nx._dispatch(graphs=None)
 def trivial_graph(create_using=None):
-    """Return the Trivial graph with one node (with label 0) and no edges."""
+    """Return the Trivial graph with one node (with label 0) and no edges.
+
+    .. plot::
+
+        >>> nx.draw(nx.trivial_graph(), with_labels=True)
+
+    """
     G = empty_graph(1, create_using)
     return G
 
@@ -793,6 +855,10 @@ def turan_graph(n, r):
     $r-(n \mod r)$ partitions of size $n/r$, rounded down, and
     $n \mod r$ partitions of size $n/r+1$, rounded down.
 
+    .. plot::
+
+        >>> nx.draw(nx.turan_graph(6, 2))
+
     Parameters
     ----------
     n : int
@@ -822,6 +888,10 @@ def wheel_graph(n, create_using=None):
 
     The wheel graph consists of a hub node connected to a cycle of (n-1) nodes.
 
+    .. plot::
+
+        >>> nx.draw(nx.wheel_graph(5))
+
     Parameters
     ----------
     n : int or iterable
@@ -851,6 +921,10 @@ def wheel_graph(n, create_using=None):
 def complete_multipartite_graph(*subset_sizes):
     """Returns the complete multipartite graph with the specified subset sizes.
 
+    .. plot::
+
+        >>> nx.draw(nx.complete_multipartite_graph(1, 2, 3))
+
     Parameters
     ----------
     subset_sizes : tuple of integers or tuple of node iterables


Test patch:
diff --git a/networkx/algorithms/approximation/tests/test_maxcut.py b/networkx/algorithms/approximation/tests/test_maxcut.py
index 39291fbf1..ef0424401 100644
--- a/networkx/algorithms/approximation/tests/test_maxcut.py
+++ b/networkx/algorithms/approximation/tests/test_maxcut.py
@@ -1,9 +1,21 @@
 import random
 
+import pytest
+
 import networkx as nx
 from networkx.algorithms.approximation import maxcut
 
 
+@pytest.mark.parametrize(
+    "f", (nx.approximation.randomized_partitioning, nx.approximation.one_exchange)
+)
+@pytest.mark.parametrize("graph_constructor", (nx.DiGraph, nx.MultiGraph))
+def test_raises_on_directed_and_multigraphs(f, graph_constructor):
+    G = graph_constructor([(0, 1), (1, 2)])
+    with pytest.raises(nx.NetworkXNotImplemented):
+        f(G)
+
+
 def _is_valid_cut(G, set1, set2):
     union = set1.union(set2)
     assert union == set(G.nodes)
diff --git a/networkx/algorithms/community/tests/test_asyn_fluid.py b/networkx/algorithms/community/tests/test_asyn_fluid.py
index cd108dda3..6c023be77 100644
--- a/networkx/algorithms/community/tests/test_asyn_fluid.py
+++ b/networkx/algorithms/community/tests/test_asyn_fluid.py
@@ -5,6 +5,13 @@ from networkx import Graph, NetworkXError
 from networkx.algorithms.community import asyn_fluidc
 
 
+@pytest.mark.parametrize("graph_constructor", (nx.DiGraph, nx.MultiGraph))
+def test_raises_on_directed_and_multigraphs(graph_constructor):
+    G = graph_constructor([(0, 1), (1, 2)])
+    with pytest.raises(nx.NetworkXNotImplemented):
+        nx.community.asyn_fluidc(G, 1)
+
+
 def test_exceptions():
     test = Graph()
     test.add_node("a")
diff --git a/networkx/algorithms/isomorphism/tests/test_tree_isomorphism.py b/networkx/algorithms/isomorphism/tests/test_tree_isomorphism.py
index 95e5fec87..fa1ab9bba 100644
--- a/networkx/algorithms/isomorphism/tests/test_tree_isomorphism.py
+++ b/networkx/algorithms/isomorphism/tests/test_tree_isomorphism.py
@@ -1,6 +1,8 @@
 import random
 import time
 
+import pytest
+
 import networkx as nx
 from networkx.algorithms.isomorphism.tree_isomorphism import (
     rooted_tree_isomorphism,
@@ -9,6 +11,14 @@ from networkx.algorithms.isomorphism.tree_isomorphism import (
 from networkx.classes.function import is_directed
 
 
+@pytest.mark.parametrize("graph_constructor", (nx.DiGraph, nx.MultiGraph))
+def test_tree_isomorphism_raises_on_directed_and_multigraphs(graph_constructor):
+    t1 = graph_constructor([(0, 1)])
+    t2 = graph_constructor([(1, 2)])
+    with pytest.raises(nx.NetworkXNotImplemented):
+        nx.isomorphism.tree_isomorphism(t1, t2)
+
+
 # have this work for graph
 # given two trees (either the directed or undirected)
 # transform t2 according to the isomorphism
diff --git a/networkx/algorithms/tests/test_distance_regular.py b/networkx/algorithms/tests/test_distance_regular.py
index 6cbeb2a6d..545fb6dee 100644
--- a/networkx/algorithms/tests/test_distance_regular.py
+++ b/networkx/algorithms/tests/test_distance_regular.py
@@ -4,6 +4,16 @@ import networkx as nx
 from networkx import is_strongly_regular
 
 
+@pytest.mark.parametrize(
+    "f", (nx.is_distance_regular, nx.intersection_array, nx.is_strongly_regular)
+)
+@pytest.mark.parametrize("graph_constructor", (nx.DiGraph, nx.MultiGraph))
+def test_raises_on_directed_and_multigraphs(f, graph_constructor):
+    G = graph_constructor([(0, 1), (1, 2)])
+    with pytest.raises(nx.NetworkXNotImplemented):
+        f(G)
+
+
 class TestDistanceRegular:
     def test_is_distance_regular(self):
         assert nx.is_distance_regular(nx.icosahedral_graph())