-
Notifications
You must be signed in to change notification settings - Fork 93
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
Implement Havel-Hakimi and Kleitman-Wang graph realization algorithms #202
base: master
Are you sure you want to change the base?
Changes from 3 commits
763f4cf
c08f978
18b03a7
afa7abd
7d06acc
1e5d43c
4b5accd
19d0b8c
238bd39
ebc05a5
c2ca53e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -989,6 +989,144 @@ function uniform_tree(n::Integer; rng::Union{Nothing,AbstractRNG}=nothing) | |||||||||
return prufer_decode(random_code) | ||||||||||
end | ||||||||||
|
||||||||||
""" | ||||||||||
havel_hakimi_graph_generator(degree_sequence::AbstractVector{<:Integer}) | ||||||||||
|
||||||||||
Returns a simple graph with a given finite degree sequence of non-negative integers generated via the Havel-Hakimi algorithm which works as follows: | ||||||||||
1. successively connect the node of highest degree to other nodes of highest degree; | ||||||||||
2. sort the remaining nodes by degree in decreasing order; | ||||||||||
3. repeat the procedure. | ||||||||||
|
||||||||||
## References | ||||||||||
1. [Hakimi (1962)](https://doi.org/10.1137/0110037); | ||||||||||
2. [Wikipedia](https://en.wikipedia.org/wiki/Havel%E2%80%93Hakimi_algorithm). | ||||||||||
""" | ||||||||||
function havel_hakimi_graph_generator(degree_sequence::AbstractVector{<:Integer}) | ||||||||||
InterdisciplinaryPhysicsTeam marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
# Check whether the degree sequence has only non-negative values | ||||||||||
all(degree_sequence .>= 0) || | ||||||||||
throw(ArgumentError("The degree sequence must contain non-negative integers only.")) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it make sense, to just call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe, in order to maximize performance and code simplicity, we should completely separate the functionalities of Then I'd suggest to either:
Similar reasoning would apply to |
||||||||||
# Instantiate an empty simple graph | ||||||||||
graph = SimpleGraph(length(degree_sequence)) | ||||||||||
# Create a (vertex, degree) ordered dictionary | ||||||||||
vertices_degrees_dict = OrderedDict( | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As long as it work correctly, we can keep it that way, but we probably could make this function much faster, by using a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We unfortunately don't have much time for this, but it would surely help. Would even be better if the final instantiation of a graph object was optional or put in a wrapper method, so that also other packages in the ecosystem may benefit from the full performance of the method (e.g. MutlilayerGraphs.jl's configuration model-like constructors). |
||||||||||
vertex => degree for (vertex, degree) in enumerate(degree_sequence) | ||||||||||
) | ||||||||||
# Havel-Hakimi algorithm | ||||||||||
while (any(values(vertices_degrees_dict) .!= 0)) | ||||||||||
InterdisciplinaryPhysicsTeam marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
# Sort the new sequence in non-increasing order | ||||||||||
vertices_degrees_dict = OrderedDict( | ||||||||||
sort(collect(vertices_degrees_dict); by=last, rev=true) | ||||||||||
) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It throws an error https://github.com/JuliaGraphs/Graphs.jl/actions/runs/3919202599/jobs/6700055795. So we reinstated the allocating line. |
||||||||||
# Remove the first vertex and distribute its stabs | ||||||||||
max_vertex, max_degree = popfirst!(vertices_degrees_dict) | ||||||||||
# Check whether the new sequence has only positive values | ||||||||||
all(collect(values(vertices_degrees_dict))[1:max_degree] .> 0) || | ||||||||||
throw(ErrorException("The degree sequence is not graphical.")) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would probably more efficient to do this check, when set There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I inserted this check in the loop. But it may be temporary, depending on what comes out of this comment. |
||||||||||
# Connect the node of highest degree to other nodes of highest degree | ||||||||||
for vertex in collect(keys(vertices_degrees_dict))[1:max_degree] | ||||||||||
InterdisciplinaryPhysicsTeam marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
add_edge!(graph, max_vertex, vertex) | ||||||||||
vertices_degrees_dict[vertex] -= 1 | ||||||||||
end | ||||||||||
end | ||||||||||
# Return the simple graph | ||||||||||
return graph | ||||||||||
end | ||||||||||
|
||||||||||
""" | ||||||||||
lexicographical_order_ntuple(A::NTuple{N,T}, B::NTuple{M,T}) where {N,T} | ||||||||||
|
||||||||||
The less than (lt) function that implements lexicographical order for `NTuple`s of equal length. | ||||||||||
|
||||||||||
See [Wikipedia](https://en.wikipedia.org/wiki/Lexicographic_order). | ||||||||||
""" | ||||||||||
function lexicographical_order_ntuple(A::Tuple{Vararg{<:Real}}, B::Tuple{Vararg{<:Real}}) | ||||||||||
length(A) == length(B) || | ||||||||||
throw(ArgumentError("The length of A must match the length of B")) | ||||||||||
for (a, b) in zip(A, B) | ||||||||||
if a != b | ||||||||||
return a < b | ||||||||||
end | ||||||||||
end | ||||||||||
|
||||||||||
return false | ||||||||||
end | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need this function? Julia already implements lexicographical orders for tuples (but it also does that for tuples of different lengths. E.g. julia> (1, 2) < (3, 4)
true
julia> (1, 2) < (1, 2)
false
julia> (1, 2) < (1, 2, 1)
true
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, we didn't think this functionality was implemented out of the box. We then removed |
||||||||||
|
||||||||||
""" | ||||||||||
kleitman_wang_graph_generator(indegree_sequence::AbstractVector{<:Integer},outdegree_sequence::AbstractVector{<:Integer}) | ||||||||||
|
||||||||||
Returns a simple directed graph with given finite in-degree and out-degree sequences of non-negative integers generated via the Kleitman-Wang algorithm, that works like follows: | ||||||||||
1. Sort the indegree-outdegree pairs in lexicographical order; | ||||||||||
2. Select a pair that has strictly positive outdegree, say the i-th pairs that has outdegree = b_i; | ||||||||||
3. Subtract 1 to the first b_i highest indegrees (the i-th being excluded), and set b_i to 0; | ||||||||||
4. Repeat from 1. until all indegree-outdegree pairs are of the form (0.0). | ||||||||||
|
||||||||||
## References | ||||||||||
- [Wikipedia](https://en.wikipedia.org/wiki/Kleitman%E2%80%93Wang_algorithms) | ||||||||||
- [Kleitman and Wang (1973)](https://doi.org/10.1016/0012-365X(73)90037-X) | ||||||||||
""" | ||||||||||
function kleitman_wang_graph_generator( | ||||||||||
indegree_sequence::AbstractVector{<:Integer}, | ||||||||||
outdegree_sequence::AbstractVector{<:Integer}, | ||||||||||
) | ||||||||||
length(indegree_sequence) == length(outdegree_sequence) || throw( | ||||||||||
ArgumentError( | ||||||||||
"The provided `indegree_sequence` and `outdegree_sequence` must be of the dame length.", | ||||||||||
), | ||||||||||
) | ||||||||||
# Check whether the indegree_sequence and outdegree_sequence have only non-negative values | ||||||||||
all(indegree_sequence .>= 0) || throw( | ||||||||||
ArgumentError( | ||||||||||
"The `indegree_sequence` sequence must contain non-negative integers only." | ||||||||||
), | ||||||||||
) | ||||||||||
all(outdegree_sequence .>= 0) || throw( | ||||||||||
ArgumentError( | ||||||||||
"The `outdegree_sequence` sequence must contain non-negative integers only." | ||||||||||
), | ||||||||||
) | ||||||||||
|
||||||||||
# Instantiate an empty simple graph | ||||||||||
graph = SimpleDiGraph(length(indegree_sequence)) | ||||||||||
# Create a (vertex, degree) ordered dictionary | ||||||||||
S = zip(deepcopy(indegree_sequence), deepcopy(outdegree_sequence)) | ||||||||||
InterdisciplinaryPhysicsTeam marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
vertices_degrees_dict = OrderedDict(i => tup for (i, tup) in enumerate(S)) | ||||||||||
# Kleitman-Wang algorithm | ||||||||||
while (any(Iterators.flatten(values(vertices_degrees_dict)) .!= 0)) | ||||||||||
# Sort the new sequence in non-increasing lexicographical order | ||||||||||
vertices_degrees_dict = OrderedDict( | ||||||||||
sort( | ||||||||||
collect(vertices_degrees_dict); | ||||||||||
by=last, | ||||||||||
lt=lexicographical_order_ntuple, | ||||||||||
rev=true, | ||||||||||
), | ||||||||||
) | ||||||||||
# Find a vertex with positive outdegree,a nd temporarily remove it from `vertices_degrees_dict` | ||||||||||
i, (a_i, b_i) = 0, (0, 0) | ||||||||||
for (_i, (_a_i, _b_i)) in collect(deepcopy(vertices_degrees_dict)) | ||||||||||
if _b_i != 0 | ||||||||||
i, a_i, b_i = (_i, _a_i, _b_i) | ||||||||||
delete!(vertices_degrees_dict, _i) | ||||||||||
break | ||||||||||
end | ||||||||||
end | ||||||||||
# Connect the vertex found above to other nodes of highest degree | ||||||||||
for (v, degs) in collect(vertices_degrees_dict)[1:b_i] | ||||||||||
add_edge!(graph, i, v) | ||||||||||
vertices_degrees_dict[v] = (degs[1] - 1, degs[2]) | ||||||||||
end | ||||||||||
# Check whether the new sequence has only positive values | ||||||||||
all( | ||||||||||
collect(Iterators.flatten(collect(values(vertices_degrees_dict))))[1:b_i] .>= 0 | ||||||||||
) || throw( | ||||||||||
ErrorException("The in-degree and out-degree sequences are not digraphical."), | ||||||||||
) | ||||||||||
# Reinsert the vertex, with zero outdegree | ||||||||||
vertices_degrees_dict[i] = (a_i, 0) | ||||||||||
end | ||||||||||
return graph | ||||||||||
end | ||||||||||
|
||||||||||
""" | ||||||||||
random_regular_digraph(n, k) | ||||||||||
|
||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if
randgraphs.jl
is the correct place for these functions, maybestaticgraphs.jl
is a better place, as generators are not random.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We moved it to
staticgraphs.jl
. Thanks for the suggestion.