Repository: networkx/networkx

Issue:
is_chordal crashes on empty graphs
<!-- 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

<!--- Tell us what happens instead of the expected behavior -->

Running `nx.is_chordal` on an empty graph raises `StopIteration`.

### Expected Behavior

<!--- Tell us what should happen -->

The function should simply return `True`, since the definition simply requires that all cycles of length >= 4 contain a chord. Paths or trees are chordal since they have no cycles, and the function does not crash on these, nor on edgeless graphs.

### Steps to Reproduce

<!--- Provide a minimal example that reproduces the bug -->
Just create an empty graph and run the function:

```python
>>> import networkx as nx
>>> graph = nx.Graph()
>>> nx.is_chordal(graph)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3/dist-packages/networkx/algorithms/chordal.py", line 89, in is_chordal
    if len(_find_chordality_breaker(G)) == 0:
  File "/usr/lib/python3/dist-packages/networkx/algorithms/chordal.py", line 320, in _find_chordality_breaker
    s = arbitrary_element(G)
  File "/usr/lib/python3/dist-packages/networkx/utils/misc.py", line 233, in arbitrary_element
    return next(iter(iterable))
StopIteration
```
### Environment

<!--- Please provide details about your local environment -->

Python version: 3.9.2
NetworkX version: 2.5

### Additional context

<!--- Add any other context about the problem here, screenshots, etc. -->



Discussion:
When `is_chordal` is called, this statement is executed - 

```python
return len(_find_chordality_breaker(G)) == 0
```
Since no source `s` has been specified in `_find_chordality_breaker`, the following statements execute which further raises `StopIteration` because there are no nodes in G.

```python
if s is None:
        s = arbitrary_element(G)
```

I believe a specific condition needs to be added to `is_chordal` that returns True when `len(G.nodes) == 0`,  in fact this condition can be extended to `len(G.nodes) <= 3` because such a graph will always be chordal.
Yes I think `len(G.nodes) <= 3` will be the correct condition to implement here as a graph with only one or two vertices is trivially chordal because it contains no cycle. A graph with three vertices can have at most three edges, and every cycle of length four or more would require at least four edges, so no such cycle can exist in a graph with three vertices.
@alabarre @navyagarwal Are you guys working on this issue, or can I take it up?
@PurviChaurasia your contributions indicate that you're more familiar with the code base than I am, so I'll gladly let you make the change. The suggested addition seems to be the only required change indeed.

Thanks!
Thanks a lot :)
I'll add bit more context for the change in the docstring as well and add relevant tests.
> I'll add bit more context for the change in the docstring as well and add relevant tests.

@PurviChaurasia Hey, I'm already working on it, was just adding test cases when I saw the comments 😅
There is actually a wrong test case in `test_chordal.py` in line 63. It is mentioned:
`assert nx.is_chordal(nx.complete_graph(3))`
I think we'll have to comment that out as well.
@navyagarwal if it's fine by you could we be co-authors on this PR? I was actually just about to submit mine when I saw your message :)
> There is actually a wrong test case in `test_chordal.py` in line 63. It is mentioned: `assert nx.is_chordal(nx.complete_graph(3))` I think we'll have to comment that out as well.

No, but why? The test case looks alright to me
Yep meant that only!
> > There is actually a wrong test case in `test_chordal.py` in line 63. It is mentioned: `assert nx.is_chordal(nx.complete_graph(3))` I think we'll have to comment that out as well.
> 
> No, but why? The test case looks alright to me

did you try running the test case after making the changes?
Wait you're right, there was issue from my end.

Resolving patch:
diff --git a/networkx/algorithms/chordal.py b/networkx/algorithms/chordal.py
--- a/networkx/algorithms/chordal.py
+++ b/networkx/algorithms/chordal.py
@@ -73,6 +73,8 @@ def is_chordal(G):
     search. It returns False when it finds that the separator for any node
     is not a clique.  Based on the algorithms in [1]_.
 
+    Self loops are ignored.
+
     References
     ----------
     .. [1] R. E. Tarjan and M. Yannakakis, Simple linear-time algorithms
@@ -80,6 +82,8 @@ def is_chordal(G):
        selectively reduce acyclic hypergraphs, SIAM J. Comput., 13 (1984),
        pp. 566–579.
     """
+    if len(G.nodes) <= 3:
+        return True
     return len(_find_chordality_breaker(G)) == 0
 
 
@@ -130,6 +134,8 @@ def find_induced_nodes(G, s, t, treewidth_bound=sys.maxsize):
     The algorithm is inspired by Algorithm 4 in [1]_.
     A formal definition of induced node can also be found on that reference.
 
+    Self Loops are ignored
+
     References
     ----------
     .. [1] Learning Bounded Treewidth Bayesian Networks.
@@ -326,9 +332,9 @@ def _find_chordality_breaker(G, s=None, treewidth_bound=sys.maxsize):
 
     If it does find one, it returns (u,v,w) where u,v,w are the three
     nodes that together with s are involved in the cycle.
+
+    It ignores any self loops.
     """
-    if nx.number_of_selfloops(G) > 0:
-        raise nx.NetworkXError("Input graph is not chordal.")
     unnumbered = set(G)
     if s is None:
         s = arbitrary_element(G)


Test patch:
diff --git a/networkx/algorithms/tests/test_chordal.py b/networkx/algorithms/tests/test_chordal.py
--- a/networkx/algorithms/tests/test_chordal.py
+++ b/networkx/algorithms/tests/test_chordal.py
@@ -60,11 +60,11 @@ def test_is_chordal(self):
         assert not nx.is_chordal(self.non_chordal_G)
         assert nx.is_chordal(self.chordal_G)
         assert nx.is_chordal(self.connected_chordal_G)
+        assert nx.is_chordal(nx.Graph())
         assert nx.is_chordal(nx.complete_graph(3))
         assert nx.is_chordal(nx.cycle_graph(3))
         assert not nx.is_chordal(nx.cycle_graph(5))
-        with pytest.raises(nx.NetworkXError, match="Input graph is not chordal"):
-            nx.is_chordal(self.self_loop_G)
+        assert nx.is_chordal(self.self_loop_G)
 
     def test_induced_nodes(self):
         G = nx.generators.classic.path_graph(10)