Repository: networkx/networkx

Issue:
network_simplex is untested
The `networksimplex` module defines a single monolithic function `network_simplex` that currently is not explicitly tested. The function itself is very large, and includes internal definitions of quite a few other functions/generators. This will likely make it very difficult to write tests for the function. Since the `networksimplex` module defines only the one function, this also might be a candidate for a refactor, breaking out the internally-defined functions to (probably) private functions in the module, with their own unit tests. Doing so would likely greatly improve the robustness of the function.

See #4641 for additional details.


Discussion:
I tried to take a look at the code, and it is also pretty unreadable: lots of one-letter variables, sometimes with unintuitive choices... It would maybe benefit from a full rewrite, but even understanding the existing code looks quite arduous. 

Resolving patch:
diff --git a/networkx/algorithms/flow/networksimplex.py b/networkx/algorithms/flow/networksimplex.py
--- a/networkx/algorithms/flow/networksimplex.py
+++ b/networkx/algorithms/flow/networksimplex.py
@@ -10,6 +10,321 @@
 from networkx.utils import not_implemented_for
 
 
+class _DataEssentialsAndFunctions:
+    def __init__(
+        self, G, multigraph, demand="demand", capacity="capacity", weight="weight"
+    ):
+
+        # Number all nodes and edges and hereafter reference them using ONLY their numbers
+        self.node_list = list(G)  # nodes
+        self.node_indices = {u: i for i, u in enumerate(self.node_list)}  # node indices
+        self.node_demands = [
+            G.nodes[u].get(demand, 0) for u in self.node_list
+        ]  # node demands
+
+        self.edge_sources = []  # edge sources
+        self.edge_targets = []  # edge targets
+        if multigraph:
+            self.edge_keys = []  # edge keys
+        self.edge_indices = {}  # edge indices
+        self.edge_capacities = []  # edge capacities
+        self.edge_weights = []  # edge weights
+
+        if not multigraph:
+            edges = G.edges(data=True)
+        else:
+            edges = G.edges(data=True, keys=True)
+
+        inf = float("inf")
+        edges = (e for e in edges if e[0] != e[1] and e[-1].get(capacity, inf) != 0)
+        for i, e in enumerate(edges):
+            self.edge_sources.append(self.node_indices[e[0]])
+            self.edge_targets.append(self.node_indices[e[1]])
+            if multigraph:
+                self.edge_keys.append(e[2])
+            self.edge_indices[e[:-1]] = i
+            self.edge_capacities.append(e[-1].get(capacity, inf))
+            self.edge_weights.append(e[-1].get(weight, 0))
+
+        # spanning tree specific data to be initialized
+
+        self.edge_count = None  # number of edges
+        self.edge_flow = None  # edge flows
+        self.node_potentials = None  # node potentials
+        self.parent = None  # parent nodes
+        self.parent_edge = None  # edges to parents
+        self.subtree_size = None  # subtree sizes
+        self.next_node_dft = None  # next nodes in depth-first thread
+        self.prev_node_dft = None  # previous nodes in depth-first thread
+        self.last_descendent_dft = None  # last descendants in depth-first thread
+        self._spanning_tree_initialized = (
+            False  # False until initialize_spanning_tree() is called
+        )
+
+    def initialize_spanning_tree(self, n, faux_inf):
+        self.edge_count = len(self.edge_indices)  # number of edges
+        self.edge_flow = list(
+            chain(repeat(0, self.edge_count), (abs(d) for d in self.node_demands))
+        )  # edge flows
+        self.node_potentials = [
+            faux_inf if d <= 0 else -faux_inf for d in self.node_demands
+        ]  # node potentials
+        self.parent = list(chain(repeat(-1, n), [None]))  # parent nodes
+        self.parent_edge = list(
+            range(self.edge_count, self.edge_count + n)
+        )  # edges to parents
+        self.subtree_size = list(chain(repeat(1, n), [n + 1]))  # subtree sizes
+        self.next_node_dft = list(
+            chain(range(1, n), [-1, 0])
+        )  # next nodes in depth-first thread
+        self.prev_node_dft = list(range(-1, n))  # previous nodes in depth-first thread
+        self.last_descendent_dft = list(
+            chain(range(n), [n - 1])
+        )  # last descendants in depth-first thread
+        self._spanning_tree_initialized = True  # True only if all the assignments pass
+
+    def find_apex(self, p, q):
+        """
+        Find the lowest common ancestor of nodes p and q in the spanning tree.
+        """
+        size_p = self.subtree_size[p]
+        size_q = self.subtree_size[q]
+        while True:
+            while size_p < size_q:
+                p = self.parent[p]
+                size_p = self.subtree_size[p]
+            while size_p > size_q:
+                q = self.parent[q]
+                size_q = self.subtree_size[q]
+            if size_p == size_q:
+                if p != q:
+                    p = self.parent[p]
+                    size_p = self.subtree_size[p]
+                    q = self.parent[q]
+                    size_q = self.subtree_size[q]
+                else:
+                    return p
+
+    def trace_path(self, p, w):
+        """
+        Returns the nodes and edges on the path from node p to its ancestor w.
+        """
+        Wn = [p]
+        We = []
+        while p != w:
+            We.append(self.parent_edge[p])
+            p = self.parent[p]
+            Wn.append(p)
+        return Wn, We
+
+    def find_cycle(self, i, p, q):
+        """
+        Returns the nodes and edges on the cycle containing edge i == (p, q)
+        when the latter is added to the spanning tree.
+
+        The cycle is oriented in the direction from p to q.
+        """
+        w = self.find_apex(p, q)
+        Wn, We = self.trace_path(p, w)
+        Wn.reverse()
+        We.reverse()
+        if We != [i]:
+            We.append(i)
+        WnR, WeR = self.trace_path(q, w)
+        del WnR[-1]
+        Wn += WnR
+        We += WeR
+        return Wn, We
+
+    def augment_flow(self, Wn, We, f):
+        """
+        Augment f units of flow along a cycle represented by Wn and We.
+        """
+        for i, p in zip(We, Wn):
+            if self.edge_sources[i] == p:
+                self.edge_flow[i] += f
+            else:
+                self.edge_flow[i] -= f
+
+    def trace_subtree(self, p):
+        """
+        Yield the nodes in the subtree rooted at a node p.
+        """
+        yield p
+        l = self.last_descendent_dft[p]
+        while p != l:
+            p = self.next_node_dft[p]
+            yield p
+
+    def remove_edge(self, s, t):
+        """
+        Remove an edge (s, t) where parent[t] == s from the spanning tree.
+        """
+        size_t = self.subtree_size[t]
+        prev_t = self.prev_node_dft[t]
+        last_t = self.last_descendent_dft[t]
+        next_last_t = self.next_node_dft[last_t]
+        # Remove (s, t).
+        self.parent[t] = None
+        self.parent_edge[t] = None
+        # Remove the subtree rooted at t from the depth-first thread.
+        self.next_node_dft[prev_t] = next_last_t
+        self.prev_node_dft[next_last_t] = prev_t
+        self.next_node_dft[last_t] = t
+        self.prev_node_dft[t] = last_t
+        # Update the subtree sizes and last descendants of the (old) acenstors
+        # of t.
+        while s is not None:
+            self.subtree_size[s] -= size_t
+            if self.last_descendent_dft[s] == last_t:
+                self.last_descendent_dft[s] = prev_t
+            s = self.parent[s]
+
+    def make_root(self, q):
+        """
+        Make a node q the root of its containing subtree.
+        """
+        ancestors = []
+        while q is not None:
+            ancestors.append(q)
+            q = self.parent[q]
+        ancestors.reverse()
+        for p, q in zip(ancestors, islice(ancestors, 1, None)):
+            size_p = self.subtree_size[p]
+            last_p = self.last_descendent_dft[p]
+            prev_q = self.prev_node_dft[q]
+            last_q = self.last_descendent_dft[q]
+            next_last_q = self.next_node_dft[last_q]
+            # Make p a child of q.
+            self.parent[p] = q
+            self.parent[q] = None
+            self.parent_edge[p] = self.parent_edge[q]
+            self.parent_edge[q] = None
+            self.subtree_size[p] = size_p - self.subtree_size[q]
+            self.subtree_size[q] = size_p
+            # Remove the subtree rooted at q from the depth-first thread.
+            self.next_node_dft[prev_q] = next_last_q
+            self.prev_node_dft[next_last_q] = prev_q
+            self.next_node_dft[last_q] = q
+            self.prev_node_dft[q] = last_q
+            if last_p == last_q:
+                self.last_descendent_dft[p] = prev_q
+                last_p = prev_q
+            # Add the remaining parts of the subtree rooted at p as a subtree
+            # of q in the depth-first thread.
+            self.prev_node_dft[p] = last_q
+            self.next_node_dft[last_q] = p
+            self.next_node_dft[last_p] = q
+            self.prev_node_dft[q] = last_p
+            self.last_descendent_dft[q] = last_p
+
+    def add_edge(self, i, p, q):
+        """
+        Add an edge (p, q) to the spanning tree where q is the root of a subtree.
+        """
+        last_p = self.last_descendent_dft[p]
+        next_last_p = self.next_node_dft[last_p]
+        size_q = self.subtree_size[q]
+        last_q = self.last_descendent_dft[q]
+        # Make q a child of p.
+        self.parent[q] = p
+        self.parent_edge[q] = i
+        # Insert the subtree rooted at q into the depth-first thread.
+        self.next_node_dft[last_p] = q
+        self.prev_node_dft[q] = last_p
+        self.prev_node_dft[next_last_p] = last_q
+        self.next_node_dft[last_q] = next_last_p
+        # Update the subtree sizes and last descendants of the (new) ancestors
+        # of q.
+        while p is not None:
+            self.subtree_size[p] += size_q
+            if self.last_descendent_dft[p] == last_p:
+                self.last_descendent_dft[p] = last_q
+            p = self.parent[p]
+
+    def update_potentials(self, i, p, q):
+        """
+        Update the potentials of the nodes in the subtree rooted at a node
+        q connected to its parent p by an edge i.
+        """
+        if q == self.edge_targets[i]:
+            d = self.node_potentials[p] - self.edge_weights[i] - self.node_potentials[q]
+        else:
+            d = self.node_potentials[p] + self.edge_weights[i] - self.node_potentials[q]
+        for q in self.trace_subtree(q):
+            self.node_potentials[q] += d
+
+    def reduced_cost(self, i):
+        """Returns the reduced cost of an edge i."""
+        c = (
+            self.edge_weights[i]
+            - self.node_potentials[self.edge_sources[i]]
+            + self.node_potentials[self.edge_targets[i]]
+        )
+        return c if self.edge_flow[i] == 0 else -c
+
+    def find_entering_edges(self):
+        """Yield entering edges until none can be found."""
+        if self.edge_count == 0:
+            return
+
+        # Entering edges are found by combining Dantzig's rule and Bland's
+        # rule. The edges are cyclically grouped into blocks of size B. Within
+        # each block, Dantzig's rule is applied to find an entering edge. The
+        # blocks to search is determined following Bland's rule.
+        B = int(ceil(sqrt(self.edge_count)))  # pivot block size
+        M = (self.edge_count + B - 1) // B  # number of blocks needed to cover all edges
+        m = 0  # number of consecutive blocks without eligible
+        # entering edges
+        f = 0  # first edge in block
+        while m < M:
+            # Determine the next block of edges.
+            l = f + B
+            if l <= self.edge_count:
+                edges = range(f, l)
+            else:
+                l -= self.edge_count
+                edges = chain(range(f, self.edge_count), range(l))
+            f = l
+            # Find the first edge with the lowest reduced cost.
+            i = min(edges, key=self.reduced_cost)
+            c = self.reduced_cost(i)
+            if c >= 0:
+                # No entering edge found in the current block.
+                m += 1
+            else:
+                # Entering edge found.
+                if self.edge_flow[i] == 0:
+                    p = self.edge_sources[i]
+                    q = self.edge_targets[i]
+                else:
+                    p = self.edge_targets[i]
+                    q = self.edge_sources[i]
+                yield i, p, q
+                m = 0
+        # All edges have nonnegative reduced costs. The current flow is
+        # optimal.
+
+    def residual_capacity(self, i, p):
+        """Returns the residual capacity of an edge i in the direction away
+        from its endpoint p.
+        """
+        return (
+            self.edge_capacities[i] - self.edge_flow[i]
+            if self.edge_sources[i] == p
+            else self.edge_flow[i]
+        )
+
+    def find_leaving_edge(self, Wn, We):
+        """Returns the leaving edge in a cycle represented by Wn and We."""
+        j, s = min(
+            zip(reversed(We), reversed(Wn)),
+            key=lambda i_p: self.residual_capacity(*i_p),
+        )
+        t = self.edge_targets[j] if self.edge_sources[j] == s else self.edge_sources[j]
+        return j, s, t
+
+
 @not_implemented_for("undirected")
 def network_simplex(G, demand="demand", capacity="capacity", weight="weight"):
     r"""Find a minimum cost flow satisfying all demands in digraph G.
@@ -180,43 +495,23 @@ def network_simplex(G, demand="demand", capacity="capacity", weight="weight"):
     if len(G) == 0:
         raise nx.NetworkXError("graph has no nodes")
 
-    # Number all nodes and edges and hereafter reference them using ONLY their
-    # numbers
-
-    N = list(G)  # nodes
-    I = {u: i for i, u in enumerate(N)}  # node indices
-    D = [G.nodes[u].get(demand, 0) for u in N]  # node demands
-
-    inf = float("inf")
-    for p, b in zip(N, D):
-        if abs(b) == inf:
-            raise nx.NetworkXError(f"node {p!r} has infinite demand")
-
     multigraph = G.is_multigraph()
-    S = []  # edge sources
-    T = []  # edge targets
-    if multigraph:
-        K = []  # edge keys
-    E = {}  # edge indices
-    U = []  # edge capacities
-    C = []  # edge weights
 
-    if not multigraph:
-        edges = G.edges(data=True)
-    else:
-        edges = G.edges(data=True, keys=True)
-    edges = (e for e in edges if e[0] != e[1] and e[-1].get(capacity, inf) != 0)
-    for i, e in enumerate(edges):
-        S.append(I[e[0]])
-        T.append(I[e[1]])
-        if multigraph:
-            K.append(e[2])
-        E[e[:-1]] = i
-        U.append(e[-1].get(capacity, inf))
-        C.append(e[-1].get(weight, 0))
+    # extracting data essential to problem
+    DEAF = _DataEssentialsAndFunctions(
+        G, multigraph, demand=demand, capacity=capacity, weight=weight
+    )
 
-    for e, c in zip(E, C):
-        if abs(c) == inf:
+    ###########################################################################
+    # Quick Error Detection
+    ###########################################################################
+
+    inf = float("inf")
+    for u, d in zip(DEAF.node_list, DEAF.node_demands):
+        if abs(d) == inf:
+            raise nx.NetworkXError(f"node {u!r} has infinite demand")
+    for e, w in zip(DEAF.edge_indices, DEAF.edge_weights):
+        if abs(w) == inf:
             raise nx.NetworkXError(f"edge {e!r} has infinite weight")
     if not multigraph:
         edges = nx.selfloop_edges(G, data=True)
@@ -227,13 +522,13 @@ def network_simplex(G, demand="demand", capacity="capacity", weight="weight"):
             raise nx.NetworkXError(f"edge {e[:-1]!r} has infinite weight")
 
     ###########################################################################
-    # Quick infeasibility detection
+    # Quick Infeasibility Detection
     ###########################################################################
 
-    if sum(D) != 0:
+    if sum(DEAF.node_demands) != 0:
         raise nx.NetworkXUnfeasible("total node demand is not zero")
-    for e, u in zip(E, U):
-        if u < 0:
+    for e, c in zip(DEAF.edge_indices, DEAF.edge_capacities):
+        if c < 0:
             raise nx.NetworkXUnfeasible(f"edge {e!r} has negative capacity")
     if not multigraph:
         edges = nx.selfloop_edges(G, data=True)
@@ -252,293 +547,65 @@ def network_simplex(G, demand="demand", capacity="capacity", weight="weight"):
     # spanning tree of the network simplex method. The new edges will used to
     # trivially satisfy the node demands and create an initial strongly
     # feasible spanning tree.
-    n = len(N)  # number of nodes
-    for p, d in enumerate(D):
+    for i, d in enumerate(DEAF.node_demands):
         # Must be greater-than here. Zero-demand nodes must have
-        # edges pointing towards the root to ensure strong
-        # feasibility.
+        # edges pointing towards the root to ensure strong feasibility.
         if d > 0:
-            S.append(-1)
-            T.append(p)
+            DEAF.edge_sources.append(-1)
+            DEAF.edge_targets.append(i)
         else:
-            S.append(p)
-            T.append(-1)
+            DEAF.edge_sources.append(i)
+            DEAF.edge_targets.append(-1)
     faux_inf = (
         3
         * max(
             chain(
-                [sum(u for u in U if u < inf), sum(abs(c) for c in C)],
-                (abs(d) for d in D),
+                [
+                    sum(c for c in DEAF.edge_capacities if c < inf),
+                    sum(abs(w) for w in DEAF.edge_weights),
+                ],
+                (abs(d) for d in DEAF.node_demands),
             )
         )
         or 1
     )
-    C.extend(repeat(faux_inf, n))
-    U.extend(repeat(faux_inf, n))
+
+    n = len(DEAF.node_list)  # number of nodes
+    DEAF.edge_weights.extend(repeat(faux_inf, n))
+    DEAF.edge_capacities.extend(repeat(faux_inf, n))
 
     # Construct the initial spanning tree.
-    e = len(E)  # number of edges
-    x = list(chain(repeat(0, e), (abs(d) for d in D)))  # edge flows
-    pi = [faux_inf if d <= 0 else -faux_inf for d in D]  # node potentials
-    parent = list(chain(repeat(-1, n), [None]))  # parent nodes
-    edge = list(range(e, e + n))  # edges to parents
-    size = list(chain(repeat(1, n), [n + 1]))  # subtree sizes
-    next = list(chain(range(1, n), [-1, 0]))  # next nodes in depth-first thread
-    prev = list(range(-1, n))  # previous nodes in depth-first thread
-    last = list(chain(range(n), [n - 1]))  # last descendants in depth-first thread
+    DEAF.initialize_spanning_tree(n, faux_inf)
 
     ###########################################################################
     # Pivot loop
     ###########################################################################
 
-    def reduced_cost(i):
-        """Returns the reduced cost of an edge i."""
-        c = C[i] - pi[S[i]] + pi[T[i]]
-        return c if x[i] == 0 else -c
-
-    def find_entering_edges():
-        """Yield entering edges until none can be found."""
-        if e == 0:
-            return
-
-        # Entering edges are found by combining Dantzig's rule and Bland's
-        # rule. The edges are cyclically grouped into blocks of size B. Within
-        # each block, Dantzig's rule is applied to find an entering edge. The
-        # blocks to search is determined following Bland's rule.
-        B = int(ceil(sqrt(e)))  # pivot block size
-        M = (e + B - 1) // B  # number of blocks needed to cover all edges
-        m = 0  # number of consecutive blocks without eligible
-        # entering edges
-        f = 0  # first edge in block
-        while m < M:
-            # Determine the next block of edges.
-            l = f + B
-            if l <= e:
-                edges = range(f, l)
-            else:
-                l -= e
-                edges = chain(range(f, e), range(l))
-            f = l
-            # Find the first edge with the lowest reduced cost.
-            i = min(edges, key=reduced_cost)
-            c = reduced_cost(i)
-            if c >= 0:
-                # No entering edge found in the current block.
-                m += 1
-            else:
-                # Entering edge found.
-                if x[i] == 0:
-                    p = S[i]
-                    q = T[i]
-                else:
-                    p = T[i]
-                    q = S[i]
-                yield i, p, q
-                m = 0
-        # All edges have nonnegative reduced costs. The current flow is
-        # optimal.
-
-    def find_apex(p, q):
-        """Find the lowest common ancestor of nodes p and q in the spanning
-        tree.
-        """
-        size_p = size[p]
-        size_q = size[q]
-        while True:
-            while size_p < size_q:
-                p = parent[p]
-                size_p = size[p]
-            while size_p > size_q:
-                q = parent[q]
-                size_q = size[q]
-            if size_p == size_q:
-                if p != q:
-                    p = parent[p]
-                    size_p = size[p]
-                    q = parent[q]
-                    size_q = size[q]
-                else:
-                    return p
-
-    def trace_path(p, w):
-        """Returns the nodes and edges on the path from node p to its ancestor
-        w.
-        """
-        Wn = [p]
-        We = []
-        while p != w:
-            We.append(edge[p])
-            p = parent[p]
-            Wn.append(p)
-        return Wn, We
-
-    def find_cycle(i, p, q):
-        """Returns the nodes and edges on the cycle containing edge i == (p, q)
-        when the latter is added to the spanning tree.
-
-        The cycle is oriented in the direction from p to q.
-        """
-        w = find_apex(p, q)
-        Wn, We = trace_path(p, w)
-        Wn.reverse()
-        We.reverse()
-        if We != [i]:
-            We.append(i)
-        WnR, WeR = trace_path(q, w)
-        del WnR[-1]
-        Wn += WnR
-        We += WeR
-        return Wn, We
-
-    def residual_capacity(i, p):
-        """Returns the residual capacity of an edge i in the direction away
-        from its endpoint p.
-        """
-        return U[i] - x[i] if S[i] == p else x[i]
-
-    def find_leaving_edge(Wn, We):
-        """Returns the leaving edge in a cycle represented by Wn and We."""
-        j, s = min(
-            zip(reversed(We), reversed(Wn)), key=lambda i_p: residual_capacity(*i_p)
-        )
-        t = T[j] if S[j] == s else S[j]
-        return j, s, t
-
-    def augment_flow(Wn, We, f):
-        """Augment f units of flow along a cycle represented by Wn and We."""
-        for i, p in zip(We, Wn):
-            if S[i] == p:
-                x[i] += f
-            else:
-                x[i] -= f
-
-    def trace_subtree(p):
-        """Yield the nodes in the subtree rooted at a node p."""
-        yield p
-        l = last[p]
-        while p != l:
-            p = next[p]
-            yield p
-
-    def remove_edge(s, t):
-        """Remove an edge (s, t) where parent[t] == s from the spanning tree."""
-        size_t = size[t]
-        prev_t = prev[t]
-        last_t = last[t]
-        next_last_t = next[last_t]
-        # Remove (s, t).
-        parent[t] = None
-        edge[t] = None
-        # Remove the subtree rooted at t from the depth-first thread.
-        next[prev_t] = next_last_t
-        prev[next_last_t] = prev_t
-        next[last_t] = t
-        prev[t] = last_t
-        # Update the subtree sizes and last descendants of the (old) acenstors
-        # of t.
-        while s is not None:
-            size[s] -= size_t
-            if last[s] == last_t:
-                last[s] = prev_t
-            s = parent[s]
-
-    def make_root(q):
-        """Make a node q the root of its containing subtree."""
-        ancestors = []
-        while q is not None:
-            ancestors.append(q)
-            q = parent[q]
-        ancestors.reverse()
-        for p, q in zip(ancestors, islice(ancestors, 1, None)):
-            size_p = size[p]
-            last_p = last[p]
-            prev_q = prev[q]
-            last_q = last[q]
-            next_last_q = next[last_q]
-            # Make p a child of q.
-            parent[p] = q
-            parent[q] = None
-            edge[p] = edge[q]
-            edge[q] = None
-            size[p] = size_p - size[q]
-            size[q] = size_p
-            # Remove the subtree rooted at q from the depth-first thread.
-            next[prev_q] = next_last_q
-            prev[next_last_q] = prev_q
-            next[last_q] = q
-            prev[q] = last_q
-            if last_p == last_q:
-                last[p] = prev_q
-                last_p = prev_q
-            # Add the remaining parts of the subtree rooted at p as a subtree
-            # of q in the depth-first thread.
-            prev[p] = last_q
-            next[last_q] = p
-            next[last_p] = q
-            prev[q] = last_p
-            last[q] = last_p
-
-    def add_edge(i, p, q):
-        """Add an edge (p, q) to the spanning tree where q is the root of a
-        subtree.
-        """
-        last_p = last[p]
-        next_last_p = next[last_p]
-        size_q = size[q]
-        last_q = last[q]
-        # Make q a child of p.
-        parent[q] = p
-        edge[q] = i
-        # Insert the subtree rooted at q into the depth-first thread.
-        next[last_p] = q
-        prev[q] = last_p
-        prev[next_last_p] = last_q
-        next[last_q] = next_last_p
-        # Update the subtree sizes and last descendants of the (new) ancestors
-        # of q.
-        while p is not None:
-            size[p] += size_q
-            if last[p] == last_p:
-                last[p] = last_q
-            p = parent[p]
-
-    def update_potentials(i, p, q):
-        """Update the potentials of the nodes in the subtree rooted at a node
-        q connected to its parent p by an edge i.
-        """
-        if q == T[i]:
-            d = pi[p] - C[i] - pi[q]
-        else:
-            d = pi[p] + C[i] - pi[q]
-        for q in trace_subtree(q):
-            pi[q] += d
-
-    # Pivot loop
-    for i, p, q in find_entering_edges():
-        Wn, We = find_cycle(i, p, q)
-        j, s, t = find_leaving_edge(Wn, We)
-        augment_flow(Wn, We, residual_capacity(j, s))
+    for i, p, q in DEAF.find_entering_edges():
+        Wn, We = DEAF.find_cycle(i, p, q)
+        j, s, t = DEAF.find_leaving_edge(Wn, We)
+        DEAF.augment_flow(Wn, We, DEAF.residual_capacity(j, s))
         # Do nothing more if the entering edge is the same as the leaving edge.
         if i != j:
-            if parent[t] != s:
+            if DEAF.parent[t] != s:
                 # Ensure that s is the parent of t.
                 s, t = t, s
             if We.index(i) > We.index(j):
                 # Ensure that q is in the subtree rooted at t.
                 p, q = q, p
-            remove_edge(s, t)
-            make_root(q)
-            add_edge(i, p, q)
-            update_potentials(i, p, q)
+            DEAF.remove_edge(s, t)
+            DEAF.make_root(q)
+            DEAF.add_edge(i, p, q)
+            DEAF.update_potentials(i, p, q)
 
     ###########################################################################
     # Infeasibility and unboundedness detection
     ###########################################################################
 
-    if any(x[i] != 0 for i in range(-n, 0)):
+    if any(DEAF.edge_flow[i] != 0 for i in range(-n, 0)):
         raise nx.NetworkXUnfeasible("no flow satisfies all node demands")
 
-    if any(x[i] * 2 >= faux_inf for i in range(e)) or any(
+    if any(DEAF.edge_flow[i] * 2 >= faux_inf for i in range(DEAF.edge_count)) or any(
         e[-1].get(capacity, inf) == inf and e[-1].get(weight, 0) < 0
         for e in nx.selfloop_edges(G, data=True)
     ):
@@ -548,9 +615,9 @@ def update_potentials(i, p, q):
     # Flow cost calculation and flow dict construction
     ###########################################################################
 
-    del x[e:]
-    flow_cost = sum(c * x for c, x in zip(C, x))
-    flow_dict = {n: {} for n in N}
+    del DEAF.edge_flow[DEAF.edge_count :]
+    flow_cost = sum(w * x for w, x in zip(DEAF.edge_weights, DEAF.edge_flow))
+    flow_dict = {n: {} for n in DEAF.node_list}
 
     def add_entry(e):
         """Add a flow dict entry."""
@@ -564,14 +631,27 @@ def add_entry(e):
                 d = t
         d[e[-2]] = e[-1]
 
-    S = (N[s] for s in S)  # Use original nodes.
-    T = (N[t] for t in T)  # Use original nodes.
+    DEAF.edge_sources = (
+        DEAF.node_list[s] for s in DEAF.edge_sources
+    )  # Use original nodes.
+    DEAF.edge_targets = (
+        DEAF.node_list[t] for t in DEAF.edge_targets
+    )  # Use original nodes.
     if not multigraph:
-        for e in zip(S, T, x):
+        for e in zip(
+            DEAF.edge_sources,
+            DEAF.edge_targets,
+            DEAF.edge_flow,
+        ):
             add_entry(e)
         edges = G.edges(data=True)
     else:
-        for e in zip(S, T, K, x):
+        for e in zip(
+            DEAF.edge_sources,
+            DEAF.edge_targets,
+            DEAF.edge_keys,
+            DEAF.edge_flow,
+        ):
             add_entry(e)
         edges = G.edges(data=True, keys=True)
     for e in edges:
@@ -579,12 +659,12 @@ def add_entry(e):
             if e[-1].get(capacity, inf) == 0:
                 add_entry(e[:-1] + (0,))
         else:
-            c = e[-1].get(weight, 0)
-            if c >= 0:
+            w = e[-1].get(weight, 0)
+            if w >= 0:
                 add_entry(e[:-1] + (0,))
             else:
-                u = e[-1][capacity]
-                flow_cost += c * u
-                add_entry(e[:-1] + (u,))
+                c = e[-1][capacity]
+                flow_cost += w * c
+                add_entry(e[:-1] + (c,))
 
     return flow_cost, flow_dict


Test patch:
diff --git a/networkx/algorithms/flow/tests/test_networksimplex.py b/networkx/algorithms/flow/tests/test_networksimplex.py
new file mode 100644
--- /dev/null
+++ b/networkx/algorithms/flow/tests/test_networksimplex.py
@@ -0,0 +1,383 @@
+import pytest
+import networkx as nx
+import os
+
+
+@pytest.fixture
+def simple_flow_graph():
+    G = nx.DiGraph()
+    G.add_node("a", demand=0)
+    G.add_node("b", demand=-5)
+    G.add_node("c", demand=50000000)
+    G.add_node("d", demand=-49999995)
+    G.add_edge("a", "b", weight=3, capacity=4)
+    G.add_edge("a", "c", weight=6, capacity=10)
+    G.add_edge("b", "d", weight=1, capacity=9)
+    G.add_edge("c", "d", weight=2, capacity=5)
+    return G
+
+
+@pytest.fixture
+def simple_no_flow_graph():
+    G = nx.DiGraph()
+    G.add_node("s", demand=-5)
+    G.add_node("t", demand=5)
+    G.add_edge("s", "a", weight=1, capacity=3)
+    G.add_edge("a", "b", weight=3)
+    G.add_edge("a", "c", weight=-6)
+    G.add_edge("b", "d", weight=1)
+    G.add_edge("c", "d", weight=-2)
+    G.add_edge("d", "t", weight=1, capacity=3)
+    return G
+
+
+def get_flowcost_from_flowdict(G, flowDict):
+    """Returns flow cost calculated from flow dictionary"""
+    flowCost = 0
+    for u in flowDict.keys():
+        for v in flowDict[u].keys():
+            flowCost += flowDict[u][v] * G[u][v]["weight"]
+    return flowCost
+
+
+def test_infinite_demand_raise(simple_flow_graph):
+    G = simple_flow_graph
+    inf = float("inf")
+    nx.set_node_attributes(G, {"a": {"demand": inf}})
+    pytest.raises(nx.NetworkXError, nx.network_simplex, G)
+
+
+def test_neg_infinite_demand_raise(simple_flow_graph):
+    G = simple_flow_graph
+    inf = float("inf")
+    nx.set_node_attributes(G, {"a": {"demand": -inf}})
+    pytest.raises(nx.NetworkXError, nx.network_simplex, G)
+
+
+def test_infinite_weight_raise(simple_flow_graph):
+    G = simple_flow_graph
+    inf = float("inf")
+    nx.set_edge_attributes(
+        G, {("a", "b"): {"weight": inf}, ("b", "d"): {"weight": inf}}
+    )
+    pytest.raises(nx.NetworkXError, nx.network_simplex, G)
+
+
+def test_nonzero_net_demand_raise(simple_flow_graph):
+    G = simple_flow_graph
+    nx.set_node_attributes(G, {"b": {"demand": -4}})
+    pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
+
+
+def test_negative_capacity_raise(simple_flow_graph):
+    G = simple_flow_graph
+    nx.set_edge_attributes(G, {("a", "b"): {"weight": 1}, ("b", "d"): {"capacity": -9}})
+    pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
+
+
+def test_no_flow_satisfying_demands(simple_no_flow_graph):
+    G = simple_no_flow_graph
+    pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
+
+
+def test_sum_demands_not_zero(simple_no_flow_graph):
+    G = simple_no_flow_graph
+    nx.set_node_attributes(G, {"t": {"demand": 4}})
+    pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
+
+
+def test_google_or_tools_example():
+    """
+    https://developers.google.com/optimization/flow/mincostflow
+    """
+    G = nx.DiGraph()
+    start_nodes = [0, 0, 1, 1, 1, 2, 2, 3, 4]
+    end_nodes = [1, 2, 2, 3, 4, 3, 4, 4, 2]
+    capacities = [15, 8, 20, 4, 10, 15, 4, 20, 5]
+    unit_costs = [4, 4, 2, 2, 6, 1, 3, 2, 3]
+    supplies = [20, 0, 0, -5, -15]
+    answer = 150
+
+    for i in range(len(supplies)):
+        G.add_node(i, demand=(-1) * supplies[i])  # supplies are negative of demand
+
+    for i in range(len(start_nodes)):
+        G.add_edge(
+            start_nodes[i],
+            end_nodes[i],
+            weight=unit_costs[i],
+            capacity=capacities[i],
+        )
+
+    flowCost, flowDict = nx.network_simplex(G)
+    assert flowCost == answer
+    assert flowCost == get_flowcost_from_flowdict(G, flowDict)
+
+
+def test_google_or_tools_example2():
+    """
+    https://developers.google.com/optimization/flow/mincostflow
+    """
+    G = nx.DiGraph()
+    start_nodes = [0, 0, 1, 1, 1, 2, 2, 3, 4, 3]
+    end_nodes = [1, 2, 2, 3, 4, 3, 4, 4, 2, 5]
+    capacities = [15, 8, 20, 4, 10, 15, 4, 20, 5, 10]
+    unit_costs = [4, 4, 2, 2, 6, 1, 3, 2, 3, 4]
+    supplies = [23, 0, 0, -5, -15, -3]
+    answer = 183
+
+    for i in range(len(supplies)):
+        G.add_node(i, demand=(-1) * supplies[i])  # supplies are negative of demand
+
+    for i in range(len(start_nodes)):
+        G.add_edge(
+            start_nodes[i],
+            end_nodes[i],
+            weight=unit_costs[i],
+            capacity=capacities[i],
+        )
+
+    flowCost, flowDict = nx.network_simplex(G)
+    assert flowCost == answer
+    assert flowCost == get_flowcost_from_flowdict(G, flowDict)
+
+
+def test_large():
+    fname = os.path.join(os.path.dirname(__file__), "netgen-2.gpickle.bz2")
+    G = nx.read_gpickle(fname)
+    flowCost, flowDict = nx.network_simplex(G)
+    assert 6749969302 == flowCost
+    assert 6749969302 == nx.cost_of_flow(G, flowDict)
+
+
+def test_simple_digraph():
+    G = nx.DiGraph()
+    G.add_node("a", demand=-5)
+    G.add_node("d", demand=5)
+    G.add_edge("a", "b", weight=3, capacity=4)
+    G.add_edge("a", "c", weight=6, capacity=10)
+    G.add_edge("b", "d", weight=1, capacity=9)
+    G.add_edge("c", "d", weight=2, capacity=5)
+    flowCost, H = nx.network_simplex(G)
+    soln = {"a": {"b": 4, "c": 1}, "b": {"d": 4}, "c": {"d": 1}, "d": {}}
+    assert flowCost == 24
+    assert nx.min_cost_flow_cost(G) == 24
+    assert H == soln
+
+
+def test_negcycle_infcap():
+    G = nx.DiGraph()
+    G.add_node("s", demand=-5)
+    G.add_node("t", demand=5)
+    G.add_edge("s", "a", weight=1, capacity=3)
+    G.add_edge("a", "b", weight=3)
+    G.add_edge("c", "a", weight=-6)
+    G.add_edge("b", "d", weight=1)
+    G.add_edge("d", "c", weight=-2)
+    G.add_edge("d", "t", weight=1, capacity=3)
+    pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
+
+
+def test_transshipment():
+    G = nx.DiGraph()
+    G.add_node("a", demand=1)
+    G.add_node("b", demand=-2)
+    G.add_node("c", demand=-2)
+    G.add_node("d", demand=3)
+    G.add_node("e", demand=-4)
+    G.add_node("f", demand=-4)
+    G.add_node("g", demand=3)
+    G.add_node("h", demand=2)
+    G.add_node("r", demand=3)
+    G.add_edge("a", "c", weight=3)
+    G.add_edge("r", "a", weight=2)
+    G.add_edge("b", "a", weight=9)
+    G.add_edge("r", "c", weight=0)
+    G.add_edge("b", "r", weight=-6)
+    G.add_edge("c", "d", weight=5)
+    G.add_edge("e", "r", weight=4)
+    G.add_edge("e", "f", weight=3)
+    G.add_edge("h", "b", weight=4)
+    G.add_edge("f", "d", weight=7)
+    G.add_edge("f", "h", weight=12)
+    G.add_edge("g", "d", weight=12)
+    G.add_edge("f", "g", weight=-1)
+    G.add_edge("h", "g", weight=-10)
+    flowCost, H = nx.network_simplex(G)
+    soln = {
+        "a": {"c": 0},
+        "b": {"a": 0, "r": 2},
+        "c": {"d": 3},
+        "d": {},
+        "e": {"r": 3, "f": 1},
+        "f": {"d": 0, "g": 3, "h": 2},
+        "g": {"d": 0},
+        "h": {"b": 0, "g": 0},
+        "r": {"a": 1, "c": 1},
+    }
+    assert flowCost == 41
+    assert H == soln
+
+
+def test_digraph1():
+    # From Bradley, S. P., Hax, A. C. and Magnanti, T. L. Applied
+    # Mathematical Programming. Addison-Wesley, 1977.
+    G = nx.DiGraph()
+    G.add_node(1, demand=-20)
+    G.add_node(4, demand=5)
+    G.add_node(5, demand=15)
+    G.add_edges_from(
+        [
+            (1, 2, {"capacity": 15, "weight": 4}),
+            (1, 3, {"capacity": 8, "weight": 4}),
+            (2, 3, {"weight": 2}),
+            (2, 4, {"capacity": 4, "weight": 2}),
+            (2, 5, {"capacity": 10, "weight": 6}),
+            (3, 4, {"capacity": 15, "weight": 1}),
+            (3, 5, {"capacity": 5, "weight": 3}),
+            (4, 5, {"weight": 2}),
+            (5, 3, {"capacity": 4, "weight": 1}),
+        ]
+    )
+    flowCost, H = nx.network_simplex(G)
+    soln = {
+        1: {2: 12, 3: 8},
+        2: {3: 8, 4: 4, 5: 0},
+        3: {4: 11, 5: 5},
+        4: {5: 10},
+        5: {3: 0},
+    }
+    assert flowCost == 150
+    assert nx.min_cost_flow_cost(G) == 150
+    assert H == soln
+
+
+def test_zero_capacity_edges():
+    """Address issue raised in ticket #617 by arv."""
+    G = nx.DiGraph()
+    G.add_edges_from(
+        [
+            (1, 2, {"capacity": 1, "weight": 1}),
+            (1, 5, {"capacity": 1, "weight": 1}),
+            (2, 3, {"capacity": 0, "weight": 1}),
+            (2, 5, {"capacity": 1, "weight": 1}),
+            (5, 3, {"capacity": 2, "weight": 1}),
+            (5, 4, {"capacity": 0, "weight": 1}),
+            (3, 4, {"capacity": 2, "weight": 1}),
+        ]
+    )
+    G.nodes[1]["demand"] = -1
+    G.nodes[2]["demand"] = -1
+    G.nodes[4]["demand"] = 2
+
+    flowCost, H = nx.network_simplex(G)
+    soln = {1: {2: 0, 5: 1}, 2: {3: 0, 5: 1}, 3: {4: 2}, 4: {}, 5: {3: 2, 4: 0}}
+    assert flowCost == 6
+    assert nx.min_cost_flow_cost(G) == 6
+    assert H == soln
+
+
+def test_digon():
+    """Check if digons are handled properly. Taken from ticket
+    #618 by arv."""
+    nodes = [(1, {}), (2, {"demand": -4}), (3, {"demand": 4})]
+    edges = [
+        (1, 2, {"capacity": 3, "weight": 600000}),
+        (2, 1, {"capacity": 2, "weight": 0}),
+        (2, 3, {"capacity": 5, "weight": 714285}),
+        (3, 2, {"capacity": 2, "weight": 0}),
+    ]
+    G = nx.DiGraph(edges)
+    G.add_nodes_from(nodes)
+    flowCost, H = nx.network_simplex(G)
+    soln = {1: {2: 0}, 2: {1: 0, 3: 4}, 3: {2: 0}}
+    assert flowCost == 2857140
+
+
+def test_deadend():
+    """Check if one-node cycles are handled properly. Taken from ticket
+    #2906 from @sshraven."""
+    G = nx.DiGraph()
+
+    G.add_nodes_from(range(5), demand=0)
+    G.nodes[4]["demand"] = -13
+    G.nodes[3]["demand"] = 13
+
+    G.add_edges_from([(0, 2), (0, 3), (2, 1)], capacity=20, weight=0.1)
+    pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
+
+
+def test_infinite_capacity_neg_digon():
+    """An infinite capacity negative cost digon results in an unbounded
+    instance."""
+    nodes = [(1, {}), (2, {"demand": -4}), (3, {"demand": 4})]
+    edges = [
+        (1, 2, {"weight": -600}),
+        (2, 1, {"weight": 0}),
+        (2, 3, {"capacity": 5, "weight": 714285}),
+        (3, 2, {"capacity": 2, "weight": 0}),
+    ]
+    G = nx.DiGraph(edges)
+    G.add_nodes_from(nodes)
+    pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G)
+
+
+def test_multidigraph():
+    """Multidigraphs are acceptable."""
+    G = nx.MultiDiGraph()
+    G.add_weighted_edges_from([(1, 2, 1), (2, 3, 2)], weight="capacity")
+    flowCost, H = nx.network_simplex(G)
+    assert flowCost == 0
+    assert H == {1: {2: {0: 0}}, 2: {3: {0: 0}}, 3: {}}
+
+
+def test_negative_selfloops():
+    """Negative selfloops should cause an exception if uncapacitated and
+    always be saturated otherwise.
+    """
+    G = nx.DiGraph()
+    G.add_edge(1, 1, weight=-1)
+    pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G)
+
+    G[1][1]["capacity"] = 2
+    flowCost, H = nx.network_simplex(G)
+    assert flowCost == -2
+    assert H == {1: {1: 2}}
+
+    G = nx.MultiDiGraph()
+    G.add_edge(1, 1, "x", weight=-1)
+    G.add_edge(1, 1, "y", weight=1)
+    pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G)
+
+    G[1][1]["x"]["capacity"] = 2
+    flowCost, H = nx.network_simplex(G)
+    assert flowCost == -2
+    assert H == {1: {1: {"x": 2, "y": 0}}}
+
+
+def test_bone_shaped():
+    # From #1283
+    G = nx.DiGraph()
+    G.add_node(0, demand=-4)
+    G.add_node(1, demand=2)
+    G.add_node(2, demand=2)
+    G.add_node(3, demand=4)
+    G.add_node(4, demand=-2)
+    G.add_node(5, demand=-2)
+    G.add_edge(0, 1, capacity=4)
+    G.add_edge(0, 2, capacity=4)
+    G.add_edge(4, 3, capacity=4)
+    G.add_edge(5, 3, capacity=4)
+    G.add_edge(0, 3, capacity=0)
+    flowCost, H = nx.network_simplex(G)
+    assert flowCost == 0
+    assert H == {0: {1: 2, 2: 2, 3: 0}, 1: {}, 2: {}, 3: {}, 4: {3: 2}, 5: {3: 2}}
+
+
+def test_graphs_type_exceptions():
+    G = nx.Graph()
+    pytest.raises(nx.NetworkXNotImplemented, nx.network_simplex, G)
+    G = nx.MultiGraph()
+    pytest.raises(nx.NetworkXNotImplemented, nx.network_simplex, G)
+    G = nx.DiGraph()
+    pytest.raises(nx.NetworkXError, nx.network_simplex, G)