Skip to content

Commit

Permalink
add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
stecrotti committed Apr 8, 2024
1 parent 864cddf commit 115c8bb
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 17 deletions.
6 changes: 2 additions & 4 deletions src/FactorGraphs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,18 @@ using Graphs

using FillArrays: Fill

using SparseArrays: sparse, SparseMatrixCSC, nzrange
using SparseArrays: sparse, SparseMatrixCSC, nzrange, nnz
using Random: AbstractRNG, default_rng
using StatsBase: sample

abstract type AbstractFactorGraph{T} end

include("factorgraph.jl")
include("generators.jl")
include("regular_factorgraph.jl")

export FactorGraph, nvariables, nfactors, variables, factors, factor, variable,
pairwise_interaction_graph,
neighbors, edges, src, dst, idx, ne, nv, degree,
edge_indices,
edge_indices, inedges, outedges,
adjacency_matrix, is_cyclic
export rand_factor_graph, rand_regular_factor_graph, rand_tree_factor_graph
export RegularFactorGraph
Expand Down
100 changes: 88 additions & 12 deletions src/factorgraph.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@ const Variable = Right
"""
FactorGraphVertex
A type to represent a vertex in a bipartite graph, to be passed as an argument to [`neighbors`](@ref), see examples therein.
A type to represent a vertex in a bipartite graph, to be passed as an argument to [`neighbors`](@ref), [`inedges`](@ref), [`outedges`](@ref), see examples therein.
It is recommended to use the [`variable`](@ref) and [`factor`](@ref) constructors.
"""
const FactorGraphVertex = BipartiteGraphVertex

"""
factor(a::Integer)
Wraps index `a` in a container such that other functions like [`neighbors`](@ref) know that it indices a factor node.
Wraps index `a` in a container such that other functions like [`neighbors`](@ref), [`inedges`](@ref), [`outedges`](@ref), knowing that it indices a factor node.
"""
factor(a::Integer) = vertex(a, Factor)

"""
variable(i::Integer)
Wraps index `i` in a container such that other functions like [`neighbors`](@ref) know that it indices a variable node.
Wraps index `i` in a container such that other functions like [`neighbors`](@ref), [`inedges`](@ref), [`outedges`](@ref), knowing that it indices a variable node.
"""
variable(i::Integer) = vertex(i, Variable)

Expand Down Expand Up @@ -130,6 +130,7 @@ function IndexedGraphs.neighbors(g::FactorGraph, i::FactorGraphVertex{Variable})
return @view g.g.A.rowval[nzrange(g.g.A, i.i)]
end


"""
IndexedGraphs.edge_indices(g::FactorGraph, v::FactorGraphVertex)
Expand All @@ -141,21 +142,24 @@ Examples
========
```jldoctest edge_indices
julia> using FactorGraphs, Random
julia> using FactorGraphs, Test
julia> g = FactorGraph([0 1 1 0;
1 0 0 0;
0 0 1 1])
FactorGraph{Int64} with 3 factors, 4 variables and 5 edges
julia> edgeprops = randn(MersenneTwister(0), ne(g));
julia> edgeprops = randn(ne(g));
julia> indices = (idx(e) for e in outedges(g, variable(3)));
julia> indices = edge_indices(g, variable(3));
julia> indices_noalloc = edge_indices(g, variable(3));
julia> edgeprops[indices]
2-element Vector{Float64}:
-0.3530074003005963
-0.13485387193052173
julia> @assert edgeprops[collect(indices)] == edgeprops[indices_noalloc]
julia> @test_throws ArgumentError edgeprops[indices]
Test Passed
Thrown: ArgumentError
```
"""
function edge_indices(g::FactorGraph, a::FactorGraphVertex{Factor})
Expand All @@ -165,6 +169,76 @@ function edge_indices(g::FactorGraph, i::FactorGraphVertex{Variable})
return nzrange(g.g.A, i.i)
end


"""
IndexedGraphs.inedges(g::FactorGraph, v::FactorGraphVertex)
Return a lazy iterators to the edges incident on vertex `v`, with `v` as the destination.
Examples
========
```jldoctest inedges
julia> using FactorGraphs
julia> g = FactorGraph([0 1 1 0;
1 0 0 0;
0 0 1 1])
FactorGraph{Int64} with 3 factors, 4 variables and 5 edges
julia> collect(inedges(g, factor(2)))
1-element Vector{IndexedGraphs.IndexedEdge{Int64}}:
Indexed Edge 1 => 2 with index 1
julia> collect(inedges(g, variable(3)))
2-element Vector{IndexedGraphs.IndexedEdge{Int64}}:
Indexed Edge 1 => 3 with index 3
Indexed Edge 3 => 3 with index 4
```
"""
function IndexedGraphs.inedges(g::FactorGraph, a::FactorGraphVertex{Factor})
return (IndexedEdge(i, a.i, id) for (i, id) in zip(neighbors(g, a), edge_indices(g, a)))
end
function IndexedGraphs.inedges(g::FactorGraph, i::FactorGraphVertex{Variable})
return (IndexedEdge(a, i.i, id) for (a, id) in zip(neighbors(g, i), edge_indices(g, i)))

end

"""
IndexedGraphs.outedges(g::FactorGraph, v::FactorGraphVertex)
Return a lazy iterators to the edges incident on vertex `v`, with `v` as the source.
Examples
========
```jldoctest outedges
julia> using FactorGraphs
julia> g = FactorGraph([0 1 1 0;
1 0 0 0;
0 0 1 1])
FactorGraph{Int64} with 3 factors, 4 variables and 5 edges
julia> collect(outedges(g, factor(2)))
1-element Vector{IndexedGraphs.IndexedEdge{Int64}}:
Indexed Edge 2 => 1 with index 1
julia> collect(outedges(g, variable(3)))
2-element Vector{IndexedGraphs.IndexedEdge{Int64}}:
Indexed Edge 3 => 1 with index 3
Indexed Edge 3 => 3 with index 4
```
"""
function IndexedGraphs.outedges(g::FactorGraph, a::FactorGraphVertex{Factor})
return (IndexedEdge(a.i, i, id) for (i, id) in zip(neighbors(g, a), edge_indices(g, a)))
end
function IndexedGraphs.outedges(g::FactorGraph, i::FactorGraphVertex{Variable})
return (IndexedEdge(i.i, a, id) for (a, id) in zip(neighbors(g, i), edge_indices(g, i)))
end


"""
edges(g::FactorGraph)
Expand Down Expand Up @@ -195,10 +269,12 @@ end
function IndexedGraphs.degree(g::FactorGraph, v::FactorGraphVertex)
return degree(g.g, linearindex(g.g, v))
end
function IndexedGraphs.degree(g::FactorGraph, i::Integer)
function IndexedGraphs.degree(::FactorGraph, ::Integer)
return throw(ArgumentError("Properties of a vertex of a `FactorGraph` such as degree, neighbors, etc. cannot be accessed using an integer. Use a `FactorGraphVertex` instead."))
end

IndexedGraphs.adjacency_matrix(g::FactorGraph, T::DataType=Int) = g.g.A
function IndexedGraphs.adjacency_matrix(g::FactorGraph, T::DataType=Int)
SparseMatrixCSC(g.g.A.m, g.g.A.n, g.g.A.colptr, g.g.A.rowval, ones(T, nnz(g.g.A)))
end

Graphs.is_cyclic(g::FactorGraph) = is_cyclic(g.g)
37 changes: 36 additions & 1 deletion test/factorgraph.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,40 @@ g = FactorGraph(A)
@test nvariables(g) == n
@test all(degree(g, factor(a)) == length(neighbors(g,factor(a))) for a in factors(g))
@test all(degree(g, variable(i)) == length(neighbors(g,variable(i))) for i in variables(g))

@test length(collect(edges(g))) == ne(g)

@test all(all(src(e)==a for (e,a) in zip(inedges(g, variable(i)), neighbors(g, variable(i)))) for i in variables(g))
@test all(all(src(e)==i for (e,i) in zip(inedges(g, factor(a)), neighbors(g, factor(a)))) for a in factors(g))
@test all(all(dst(e)==a for (e,a) in zip(outedges(g, variable(i)), neighbors(g, variable(i)))) for i in variables(g))
@test all(all(dst(e)==i for (e,i) in zip(outedges(g, factor(a)), neighbors(g, factor(a)))) for a in factors(g))

@test_throws ArgumentError degree(g, 1)
end
end

@testset "Broadcasting" begin
ids = [variable(i) for i in rand(1:nvariables(g), 5)]
@test degree.(g, ids) == degree.((g,), ids)
end

@testset "Pairwise interactions" begin
g_pairwise = Graphs.erdos_renyi(n, 0.1; seed=0)
g_factorgraph = pairwise_interaction_graph(g_pairwise)
@test all(1:n) do i
neigs_pairwise = neighbors(g_pairwise, i)
neigs_factorgraph = reduce(vcat, neighbors(g_factorgraph, factor(a))
for a in neighbors(g_factorgraph, variable(i)))
unique!(neigs_factorgraph)
filter!(!isequal(i), neigs_factorgraph)
neigs_pairwise == neigs_factorgraph
end
end

@testset "Edge indices" begin
@test all(variables(g)) do i
idx.(collect(inedges(g, variable(i)))) == edge_indices(g, variable(i))
end
@test all(factors(g)) do a
idx.(collect(inedges(g, factor(a)))) == edge_indices(g, factor(a))
end
end

0 comments on commit 115c8bb

Please sign in to comment.