From 763f4cffaea3618057cd4504572969fe8f7d9f28 Mon Sep 17 00:00:00 2001 From: "Interdisciplinary Physics Team (InPhyT)" <63546437+InterdisciplinaryPhysicsTeam@users.noreply.github.com> Date: Fri, 16 Dec 2022 18:51:51 +0100 Subject: [PATCH 1/9] Implement Havel-Hakimi and Kleitman-Wang Co-Authored-By: Pietro Monticone <38562595+pitmonticone@users.noreply.github.com> Co-Authored-By: Claudio Moroni <43729990+ClaudMor@users.noreply.github.com> --- src/SimpleGraphs/SimpleGraphs.jl | 3 + src/SimpleGraphs/generators/randgraphs.jl | 138 +++++++++++++++++++++ test/simplegraphs/generators/randgraphs.jl | 49 ++++++++ 3 files changed, 190 insertions(+) diff --git a/src/SimpleGraphs/SimpleGraphs.jl b/src/SimpleGraphs/SimpleGraphs.jl index d60d56436..b477a7bf7 100644 --- a/src/SimpleGraphs/SimpleGraphs.jl +++ b/src/SimpleGraphs/SimpleGraphs.jl @@ -4,6 +4,7 @@ using SparseArrays using LinearAlgebra using Graphs using SimpleTraits +using DataStructures: OrderedDict import Random: AbstractRNG @@ -61,6 +62,8 @@ export AbstractSimpleGraph, random_regular_graph, random_regular_digraph, random_configuration_model, + havel_hakimi_graph_generator, + kleitman_wang_graph_generator, random_tournament_digraph, StochasticBlockModel, make_edgestream, diff --git a/src/SimpleGraphs/generators/randgraphs.jl b/src/SimpleGraphs/generators/randgraphs.jl index efc122a87..28b5add05 100644 --- a/src/SimpleGraphs/generators/randgraphs.jl +++ b/src/SimpleGraphs/generators/randgraphs.jl @@ -968,6 +968,144 @@ function random_configuration_model( return g 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}) + # 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(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(values(vertices_degrees_dict) .!= 0)) + # 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) + # 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.")) + # Connect the node of highest degree to other nodes of highest degree + for vertex in collect(keys(vertices_degrees_dict))[1:max_degree] + 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 + +""" + 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)) + 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) diff --git a/test/simplegraphs/generators/randgraphs.jl b/test/simplegraphs/generators/randgraphs.jl index 0932a6775..e924c0eb3 100644 --- a/test/simplegraphs/generators/randgraphs.jl +++ b/test/simplegraphs/generators/randgraphs.jl @@ -271,6 +271,55 @@ @test is_directed(rr) == false end + @testset "havel hakimi" begin + rr = havel_hakimi_graph_generator(repeat([2, 4], 5)) + @test nv(rr) == 10 + @test ne(rr) == 15 + @test is_directed(rr) == false + + rr = havel_hakimi_graph_generator(zeros(Int, 1000)) + @test nv(rr) == 1000 + @test ne(rr) == 0 + @test is_directed(rr) == false + + rr = havel_hakimi_graph_generator([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_generator(degree_sequence) + @test nv(rr) == 10 + @test ne(rr) == 15 + @test is_directed(rr) == false + end + + @testset "kleitman wang" begin + rr = kleitman_wang_graph_generator(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_generator(zeros(Int, 1000), zeros(Int, 1000)) + @test nv(rr) == 1000 + @test ne(rr) == 0 + @test is_directed(rr) == true + + rr = kleitman_wang_graph_generator([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_generator(indegree_sequence, outdegree_sequence) + @test nv(rr) == 10 + @test ne(rr) == 15 + @test is_directed(rr) == true + end + @testset "random tournament" begin rt = random_tournament_digraph(10; rng=rng) @test nv(rt) == 10 From afa7abd10cecf04e501a344b23f604c20f34d3bc Mon Sep 17 00:00:00 2001 From: "Interdisciplinary Physics Team (InPhyT)" Date: Sat, 14 Jan 2023 16:17:47 +0100 Subject: [PATCH 2/9] Update src/SimpleGraphs/generators/randgraphs.jl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Simon Schölly --- src/SimpleGraphs/generators/randgraphs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SimpleGraphs/generators/randgraphs.jl b/src/SimpleGraphs/generators/randgraphs.jl index f19adc441..f12af8450 100644 --- a/src/SimpleGraphs/generators/randgraphs.jl +++ b/src/SimpleGraphs/generators/randgraphs.jl @@ -1023,7 +1023,7 @@ function havel_hakimi_graph_generator(degree_sequence::AbstractVector{<:Integer} all(collect(values(vertices_degrees_dict))[1:max_degree] .> 0) || throw(ErrorException("The degree sequence is not graphical.")) # Connect the node of highest degree to other nodes of highest degree - for vertex in collect(keys(vertices_degrees_dict))[1:max_degree] + for vertex in Iterators.take(keys(vertices_degrees_dict), max_degree) add_edge!(graph, max_vertex, vertex) vertices_degrees_dict[vertex] -= 1 end From 7d06acc1ee1f360e479837bc88b37f3a8630c0ec Mon Sep 17 00:00:00 2001 From: "Interdisciplinary Physics Team (InPhyT)" Date: Sat, 14 Jan 2023 16:18:15 +0100 Subject: [PATCH 3/9] Update src/SimpleGraphs/generators/randgraphs.jl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Simon Schölly --- src/SimpleGraphs/generators/randgraphs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SimpleGraphs/generators/randgraphs.jl b/src/SimpleGraphs/generators/randgraphs.jl index f12af8450..cb3d52232 100644 --- a/src/SimpleGraphs/generators/randgraphs.jl +++ b/src/SimpleGraphs/generators/randgraphs.jl @@ -1001,7 +1001,7 @@ Returns a simple graph with a given finite degree sequence of non-negative integ 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}) +function havel_hakimi_graph(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.")) From 1e5d43c1f0ba6ec48b8b46489e930bfc5b6e91df Mon Sep 17 00:00:00 2001 From: "Interdisciplinary Physics Team (InPhyT)" Date: Sat, 14 Jan 2023 16:18:48 +0100 Subject: [PATCH 4/9] Update src/SimpleGraphs/generators/randgraphs.jl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Simon Schölly --- src/SimpleGraphs/generators/randgraphs.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/SimpleGraphs/generators/randgraphs.jl b/src/SimpleGraphs/generators/randgraphs.jl index cb3d52232..d8ff3ef79 100644 --- a/src/SimpleGraphs/generators/randgraphs.jl +++ b/src/SimpleGraphs/generators/randgraphs.jl @@ -1014,9 +1014,7 @@ function havel_hakimi_graph(degree_sequence::AbstractVector{<:Integer}) # Havel-Hakimi algorithm while (any(values(vertices_degrees_dict) .!= 0)) # Sort the new sequence in non-increasing order - vertices_degrees_dict = OrderedDict( - sort(collect(vertices_degrees_dict); by=last, rev=true) - ) + sort!(vertices_degrees_dict, byvalues=true, rev=true) # 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 From 4b5accd166e666b031012de3a56e55e1cd387ab1 Mon Sep 17 00:00:00 2001 From: "Interdisciplinary Physics Team (InPhyT)" Date: Sat, 14 Jan 2023 16:18:57 +0100 Subject: [PATCH 5/9] Update src/SimpleGraphs/generators/randgraphs.jl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Simon Schölly --- src/SimpleGraphs/generators/randgraphs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SimpleGraphs/generators/randgraphs.jl b/src/SimpleGraphs/generators/randgraphs.jl index d8ff3ef79..028795e1d 100644 --- a/src/SimpleGraphs/generators/randgraphs.jl +++ b/src/SimpleGraphs/generators/randgraphs.jl @@ -1012,7 +1012,7 @@ function havel_hakimi_graph(degree_sequence::AbstractVector{<:Integer}) vertex => degree for (vertex, degree) in enumerate(degree_sequence) ) # Havel-Hakimi algorithm - while (any(values(vertices_degrees_dict) .!= 0)) + while (any(!=(0), values(vertices_degrees_dict))) # Sort the new sequence in non-increasing order sort!(vertices_degrees_dict, byvalues=true, rev=true) # Remove the first vertex and distribute its stabs From 19d0b8c6e2ff9e33c977f10affadaa0e1f00975b Mon Sep 17 00:00:00 2001 From: "Interdisciplinary Physics Team (InPhyT)" Date: Sat, 14 Jan 2023 16:31:27 +0100 Subject: [PATCH 6/9] Update src/SimpleGraphs/generators/randgraphs.jl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Simon Schölly --- src/SimpleGraphs/generators/randgraphs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SimpleGraphs/generators/randgraphs.jl b/src/SimpleGraphs/generators/randgraphs.jl index 028795e1d..acc94f594 100644 --- a/src/SimpleGraphs/generators/randgraphs.jl +++ b/src/SimpleGraphs/generators/randgraphs.jl @@ -1086,7 +1086,7 @@ function kleitman_wang_graph_generator( # Instantiate an empty simple graph graph = SimpleDiGraph(length(indegree_sequence)) # Create a (vertex, degree) ordered dictionary - S = zip(deepcopy(indegree_sequence), deepcopy(outdegree_sequence)) + 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)) From 238bd39beacfc40d74326e538eb5813c9d452c9a Mon Sep 17 00:00:00 2001 From: "Interdisciplinary Physics Team (InPhyT)" <63546437+InterdisciplinaryPhysicsTeam@users.noreply.github.com> Date: Sat, 14 Jan 2023 16:46:25 +0100 Subject: [PATCH 7/9] Requested changes Co-Authored-By: Pietro Monticone <38562595+pitmonticone@users.noreply.github.com> Co-Authored-By: Claudio Moroni <43729990+ClaudMor@users.noreply.github.com> --- src/SimpleGraphs/SimpleGraphs.jl | 4 +- src/SimpleGraphs/generators/randgraphs.jl | 135 --------------- src/SimpleGraphs/generators/staticgraphs.jl | 163 +++++++++++++++++++ test/simplegraphs/generators/randgraphs.jl | 49 ------ test/simplegraphs/generators/staticgraphs.jl | 50 ++++++ 5 files changed, 215 insertions(+), 186 deletions(-) diff --git a/src/SimpleGraphs/SimpleGraphs.jl b/src/SimpleGraphs/SimpleGraphs.jl index 80384bdf5..1ac93f7f7 100644 --- a/src/SimpleGraphs/SimpleGraphs.jl +++ b/src/SimpleGraphs/SimpleGraphs.jl @@ -63,8 +63,8 @@ export AbstractSimpleGraph, random_regular_graph, random_regular_digraph, random_configuration_model, - havel_hakimi_graph_generator, - kleitman_wang_graph_generator, + havel_hakimi_graph, + kleitman_wang_graph, uniform_tree, random_tournament_digraph, StochasticBlockModel, diff --git a/src/SimpleGraphs/generators/randgraphs.jl b/src/SimpleGraphs/generators/randgraphs.jl index acc94f594..43e84fde1 100644 --- a/src/SimpleGraphs/generators/randgraphs.jl +++ b/src/SimpleGraphs/generators/randgraphs.jl @@ -989,141 +989,6 @@ 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(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(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 - sort!(vertices_degrees_dict, byvalues=true, rev=true) - # 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.")) - # 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 - 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 - -""" - 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(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, - 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) diff --git a/src/SimpleGraphs/generators/staticgraphs.jl b/src/SimpleGraphs/generators/staticgraphs.jl index 64e004877..a62b002f8 100644 --- a/src/SimpleGraphs/generators/staticgraphs.jl +++ b/src/SimpleGraphs/generators/staticgraphs.jl @@ -760,3 +760,166 @@ 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 + sort!(vertices_degrees_dict, byvalues=true, rev=true) + # 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.")) + # 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 + # 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]) + # vertices_degrees_dict[v][1] >= 0 || throw(ErrorException("The indegree and outdegree sequences are not graphical.")) + 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 diff --git a/test/simplegraphs/generators/randgraphs.jl b/test/simplegraphs/generators/randgraphs.jl index 570d8d5d2..8004b7cc8 100644 --- a/test/simplegraphs/generators/randgraphs.jl +++ b/test/simplegraphs/generators/randgraphs.jl @@ -283,55 +283,6 @@ @test is_directed(rr) == false end - @testset "havel hakimi" begin - rr = havel_hakimi_graph_generator(repeat([2, 4], 5)) - @test nv(rr) == 10 - @test ne(rr) == 15 - @test is_directed(rr) == false - - rr = havel_hakimi_graph_generator(zeros(Int, 1000)) - @test nv(rr) == 1000 - @test ne(rr) == 0 - @test is_directed(rr) == false - - rr = havel_hakimi_graph_generator([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_generator(degree_sequence) - @test nv(rr) == 10 - @test ne(rr) == 15 - @test is_directed(rr) == false - end - - @testset "kleitman wang" begin - rr = kleitman_wang_graph_generator(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_generator(zeros(Int, 1000), zeros(Int, 1000)) - @test nv(rr) == 1000 - @test ne(rr) == 0 - @test is_directed(rr) == true - - rr = kleitman_wang_graph_generator([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_generator(indegree_sequence, outdegree_sequence) - @test nv(rr) == 10 - @test ne(rr) == 15 - @test is_directed(rr) == true - end - @testset "random tournament" begin rt = random_tournament_digraph(10; rng=rng) @test nv(rt) == 10 diff --git a/test/simplegraphs/generators/staticgraphs.jl b/test/simplegraphs/generators/staticgraphs.jl index 17107adce..088f9c128 100644 --- a/test/simplegraphs/generators/staticgraphs.jl +++ b/test/simplegraphs/generators/staticgraphs.jl @@ -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 From ebc05a510af77cc7129b1ab96ff757fa0cd3d679 Mon Sep 17 00:00:00 2001 From: "Interdisciplinary Physics Team (InPhyT)" <63546437+InterdisciplinaryPhysicsTeam@users.noreply.github.com> Date: Sat, 14 Jan 2023 16:58:35 +0100 Subject: [PATCH 8/9] Update staticgraphs.jl Co-Authored-By: Pietro Monticone <38562595+pitmonticone@users.noreply.github.com> Co-Authored-By: Claudio Moroni <43729990+ClaudMor@users.noreply.github.com> --- src/SimpleGraphs/generators/staticgraphs.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/SimpleGraphs/generators/staticgraphs.jl b/src/SimpleGraphs/generators/staticgraphs.jl index a62b002f8..13a407482 100644 --- a/src/SimpleGraphs/generators/staticgraphs.jl +++ b/src/SimpleGraphs/generators/staticgraphs.jl @@ -807,7 +807,9 @@ function havel_hakimi_graph(T::Type{<:Integer}, degree_sequence::AbstractVector{ # Havel-Hakimi algorithm while (any(!=(0), values(vertices_degrees_dict))) # Sort the new sequence in non-increasing order - sort!(vertices_degrees_dict, byvalues=true, rev=true) + 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) # Check whether the new sequence has only positive values From c2ca53e716961ba39c90d8db54670b5be8067daa Mon Sep 17 00:00:00 2001 From: "Interdisciplinary Physics Team (InPhyT)" <63546437+InterdisciplinaryPhysicsTeam@users.noreply.github.com> Date: Sat, 14 Jan 2023 17:26:51 +0100 Subject: [PATCH 9/9] Update staticgraphs.jl Co-Authored-By: Pietro Monticone <38562595+pitmonticone@users.noreply.github.com> Co-Authored-By: Claudio Moroni <43729990+ClaudMor@users.noreply.github.com> --- src/SimpleGraphs/generators/staticgraphs.jl | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/SimpleGraphs/generators/staticgraphs.jl b/src/SimpleGraphs/generators/staticgraphs.jl index 13a407482..8c6d3fb6a 100644 --- a/src/SimpleGraphs/generators/staticgraphs.jl +++ b/src/SimpleGraphs/generators/staticgraphs.jl @@ -812,14 +812,12 @@ function havel_hakimi_graph(T::Type{<:Integer}, degree_sequence::AbstractVector{ ) # 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.")) # 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 - # vertices_degrees_dict[vertex] >= 0 || throw(ErrorException("The degree sequence is not graphical.")) + # 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 @@ -912,14 +910,9 @@ function kleitman_wang_graph( T::Type{<:Integer}, 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]) - # vertices_degrees_dict[v][1] >= 0 || throw(ErrorException("The indegree and outdegree sequences are not graphical.")) + # Check whether the remaining indegree is nonnegative + vertices_degrees_dict[v][1] >= 0 || throw(ErrorException("The indegree and outdegree sequences are not graphical.")) 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