docs: def nx_transitive_reduction(G, mode=1):
      """
      References:
          https://en.wikipedia.org/wiki/Transitive_reduction#Computing_the_reduction_using_the_closure
          http://dept-info.labri.fr/~thibault/tmp/0201008.pdf
          http://stackoverflow.com/questions/17078696/transitive-reduction-of-directed-graph-in-python
  
      CommandLine:
          python -m utool.util_graph nx_transitive_reduction --show
  
      Example:
          >>> # DISABLE_DOCTEST
          >>> from utool.util_graph import *  # NOQA
          >>> import utool as ut
          >>> G = nx.DiGraph([('a', 'b'), ('a', 'c'), ('a', 'e'),
          >>>                 ('a', 'd'), ('b', 'd'), ('c', 'e'),
          >>>                 ('d', 'e'), ('c', 'e'), ('c', 'd')])
          >>> G = testdata_graph()[1]
          >>> G_tr = nx_transitive_reduction(G, mode=1)
          >>> G_tr2 = nx_transitive_reduction(G, mode=1)
          >>> ut.quit_if_noshow()
          >>> import plottool as pt
          >>> G_ = nx.dag.transitive_closure(G)
          >>> pt.show_nx(G    , pnum=(1, 5, 1), fnum=1)
          >>> pt.show_nx(G_tr , pnum=(1, 5, 2), fnum=1)
          >>> pt.show_nx(G_tr2 , pnum=(1, 5, 3), fnum=1)
          >>> pt.show_nx(G_   , pnum=(1, 5, 4), fnum=1)
          >>> pt.show_nx(nx.dag.transitive_closure(G_tr), pnum=(1, 5, 5), fnum=1)
          >>> ut.show_if_requested()
      """
  
      import utool as ut
      has_cycles = not nx.is_directed_acyclic_graph(G)
      if has_cycles:
          # FIXME: this does not work for cycle graphs.
          # Need to do algorithm on SCCs
          G_orig = G
          G = nx.condensation(G_orig)
  
      nodes = list(G.nodes())
      node2_idx = ut.make_index_lookup(nodes)
  
      # For each node u, perform DFS consider its set of (non-self) children C.
      # For each descendant v, of a node in C, remove any edge from u to v.
  
      if mode == 1:
          G_tr = G.copy()
  
          for parent in G_tr.nodes():
              # Remove self loops
              if G_tr.has_edge(parent, parent):
                  G_tr.remove_edge(parent, parent)
              # For each child of the parent
              for child in list(G_tr.successors(parent)):
                  # Preorder nodes includes its argument (no added complexity)
                  for gchild in list(G_tr.successors(child)):
                      # Remove all edges from parent to non-child descendants
                      for descendant in nx.dfs_preorder_nodes(G_tr, gchild):
                          if G_tr.has_edge(parent, descendant):
                              G_tr.remove_edge(parent, descendant)
  
          if has_cycles:
              # Uncondense graph
              uncondensed_G_tr = G.__class__()
              mapping = G.graph['mapping']
              uncondensed_G_tr.add_nodes_from(mapping.keys())
              inv_mapping = ut.invert_dict(mapping, unique_vals=False)
              for u, v in G_tr.edges():
                  u_ = inv_mapping[u][0]
                  v_ = inv_mapping[v][0]
                  uncondensed_G_tr.add_edge(u_, v_)
  
              for key, path in inv_mapping.items():
                  if len(path) > 1:
                      directed_cycle = list(ut.itertwo(path, wrap=True))
                      uncondensed_G_tr.add_edges_from(directed_cycle)
              G_tr = uncondensed_G_tr
  
      else:
  
          def make_adj_matrix(G):
              edges = list(G.edges())
              edge2_idx = ut.partial(ut.dict_take, node2_idx)
              uv_list = ut.lmap(edge2_idx, edges)
              A = np.zeros((len(nodes), len(nodes)))
              A[tuple(np.array(uv_list).T)] = 1
              return A
  
          G_ = nx.dag.transitive_closure(G)
  
          A = make_adj_matrix(G)
          B = make_adj_matrix(G_)
  
          #AB = A * B
          #AB = A.T.dot(B)
          AB = A.dot(B)
          #AB = A.dot(B.T)
  
          A_and_notAB = np.logical_and(A, np.logical_not(AB))
          tr_uvs = np.where(A_and_notAB)
  
          #nodes = G.nodes()
          edges = list(zip(*ut.unflat_take(nodes, tr_uvs)))
  
          G_tr = G.__class__()
          G_tr.add_nodes_from(nodes)
          G_tr.add_edges_from(edges)
  
          if has_cycles:
              # Uncondense graph
              uncondensed_G_tr = G.__class__()
              mapping = G.graph['mapping']
              uncondensed_G_tr.add_nodes_from(mapping.keys())
              inv_mapping = ut.invert_dict(mapping, unique_vals=False)
              for u, v in G_tr.edges():
                  u_ = inv_mapping[u][0]
                  v_ = inv_mapping[v][0]
                  uncondensed_G_tr.add_edge(u_, v_)
  
              for key, path in inv_mapping.items():
                  if len(path) > 1:
                      directed_cycle = list(ut.itertwo(path, wrap=True))
                      uncondensed_G_tr.add_edges_from(directed_cycle)
              G_tr = uncondensed_G_tr
      return G_tr