Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lex_BFS method of DiGraph is not well-defined (the method could compute ordering that fails doctests) #38234

Open
2 tasks done
cyrilbouvier opened this issue Jun 17, 2024 · 5 comments · May be fixed by #38269
Open
2 tasks done
Labels

Comments

@cyrilbouvier
Copy link
Contributor

Steps To Reproduce

Load the content of the function _is_valid_lex_BFS_order from graphs/traversals.pyx, then run

sage: _is_valid_lex_BFS_order(DiGraph([(0,1), (0,2), (1, 2)]), [1,0,2])
sage: _is_valid_lex_BFS_order(Graph([(0,1), (0,2), (1, 2)]), [1,0,2])

Expected Behavior

True
True

Actual Behavior

False
True

Additional Information

As the code is written, it is not clear what is the semantic of a lexBFS ordering for directed graphs (DiGraph).

In the lex_BFS method in graphs/traversals.pyx, the graph is first converted to a simple undirected graph, via the hidden default parameter to_undirected=True of the call to the to_simple method of those lines

if G.has_loops() or G.has_multiple_edges():
  G = G.to_simple(immutable=False)

It seems to indicate that directed graphs are considered as undirected for lexBFS.
The doctest of the function strengthens this impression

r"""
    Lex BFS ordering of a symmetric digraph should be the same as the Lex BFS
    ordering of the corresponding undirected graph::
"""

But, in the function _is_valid_lex_BFS_order that is used in doctests, directed graphs are considered directed, as shown by the use of the methods G.neighbor_in_iterator and G.has_edge that are direction-dependent.

So it is possible to build graphs where the lexBFS ordering computed by lex_BFS is not considered valid.

What should be the fix ?
My first impulse would be to do the two following changes in _is_valid_lex_BFS_order:

  • use G.neighbor_iterator for Graph and DiGraph
  • use G.has_edge(u,v) or G.has_edge(v,u)

Is it the correct approch ?

I am very interested by the response as I am currently writing an extended lexBFS algorithm, and I would like to be able to rely on this function to test the output of the new implementation.

Environment

- **OS**: Debian 11
- **Sage Version**: 10.3

Checklist

  • I have searched the existing issues for a bug report that matches the one I want to file, without success.
  • I have read the documentation and troubleshoot guide
@dcoudert
Copy link
Contributor

Thanks for reporting this issue.
Indeed, lex_BFS returns an ordering on the underlying undirected graph (this should be clearly documented), but _is_valid_lex_BFS_order considers something else.
You can do the modifications you proposed in _is_valid_lex_BFS_order as this is an internal method used only to check the output of lex_BFS. As long are we are consistent, this is ok.

@cyrilbouvier
Copy link
Contributor Author

Thanks for reporting this issue. Indeed, lex_BFS returns an ordering on the underlying undirected graph (this should be clearly documented), but _is_valid_lex_BFS_order considers something else. You can do the modifications you proposed in _is_valid_lex_BFS_order as this is an internal method used only to check the output of lex_BFS. As long are we are consistent, this is ok.

I writing the PR but I may have found errors in the doctests (I am still new to graph theory so it may be a misunderstanding from my part).
In the doctests of _is_valid_lex_BFS_order, there is the following check:

         sage: G = DiGraph("I?O@??A?CCA?A??C??")
         sage: _is_valid_lex_BFS_order(G, [0, 7, 1, 2, 3, 4, 5, 8, 6, 9])
         True

But from what I understand of lex_BFS ordering [0, 7, 1, 2, 3, 4, 5, 8, 6, 9] is not a valid lex_BFS order for the undirected graph corresponding to G: 2 is an isolated vertex but 8 and 5 are neighbors of 7 and 1, so 2 must appear after them in the lex_BFS order.
Can anyone confirm this ?

The problem is that with Sage 10.4.beta9, the output of G.lex_BFS() (which is [0, 7, 2, 3, 4, 5, 1, 8, 6, 9]) is, with the same reasoning as above, not a valid lex_BFS order.
So the following statements from the docs may be wrong

r"""
    Lex BFS ordering of a symmetric digraph should be the same as the Lex BFS
    ordering of the corresponding undirected graph::
"""

@cyrilbouvier
Copy link
Contributor Author

I think I found a problem in the lex_BFS code for Digraph. Consider the following example:

sage: G = DiGraph("I?O@??A?CCA?A??C??")
sage: G.lex_BFS()
[0, 7, 2, 3, 4, 5, 1, 8, 6, 9] # not valid
sage: G.allow_loops(True)
sage: G.add_edge(0,0)
sage: G.lex_BFS()
[0, 7, 1, 8, 5, 4, 3, 2, 6, 9] # valid

The problem comes from the following lines

    # Loops and multiple edges are not needed in Lex BFS
    if G.has_loops() or G.has_multiple_edges():
        G = G.to_simple(immutable=False)

The graph is converted to undirected graph only if it has loops or multiedges. Otherwise the conversion never happens and the algorithm is applied to a directed graph (so only out_neighbors are considered for DiGraph).

If someone confirm my reasoning, I will include the fix in my PR and update the wrong doctests.

@dcoudert
Copy link
Contributor

You are right, this is a bug when considering digraphs. A simple fix is to use:

    # Loops and multiple edges are not needed in Lex BFS
    if G.is_directed() or G.has_loops() or G.has_multiple_edges():
        G = G.to_simple(to_undirected=True, immutable=False)

The alternative is to avoid the conversion to short_digraph, but I don't know if the resulting code will be as fast...

The same fix should certainly be done in all lex_* methods.

Note also that parameter immutable could be True as G is not modified.

@cyrilbouvier cyrilbouvier linked a pull request Jun 24, 2024 that will close this issue
5 tasks
@cyrilbouvier
Copy link
Contributor Author

I just open PR #38269

The alternative is to avoid the conversion to short_digraph, but I don't know if the resulting code will be as fast...

I think it will be faster to avoid the conversion to short_digraph (there would be one enumeration of the edges instead of two). But I kept this change for another PR.

vbraun pushed a commit to vbraun/sage that referenced this issue Jul 20, 2024
    
<!-- ^ Please provide a concise and informative title. -->
<!-- ^ Don't put issue numbers in the title, do this in the PR
description below. -->
<!-- ^ For example, instead of "Fixes sagemath#12345" use "Introduce new method
to calculate 1 + 2". -->
<!-- v Describe your changes below in detail. -->
<!-- v Why is this change required? What problem does it solve? -->
<!-- v If this PR resolves an open issue, please link to it here. For
example, "Fixes sagemath#12345". -->

For directed graphs, the semantic of `lex_BFS`, `lex_DFS`, `lex_DOWN`
and `lex_UP` methods are not consistent. In some parts of the code,
directed graphs were converted into undirected graphs before the actual
computation and in some other parts (in particular in the check function
`_is_valid_lex_BFS_order`) directed graphs were explicitly considered as
directed.

In this PR, the following changes are implemented:
- always consider (and convert) directed graphs into undirected graphs
for `lex_*` methods
- do the same conversion in `_is_valid_lex_BFS_order` for consistency
- add the following line in the documentation of the `lex_*` methods:
```py
r"""
   Loops and multiple edges are ignored during the computation of <name
of the method> and
   directed graphs are converted to undirected graphs.
"""
```
- change some doctests: with the previous changes, methods can return a
different (but still valid) order
- set the copy of the graph to immutable

Fixes sagemath#38234

### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. -->

- [x] The title is concise and informative.
- [x] The description explains in detail what this PR is about.
- [x] I have linked a relevant issue or discussion.
- [x] I have created tests covering the changes.
- [x] I have updated the documentation and checked the documentation
preview.

### ⌛ Dependencies

<!-- List all open PRs that this PR logically depends on. For example,
-->
<!-- - sagemath#12345: short description why this is a dependency -->
<!-- - sagemath#34567: ... -->
    
URL: sagemath#38269
Reported by: cyrilbouvier
Reviewer(s): cyrilbouvier, David Coudert
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants