Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "IndexedFactorGraphs"
uuid = "edf0a622-c16b-410a-a162-e53dc16a76ae"
authors = ["stecrotti <stefano.crotti@polito.it>"]
version = "0.1.3"
version = "0.2.0"

[deps]
FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b"
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ FactorGraph{Int64} with 5 variables, 3 factors, and 6 edges

### Graph navigation
Given a factor graph with $N$ variables and $M$ factors, variables are indexed by $i\in\{1,\ldots,N\}$, factors are indexed by $a\in\{1,\ldots,M\}$.
Properties of a vertex can be queried by wrapping the vertex index in a `variable` or `factor`. For example, the list of neighbors of variable $i=2$ is found by
Properties of a vertex can be queried by wrapping the vertex index in a `v_vertex` or `f_vertex`. For example, the list of neighbors of variable $i=2$ is found by
```julia
julia> ∂i = neighbors(g, variable(2));

Expand All @@ -59,7 +59,7 @@ julia> collect(ea)
Querying properties of a vertex without specifying whether it's a variable or a factor will throw an error
```julia
julia> outedges(g, 3)
ERROR: ArgumentError: Properties of a vertex of an `AbstractFactorGraph` such as degree, neighbors, etc. cannot be accessed by an integer. Use a `variable` or `factor` wrapper instead.
ERROR: ArgumentError: Properties of a vertex of an `AbstractFactorGraph` such as degree, neighbors, etc. cannot be accessed by an integer. Use a `v_vertex` or `f_vertex` wrapper instead.
```

## See also
Expand Down
8 changes: 4 additions & 4 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ FactorGraph{Int64} with 5 variables, 3 factors, and 6 edges

### Graph navigation
Given a factor graph with $N$ variables and $M$ factors, variables are indexed by $i\in\{1,\ldots,N\}$, factors are indexed by $a\in\{1,\ldots,M\}$.
Properties of a vertex can be queried by wrapping the vertex index in a `variable` or `factor`. For example, the list of neighbors of variable $i=2$ is found by
Properties of a vertex can be queried by wrapping the vertex index in a `v_vertex` or `f_vertex`. For example, the list of neighbors of variable $i=2$ is found by
```julia
julia> ∂i = neighbors(g, variable(2));
julia> ∂i = neighbors(g, v_vertex(2));

julia> collect(∂i)
2-element Vector{Int64}:
Expand All @@ -51,7 +51,7 @@ where $1,3$ are to be interpreted as indices of factor vertices.

The list of edges adjacent to factor $a=1$ is found by
```julia
julia> ea = inedges(g, factor(1));
julia> ea = inedges(g, f_vertex(1));

julia> collect(ea)
3-element Vector{IndexedGraphs.IndexedEdge{Int64}}:
Expand All @@ -63,7 +63,7 @@ julia> collect(ea)
Querying properties of a vertex without specifying whether it's a variable or a factor will throw an error
```julia
julia> outedges(g, 3)
ERROR: ArgumentError: Properties of a vertex of an `AbstractFactorGraph` such as degree, neighbors, etc. cannot be accessed by an integer. Use a `variable` or `factor` wrapper instead.
ERROR: ArgumentError: Properties of a vertex of an `AbstractFactorGraph` such as degree, neighbors, etc. cannot be accessed by an integer. Use a `v_vertex` or `f_vertex` wrapper instead.
```

## See also
Expand Down
4 changes: 2 additions & 2 deletions src/IndexedFactorGraphs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module IndexedFactorGraphs

using IndexedGraphs:
IndexedGraphs,
IndexedEdge, BipartiteIndexedGraph, BipartiteGraphVertex, NullNumber,
IndexedEdge, IndexedGraph, BipartiteIndexedGraph, BipartiteGraphVertex, NullNumber,
src, dst, idx, edges, nv, ne, degree, linearindex, inedges, outedges, vertex,
nv_left, nv_right, vertex, Left, Right,
rand_bipartite_graph, rand_regular_bipartite_graph, rand_bipartite_tree
Expand All @@ -19,7 +19,7 @@ include("factorgraph.jl")
include("generators.jl")
include("infinite_regular_factorgraph.jl")

export AbstractFactorGraph, FactorGraph, nvariables, nfactors, variables, factors, factor, variable,
export AbstractFactorGraph, FactorGraph, nvariables, nfactors, eachvariable, eachfactor, f_vertex, v_vertex,
pairwise_interaction_graph,
neighbors, edges, src, dst, idx, ne, nv, degree,
edge_indices, inedges, outedges,
Expand Down
62 changes: 32 additions & 30 deletions src/factorgraph.jl
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
const Factor = Left
const Variable = Right
const FactorVertex = Left
const VariableVertex = Right

"""
FactorGraphVertex

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.
It is recommended to use the [`v_vertex`](@ref) and [`f_vertex`](@ref) constructors.
"""
const FactorGraphVertex = BipartiteGraphVertex

"""
factor(a::Integer)
f_vertex(a::Integer)

Wraps index `a` in a container such that other functions like [`neighbors`](@ref), [`inedges`](@ref), [`outedges`](@ref), knowing 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 vertex.
"""
factor(a::Integer) = vertex(a, Factor)
f_vertex(a::Integer) = vertex(a, FactorVertex)

"""
variable(i::Integer)
v_vertex(i::Integer)

Wraps index `i` in a container such that other functions like [`neighbors`](@ref), [`inedges`](@ref), [`outedges`](@ref), knowing 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 vertex.
"""
variable(i::Integer) = vertex(i, Variable)
v_vertex(i::Integer) = vertex(i, VariableVertex)

abstract type AbstractFactorGraph{T} end

Expand Down Expand Up @@ -61,6 +61,7 @@ function pairwise_interaction_graph(g::AbstractGraph)
A = sparse(I, J, K, E, N)
FactorGraph(A)
end
pairwise_interaction_graph(A::AbstractMatrix) = pairwise_interaction_graph(IndexedGraph(A))

function Base.show(io::IO, g::FactorGraph{T}) where T
nfact = nfactors(g)
Expand All @@ -82,22 +83,23 @@ nvariables(g::FactorGraph) = nv_right(g.g)
Return the number of actors vertices in `g`.
"""
nfactors(g::FactorGraph) = nv_left(g.g)

IndexedGraphs.nv(g::FactorGraph) = nv(g.g)
IndexedGraphs.ne(g::FactorGraph) = ne(g.g)

"""
variables(g::FactorGraph)
eachvariable(g::FactorGraph)

Return a lazy iterator to the indices of variable vertices in `g`.
"""
variables(g::FactorGraph) = 1:nvariables(g)
eachvariable(g::FactorGraph) = 1:nvariables(g)

"""
factors(g::FactorGraph)
eachfactor(g::FactorGraph)

Return a lazy iterator to the indices of factor vertices in `g`.
"""
factors(g::FactorGraph) = 1:nfactors(g)
eachfactor(g::FactorGraph) = 1:nfactors(g)

"""
IndexedGraphs.neighbors(g::FactorGraph, v::FactorGraphVertex)
Expand All @@ -115,20 +117,20 @@ julia> g = FactorGraph([0 1 1 0;
0 0 1 1])
FactorGraph{Int64} with 4 variables, 3 factors, and 5 edges

julia> collect(neighbors(g, variable(3)))
julia> collect(neighbors(g, v_vertex(3)))
2-element Vector{Int64}:
1
3

julia> collect(neighbors(g, factor(2)))
julia> collect(neighbors(g, f_vertex(2)))
1-element Vector{Int64}:
1
```
"""
function IndexedGraphs.neighbors(g::FactorGraph, a::FactorGraphVertex{Factor})
function IndexedGraphs.neighbors(g::FactorGraph, a::FactorGraphVertex{FactorVertex})
return @view g.g.X.rowval[nzrange(g.g.X, a.i)]
end
function IndexedGraphs.neighbors(g::FactorGraph, i::FactorGraphVertex{Variable})
function IndexedGraphs.neighbors(g::FactorGraph, i::FactorGraphVertex{VariableVertex})
return @view g.g.A.rowval[nzrange(g.g.A, i.i)]
end

Expand All @@ -153,9 +155,9 @@ FactorGraph{Int64} with 4 variables, 3 factors, and 5 edges

julia> edgeprops = randn(ne(g));

julia> indices = (idx(e) for e in outedges(g, variable(3)));
julia> indices = (idx(e) for e in outedges(g, v_vertex(3)));

julia> indices_noalloc = edge_indices(g, variable(3));
julia> indices_noalloc = edge_indices(g, v_vertex(3));

julia> @assert edgeprops[collect(indices)] == edgeprops[indices_noalloc]

Expand All @@ -164,10 +166,10 @@ Test Passed
Thrown: ArgumentError
```
"""
function edge_indices(g::FactorGraph, a::FactorGraphVertex{Factor})
function edge_indices(g::FactorGraph, a::FactorGraphVertex{FactorVertex})
return @view g.g.X.nzval[nzrange(g.g.X, a.i)]
end
function edge_indices(g::FactorGraph, i::FactorGraphVertex{Variable})
function edge_indices(g::FactorGraph, i::FactorGraphVertex{VariableVertex})
return nzrange(g.g.A, i.i)
end

Expand Down Expand Up @@ -195,21 +197,21 @@ julia> g = FactorGraph([0 1 1 0;
0 0 1 1])
FactorGraph{Int64} with 4 variables, 3 factors, and 5 edges

julia> collect(inedges(g, factor(2)))
julia> collect(inedges(g, f_vertex(2)))
1-element Vector{IndexedGraphs.IndexedEdge{Int64}}:
Indexed Edge 1 => 2 with index 1


julia> collect(inedges(g, variable(3)))
julia> collect(inedges(g, v_vertex(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})
function IndexedGraphs.inedges(g::FactorGraph, a::FactorGraphVertex{FactorVertex})
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})
function IndexedGraphs.inedges(g::FactorGraph, i::FactorGraphVertex{VariableVertex})
return (IndexedEdge(a, i.i, id) for (a, id) in zip(neighbors(g, i), edge_indices(g, i)))

end
Expand All @@ -230,20 +232,20 @@ julia> g = FactorGraph([0 1 1 0;
0 0 1 1])
FactorGraph{Int64} with 4 variables, 3 factors, and 5 edges

julia> collect(outedges(g, factor(2)))
julia> collect(outedges(g, f_vertex(2)))
1-element Vector{IndexedGraphs.IndexedEdge{Int64}}:
Indexed Edge 2 => 1 with index 1

julia> collect(outedges(g, variable(3)))
julia> collect(outedges(g, v_vertex(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})
function IndexedGraphs.outedges(g::FactorGraph, a::FactorGraphVertex{FactorVertex})
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})
function IndexedGraphs.outedges(g::FactorGraph, i::FactorGraphVertex{VariableVertex})
return (IndexedEdge(i.i, a, id) for (a, id) in zip(neighbors(g, i), edge_indices(g, i)))
end

Expand Down Expand Up @@ -282,7 +284,7 @@ end
for method in [:(IndexedGraphs.degree), :(IndexedGraphs.inedges), :(IndexedGraphs.outedges), :(IndexedGraphs.neighbors), :(edge_indices)]
@eval begin
function $method(::AbstractFactorGraph, ::Integer)
return throw(ArgumentError("Properties of a vertex of an `AbstractFactorGraph` such as degree, neighbors, etc. cannot be accessed by an integer. Use a `variable` or `factor` wrapper instead.\n"))
return throw(ArgumentError("Properties of a vertex of an `AbstractFactorGraph` such as degree, neighbors, etc. cannot be accessed by an integer. Use a `v_vertex` or `f_vertex` wrapper instead.\n"))
end
end
end
Expand Down
22 changes: 11 additions & 11 deletions src/infinite_regular_factorgraph.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ struct InfiniteRegularFactorGraph{T<:Integer} <: AbstractFactorGraph{T}
Construct an `InfiniteRegularFactorGraph` with variable degree `kᵢ` and factor degree `kₐ`
"""
function InfiniteRegularFactorGraph(kᵢ::T, kₐ::T) where {T<:Integer}
kᵢ > 0 || throw(ArgumentError("Factor degree must be positive, got $kᵢ"))
kᵢ > 0 || throw(ArgumentError("Variable degree must be positive, got $kᵢ"))
kₐ > 0 || throw(ArgumentError("Factor degree must be positive, got $kₐ"))
return new{T}(kᵢ, kₐ)
end
Expand All @@ -27,31 +27,31 @@ nvariables(::InfiniteRegularFactorGraph) = 1
nfactors(::InfiniteRegularFactorGraph) = 1
Graphs.ne(::InfiniteRegularFactorGraph) = 1
Graphs.edges(::InfiniteRegularFactorGraph) = (IndexedEdge(1,1,1) for _ in 1:1)
variables(::InfiniteRegularFactorGraph) = 1:1
factors(::InfiniteRegularFactorGraph) = 1:1
eachvariable(::InfiniteRegularFactorGraph) = 1:1
eachfactor(::InfiniteRegularFactorGraph) = 1:1

IndexedGraphs.degree(g::InfiniteRegularFactorGraph, v::FactorGraphVertex) = length(neighbors(g, v))

function IndexedGraphs.neighbors(g::InfiniteRegularFactorGraph, ::FactorGraphVertex{Factor})
function IndexedGraphs.neighbors(g::InfiniteRegularFactorGraph, ::FactorGraphVertex{FactorVertex})
return Fill(1, g.kₐ)
end
function IndexedGraphs.neighbors(g::InfiniteRegularFactorGraph, ::FactorGraphVertex{Variable})
function IndexedGraphs.neighbors(g::InfiniteRegularFactorGraph, ::FactorGraphVertex{VariableVertex})
return Fill(1, g.kᵢ)
end

edge_indices(g::InfiniteRegularFactorGraph, ::FactorGraphVertex{Factor}) = Fill(1, g.kₐ)
edge_indices(g::InfiniteRegularFactorGraph, ::FactorGraphVertex{Variable}) = Fill(1, g.kᵢ)
edge_indices(g::InfiniteRegularFactorGraph, ::FactorGraphVertex{FactorVertex}) = Fill(1, g.kₐ)
edge_indices(g::InfiniteRegularFactorGraph, ::FactorGraphVertex{VariableVertex}) = Fill(1, g.kᵢ)

function IndexedGraphs.inedges(g::InfiniteRegularFactorGraph, ::FactorGraphVertex{Factor})
function IndexedGraphs.inedges(g::InfiniteRegularFactorGraph, ::FactorGraphVertex{FactorVertex})
return (IndexedEdge(1,1,1) for _ in 1:g.kₐ)
end
function IndexedGraphs.inedges(g::InfiniteRegularFactorGraph, ::FactorGraphVertex{Variable})
function IndexedGraphs.inedges(g::InfiniteRegularFactorGraph, ::FactorGraphVertex{VariableVertex})
return (IndexedEdge(1,1,1) for _ in 1:g.kᵢ)

end
function IndexedGraphs.outedges(g::InfiniteRegularFactorGraph, ::FactorGraphVertex{Factor})
function IndexedGraphs.outedges(g::InfiniteRegularFactorGraph, ::FactorGraphVertex{FactorVertex})
return (IndexedEdge(1,1,1) for _ in 1:g.kₐ)
end
function IndexedGraphs.outedges(g::InfiniteRegularFactorGraph, ::FactorGraphVertex{Variable})
function IndexedGraphs.outedges(g::InfiniteRegularFactorGraph, ::FactorGraphVertex{VariableVertex})
return (IndexedEdge(1,1,1) for _ in 1:g.kᵢ)
end
28 changes: 15 additions & 13 deletions test/factorgraph.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ g = FactorGraph(A)
@testset "Basics" begin
@test @inferred nfactors(g) == m
@test @inferred nvariables(g) == n
@test all(@inferred degree(g, factor(a)) == length(@inferred neighbors(g,factor(a))) for a in factors(g))
@test all(@inferred degree(g, variable(i)) == length(@inferred neighbors(g,variable(i))) for i in variables(g))
@test all(@inferred degree(g, f_vertex(a)) == length(@inferred neighbors(g,f_vertex(a))) for a in eachfactor(g))
@test all(@inferred degree(g, v_vertex(i)) == length(@inferred neighbors(g,v_vertex(i))) for i in eachvariable(g))

@test length(collect(@inferred edges(g))) == @inferred 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 all(all(src(e)==a for (e,a) in zip(inedges(g, v_vertex(i)), neighbors(g, v_vertex(i)))) for i in eachvariable(g))
@test all(all(src(e)==i for (e,i) in zip(inedges(g, f_vertex(a)), neighbors(g, f_vertex(a)))) for a in eachfactor(g))
@test all(all(dst(e)==a for (e,a) in zip(outedges(g, v_vertex(i)), neighbors(g, v_vertex(i)))) for i in eachvariable(g))
@test all(all(dst(e)==i for (e,i) in zip(outedges(g, f_vertex(a)), neighbors(g, f_vertex(a)))) for a in eachfactor(g))

@test_throws ArgumentError degree(g, 1)
@test_throws ArgumentError neighbors(g, 1)
Expand All @@ -29,7 +29,7 @@ end
end

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

Expand All @@ -38,22 +38,24 @@ end
# to test on isolated nodes
add_vertex!(g_pairwise)
g_factorgraph = pairwise_interaction_graph(g_pairwise)
g_factorgraph_A = pairwise_interaction_graph(adjacency_matrix(g_pairwise))
@test adjacency_matrix(g_factorgraph) == adjacency_matrix(g_factorgraph)
@test all(1:n-1) 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)); init=Int[])
neigs_factorgraph = reduce(vcat, neighbors(g_factorgraph, f_vertex(a))
for a in neighbors(g_factorgraph, v_vertex(i)); init=Int[])
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))
@test all(eachvariable(g)) do i
idx.(collect(inedges(g, v_vertex(i)))) == edge_indices(g, v_vertex(i))
end
@test all(factors(g)) do a
idx.(collect(inedges(g, factor(a)))) == edge_indices(g, factor(a))
@test all(eachfactor(g)) do a
idx.(collect(inedges(g, f_vertex(a)))) == edge_indices(g, f_vertex(a))
end
end

Expand Down
Loading
Loading