Repository: networkx/networkx

Issue:
Docstring of `johnson` algorithm handling of weight incorrect
<!-- 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 -->

### Current Behavior

The documentation says:
```
If no such edge attribute exists, the weight of the edge is assumed to be one.
```
### Expected Behavior

The behavior to match the docstring or the docstring to match the behavior.

### Steps to Reproduce

```python
In [1]: import networkx as nx

In [2]: G = nx.Graph()

In [3]: G.add_edge(0, 1, weight=2)

In [4]: G.add_edge(0, 2)

In [5]: nx.johnson(G)
---------------------------------------------------------------------------
NetworkXError                             Traceback (most recent call last)
Cell In[5], line 1
----> 1 nx.johnson(G)

File ~/miniconda3/envs/gb8/lib/python3.11/site-packages/networkx/algorithms/shortest_paths/weighted.py:2469, in johnson(G, weight)
   2402 r"""Uses Johnson's Algorithm to compute shortest paths.
   2403
   2404 Johnson's Algorithm finds a shortest path between each pair of
   (...)
   2466
   2467 """
   2468 if not nx.is_weighted(G, weight=weight):
-> 2469     raise nx.NetworkXError("Graph is not weighted.")
   2471 dist = {v: 0 for v in G}
   2472 pred = {v: [] for v in G}

NetworkXError: Graph is not weighted.
```

### Environment

<!--- Please provide details about your local environment -->

Python version: 3.11
NetworkX version: 3.1

### Additional context

It uses `nx.is_weighted`, which requires that _all_ edges have the attribute to be considered weighted.

Discussion:

Patch:
diff --git a/networkx/algorithms/operators/all.py b/networkx/algorithms/operators/all.py
index 2dd46434b..73b02f60a 100644
--- a/networkx/algorithms/operators/all.py
+++ b/networkx/algorithms/operators/all.py
@@ -32,8 +32,16 @@ def union_all(graphs, rename=()):
     ValueError
        If `graphs` is an empty list.
 
+    NetworkXError
+        In case of mixed type graphs, like MultiGraph and Graph, or directed and undirected graphs.
+
     Notes
     -----
+    For operating on mixed type graphs, they should be converted to the same type.
+    >>> G = nx.Graph()
+    >>> H = nx.DiGraph()
+    >>> GH = union_all([nx.DiGraph(G), H])
+
     To force a disjoint union with node relabeling, use
     disjoint_union_all(G,H) or convert_node_labels_to integers().
 
@@ -67,6 +75,8 @@ def union_all(graphs, rename=()):
         if i == 0:
             # Union is the same type as first graph
             R = G.__class__()
+        elif G.is_directed() != R.is_directed():
+            raise nx.NetworkXError("All graphs must be directed or undirected.")
         elif G.is_multigraph() != R.is_multigraph():
             raise nx.NetworkXError("All graphs must be graphs or multigraphs.")
         elif not seen_nodes.isdisjoint(G_nodes_set):
@@ -110,9 +120,12 @@ def disjoint_union_all(graphs):
     ValueError
        If `graphs` is an empty list.
 
+    NetworkXError
+        In case of mixed type graphs, like MultiGraph and Graph, or directed and undirected graphs.
+
     Notes
     -----
-    It is recommended that the graphs be either all directed or all undirected.
+    For operating on mixed type graphs, they should be converted to the same type.
 
     Graph, edge, and node attributes are propagated to the union graph.
     If a graph attribute is present in multiple graphs, then the value
@@ -150,10 +163,12 @@ def compose_all(graphs):
     ValueError
        If `graphs` is an empty list.
 
+    NetworkXError
+        In case of mixed type graphs, like MultiGraph and Graph, or directed and undirected graphs.
+
     Notes
     -----
-    It is recommended that the supplied graphs be either all directed or all
-    undirected.
+    For operating on mixed type graphs, they should be converted to the same type.
 
     Graph, edge, and node attributes are propagated to the union graph.
     If a graph attribute is present in multiple graphs, then the value
@@ -166,6 +181,8 @@ def compose_all(graphs):
         if i == 0:
             # create new graph
             R = G.__class__()
+        elif G.is_directed() != R.is_directed():
+            raise nx.NetworkXError("All graphs must be directed or undirected.")
         elif G.is_multigraph() != R.is_multigraph():
             raise nx.NetworkXError("All graphs must be graphs or multigraphs.")
 
@@ -199,29 +216,56 @@ def intersection_all(graphs):
     ValueError
        If `graphs` is an empty list.
 
+    NetworkXError
+        In case of mixed type graphs, like MultiGraph and Graph, or directed and undirected graphs.
+
     Notes
     -----
+    For operating on mixed type graphs, they should be converted to the same type.
+
     Attributes from the graph, nodes, and edges are not copied to the new
     graph.
+
+    The resulting graph can be updated with attributes if desired. For example, code which adds the minimum attribute for each node across all graphs could work.
+    >>> g = nx.Graph()
+    >>> g.add_node(0, capacity=4)
+    >>> g.add_node(1, capacity=3)
+    >>> g.add_edge(0, 1)
+
+    >>> h = g.copy()
+    >>> h.nodes[0]["capacity"] = 2
+
+    >>> gh = nx.intersection_all([g, h])
+
+    >>> new_node_attr = {n: min(*(anyG.nodes[n].get('capacity', float('inf')) for anyG in [g, h])) for n in gh}
+    >>> nx.set_node_attributes(gh, new_node_attr, 'new_capacity')
+    >>> gh.nodes(data=True)
+    NodeDataView({0: {'new_capacity': 2}, 1: {'new_capacity': 3}})
+
     """
     R = None
 
     for i, G in enumerate(graphs):
         G_nodes_set = set(G.nodes)
-        G_edges_set = set(G.edges(keys=True) if G.is_multigraph() else G.edges())
+        G_edges_set = set(G.edges)
+        if not G.is_directed():
+            if type(G) is nx.MultiGraph:
+                G_edges_set.update((v, u, k) for u, v, k in list(G_edges_set))
+            else:
+                G_edges_set.update((v, u) for u, v in list(G_edges_set))
         if i == 0:
             # create new graph
             R = G.__class__()
             node_intersection = G_nodes_set
             edge_intersection = G_edges_set
+        elif G.is_directed() != R.is_directed():
+            raise nx.NetworkXError("All graphs must be directed or undirected.")
         elif G.is_multigraph() != R.is_multigraph():
             raise nx.NetworkXError("All graphs must be graphs or multigraphs.")
         else:
             node_intersection &= G_nodes_set
             edge_intersection &= G_edges_set
 
-        R.graph.update(G.graph)
-
     if R is None:
         raise ValueError("cannot apply intersection_all to an empty list")
 


Test patch:
diff --git a/networkx/algorithms/operators/tests/test_all.py b/networkx/algorithms/operators/tests/test_all.py
index 24548518c..8ec29c150 100644
--- a/networkx/algorithms/operators/tests/test_all.py
+++ b/networkx/algorithms/operators/tests/test_all.py
@@ -47,7 +47,7 @@ def test_intersection_all():
     I = nx.intersection_all([G, H, R])
     assert set(I.nodes()) == {1, 2, 3, 4}
     assert sorted(I.edges()) == [(2, 3)]
-    assert I.graph["awesome"]
+    assert I.graph == {}
 
 
 def test_intersection_all_different_node_sets():
@@ -130,11 +130,21 @@ def test_intersection_all_multigraph_attributes_different_node_sets():
     g.add_edge(1, 2, key=2)
     h = nx.MultiGraph()
     h.add_edge(0, 1, key=0)
+    h.add_edge(0, 1, key=2)
     h.add_edge(0, 1, key=3)
     gh = nx.intersection_all([g, h])
     assert set(gh.nodes()) == set(h.nodes())
-    assert sorted(gh.edges()) == [(0, 1)]
-    assert sorted(gh.edges(keys=True)) == [(0, 1, 0)]
+    assert sorted(gh.edges()) == [(0, 1), (0, 1)]
+    assert sorted(gh.edges(keys=True)) == [(0, 1, 0), (0, 1, 2)]
+
+
+def test_intersection_all_digraph():
+    g = nx.DiGraph()
+    g.add_edges_from([(1, 2), (2, 3)])
+    h = nx.DiGraph()
+    h.add_edges_from([(2, 1), (2, 3)])
+    gh = nx.intersection_all([g, h])
+    assert sorted(gh.edges()) == [(2, 3)]
 
 
 def test_union_all_and_compose_all():
@@ -256,6 +266,10 @@ def test_mixed_type_union():
         H = nx.MultiGraph()
         I = nx.Graph()
         U = nx.union_all([G, H, I])
+    with pytest.raises(nx.NetworkXError):
+        X = nx.Graph()
+        Y = nx.DiGraph()
+        XY = nx.union_all([X, Y])
 
 
 def test_mixed_type_disjoint_union():
@@ -264,6 +278,10 @@ def test_mixed_type_disjoint_union():
         H = nx.MultiGraph()
         I = nx.Graph()
         U = nx.disjoint_union_all([G, H, I])
+    with pytest.raises(nx.NetworkXError):
+        X = nx.Graph()
+        Y = nx.DiGraph()
+        XY = nx.disjoint_union_all([X, Y])
 
 
 def test_mixed_type_intersection():
@@ -272,6 +290,10 @@ def test_mixed_type_intersection():
         H = nx.MultiGraph()
         I = nx.Graph()
         U = nx.intersection_all([G, H, I])
+    with pytest.raises(nx.NetworkXError):
+        X = nx.Graph()
+        Y = nx.DiGraph()
+        XY = nx.intersection_all([X, Y])
 
 
 def test_mixed_type_compose():
@@ -280,6 +302,10 @@ def test_mixed_type_compose():
         H = nx.MultiGraph()
         I = nx.Graph()
         U = nx.compose_all([G, H, I])
+    with pytest.raises(nx.NetworkXError):
+        X = nx.Graph()
+        Y = nx.DiGraph()
+        XY = nx.compose_all([X, Y])
 
 
 def test_empty_union():
diff --git a/networkx/algorithms/shortest_paths/tests/test_weighted.py b/networkx/algorithms/shortest_paths/tests/test_weighted.py
index d1bfea2ac..5766edf48 100644
--- a/networkx/algorithms/shortest_paths/tests/test_weighted.py
+++ b/networkx/algorithms/shortest_paths/tests/test_weighted.py
@@ -865,10 +865,9 @@ class TestBellmanFordAndGoldbergRadzik(WeightedTestBase):
 
 class TestJohnsonAlgorithm(WeightedTestBase):
     def test_single_node_graph(self):
-        with pytest.raises(nx.NetworkXError):
-            G = nx.DiGraph()
-            G.add_node(0)
-            nx.johnson(G)
+        G = nx.DiGraph()
+        G.add_node(0)
+        assert nx.johnson(G) == {0: {0: [0]}}
 
     def test_negative_cycle(self):
         G = nx.DiGraph()
@@ -915,9 +914,27 @@ class TestJohnsonAlgorithm(WeightedTestBase):
         }
 
     def test_unweighted_graph(self):
-        with pytest.raises(nx.NetworkXError):
-            G = nx.path_graph(5)
-            nx.johnson(G)
+        G = nx.Graph()
+        G.add_edges_from([(1, 0), (2, 1)])
+        H = G.copy()
+        nx.set_edge_attributes(H, values=1, name="weight")
+        assert nx.johnson(G) == nx.johnson(H)
+
+    def test_partially_weighted_graph_with_negative_edges(self):
+        G = nx.DiGraph()
+        G.add_edges_from([(0, 1), (1, 2), (2, 0), (1, 0)])
+        G[1][0]["weight"] = -2
+        G[0][1]["weight"] = 3
+        G[1][2]["weight"] = -4
+
+        H = G.copy()
+        H[2][0]["weight"] = 1
+
+        I = G.copy()
+        I[2][0]["weight"] = 8
+
+        assert nx.johnson(G) == nx.johnson(H)
+        assert nx.johnson(G) != nx.johnson(I)
 
     def test_graphs(self):
         validate_path(self.XG, "s", "v", 9, nx.johnson(self.XG)["s"]["v"])
diff --git a/networkx/algorithms/shortest_paths/weighted.py b/networkx/algorithms/shortest_paths/weighted.py
index 9d87f8e5c..a7405873e 100644
--- a/networkx/algorithms/shortest_paths/weighted.py
+++ b/networkx/algorithms/shortest_paths/weighted.py
@@ -2426,11 +2426,6 @@ def johnson(G, weight="weight"):
     distance : dictionary
         Dictionary, keyed by source and target, of shortest paths.
 
-    Raises
-    ------
-    NetworkXError
-        If given graph is not weighted.
-
     Examples
     --------
     >>> graph = nx.DiGraph()
@@ -2465,9 +2460,6 @@ def johnson(G, weight="weight"):
     all_pairs_bellman_ford_path_length
 
     """
-    if not nx.is_weighted(G, weight=weight):
-        raise nx.NetworkXError("Graph is not weighted.")
-
     dist = {v: 0 for v in G}
     pred = {v: [] for v in G}
     weight = _weight_function(G, weight)