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

Implement Havel-Hakimi and Kleitman-Wang graph realization algorithms #202

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
3 changes: 3 additions & 0 deletions src/SimpleGraphs/SimpleGraphs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ using SparseArrays
using LinearAlgebra
using Graphs
using SimpleTraits
using DataStructures: OrderedDict

import Random: AbstractRNG

Expand Down Expand Up @@ -62,6 +63,8 @@ export AbstractSimpleGraph,
random_regular_graph,
random_regular_digraph,
random_configuration_model,
havel_hakimi_graph,
kleitman_wang_graph,
uniform_tree,
random_tournament_digraph,
StochasticBlockModel,
Expand Down
1 change: 1 addition & 0 deletions src/SimpleGraphs/generators/randgraphs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -989,6 +989,7 @@ function uniform_tree(n::Integer; rng::Union{Nothing,AbstractRNG}=nothing)
return prufer_decode(random_code)
end


"""
random_regular_digraph(n, k)

Expand Down
158 changes: 158 additions & 0 deletions src/SimpleGraphs/generators/staticgraphs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -760,3 +760,161 @@ function lollipop_graph(n1::T, n2::T) where {T<:Integer}
add_edge!(g, n1, n1 + 1)
return g
end


"""
havel_hakimi_graph(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.

The `eltype` of the returned graph will be `Int64`.

## 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(degree_sequence::AbstractVector{<:Integer})
return havel_hakimi_graph(Int, degree_sequence)
end

"""
havel_hakimi_graph(T::Type{<:Integer}, 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.

The `eltype` of the returned graph will be `T`.

## 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(T::Type{<:Integer}, degree_sequence::AbstractVector{<:Integer})
# 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."))
# Instantiate an empty simple graph
graph = SimpleGraph{T}(length(degree_sequence))
# Create a (vertex, degree) ordered dictionary
vertices_degrees_dict = OrderedDict(
vertex => degree for (vertex, degree) in enumerate(degree_sequence)
)
# Havel-Hakimi algorithm
while (any(!=(0), values(vertices_degrees_dict)))
# Sort the new sequence in non-increasing order
vertices_degrees_dict = OrderedDict(
sort(collect(vertices_degrees_dict); by=last, rev=true)
)
# Remove the first vertex and distribute its stabs
max_vertex, max_degree = popfirst!(vertices_degrees_dict)
# Connect the node of highest degree to other nodes of highest degree
for vertex in Iterators.take(keys(vertices_degrees_dict), max_degree)
add_edge!(graph, max_vertex, vertex)
vertices_degrees_dict[vertex] -= 1
# Check whether the remaining degree is nonnegative
vertices_degrees_dict[vertex] >= 0 || throw(ErrorException("The degree sequence is not graphical."))
end
end
# Return the simple graph
return graph
end


"""
kleitman_wang_graph(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).

The `eltype` of the returned graph will be `Int64`.

## 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( indegree_sequence::AbstractVector{<:Integer}, outdegree_sequence::AbstractVector{<:Integer},)
return kleitman_wang_graph(Int, indegree_sequence, outdegree_sequence)
end



"""
kleitman_wang_graph(T::Type{<:Integer}, 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).

The `eltype` of the returned graph will be `T`.

## 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( T::Type{<:Integer},
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{T}(length(indegree_sequence))
# Create a (vertex, degree) ordered dictionary
S = zip(indegree_sequence, outdegree_sequence)
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,
rev=true,
),
)
# Find a vertex with positive outdegree,and 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])
# Check whether the remaining indegree is nonnegative
vertices_degrees_dict[v][1] >= 0 || throw(ErrorException("The indegree and outdegree sequences are not graphical."))
end
# Reinsert the vertex, with zero outdegree
vertices_degrees_dict[i] = (a_i, 0)
end
return graph
end
50 changes: 50 additions & 0 deletions test/simplegraphs/generators/staticgraphs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -648,3 +648,53 @@
@test_throws DomainError lollipop_graph(-1, -1)
end
end


@testset "havel hakimi" begin
rr = havel_hakimi_graph(repeat([2, 4], 5))
@test nv(rr) == 10
@test ne(rr) == 15
@test is_directed(rr) == false

rr = havel_hakimi_graph(zeros(Int, 1000))
@test nv(rr) == 1000
@test ne(rr) == 0
@test is_directed(rr) == false

rr = havel_hakimi_graph([2, 2, 2])
@test nv(rr) == 3
@test ne(rr) == 3
@test is_directed(rr) == false

graph = SimpleGraph(10, 15)
degree_sequence = degree(graph)
rr = havel_hakimi_graph(degree_sequence)
@test nv(rr) == 10
@test ne(rr) == 15
@test is_directed(rr) == false
end

@testset "kleitman wang" begin
rr = kleitman_wang_graph(repeat([2, 4], 5), repeat([2, 4], 5))
@test nv(rr) == 10
@test ne(rr) == 30
@test is_directed(rr) == true

rr = kleitman_wang_graph(zeros(Int, 1000), zeros(Int, 1000))
@test nv(rr) == 1000
@test ne(rr) == 0
@test is_directed(rr) == true

rr = kleitman_wang_graph([2, 2, 2], [2, 2, 2])
@test nv(rr) == 3
@test ne(rr) == 6
@test is_directed(rr) == true

graph = SimpleDiGraph(10, 15)
indegree_sequence = indegree(graph)
outdegree_sequence = outdegree(graph)
rr = kleitman_wang_graph(indegree_sequence, outdegree_sequence)
@test nv(rr) == 10
@test ne(rr) == 15
@test is_directed(rr) == true
end