Repository: networkx/networkx

Issue:
In smallworld.py: ZeroDivisionError when passing a graph without edges
This is a similar error to #6144 that comes from the use of `nx.utils.cumulative_distribution` with graphs without edges. In #6144 there is a deeper explanation of the bug. 


Discussion:
rossbar: FWIW the errors here seem to be due to various misspellings in the exception checking - should be an easy fix!

Patch:
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index 2e3f86b6c..b9f4e96de 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -19,7 +19,7 @@ jobs:
     steps:
       - uses: actions/checkout@v3
       - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v3
+        uses: actions/setup-python@v4
         with:
           python-version: ${{ matrix.python-version }}
 
diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml
index df8734046..50715e216 100644
--- a/.github/workflows/deploy-docs.yml
+++ b/.github/workflows/deploy-docs.yml
@@ -14,7 +14,7 @@ jobs:
     steps:
       - uses: actions/checkout@v3
       - name: Set up Python
-        uses: actions/setup-python@v3
+        uses: actions/setup-python@v4
         with:
           python-version: "3.9"
 
@@ -47,7 +47,7 @@ jobs:
       #   - Make sure the name is the same as below: CI_DEPLOY_KEY
       - name: Install SSH agent
         if: github.ref == 'refs/heads/main'
-        uses: webfactory/ssh-agent@v0.5.4
+        uses: webfactory/ssh-agent@v0.7.0
         with:
           ssh-private-key: ${{ secrets.CI_DEPLOY_KEY }}
 
@@ -61,7 +61,7 @@ jobs:
 
       - name: Deploy docs
         if: github.ref == 'refs/heads/main'
-        uses: JamesIves/github-pages-deploy-action@releases/v3
+        uses: JamesIves/github-pages-deploy-action@releases/v4
         with:
           GIT_CONFIG_NAME: nx-doc-deploy-bot
           GIT_CONFIG_EMAIL: nx-doc-deploy-bot@nomail
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 5d27750db..1f4738856 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -17,7 +17,7 @@ jobs:
       - uses: actions/checkout@v3
 
       - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v3
+        uses: actions/setup-python@v4
         with:
           python-version: ${{ matrix.python-version }}
 
diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml
index b6451876a..2aacbeb21 100644
--- a/.github/workflows/mypy.yml
+++ b/.github/workflows/mypy.yml
@@ -17,7 +17,7 @@ jobs:
       - uses: actions/checkout@v3
 
       - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v3
+        uses: actions/setup-python@v4
         with:
           python-version: ${{ matrix.python-version }}
 
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 12e641ecc..4c91cfe05 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,11 +3,11 @@
 
 repos:
   - repo: https://github.com/psf/black
-    rev: 22.10.0
+    rev: 22.12.0
     hooks:
       - id: black
   - repo: https://github.com/asottile/pyupgrade
-    rev: v3.2.2
+    rev: v3.3.1
     hooks:
       - id: pyupgrade
         args: [--py38-plus]
@@ -16,7 +16,7 @@ repos:
     hooks:
       - id: blacken-docs
   - repo: https://github.com/pycqa/isort
-    rev: 5.10.1
+    rev: 5.11.2
     hooks:
       - id: isort
         name: isort (python)
diff --git a/doc/conf.py b/doc/conf.py
index f5b6d55a3..4059015c5 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -222,11 +222,11 @@ latex_appendices = ["tutorial"]
 intersphinx_mapping = {
     "python": ("https://docs.python.org/3/", None),
     "numpy": ("https://numpy.org/doc/stable/", None),
-    "neps": ("https://numpy.org/neps", None),
-    "matplotlib": ("https://matplotlib.org/stable", None),
-    "scipy": ("https://docs.scipy.org/doc/scipy/reference", None),
-    "pandas": ("https://pandas.pydata.org/pandas-docs/stable", None),
-    "geopandas": ("https://geopandas.org/", None),
+    "neps": ("https://numpy.org/neps/", None),
+    "matplotlib": ("https://matplotlib.org/stable/", None),
+    "scipy": ("https://docs.scipy.org/doc/scipy/", None),
+    "pandas": ("https://pandas.pydata.org/pandas-docs/stable/", None),
+    "geopandas": ("https://geopandas.org/en/stable/", None),
     "pygraphviz": ("https://pygraphviz.github.io/documentation/stable/", None),
     "sphinx-gallery": ("https://sphinx-gallery.github.io/stable/", None),
     "nx-guides": ("https://networkx.org/nx-guides/", None),
diff --git a/doc/release/migration_guide_from_2.x_to_3.0.rst b/doc/release/migration_guide_from_2.x_to_3.0.rst
index a7dc8a4f8..8035f4d3a 100644
--- a/doc/release/migration_guide_from_2.x_to_3.0.rst
+++ b/doc/release/migration_guide_from_2.x_to_3.0.rst
@@ -1,8 +1,8 @@
 :orphan:
 
-*****************************
-Preparing for the 3.0 release
-*****************************
+*******************************
+Migration guide from 2.X to 3.0
+*******************************
 
 .. note::
    Much of the work leading to the NetworkX 3.0 release will be included
@@ -11,7 +11,7 @@ Preparing for the 3.0 release
    ongoing work and will help you understand what changes you can make now
    to minimize the disruption caused by the move to 3.0.
 
-This is a guide for people moving from NetworkX 2.X to NetworkX 3.0
+This is a guide for people moving from NetworkX 2.X to NetworkX 3.0.
 
 Any issues with these can be discussed on the `mailing list
 <https://groups.google.com/forum/#!forum/networkx-discuss>`_.
@@ -34,9 +34,6 @@ structures (``Graph``, ``DiGraph``, etc.) and common algorithms, but some
 functionality, e.g. functions found in the ``networkx.linalg`` package, are
 only available if these additional libraries are installed.
 
-.. **TODO**: Generate a table showing dependencies of individual nx objects?
-.. Probably overkill...
-
 Improved integration with scientific Python
 -------------------------------------------
 
@@ -179,15 +176,7 @@ improving supported for array representations of multi-attribute adjacency::
 Deprecated code
 ---------------
 
-The 2.6 release deprecates over 30 functions.
-See :ref:`networkx_2.6`.
-
-.. **TODO**: A table summarizing one deprecation per row w/ 3 columns: 1. the
-.. deprecated function, 2. the old usage, 3. the replacement usage.
-
----
-
-The functions `read_gpickle` and `write_gpickle` will be removed in 3.0.
+The functions `read_gpickle` and `write_gpickle` were removed in 3.0.
 You can read and write NetworkX graphs as Python pickles.
 
 >>> import pickle
@@ -199,7 +188,7 @@ You can read and write NetworkX graphs as Python pickles.
 ...     G = pickle.load(f)
 ... 
 
-The functions `read_yaml` and `write_yaml` will be removed in 3.0.
+The functions `read_yaml` and `write_yaml` were removed in 3.0.
 You can read and write NetworkX graphs in YAML format
 using pyyaml.
 
diff --git a/doc/release/release_dev.rst b/doc/release/release_dev.rst
index 94b62794b..2e1b84470 100644
--- a/doc/release/release_dev.rst
+++ b/doc/release/release_dev.rst
@@ -16,9 +16,9 @@ Please send comments and questions to the `networkx-discuss mailing list
 Highlights
 ----------
 
-This release is the result of 4 months of work with over 217 pull requests by
-37 contributors. We also have a :ref:`guide for people moving from NetworkX 2.X
-to NetworkX 3.0 <migration_guide_from_2.x_to_3.0>`. Highlights include:
+This release is the result of 8 months of work with over 217 pull requests by
+37 contributors. We also have a `guide for people moving from NetworkX 2.X
+to NetworkX 3.0 <https://networkx.org/documentation/latest/release/migration_guide_from_2.x_to_3.0.html>`. Highlights include:
 
 - Better syncing between G._succ and G._adj for directed G.
   And slightly better speed from all the core adjacency data structures.
@@ -42,8 +42,7 @@ to NetworkX 3.0 <migration_guide_from_2.x_to_3.0>`. Highlights include:
 - We have added an experimental plugin feature which let users choose alternate
   backends like GraphBLAS, CuGraph for computation. This is an opt-in feature and
   may change in future releases.
-- Improved integration with the general Scientific Python ecosystem: <link to
-  the migration guide section?>
+- Improved integration with the general `Scientific Python ecosystem <https://networkx.org/documentation/latest/release/migration_guide_from_2.x_to_3.0.html#improved-integration-with-scientific-python>`
 
 Improvements
 ------------
diff --git a/networkx/algorithms/smallworld.py b/networkx/algorithms/smallworld.py
index fc64d138d..7b72d85b2 100644
--- a/networkx/algorithms/smallworld.py
+++ b/networkx/algorithms/smallworld.py
@@ -46,6 +46,11 @@ def random_reference(G, niter=1, connectivity=True, seed=None):
     G : graph
         The randomized graph.
 
+    Raises
+    ------
+    NetworkXError
+        If there are fewer than 4 nodes or 2 edges in `G`
+
     Notes
     -----
     The implementation is adapted from the algorithm by Maslov and Sneppen
@@ -58,7 +63,9 @@ def random_reference(G, niter=1, connectivity=True, seed=None):
            Science 296.5569 (2002): 910-913.
     """
     if len(G) < 4:
-        raise nx.NetworkXError("Graph has less than four nodes.")
+        raise nx.NetworkXError("Graph has fewer than four nodes.")
+    if len(G.edges) < 2:
+        raise nx.NetworkXError("Graph has fewer that 2 edges")
 
     from networkx.utils import cumulative_distribution, discrete_sequence
 
@@ -119,7 +126,7 @@ def lattice_reference(G, niter=5, D=None, connectivity=True, seed=None):
     Parameters
     ----------
     G : graph
-        An undirected graph with 4 or more nodes.
+        An undirected graph.
 
     niter : integer (optional, default=1)
         An edge is rewired approximatively niter times.
@@ -139,6 +146,11 @@ def lattice_reference(G, niter=5, D=None, connectivity=True, seed=None):
     G : graph
         The latticized graph.
 
+    Raises
+    ------
+    NetworkXError
+        If there are fewer than 4 nodes or 2 edges in `G`
+
     Notes
     -----
     The implementation is adapted from the algorithm by Sporns et al. [1]_.
@@ -160,7 +172,9 @@ def lattice_reference(G, niter=5, D=None, connectivity=True, seed=None):
     local_conn = nx.connectivity.local_edge_connectivity
 
     if len(G) < 4:
-        raise nx.NetworkXError("Graph has less than four nodes.")
+        raise nx.NetworkXError("Graph has fewer than four nodes.")
+    if len(G.edges) < 2:
+        raise nx.NetworkXError("Graph has fewer that 2 edges")
     # Instead of choosing uniformly at random from a generated edge list,
     # this algorithm chooses nonuniformly from the set of nodes with
     # probability weighted by degree.
diff --git a/networkx/algorithms/swap.py b/networkx/algorithms/swap.py
index 9b2f06db6..c02a4b3ca 100644
--- a/networkx/algorithms/swap.py
+++ b/networkx/algorithms/swap.py
@@ -46,7 +46,7 @@ def directed_edge_swap(G, *, nswap=1, max_tries=100, seed=None):
     NetworkXError
         If `G` is not directed, or
         If nswap > max_tries, or
-        If there are fewer than 4 nodes in `G`
+        If there are fewer than 4 nodes or 3 edges in `G`.
     NetworkXAlgorithmError
         If the number of swap attempts exceeds `max_tries` before `nswap` swaps are made
 
@@ -70,7 +70,9 @@ def directed_edge_swap(G, *, nswap=1, max_tries=100, seed=None):
     if nswap > max_tries:
         raise nx.NetworkXError("Number of swaps > number of tries allowed.")
     if len(G) < 4:
-        raise nx.NetworkXError("Graph has less than four nodes.")
+        raise nx.NetworkXError("DiGraph has fewer than four nodes.")
+    if len(G.edges) < 3:
+        raise nx.NetworkXError("DiGraph has fewer than 3 edges")
 
     # Instead of choosing uniformly at random from a generated edge list,
     # this algorithm chooses nonuniformly from the set of nodes with
@@ -161,6 +163,15 @@ def double_edge_swap(G, nswap=1, max_tries=100, seed=None):
     G : graph
        The graph after double edge swaps.
 
+    Raises
+    ------
+    NetworkXError
+        If `G` is directed, or
+        If `nswap` > `max_tries`, or
+        If there are fewer than 4 nodes or 2 edges in `G`.
+    NetworkXAlgorithmError
+        If the number of swap attempts exceeds `max_tries` before `nswap` swaps are made
+
     Notes
     -----
     Does not enforce any connectivity constraints.
@@ -174,7 +185,9 @@ def double_edge_swap(G, nswap=1, max_tries=100, seed=None):
     if nswap > max_tries:
         raise nx.NetworkXError("Number of swaps > number of tries allowed.")
     if len(G) < 4:
-        raise nx.NetworkXError("Graph has less than four nodes.")
+        raise nx.NetworkXError("Graph has fewer than four nodes.")
+    if len(G.edges) < 2:
+        raise nx.NetworkXError("Graph has fewer than 2 edges")
     # Instead of choosing uniformly at random from a generated edge list,
     # this algorithm chooses nonuniformly from the set of nodes with
     # probability weighted by degree.
@@ -285,7 +298,7 @@ def connected_double_edge_swap(G, nswap=1, _window_threshold=3, seed=None):
     if not nx.is_connected(G):
         raise nx.NetworkXError("Graph not connected")
     if len(G) < 4:
-        raise nx.NetworkXError("Graph has less than four nodes.")
+        raise nx.NetworkXError("Graph has fewer than four nodes.")
     n = 0
     swapcount = 0
     deg = G.degree()
diff --git a/requirements/developer.txt b/requirements/developer.txt
index 16900885e..7a8aa6e19 100644
--- a/requirements/developer.txt
+++ b/requirements/developer.txt
@@ -1,2 +1,2 @@
 pre-commit>=2.20
-mypy>=0.990
+mypy>=0.991
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 70d8eab71..7721db0f8 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -1,4 +1,4 @@
-sphinx>=5.2
+sphinx==5.2.3
 pydata-sphinx-theme>=0.11
 sphinx-gallery>=0.11
 numpydoc>=1.5
diff --git a/requirements/release.txt b/requirements/release.txt
index 465bec54e..384b780ce 100644
--- a/requirements/release.txt
+++ b/requirements/release.txt
@@ -1,3 +1,3 @@
-build>=0.8
+build>=0.9
 twine>=4.0
-wheel>=0.37
+wheel>=0.38


Test patch:
diff --git a/.github/workflows/pytest-randomly.yml b/.github/workflows/pytest-randomly.yml
index 74cce8d8e..39c72ddcd 100644
--- a/.github/workflows/pytest-randomly.yml
+++ b/.github/workflows/pytest-randomly.yml
@@ -9,9 +9,9 @@ jobs:
     if: github.repository == 'networkx/networkx'
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - name: Set up Python
-        uses: actions/setup-python@v2
+        uses: actions/setup-python@v4
         with:
           python-version: "3.9"
 
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index ae36c3167..20f9ecfe6 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -16,7 +16,7 @@ jobs:
     steps:
       - uses: actions/checkout@v3
       - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v3
+        uses: actions/setup-python@v4
         with:
           python-version: ${{ matrix.python-version }}
 
@@ -40,7 +40,7 @@ jobs:
     steps:
       - uses: actions/checkout@v3
       - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v3
+        uses: actions/setup-python@v4
         with:
           python-version: ${{ matrix.python-version }}
 
@@ -64,7 +64,7 @@ jobs:
     steps:
       - uses: actions/checkout@v3
       - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v3
+        uses: actions/setup-python@v4
         with:
           python-version: ${{ matrix.python-version }}
 
@@ -124,7 +124,7 @@ jobs:
     steps:
       - uses: actions/checkout@v3
       - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v3
+        uses: actions/setup-python@v4
         with:
           python-version: ${{ matrix.python-version }}
 
diff --git a/networkx/algorithms/tests/test_smallworld.py b/networkx/algorithms/tests/test_smallworld.py
index 42ede0edd..d115dd99b 100644
--- a/networkx/algorithms/tests/test_smallworld.py
+++ b/networkx/algorithms/tests/test_smallworld.py
@@ -68,3 +68,11 @@ def test_omega():
 
     for o in omegas:
         assert -1 <= o <= 1
+
+
+@pytest.mark.parametrize("f", (nx.random_reference, nx.lattice_reference))
+def test_graph_no_edges(f):
+    G = nx.Graph()
+    G.add_nodes_from([0, 1, 2, 3])
+    with pytest.raises(nx.NetworkXError, match="Graph has fewer that 2 edges"):
+        f(G)
diff --git a/networkx/algorithms/tests/test_swap.py b/networkx/algorithms/tests/test_swap.py
index 2bc25df23..49dd5f8e8 100644
--- a/networkx/algorithms/tests/test_swap.py
+++ b/networkx/algorithms/tests/test_swap.py
@@ -19,9 +19,6 @@ def test_edge_cases_directed_edge_swap():
         "Maximum number of swap attempts \\(11\\) exceeded "
         "before desired swaps achieved \\(\\d\\)."
     )
-    graph = nx.DiGraph([(0, 1), (2, 3)])
-    with pytest.raises(nx.NetworkXAlgorithmError, match=e):
-        nx.directed_edge_swap(graph, nswap=4, max_tries=10, seed=1)
     graph = nx.DiGraph([(0, 0), (0, 1), (1, 0), (2, 3), (3, 2)])
     with pytest.raises(nx.NetworkXAlgorithmError, match=e):
         nx.directed_edge_swap(graph, nswap=1, max_tries=10, seed=1)
@@ -138,3 +135,22 @@ def test_degree_seq_c4():
     degrees = sorted(d for n, d in G.degree())
     G = nx.double_edge_swap(G, 1, 100)
     assert degrees == sorted(d for n, d in G.degree())
+
+
+def test_fewer_than_4_nodes():
+    G = nx.DiGraph()
+    G.add_nodes_from([0, 1, 2])
+    with pytest.raises(nx.NetworkXError, match=".*fewer than four nodes."):
+        nx.directed_edge_swap(G)
+
+
+def test_less_than_3_edges():
+    G = nx.DiGraph([(0, 1), (1, 2)])
+    G.add_nodes_from([3, 4])
+    with pytest.raises(nx.NetworkXError, match=".*fewer than 3 edges"):
+        nx.directed_edge_swap(G)
+
+    G = nx.Graph()
+    G.add_nodes_from([0, 1, 2, 3])
+    with pytest.raises(nx.NetworkXError, match=".*fewer than 2 edges"):
+        nx.double_edge_swap(G)
diff --git a/networkx/generators/tests/test_line.py b/networkx/generators/tests/test_line.py
index 96380ac8e..f064c6618 100644
--- a/networkx/generators/tests/test_line.py
+++ b/networkx/generators/tests/test_line.py
@@ -174,6 +174,12 @@ class TestGeneratorInverseLine:
         G.add_edges_from(G_edges)
         pytest.raises(nx.NetworkXError, nx.inverse_line_graph, G)
 
+    def test_edgeless_graph(self):
+        G = nx.Graph()
+        G.add_nodes_from([0, 1])
+        with pytest.raises(nx.NetworkXError, match=".*edgeless graph"):
+            nx.inverse_line_graph(G)
+
     def test_non_line_graph(self):
         # These are other non-line graphs