Repository: networkx/networkx

Issue:
`strategy_saturation_largest_first` endless generator
<!-- 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

`strategy_saturation_largest_first` generator does not terminate. See the stopping condition here:
https://github.com/networkx/networkx/blob/9cc8b422a512e7e7819238d597fd6815ec9c1c8e/networkx/algorithms/coloring/greedy_coloring.py#L243

but neither `G` nor `colors` is updated within the loop! The result is the same `node` is yielded repeatedly.

### Expected Behavior

I would expect the generator to terminate. I don't grok this function, but endless generator doesn't seem right.

### Steps to Reproduce

```python
import networkx as nx

G = nx.empty_graph()
G.add_node(1)
list(nx.algorithms.coloring.greedy_coloring.strategy_saturation_largest_first(G, {}))
```

### Environment

Python version: 3.10
NetworkX version: dev

### Additional context

I (of course) encountered this while tinkering with dispatching machinery!


Discussion:
The design seems to be that changes to the `colors` dict [occur between the yield](https://github.com/networkx/networkx/blob/9cc8b422a512e7e7819238d597fd6815ec9c1c8e/networkx/algorithms/coloring/greedy_coloring.py#L381) and the `next` call to the generator. The generator does not have control over that logic. But the calling function does.

Note also that it is "ok" to have a generator that never terminates (e.g. `all_prime_numbers()`).  Is there a better way to handle this coding situation? Or maybe I should also ask -- what problem does this cause for dispatching?
> Or maybe I should also ask -- what problem does this cause for dispatching?

I need to explicitly exclude the function when creating the "dependencies between dispatched networkx functions" graph here
https://gist.github.com/eriknw/6017eec12e3dc61fbfba191144b0ae6b#file-get_func_deps-diff
otherwise tests never finish.

So, yeah, I'm doing something pretty hacky, so if this behavior is expected, then feel free to close. It would seem that consuming the iterator like I do in the patch above has unintended (and surprising to me) side-effects. These side-effects don't result in "stalled code" in normal use.

For example, this script stalls when the above patch is applied, but runs fine without the patch:
```python
import networkx as nx

G = nx.Graph()
G.add_node(1)
nx.greedy_color(G, strategy='saturation_largest_first', interchange=False)
```
Thanks for taking a look and educating me!
I think this points out how interrelated this function is to the calling function. It might make sense to make these strategy functions private. But they have been public for quite a long time.

Could we create a new category of functions -- those that are public but for which the dispatching is not appropriate?

Also, why don't the other strategy functions give this same kind of trouble? It looks like none of them are "infinite generators".  But we should support infinite generators (IMO)... I think.....???
> Could we create a new category of functions -- those that are public but for which the dispatching is not appropriate?

Sure... should we simply remove `@nx._dispatch` from these functions?
A quick ping on this one - IMO the best course of action is to *not* dispatch the `strategy_` functions. AFAICT, those are callables that are intended only for explicit use with `greedy_coloring`.

Resolving patch:
diff --git a/networkx/algorithms/coloring/greedy_coloring.py b/networkx/algorithms/coloring/greedy_coloring.py
--- a/networkx/algorithms/coloring/greedy_coloring.py
+++ b/networkx/algorithms/coloring/greedy_coloring.py
@@ -20,7 +20,6 @@
 ]
 
 
-@nx._dispatchable
 def strategy_largest_first(G, colors):
     """Returns a list of the nodes of ``G`` in decreasing order by
     degree.
@@ -32,7 +31,6 @@ def strategy_largest_first(G, colors):
 
 
 @py_random_state(2)
-@nx._dispatchable
 def strategy_random_sequential(G, colors, seed=None):
     """Returns a random permutation of the nodes of ``G`` as a list.
 
@@ -47,7 +45,6 @@ def strategy_random_sequential(G, colors, seed=None):
     return nodes
 
 
-@nx._dispatchable
 def strategy_smallest_last(G, colors):
     """Returns a deque of the nodes of ``G``, "smallest" last.
 
@@ -121,7 +118,6 @@ def _maximal_independent_set(G):
     return result
 
 
-@nx._dispatchable
 def strategy_independent_set(G, colors):
     """Uses a greedy independent set removal strategy to determine the
     colors.
@@ -146,7 +142,6 @@ def strategy_independent_set(G, colors):
         yield from nodes
 
 
-@nx._dispatchable
 def strategy_connected_sequential_bfs(G, colors):
     """Returns an iterable over nodes in ``G`` in the order given by a
     breadth-first traversal.
@@ -160,7 +155,6 @@ def strategy_connected_sequential_bfs(G, colors):
     return strategy_connected_sequential(G, colors, "bfs")
 
 
-@nx._dispatchable
 def strategy_connected_sequential_dfs(G, colors):
     """Returns an iterable over nodes in ``G`` in the order given by a
     depth-first traversal.
@@ -174,7 +168,6 @@ def strategy_connected_sequential_dfs(G, colors):
     return strategy_connected_sequential(G, colors, "dfs")
 
 
-@nx._dispatchable
 def strategy_connected_sequential(G, colors, traversal="bfs"):
     """Returns an iterable over nodes in ``G`` in the order given by a
     breadth-first or depth-first traversal.
@@ -207,7 +200,6 @@ def strategy_connected_sequential(G, colors, traversal="bfs"):
             yield end
 
 
-@nx._dispatchable
 def strategy_saturation_largest_first(G, colors):
     """Iterates over all the nodes of ``G`` in "saturation order" (also
     known as "DSATUR").


Test patch: