Repository: networkx/networkx

Issue:
Improve test coverage for algorithms in maxflow.py
<!-- If you have a general question about NetworkX, please use the discussions tab to create a new discussion -->

<!--- Provide a general summary of the issue in the Title above -->

Currently we don't have full coverage for algorithms in maxflow.py. Code blocks which are highlighted with red at codcov https://app.codecov.io/gh/networkx/networkx/blob/main/networkx/algorithms/flow/maxflow.py don't have corresponding tests. The tests should be added in https://github.com/networkx/networkx/blob/main/networkx/algorithms/flow/tests/test_maxflow.py

### Current Behavior

<!--- Tell us what happens instead of the expected behavior -->

We don't test all the paths the code can take us.

### Expected Behavior

<!--- Tell us what should happen -->

We should be testing everything so there aren't any surprises.

### Steps to Reproduce

<!--- Provide a minimal example that reproduces the bug -->

Visit https://app.codecov.io/gh/networkx/networkx/blob/main/networkx/algorithms/flow/maxflow.py


Discussion:

Patch:
diff --git a/benchmarks/benchmarks/benchmark_algorithms.py b/benchmarks/benchmarks/benchmark_algorithms.py
index 8991912c7..34df6ed00 100644
--- a/benchmarks/benchmarks/benchmark_algorithms.py
+++ b/benchmarks/benchmarks/benchmark_algorithms.py
@@ -48,3 +48,28 @@ class AlgorithmBenchmarks:
 
     def time_average_clustering(self, graph):
         _ = nx.average_clustering(self.graphs_dict[graph])
+
+
+class AlgorithmBenchmarksConnectedGraphsOnly:
+    timeout = 120
+    nodes = 100
+    _graphs = [
+        nx.erdos_renyi_graph(nodes, 0.1),
+        nx.erdos_renyi_graph(nodes, 0.5),
+        nx.erdos_renyi_graph(nodes, 0.9),
+    ]
+    params = [
+        "Erdos Renyi (100, 0.1)",
+        "Erdos Renyi (100, 0.5)",
+        "Erdos Renyi (100, 0.9)",
+    ]
+
+    param_names = ["graph"]
+
+    def setup(self, graph):
+        self.graphs_dict = dict(zip(self.params, self._graphs))
+
+    def time_eigenvector_centrality_numpy(self, graph):
+        # Added to ensure the connectivity check doesn't affect
+        # performance too much (see gh-6888, gh-7549).
+        _ = nx.eigenvector_centrality_numpy(self.graphs_dict[graph])
diff --git a/doc/reference/algorithms/distance_measures.rst b/doc/reference/algorithms/distance_measures.rst
index 172e5b6dd..00da08c10 100644
--- a/doc/reference/algorithms/distance_measures.rst
+++ b/doc/reference/algorithms/distance_measures.rst
@@ -9,6 +9,7 @@ Distance Measures
    barycenter
    center
    diameter
+   harmonic_diameter
    eccentricity
    effective_graph_resistance
    kemeny_constant
diff --git a/networkx/algorithms/centrality/eigenvector.py b/networkx/algorithms/centrality/eigenvector.py
index 2cef241b0..b8cf63e8d 100644
--- a/networkx/algorithms/centrality/eigenvector.py
+++ b/networkx/algorithms/centrality/eigenvector.py
@@ -196,7 +196,7 @@ def eigenvector_centrality(G, max_iter=100, tol=1.0e-6, nstart=None, weight=None
 
 @nx._dispatchable(edge_attrs="weight")
 def eigenvector_centrality_numpy(G, weight=None, max_iter=50, tol=0):
-    r"""Compute the eigenvector centrality for the graph G.
+    r"""Compute the eigenvector centrality for the graph `G`.
 
     Eigenvector centrality computes the centrality for a node by adding
     the centrality of its predecessors. The centrality for node $i$ is the
@@ -208,7 +208,7 @@ def eigenvector_centrality_numpy(G, weight=None, max_iter=50, tol=0):
 
          \lambda x^T = x^T A,
 
-    where $A$ is the adjacency matrix of the graph G. By definition of
+    where $A$ is the adjacency matrix of the graph `G`. By definition of
     row-column product, the equation above is equivalent to
 
     .. math::
@@ -220,37 +220,42 @@ def eigenvector_centrality_numpy(G, weight=None, max_iter=50, tol=0):
     $\lambda$. In the case of undirected graphs, $x$ also solves the familiar
     right-eigenvector equation $Ax = \lambda x$.
 
-    By virtue of the Perron–Frobenius theorem [1]_, if G is strongly
-    connected there is a unique eigenvector $x$, and all its entries
+    By virtue of the Perron--Frobenius theorem [1]_, if `G` is (strongly)
+    connected, there is a unique eigenvector $x$, and all its entries
     are strictly positive.
 
-    If G is not strongly connected there might be several left
+    However, if `G` is not (strongly) connected, there might be several left
     eigenvectors associated with $\lambda$, and some of their elements
     might be zero.
+    Depending on the method used to choose eigenvectors, round-off error can affect
+    which of the infinitely many eigenvectors is reported.
+    This can lead to inconsistent results for the same graph,
+    which the underlying implementation is not robust to.
+    For this reason, only (strongly) connected graphs are accepted.
 
     Parameters
     ----------
     G : graph
-      A networkx graph.
+        A connected NetworkX graph.
+
+    weight : None or string, optional (default=None)
+        If ``None``, all edge weights are considered equal. Otherwise holds the
+        name of the edge attribute used as weight. In this measure the
+        weight is interpreted as the connection strength.
 
     max_iter : integer, optional (default=50)
-      Maximum number of Arnoldi update iterations allowed.
+        Maximum number of Arnoldi update iterations allowed.
 
     tol : float, optional (default=0)
-      Relative accuracy for eigenvalues (stopping criterion).
-      The default value of 0 implies machine precision.
-
-    weight : None or string, optional (default=None)
-      If None, all edge weights are considered equal. Otherwise holds the
-      name of the edge attribute used as weight. In this measure the
-      weight is interpreted as the connection strength.
+        Relative accuracy for eigenvalues (stopping criterion).
+        The default value of 0 implies machine precision.
 
     Returns
     -------
-    nodes : dictionary
-       Dictionary of nodes with eigenvector centrality as the value. The
-       associated vector has unit Euclidean norm and the values are
-       nonegative.
+    nodes : dict of nodes
+        Dictionary of nodes with eigenvector centrality as the value. The
+        associated vector has unit Euclidean norm and the values are
+        nonnegative.
 
     Examples
     --------
@@ -262,13 +267,16 @@ def eigenvector_centrality_numpy(G, weight=None, max_iter=50, tol=0):
     Raises
     ------
     NetworkXPointlessConcept
-        If the graph G is the null graph.
+        If the graph `G` is the null graph.
 
     ArpackNoConvergence
         When the requested convergence is not obtained. The currently
         converged eigenvalues and eigenvectors can be found as
         eigenvalues and eigenvectors attributes of the exception object.
 
+    AmbiguousSolution
+        If `G` is not connected.
+
     See Also
     --------
     :func:`scipy.sparse.linalg.eigs`
@@ -298,33 +306,35 @@ def eigenvector_centrality_numpy(G, weight=None, max_iter=50, tol=0):
     References
     ----------
     .. [1] Abraham Berman and Robert J. Plemmons.
-       "Nonnegative Matrices in the Mathematical Sciences."
+       "Nonnegative Matrices in the Mathematical Sciences".
        Classics in Applied Mathematics. SIAM, 1994.
 
     .. [2] Edmund Landau.
-       "Zur relativen Wertbemessung der Turnierresultate."
-       Deutsches Wochenschach, 11:366–369, 1895.
+       "Zur relativen Wertbemessung der Turnierresultate".
+       Deutsches Wochenschach, 11:366--369, 1895.
 
     .. [3] Teh-Hsing Wei.
-       "The Algebraic Foundations of Ranking Theory."
+       "The Algebraic Foundations of Ranking Theory".
        PhD thesis, University of Cambridge, 1952.
 
     .. [4] Maurice G. Kendall.
-       "Further contributions to the theory of paired comparisons."
-       Biometrics, 11(1):43–62, 1955.
+       "Further contributions to the theory of paired comparisons".
+       Biometrics, 11(1):43--62, 1955.
        https://www.jstor.org/stable/3001479
 
-    .. [5] Claude Berge
-       "Théorie des graphes et ses applications."
+    .. [5] Claude Berge.
+       "Théorie des graphes et ses applications".
        Dunod, Paris, France, 1958.
 
     .. [6] Phillip Bonacich.
-       "Technique for analyzing overlapping memberships."
-       Sociological Methodology, 4:176–185, 1972.
+       "Technique for analyzing overlapping memberships".
+       Sociological Methodology, 4:176--185, 1972.
        https://www.jstor.org/stable/270732
 
-    .. [7] Arnoldi iteration:: https://en.wikipedia.org/wiki/Arnoldi_iteration
-
+    .. [7] Arnoldi, W. E. (1951).
+       "The principle of minimized iterations in the solution of the matrix eigenvalue problem".
+       Quarterly of Applied Mathematics. 9 (1): 17--29.
+       https://doi.org/10.1090/qam/42792
     """
     import numpy as np
     import scipy as sp
@@ -333,6 +343,11 @@ def eigenvector_centrality_numpy(G, weight=None, max_iter=50, tol=0):
         raise nx.NetworkXPointlessConcept(
             "cannot compute centrality for the null graph"
         )
+    connected = nx.is_strongly_connected(G) if G.is_directed() else nx.is_connected(G)
+    if not connected:  # See gh-6888.
+        raise nx.AmbiguousSolution(
+            "`eigenvector_centrality_numpy` does not give consistent results for disconnected graphs"
+        )
     M = nx.to_scipy_sparse_array(G, nodelist=list(G), weight=weight, dtype=float)
     _, eigenvector = sp.sparse.linalg.eigs(
         M.T, k=1, which="LR", maxiter=max_iter, tol=tol
diff --git a/networkx/algorithms/centrality/reaching.py b/networkx/algorithms/centrality/reaching.py
index 93cb75a96..378e8a058 100644
--- a/networkx/algorithms/centrality/reaching.py
+++ b/networkx/algorithms/centrality/reaching.py
@@ -89,7 +89,6 @@ def global_reaching_centrality(G, weight=None, normalized=True):
     total_weight = G.size(weight=weight)
     if total_weight <= 0:
         raise nx.NetworkXError("Size of G must be positive")
-
     # If provided, weights must be interpreted as connection strength
     # (so higher weights are more likely to be chosen). However, the
     # shortest path algorithms in NetworkX assume the provided "weight"
@@ -178,10 +177,14 @@ def local_reaching_centrality(G, v, paths=None, weight=None, normalized=True):
            *PLoS ONE* 7.3 (2012): e33799.
            https://doi.org/10.1371/journal.pone.0033799
     """
+    # Corner case: graph with single node containing a self-loop
+    if (total_weight := G.size(weight=weight)) > 0 and len(G) == 1:
+        raise nx.NetworkXError(
+            "local_reaching_centrality of a single node with self-loop not well-defined"
+        )
     if paths is None:
         if nx.is_negatively_weighted(G, weight=weight):
             raise nx.NetworkXError("edge weights must be positive")
-        total_weight = G.size(weight=weight)
         if total_weight <= 0:
             raise nx.NetworkXError("Size of G must be positive")
         if weight is not None:
diff --git a/networkx/algorithms/distance_measures.py b/networkx/algorithms/distance_measures.py
index 0d8e52bf7..8e15bf8d9 100644
--- a/networkx/algorithms/distance_measures.py
+++ b/networkx/algorithms/distance_measures.py
@@ -1,11 +1,14 @@
 """Graph diameter, radius, eccentricity and other properties."""
 
+import math
+
 import networkx as nx
 from networkx.utils import not_implemented_for
 
 __all__ = [
     "eccentricity",
     "diameter",
+    "harmonic_diameter",
     "radius",
     "periphery",
     "center",
@@ -384,6 +387,72 @@ def diameter(G, e=None, usebounds=False, weight=None):
     return max(e.values())
 
 
+@nx._dispatchable
+def harmonic_diameter(G, sp=None):
+    """Returns the harmonic diameter of the graph G.
+
+    The harmonic diameter of a graph is the harmonic mean of the distances
+    between all pairs of distinct vertices. Graphs that are not strongly
+    connected have infinite diameter and mean distance, making such
+    measures not useful. Restricting the diameter or mean distance to
+    finite distances yields paradoxical values (e.g., a perfect match
+    would have diameter one). The harmonic mean handles gracefully
+    infinite distances (e.g., a perfect match has harmonic diameter equal
+    to the number of vertices minus one), making it possible to assign a
+    meaningful value to all graphs.
+
+    Note that in [1] the harmonic diameter is called "connectivity length":
+    however, "harmonic diameter" is a more standard name from the
+    theory of metric spaces. The name "harmonic mean distance" is perhaps
+    a more descriptive name, but is not used in the literature, so we use the
+    name "harmonic diameter" here.
+
+    Parameters
+    ----------
+    G : NetworkX graph
+       A graph
+
+    sp : dict of dicts, optional
+       All-pairs shortest path lengths as a dictionary of dictionaries
+
+    Returns
+    -------
+    hd : float
+       Harmonic diameter of graph
+
+    References
+    ----------
+    .. [1] Massimo Marchiori and Vito Latora, "Harmony in the small-world".
+           *Physica A: Statistical Mechanics and Its Applications*
+           285(3-4), pages 539-546, 2000.
+           <https://doi.org/10.1016/S0378-4371(00)00311-3>
+    """
+    order = G.order()
+
+    sum_invd = 0
+    for n in G:
+        if sp is None:
+            length = nx.single_source_shortest_path_length(G, n)
+        else:
+            try:
+                length = sp[n]
+                L = len(length)
+            except TypeError as err:
+                raise nx.NetworkXError('Format of "sp" is invalid.') from err
+
+        for d in length.values():
+            # Note that this will skip the zero distance from n to itself,
+            # as it should be, but also zero-weight paths in weighted graphs.
+            if d != 0:
+                sum_invd += 1 / d
+
+    if sum_invd != 0:
+        return order * (order - 1) / sum_invd
+    if order > 1:
+        return math.inf
+    return math.nan
+
+
 @nx._dispatchable(edge_attrs="weight")
 def periphery(G, e=None, usebounds=False, weight=None):
     """Returns the periphery of the graph G.
diff --git a/networkx/algorithms/flow/maxflow.py b/networkx/algorithms/flow/maxflow.py
index 7ad2136d3..7993d87ba 100644
--- a/networkx/algorithms/flow/maxflow.py
+++ b/networkx/algorithms/flow/maxflow.py
@@ -463,8 +463,7 @@ def minimum_cut(flowG, _s, _t, capacity="capacity", flow_func=None, **kwargs):
     partition = (set(flowG) - non_reachable, non_reachable)
     # Finally add again cutset edges to the residual network to make
     # sure that it is reusable.
-    if cutset is not None:
-        R.add_edges_from(cutset)
+    R.add_edges_from(cutset)
     return (R.graph["flow_value"], partition)
 
 


Test patch:
diff --git a/networkx/algorithms/centrality/tests/test_eigenvector_centrality.py b/networkx/algorithms/centrality/tests/test_eigenvector_centrality.py
index b8620056a..cfc9ee795 100644
--- a/networkx/algorithms/centrality/tests/test_eigenvector_centrality.py
+++ b/networkx/algorithms/centrality/tests/test_eigenvector_centrality.py
@@ -159,14 +159,26 @@ class TestEigenvectorCentralityExceptions:
         with pytest.raises(nx.NetworkXException):
             nx.eigenvector_centrality_numpy(nx.MultiGraph())
 
-    def test_empty(self):
+    def test_null(self):
         with pytest.raises(nx.NetworkXException):
             nx.eigenvector_centrality(nx.Graph())
 
-    def test_empty_numpy(self):
+    def test_null_numpy(self):
         with pytest.raises(nx.NetworkXException):
             nx.eigenvector_centrality_numpy(nx.Graph())
 
+    @pytest.mark.parametrize(
+        "G",
+        [
+            nx.empty_graph(3),
+            nx.DiGraph([(0, 1), (1, 2)]),
+        ],
+    )
+    def test_disconnected_numpy(self, G):
+        msg = "does not give consistent results for disconnected"
+        with pytest.raises(nx.AmbiguousSolution, match=msg):
+            nx.eigenvector_centrality_numpy(G)
+
     def test_zero_nstart(self):
         G = nx.Graph([(1, 2), (1, 3), (2, 3)])
         with pytest.raises(
diff --git a/networkx/algorithms/centrality/tests/test_reaching.py b/networkx/algorithms/centrality/tests/test_reaching.py
index 7267d8990..35d50e701 100644
--- a/networkx/algorithms/centrality/tests/test_reaching.py
+++ b/networkx/algorithms/centrality/tests/test_reaching.py
@@ -80,6 +80,17 @@ class TestGlobalReachingCentrality:
         actual = grc(G, normalized=False, weight="weight")
         assert expected == pytest.approx(actual, abs=1e-7)
 
+    def test_single_node_with_cycle(self):
+        G = nx.DiGraph([(1, 1)])
+        with pytest.raises(nx.NetworkXError, match="local_reaching_centrality"):
+            nx.global_reaching_centrality(G)
+
+    def test_single_node_with_weighted_cycle(self):
+        G = nx.DiGraph()
+        G.add_weighted_edges_from([(1, 1, 2)])
+        with pytest.raises(nx.NetworkXError, match="local_reaching_centrality"):
+            nx.global_reaching_centrality(G, weight="weight")
+
 
 class TestLocalReachingCentrality:
     """Unit tests for the local reaching centrality function."""
@@ -116,3 +127,14 @@ class TestLocalReachingCentrality:
             G, 1, normalized=True, weight="weight"
         )
         assert centrality == 1.0
+
+    def test_single_node_with_cycle(self):
+        G = nx.DiGraph([(1, 1)])
+        with pytest.raises(nx.NetworkXError, match="local_reaching_centrality"):
+            nx.local_reaching_centrality(G, 1)
+
+    def test_single_node_with_weighted_cycle(self):
+        G = nx.DiGraph()
+        G.add_weighted_edges_from([(1, 1, 2)])
+        with pytest.raises(nx.NetworkXError, match="local_reaching_centrality"):
+            nx.local_reaching_centrality(G, 1, weight="weight")
diff --git a/networkx/algorithms/flow/tests/test_maxflow.py b/networkx/algorithms/flow/tests/test_maxflow.py
index f8d022cbb..d7305a7b6 100644
--- a/networkx/algorithms/flow/tests/test_maxflow.py
+++ b/networkx/algorithms/flow/tests/test_maxflow.py
@@ -23,8 +23,8 @@ flow_funcs = {
 
 max_min_funcs = {nx.maximum_flow, nx.minimum_cut}
 flow_value_funcs = {nx.maximum_flow_value, nx.minimum_cut_value}
-interface_funcs = max_min_funcs & flow_value_funcs
-all_funcs = flow_funcs & interface_funcs
+interface_funcs = max_min_funcs | flow_value_funcs
+all_funcs = flow_funcs | interface_funcs
 
 
 def compute_cutset(G, partition):
@@ -72,7 +72,7 @@ def validate_cuts(G, s, t, solnValue, partition, capacity, flow_func):
         assert not nx.is_strongly_connected(H), errmsg
 
 
-def compare_flows_and_cuts(G, s, t, solnFlows, solnValue, capacity="capacity"):
+def compare_flows_and_cuts(G, s, t, solnValue, capacity="capacity"):
     for flow_func in flow_funcs:
         errmsg = f"Assertion failed in function: {flow_func.__name__}"
         R = flow_func(G, s, t, capacity)
@@ -94,9 +94,10 @@ class TestMaxflowMinCutCommon:
         G = nx.Graph()
         G.add_edge(1, 2, capacity=1.0)
 
-        solnFlows = {1: {2: 1.0}, 2: {1: 1.0}}
+        # solution flows
+        # {1: {2: 1.0}, 2: {1: 1.0}}
 
-        compare_flows_and_cuts(G, 1, 2, solnFlows, 1.0)
+        compare_flows_and_cuts(G, 1, 2, 1.0)
 
     def test_graph2(self):
         # A more complex undirected graph
@@ -111,17 +112,18 @@ class TestMaxflowMinCutCommon:
         G.add_edge("c", "y", capacity=2.0)
         G.add_edge("e", "y", capacity=3.0)
 
-        H = {
-            "x": {"a": 3, "b": 1},
-            "a": {"c": 3, "x": 3},
-            "b": {"c": 1, "d": 2, "x": 1},
-            "c": {"a": 3, "b": 1, "y": 2},
-            "d": {"b": 2, "e": 2},
-            "e": {"d": 2, "y": 2},
-            "y": {"c": 2, "e": 2},
-        }
+        # H
+        # {
+        #     "x": {"a": 3, "b": 1},
+        #     "a": {"c": 3, "x": 3},
+        #     "b": {"c": 1, "d": 2, "x": 1},
+        #     "c": {"a": 3, "b": 1, "y": 2},
+        #     "d": {"b": 2, "e": 2},
+        #     "e": {"d": 2, "y": 2},
+        #     "y": {"c": 2, "e": 2},
+        # }
 
-        compare_flows_and_cuts(G, "x", "y", H, 4.0)
+        compare_flows_and_cuts(G, "x", "y", 4.0)
 
     def test_digraph1(self):
         # The classic directed graph example
@@ -132,14 +134,15 @@ class TestMaxflowMinCutCommon:
         G.add_edge("b", "d", capacity=1000.0)
         G.add_edge("c", "d", capacity=1000.0)
 
-        H = {
-            "a": {"b": 1000.0, "c": 1000.0},
-            "b": {"c": 0, "d": 1000.0},
-            "c": {"d": 1000.0},
-            "d": {},
-        }
+        # H
+        # {
+        #     "a": {"b": 1000.0, "c": 1000.0},
+        #     "b": {"c": 0, "d": 1000.0},
+        #     "c": {"d": 1000.0},
+        #     "d": {},
+        # }
 
-        compare_flows_and_cuts(G, "a", "d", H, 2000.0)
+        compare_flows_and_cuts(G, "a", "d", 2000.0)
 
     def test_digraph2(self):
         # An example in which some edges end up with zero flow.
@@ -151,16 +154,17 @@ class TestMaxflowMinCutCommon:
         G.add_edge("b", "a", capacity=2)
         G.add_edge("a", "t", capacity=2)
 
-        H = {
-            "s": {"b": 2, "c": 0},
-            "c": {"d": 0},
-            "d": {"a": 0},
-            "b": {"a": 2},
-            "a": {"t": 2},
-            "t": {},
-        }
+        # H
+        # {
+        #     "s": {"b": 2, "c": 0},
+        #     "c": {"d": 0},
+        #     "d": {"a": 0},
+        #     "b": {"a": 2},
+        #     "a": {"t": 2},
+        #     "t": {},
+        # }
 
-        compare_flows_and_cuts(G, "s", "t", H, 2)
+        compare_flows_and_cuts(G, "s", "t", 2)
 
     def test_digraph3(self):
         # A directed graph example from Cormen et al.
@@ -176,16 +180,17 @@ class TestMaxflowMinCutCommon:
         G.add_edge("v3", "t", capacity=20.0)
         G.add_edge("v4", "t", capacity=4.0)
 
-        H = {
-            "s": {"v1": 12.0, "v2": 11.0},
-            "v2": {"v1": 0, "v4": 11.0},
-            "v1": {"v2": 0, "v3": 12.0},
-            "v3": {"v2": 0, "t": 19.0},
-            "v4": {"v3": 7.0, "t": 4.0},
-            "t": {},
-        }
+        # H
+        # {
+        #     "s": {"v1": 12.0, "v2": 11.0},
+        #     "v2": {"v1": 0, "v4": 11.0},
+        #     "v1": {"v2": 0, "v3": 12.0},
+        #     "v3": {"v2": 0, "t": 19.0},
+        #     "v4": {"v3": 7.0, "t": 4.0},
+        #     "t": {},
+        # }
 
-        compare_flows_and_cuts(G, "s", "t", H, 23.0)
+        compare_flows_and_cuts(G, "s", "t", 23.0)
 
     def test_digraph4(self):
         # A more complex directed graph
@@ -200,17 +205,18 @@ class TestMaxflowMinCutCommon:
         G.add_edge("c", "y", capacity=2.0)
         G.add_edge("e", "y", capacity=3.0)
 
-        H = {
-            "x": {"a": 2.0, "b": 1.0},
-            "a": {"c": 2.0},
-            "b": {"c": 0, "d": 1.0},
-            "c": {"y": 2.0},
-            "d": {"e": 1.0},
-            "e": {"y": 1.0},
-            "y": {},
-        }
+        # H
+        # {
+        #     "x": {"a": 2.0, "b": 1.0},
+        #     "a": {"c": 2.0},
+        #     "b": {"c": 0, "d": 1.0},
+        #     "c": {"y": 2.0},
+        #     "d": {"e": 1.0},
+        #     "e": {"y": 1.0},
+        #     "y": {},
+        # }
 
-        compare_flows_and_cuts(G, "x", "y", H, 3.0)
+        compare_flows_and_cuts(G, "x", "y", 3.0)
 
     def test_wikipedia_dinitz_example(self):
         # Nice example from https://en.wikipedia.org/wiki/Dinic's_algorithm
@@ -225,16 +231,17 @@ class TestMaxflowMinCutCommon:
         G.add_edge(4, 3, capacity=6)
         G.add_edge(4, "t", capacity=10)
 
-        solnFlows = {
-            1: {2: 0, 3: 4, 4: 6},
-            2: {4: 9},
-            3: {"t": 9},
-            4: {3: 5, "t": 10},
-            "s": {1: 10, 2: 9},
-            "t": {},
-        }
+        # solution flows
+        # {
+        #     1: {2: 0, 3: 4, 4: 6},
+        #     2: {4: 9},
+        #     3: {"t": 9},
+        #     4: {3: 5, "t": 10},
+        #     "s": {1: 10, 2: 9},
+        #     "t": {},
+        # }
 
-        compare_flows_and_cuts(G, "s", "t", solnFlows, 19)
+        compare_flows_and_cuts(G, "s", "t", 19)
 
     def test_optional_capacity(self):
         # Test optional capacity parameter.
@@ -248,20 +255,21 @@ class TestMaxflowMinCutCommon:
         G.add_edge("c", "y", spam=2.0)
         G.add_edge("e", "y", spam=3.0)
 
-        solnFlows = {
-            "x": {"a": 2.0, "b": 1.0},
-            "a": {"c": 2.0},
-            "b": {"c": 0, "d": 1.0},
-            "c": {"y": 2.0},
-            "d": {"e": 1.0},
-            "e": {"y": 1.0},
-            "y": {},
-        }
+        # solution flows
+        # {
+        #     "x": {"a": 2.0, "b": 1.0},
+        #     "a": {"c": 2.0},
+        #     "b": {"c": 0, "d": 1.0},
+        #     "c": {"y": 2.0},
+        #     "d": {"e": 1.0},
+        #     "e": {"y": 1.0},
+        #     "y": {},
+        # }
         solnValue = 3.0
         s = "x"
         t = "y"
 
-        compare_flows_and_cuts(G, s, t, solnFlows, solnValue, capacity="spam")
+        compare_flows_and_cuts(G, s, t, solnValue, capacity="spam")
 
     def test_digraph_infcap_edges(self):
         # DiGraph with infinite capacity edges
@@ -273,15 +281,16 @@ class TestMaxflowMinCutCommon:
         G.add_edge("a", "t", capacity=60)
         G.add_edge("c", "t")
 
-        H = {
-            "s": {"a": 85, "b": 12},
-            "a": {"c": 25, "t": 60},
-            "b": {"c": 12},
-            "c": {"t": 37},
-            "t": {},
-        }
+        # H
+        # {
+        #     "s": {"a": 85, "b": 12},
+        #     "a": {"c": 25, "t": 60},
+        #     "b": {"c": 12},
+        #     "c": {"t": 37},
+        #     "t": {},
+        # }
 
-        compare_flows_and_cuts(G, "s", "t", H, 97)
+        compare_flows_and_cuts(G, "s", "t", 97)
 
         # DiGraph with infinite capacity digon
         G = nx.DiGraph()
@@ -293,15 +302,16 @@ class TestMaxflowMinCutCommon:
         G.add_edge("a", "t", capacity=60)
         G.add_edge("c", "t", capacity=37)
 
-        H = {
-            "s": {"a": 85, "b": 12},
-            "a": {"c": 25, "t": 60},
-            "c": {"a": 0, "t": 37},
-            "b": {"c": 12},
-            "t": {},
-        }
+        # H
+        # {
+        #     "s": {"a": 85, "b": 12},
+        #     "a": {"c": 25, "t": 60},
+        #     "c": {"a": 0, "t": 37},
+        #     "b": {"c": 12},
+        #     "t": {},
+        # }
 
-        compare_flows_and_cuts(G, "s", "t", H, 97)
+        compare_flows_and_cuts(G, "s", "t", 97)
 
     def test_digraph_infcap_path(self):
         # Graph with infinite capacity (s, t)-path
@@ -326,15 +336,16 @@ class TestMaxflowMinCutCommon:
         G.add_edge("a", "t", capacity=60)
         G.add_edge("c", "t")
 
-        H = {
-            "s": {"a": 85, "b": 12},
-            "a": {"c": 25, "s": 85, "t": 60},
-            "b": {"c": 12, "s": 12},
-            "c": {"a": 25, "b": 12, "t": 37},
-            "t": {"a": 60, "c": 37},
-        }
+        # H
+        # {
+        #     "s": {"a": 85, "b": 12},
+        #     "a": {"c": 25, "s": 85, "t": 60},
+        #     "b": {"c": 12, "s": 12},
+        #     "c": {"a": 25, "b": 12, "t": 37},
+        #     "t": {"a": 60, "c": 37},
+        # }
 
-        compare_flows_and_cuts(G, "s", "t", H, 97)
+        compare_flows_and_cuts(G, "s", "t", 97)
 
     def test_digraph5(self):
         # From ticket #429 by mfrasca.
@@ -345,21 +356,23 @@ class TestMaxflowMinCutCommon:
         G.add_edge("a", "t", capacity=1)
         G.add_edge("b", "a", capacity=1)
         G.add_edge("b", "t", capacity=3)
-        flowSoln = {
-            "a": {"b": 1, "t": 1},
-            "b": {"a": 0, "t": 3},
-            "s": {"a": 2, "b": 2},
-            "t": {},
-        }
-        compare_flows_and_cuts(G, "s", "t", flowSoln, 4)
+        # flow solution
+        # {
+        #     "a": {"b": 1, "t": 1},
+        #     "b": {"a": 0, "t": 3},
+        #     "s": {"a": 2, "b": 2},
+        #     "t": {},
+        # }
+        compare_flows_and_cuts(G, "s", "t", 4)
 
     def test_disconnected(self):
         G = nx.Graph()
         G.add_weighted_edges_from([(0, 1, 1), (1, 2, 1), (2, 3, 1)], weight="capacity")
         G.remove_node(1)
         assert nx.maximum_flow_value(G, 0, 3) == 0
-        flowSoln = {0: {}, 2: {3: 0}, 3: {2: 0}}
-        compare_flows_and_cuts(G, 0, 3, flowSoln, 0)
+        # flow solution
+        # {0: {}, 2: {3: 0}, 3: {2: 0}}
+        compare_flows_and_cuts(G, 0, 3, 0)
 
     def test_source_target_not_in_graph(self):
         G = nx.Graph()
diff --git a/networkx/algorithms/tests/test_distance_measures.py b/networkx/algorithms/tests/test_distance_measures.py
index 97c3547f9..0b3840fd2 100644
--- a/networkx/algorithms/tests/test_distance_measures.py
+++ b/networkx/algorithms/tests/test_distance_measures.py
@@ -1,3 +1,4 @@
+import math
 from random import Random
 
 import pytest
@@ -52,6 +53,23 @@ class TestDistance:
     def test_diameter(self):
         assert nx.diameter(self.G) == 6
 
+    def test_harmonic_diameter(self):
+        assert abs(nx.harmonic_diameter(self.G) - 2.0477815699658715) < 1e-12
+
+    def test_harmonic_diameter_empty(self):
+        assert math.isnan(nx.harmonic_diameter(nx.empty_graph()))
+
+    def test_harmonic_diameter_single_node(self):
+        assert math.isnan(nx.harmonic_diameter(nx.empty_graph(1)))
+
+    def test_harmonic_diameter_discrete(self):
+        assert math.isinf(nx.harmonic_diameter(nx.empty_graph(3)))
+
+    def test_harmonic_diameter_not_strongly_connected(self):
+        DG = nx.DiGraph()
+        DG.add_edge(0, 1)
+        assert nx.harmonic_diameter(DG) == 2
+
     def test_radius(self):
         assert nx.radius(self.G) == 4