Skip to content
This repository has been archived by the owner on Oct 8, 2021. It is now read-only.

Commit

Permalink
Added function transitivereduction (#877)
Browse files Browse the repository at this point in the history
* added function transitivereduction

* Update transitivity.jl

docstring formatting

* Fixed some tests && added testdigraphs for all tests
  • Loading branch information
simonschoelly authored and sbromberger committed May 7, 2018
1 parent 4bea344 commit 2b7689a
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/LightGraphs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ MaximumAdjacency, AbstractMASVisitor, mincut, maximum_adjacency_visit,
# a-star, dijkstra, bellman-ford, floyd-warshall
a_star, dijkstra_shortest_paths, bellman_ford_shortest_paths,
has_negative_edge_cycle, enumerate_paths, floyd_warshall_shortest_paths,
transitiveclosure!, transitiveclosure, yen_k_shortest_paths,
transitiveclosure!, transitiveclosure, transitivereduction, yen_k_shortest_paths,
parallel_multisource_dijkstra_shortest_paths,

# centrality
Expand Down
77 changes: 77 additions & 0 deletions src/digraph/transitivity.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,80 @@ function transitiveclosure(g::DiGraph, selflooped = false)
copyg = copy(g)
return transitiveclosure!(copyg, selflooped)
end

"""
transitivereduction(g; selflooped=false)
Compute the transitive reduction of a directed graph. If the graph contains
cycles, each strongly connected component is replaced by a directed cycle and
the transitive reduction is calculated on the condensation graph connecting the
components. If `selflooped` is true, self loops on strongly connected components
of size one will be preserved.
### Performance
Time complexity is \\mathcal{O}(|V||E|).
"""
function transitivereducion end
@traitfn function transitivereduction(g::::IsDirected; selflooped::Bool=false)
scc = strongly_connected_components(g)
cg = condensation(g, scc)

reachable = Vector{Bool}(undef, nv(cg))
visited = Vector{Bool}(undef, nv(cg))
stack = Vector{eltype(cg)}(undef, nv(cg))
resultg = SimpleDiGraph{eltype(g)}(nv(g))

# Calculate the transitive reduction of the acyclic condensation graph.
@inbounds(
for u in vertices(cg)
fill!(reachable, false) # vertices reachable from u on a path of length >= 2
fill!(visited, false)
stacksize = 0
for v in outneighbors(cg,u)
@simd for w in outneighbors(cg, v)
if !visited[w]
visited[w] = true
stacksize += 1
stack[stacksize] = w
end
end
end
while stacksize > 0
v = stack[stacksize]
stacksize -= 1
reachable[v] = true
@simd for w in outneighbors(cg, v)
if !visited[w]
visited[w] = true
stacksize += 1
stack[stacksize] = w
end
end
end
# Add the edges from the condensation graph to the resulting graph.
@simd for v in outneighbors(cg,u)
if !reachable[v]
add_edge!(resultg, scc[u][1], scc[v][1])
end
end
end)

# Replace each strongly connected component with a directed cycle.
@inbounds(
for component in scc
nvc = length(component)
if nvc == 1
if selflooped && has_edge(g, component[1], component[1])
add_edge!(resultg, component[1], component[1])
end
continue
end
for i in 1:(nvc-1)
add_edge!(resultg, component[i], component[i+1])
end
add_edge!(resultg, component[nvc], component[1])
end)

return resultg
end

75 changes: 75 additions & 0 deletions test/digraph/transitivity.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,79 @@
@test newcircle == @inferred(transitiveclosure!(circle, true))
@test ne(circle) == 16
end

# transitivereduction
let
# transitive reduction of the nullgraph is again a nullgraph
nullgraph = SimpleDiGraph(0)
for g in testdigraphs(nullgraph)
@test g == @inferred(transitivereduction(g))
end

# transitive reduction of a path is a path again
pathgraph = PathDiGraph(10)
for g in testdigraphs(pathgraph)
@test g == @inferred(transitivereduction(g))
end

# transitive reduction of the transitive closure of a path is a path again
for g in testdigraphs(pathgraph)
gclosure = transitiveclosure(g)
@test g == @inferred(transitivereduction(gclosure))
end

# Transitive reduction of a complete graph should be s simple cycle
completegraph = CompleteDiGraph(9)
for g in testdigraphs(completegraph)
greduced = @inferred(transitivereduction(g))
@test length(strongly_connected_components(greduced)) == 1 &&
ne(greduced) == nv(g)
end

# transitive reduction a graph with no edges is the same graph again
noedgegraph = SimpleDiGraph(7)
for g in testdigraphs(noedgegraph)
@test g == @inferred(transitivereduction(g))
end

# transitve reduction should maintain a selfloop only when selflooped==true
selfloopgraph = SimpleDiGraph(1)
add_edge!(selfloopgraph, 1, 1)
for g in testdigraphs(selfloopgraph)
@test g == @inferred(transitivereduction(g; selflooped=true));
@test g != @inferred(transitivereduction(g; selflooped=false));
end

# transitive should not maintain selfloops for strongly connected components
# of size > 1
selfloopgraph2 = SimpleDiGraph(2)
add_edge!(selfloopgraph2, 1, 1)
add_edge!(selfloopgraph2, 1, 2)
add_edge!(selfloopgraph2, 2, 1)
add_edge!(selfloopgraph2, 2, 2)
for g in testdigraphs(selfloopgraph2)
@test g != @inferred(transitivereduction(g; selflooped=true));
end

# directed barbell graph should result in two cycles connected by a single edge
barbellgraph = SimpleDiGraph(9)
for i in 1:4, j in 1:4
i == j && continue
add_edge!(barbellgraph, i, j)
add_edge!(barbellgraph, j, i)
end
for i in 5:9, j in 5:9
i == j && continue
add_edge!(barbellgraph, i, j)
add_edge!(barbellgraph, j, i)
end
add_edge!(barbellgraph, 1, 5);
for g in testdigraphs(barbellgraph)
greduced = @inferred(transitivereduction(g))
scc = strongly_connected_components(greduced)
@test Set(scc) == Set([[1:4;], [5:9;]])
@test ne(greduced) == 10
@test length(weakly_connected_components(greduced)) == 1
end
end
end

0 comments on commit 2b7689a

Please sign in to comment.