Skip to content

Commit

Permalink
add generators for bipartite graphs
Browse files Browse the repository at this point in the history
  • Loading branch information
stecrotti committed Sep 27, 2024
1 parent d96dcff commit c37e0fa
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 7 deletions.
8 changes: 5 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ version = "0.5.1"
[deps]
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
TrackingHeaps = "d94bfb22-46ad-56c3-87d0-f6c298cb800e"

[compat]
Graphs = "1.4 - 1.9"
Graphs = "1.4"
TrackingHeaps = "0.1"
julia = "1"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test", "Graphs"]
test = ["Test", "Graphs", "Random"]
10 changes: 8 additions & 2 deletions src/IndexedGraphs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ using SparseArrays: sparse, SparseMatrixCSC, nnz, nzrange, rowvals, nonzeros, sp
using Graphs: Graphs, AbstractGraph, SimpleGraph, AbstractSimpleGraph, AbstractEdge,
src, dst, edgetype, has_vertex, has_edge, ne, nv,
edges, vertices, neighbors, inneighbors, outneighbors, is_directed, is_bipartite,
bipartite_map, DijkstraState, dijkstra_shortest_paths
bipartite_map, DijkstraState, dijkstra_shortest_paths,
prufer_decode
import Graphs: degree

using Graphs.LinAlg

using LinearAlgebra: LinearAlgebra, issymmetric

using Random: AbstractRNG, default_rng

using StatsBase: sample

using TrackingHeaps: TrackingHeap, pop!, NoTrainingWheels, MinHeapOrder

export
Expand All @@ -31,7 +36,8 @@ export
nv_left, nv_right, vertex, linearindex, vertices_left, vertices_right,
vertex_left, vertex_right,
is_directed, issymmetric,
bidirected_with_mappings
bidirected_with_mappings,
rand_bipartite_graph, rand_regular_bipartite_graph, rand_bipartite_tree

"""
AbstractIndexedEdge{T<:Integer} <: AbstractEdge{T}
Expand Down
94 changes: 93 additions & 1 deletion src/bipartiteindexedgraph.jl
Original file line number Diff line number Diff line change
Expand Up @@ -309,4 +309,96 @@ function Graphs.adjacency_matrix(g::BipartiteIndexedGraph, T::DataType=Int)
A = SparseMatrixCSC(g.A.m, g.A.n, g.A.colptr, g.A.rowval, ones(T, nnz(g.A)))
return [ spzeros(T, m, m) A ;
A' spzeros(T, n, n) ]
end
end


##### GENERATORS

"""
rand_bipartite_graph([rng=default_rng()], nleft, nright, ned)
Create a bipartite graph with `nleft` nodes in the left block, `nright` nodes in the right block and `ned` edges taken uniformly at random.
"""
function rand_bipartite_graph(rng::AbstractRNG, nleft::Integer, nright::Integer, ned::Integer)
nleft > 0 || throw(ArgumentError("Number of variable nodes must be positive, got $nleft"))
nright > 0 || throw(ArgumentError("Number of left nodes must be positive, got $nright"))
ned > 0 || throw(ArgumentError("Number of edges must be positive, got $ned"))
nedmax = nleft * nright
ned nedmax || throw(ArgumentError("Maximum number of edges is $nleft*$nright=$nedmax, got $ned"))

I = zeros(Int, ned)
J = zeros(Int, ned)
K = ones(Int, ned)
n = 1
while n ned
I[n] = rand(rng, 1:nleft)
J[n] = rand(rng, 1:nright)
if !any((i,j) == (I[n], J[n]) for (i,j) in Iterators.take(zip(I,J), n-1))
n += 1
end
end
A = sparse(I, J, K, nleft, nright)
return BipartiteIndexedGraph(A)
end
function rand_bipartite_graph(nleft::Integer, nright::Integer, ned::Integer)
rand_bipartite_graph(default_rng(), nleft, nright, ned)
end

"""
rand_bipartite_graph([rng=default_rng()], nleft, nright, p)
Create a bipartite graph with `nleft` nodes in the left block, `nright` nodes in the right block and edges taken independently with probability `p`.
"""
function rand_bipartite_graph(rng::AbstractRNG, nright::Integer, nleft::Integer, p::Real)
nright > 0 || throw(ArgumentError("Number of right nodes must be positive, got $nright"))
nleft > 0 || throw(ArgumentError("Number of left nodes must be positive, got $nleft"))
0 p 1 || throw(ArgumentError("Probability must be in [0,1], got $ned"))

I = zeros(Int, 0)
J = zeros(Int, 0)
for (a, i) in Iterators.product(1:nleft, 1:nright)
if rand(rng) < p
push!(I, a)
push!(J, i)
end
end
K = ones(Int, length(I))
A = sparse(I, J, K, nleft, nright)
return BipartiteIndexedGraph(A)
end
function rand_bipartite_graph(nright::Integer, nleft::Integer, p::Real)
rand_bipartite_graph(default_rng(), nleft, nright, p)
end

"""
rand_regular_bipartite_graph([rng=default_rng()], nleft, nright, k)
Create a bipartite graph with `nleft` nodes in the left block, `nright` nodes in the right block, where all left nodes have degree `k`.
"""
function rand_regular_bipartite_graph(rng::AbstractRNG, nright::Integer, nleft::Integer,
k::Integer)
nright > 0 || throw(ArgumentError("Number of right nodes must be positive, got $nright"))
nleft > 0 || throw(ArgumentError("Number of left nodes must be positive, got $nleft"))
k > 0 || throw(ArgumentError("Degree `k` must be positive, got $k"))
k nright || throw(ArgumentError("Degree `k` must be smaller or equal than number of rights, got $k"))

I = reduce(vcat, fill(a, k) for a in 1:nleft)
J = reduce(vcat, sample(rng, 1:nright, k; replace=false) for _ in 1:nleft)
K = ones(Int, length(I))
A = sparse(I, J, K, nleft, nright)
return BipartiteIndexedGraph(A)
end
function rand_regular_bipartite_graph(nright::Integer, nleft::Integer, k::Integer)
rand_regular_bipartite_graph(default_rng(), nleft, nright, k)
end

"""
rand_bipartite_tree([rng=default_rng()], n)
Create a tree bipartite graph with `n` vertices in total. The proportion of left/right nodes is random.
"""
function rand_bipartite_tree(rng::AbstractRNG, n::Integer)
gg = prufer_decode(rand(rng, 1:n, n-2))
return BipartiteIndexedGraph(gg)
end
rand_bipartite_tree(n::Integer) = rand_bipartite_tree(default_rng(), n)
40 changes: 39 additions & 1 deletion test/bipartite.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Graphs, IndexedGraphs, SparseArrays
using Graphs, IndexedGraphs, SparseArrays, Random

@testset "bipartite graph" begin
nl = 15
Expand Down Expand Up @@ -64,4 +64,42 @@ using Graphs, IndexedGraphs, SparseArrays
db = dijkstra_shortest_paths(gb, sources, distvec)
@test all(getproperty(d, p) == getproperty(db, p) for p in fieldnames(typeof(d)))
end

@testset "bipartite generators" begin
rng = MersenneTwister(0)
ngraphs = 20
nrights = rand(rng, 5:50, ngraphs)
nlefts = rand(rng, 5:50, ngraphs)
es = [rand(rng, 1:n*m) for (n, m) in zip(nrights, nlefts)]

@testset "Random bipartite graph - fixed # edges" begin
@test all(zip(nrights, nlefts, es)) do (n, m, e)
g = rand_bipartite_graph(rng, m, n, e)
nv_right(g) == n && nv_left(g) == m && ne(g) == e
end
end

@testset "Random bipartite graph - prob of edges" begin
p = 0.1
@test all(zip(nrights, nlefts)) do (n, m)
g = rand_bipartite_graph(rng, n, m, p)
nv_right(g) == n && nv_left(g) == m
end
end

@testset "Random regular bipartite graph" begin
k = 4
@test all(zip(nrights, nlefts)) do (n, m)
g = rand_regular_bipartite_graph(rng, n, m, k)
nv_right(g) == n && nv_left(g) == m && ne(g) == m * k
end
end

@testset "Random bipartite tree" begin
@test all(nrights) do n
g = rand_bipartite_tree(rng, n)
nv(g) == n && !is_cyclic(g)
end
end
end
end

0 comments on commit c37e0fa

Please sign in to comment.