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

fix is_cyclic #168

Merged
merged 18 commits into from
Sep 20, 2022
Merged

fix is_cyclic #168

merged 18 commits into from
Sep 20, 2022

Conversation

aurorarossi
Copy link
Member

issue #34, now is_cyclic works for undirected connected and disconnected graphs.

@codecov
Copy link

codecov bot commented Aug 26, 2022

Codecov Report

Merging #168 (260632c) into master (a3fb98b) will decrease coverage by 0.06%.
The diff coverage is 100.00%.

@@            Coverage Diff             @@
##           master     #168      +/-   ##
==========================================
- Coverage   97.54%   97.47%   -0.07%     
==========================================
  Files         109      109              
  Lines        6314     6349      +35     
==========================================
+ Hits         6159     6189      +30     
- Misses        155      160       +5     

@aurorarossi aurorarossi marked this pull request as draft August 26, 2022 11:27
@aurorarossi aurorarossi marked this pull request as ready for review August 26, 2022 11:33
@etiennedeg etiennedeg mentioned this pull request Aug 26, 2022
Copy link
Member

@gdalle gdalle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your contribution! This is a good first step, although I have questions about the performance. Can we do better on that point? The rest is mostly details

@traitfn function is_cyclic(g::::(!IsDirected))
b=false
for vcomponent in connected_components(g)
sg, _ = induced_subgraph(g,vcomponent)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to do this without calling induced_subgraph? Judging by the implementation this function might lead to significant overhead, so I'm wondering if a more efficient option exists

test/traversals/dfs.jl Outdated Show resolved Hide resolved
test/traversals/dfs.jl Outdated Show resolved Hide resolved
src/traversals/dfs.jl Outdated Show resolved Hide resolved
@etiennedeg
Copy link
Member

The most performant would be to use the same BFS than for the directed case, but without taking the edge from where we come from. This allows the algorithm to break early if it meets a cycle. By calling connected_components, we do a full BFS everytime...

@aurorarossi aurorarossi marked this pull request as draft September 2, 2022 11:00
@aurorarossi aurorarossi marked this pull request as ready for review September 7, 2022 18:20
Copy link
Contributor

@Liozou Liozou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi and thank for your contribution! Here are some comments, mostly about code style, except for the @inferred note in the test which is a faulty test.
I'm not a maintainer of Graphs.jl so feel free to ignore the rest if you disagree, although I do believe it makes the code cleaner.

src/traversals/dfs.jl Outdated Show resolved Hide resolved
src/traversals/dfs.jl Outdated Show resolved Hide resolved
test/traversals/dfs.jl Outdated Show resolved Hide resolved
test/traversals/dfs.jl Outdated Show resolved Hide resolved
"""
function is_cyclic end
@traitfn is_cyclic(g::::(!IsDirected)) = ne(g) > 0
@enum Vertex_state unvisited visited
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@enum Vertex_state unvisited visited

Since this enum is binary, I believe it's clearer to replace it by a vector of booleans. It's actually also slightly more memory-friendly since those can be nicely packed into a BitVector

src/traversals/dfs.jl Outdated Show resolved Hide resolved
fill!(vertex_state,unvisited)
parent = zeros(nv(g))
for v in vertices(g)
if vertex_state[v] == unvisited
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if vertex_state[v] == unvisited
if unvisited[v]

Comment on lines 23 to 24
if vertex_state[w] == unvisited
vertex_state[w] = visited
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if vertex_state[w] == unvisited
vertex_state[w] = visited
if unvisited[w]
unvisited[w] = false

src/traversals/dfs.jl Outdated Show resolved Hide resolved
aurorarossi and others added 4 commits September 8, 2022 16:57
Co-authored-by: Lionel Zoubritzky <Liozou@users.noreply.github.com>
Co-authored-by: Lionel Zoubritzky <Liozou@users.noreply.github.com>
Co-authored-by: Lionel Zoubritzky <Liozou@users.noreply.github.com>
Co-authored-by: Lionel Zoubritzky <Liozou@users.noreply.github.com>
src/traversals/dfs.jl Outdated Show resolved Hide resolved
aurorarossi and others added 3 commits September 8, 2022 18:24
Co-authored-by: Lionel Zoubritzky <Liozou@users.noreply.github.com>
Co-authored-by: Lionel Zoubritzky <Liozou@users.noreply.github.com>
Co-authored-by: Lionel Zoubritzky <Liozou@users.noreply.github.com>
Co-authored-by: Lionel Zoubritzky <Liozou@users.noreply.github.com>
@aurorarossi
Copy link
Member Author

aurorarossi commented Sep 9, 2022

I noticed that sometimes some checks fail, in particular the failing is caused by test of test/traversals/diffusion.jl:120. It is a file that I didn't modify. Should I open an issue?

@Liozou
Copy link
Contributor

Liozou commented Sep 9, 2022

Thanks a lot @aurorarossi for sticking with my many comments! A PR review process can be quite lengthy...
And indeed, after thinking a bit, I believe there is an alternative implementation that bypasses the creation of the extra parent array by keeping the only relevant parent directly in the S list, like so:

@traitfn function is_cyclic(g::AG::(!IsDirected)) where {T, AG<:AbstractGraph{T}}
    visited = falses(nv(g)) 
    for v in vertices(g)
        visited[v] && continue
        visited[v] = true
        S = [(v,v)] # the first v is a dummy "parent"
        while !isempty(S)
            parent, w = pop!(S)
            for u in neighbors(g, w)
                u == parent && continue
                visited[u] && return true
                visited[u] = true
                push!(S, (w, u))
            end
        end
    end
    return false
end

I believe it should be faster than your proposed PR, but we should benchmark it to check. Would you know how to generate cyclic and acyclic graphs to do so?
If it's faster, the code above can be adapted with your @enum if that's preferred. And if it's slower, then your current PR looks good to me as it is.

Regarding the irrelevant failing test, I guess it is a good idea to open an issue to keep track of that separately (but I'm not a maintainer of Graphs.jl so my word is just my opinion).

@aurorarossi
Copy link
Member Author

@etiennedeg now the version of the algorithm is faster. I would like to thank @Liozou for the help!

@aurorarossi
Copy link
Member Author

@matbesancon

end
end
return false
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks correct to me, but there is a small edge that we also have to consider: If an undirected graph has a self-loop (i.e. a vertex that is connected to itself), then this should also count as a cycle in my opinion. (or if not, at least we have to document this).

@test @inferred(!is_cyclic(zero(g)))
end
@test @inferred(is_cyclic((gcyclic)))
@test @inferred(!is_cyclic(gtree))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there are a few more test-cases we have to cover. First, its often good to use graphs with different eltypes (here you use only Int) as a lot of errors happen when that is not the case.

Apart from that, it is often good to have these edge cases in your test:

  • A graph with zero vertices
  • A graph with isolated vertices
  • A graph with multiple components (this is already handled by your test case)
  • A graph with self-loops (we definitely need this test case here)

@simonschoelly
Copy link
Contributor

Can we also add to the function documentation, what the behavior is, for undirected graphs, especially with respect to self-loops?

Co-authored-by: Simon Schölly <sischoel@gmail.com>
Copy link
Contributor

@simonschoelly simonschoelly left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thanks a lot!

@simonschoelly
Copy link
Contributor

@gdalle Can I merge this? Github blocks me from merging, as long you have ongoing requests.

@simonschoelly simonschoelly merged commit 237d345 into JuliaGraphs:master Sep 20, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants