Advanced Usage
Custom Data Types
You can use custom types for variables, factors, and edges:
using BipartiteFactorGraphs
struct VariableData
name::String
value::Float64
domain::Vector{Float64}
end
struct FactorData
function_type::Symbol
parameters::Dict{Symbol, Any}
end
struct EdgeData
weight::Float64
metadata::Dict{Symbol, Any}
end
# Create graph with custom types
g = BipartiteFactorGraph(VariableData, FactorData, EdgeData)
BipartiteFactorGraph{Main.VariableData, Main.FactorData, Main.EdgeData} with 0 variables, 0 factors, and 0 edges
# Add variable with custom data
var_data = VariableData("x1", 0.5, [-1.0, 1.0])
v1 = add_variable!(g, var_data)
# Add factor with custom data
factor_data = FactorData(:gaussian, Dict(:mean => 0.0, :variance => 1.0))
f1 = add_factor!(g, factor_data)
# Add edge with custom data
edge_data = EdgeData(1.0, Dict(:message => "hello"))
add_edge!(g, v1, f1, edge_data)
Using a Different Dictionary Type
By default, BipartiteFactorGraph uses Dict
to store node and edge data. You can specify a different dictionary type. For example, the package implements an extension for the Dictionaries.jl
package.
Dictionaries.jl do not subtype from the AbstractDict
type, so internally BipartiteFactorGraph
wraps the dictionary in a special wrapper type that implements the AbstractDict
interface.
using Dictionaries # Make sure to add this package to your project
# Create a graph using Dictionaries.jl
g = BipartiteFactorGraph(Float64, String, Int, Dictionary)
BipartiteFactorGraph{Float64, String, Int64} with 0 variables, 0 factors, and 0 edges
Performance Tips
For large graphs, consider the following performance optimizations:
- Preallocate arrays when iterating over many nodes
- Use specific queries (like
variable_neighbors
) instead of filtering general results - Create separate graphs for different data domains if appropriate
- For very large graphs, consider specialized dictionary types optimized for your use case
Example: Simple Inference on a Factor Graph
Here's a simple example of how BipartiteFactorGraphs might be used in a belief propagation algorithm:
using BipartiteFactorGraphs
using LinearAlgebra
# Create a simple Gaussian factor graph
g = BipartiteFactorGraph(Vector{Float64}, Function, Matrix{Float64})
# Add variable nodes (mean and covariance)
v1 = add_variable!(g, [0.0, 0.0]) # Prior belief
v2 = add_variable!(g, [0.0, 0.0]) # Prior belief
# Add factor nodes (functions that compute messages)
f_prior = add_factor!(g, x -> exp(-0.5 * dot(x, x))) # Prior factor (zero mean, unit covariance)
f_likelihood = add_factor!(g, (x, y) -> exp(-0.5 * norm(y - x)^2)) # Likelihood factor
# Add edges with covariances
add_edge!(g, v1, f_prior, Matrix(1.0I, 2, 2))
add_edge!(g, v1, f_likelihood, Matrix(1.0I, 2, 2))
add_edge!(g, v2, f_likelihood, Matrix(1.0I, 2, 2))
# Perform simple message passing (in a real implementation, this would be more complex)
function update_beliefs!(g)
# Update variable beliefs based on connected factors
for v in variables(g)
factors = factor_neighbors(g, v)
new_belief = zeros(length(get_variable_data(g, v)))
for f in factors
# In a real implementation, compute messages from factors
# Here we just illustrate the pattern
factor_fn = get_factor_data(g, f)
edge_info = get_edge_data(g, v, f)
# Update beliefs using the factor and edge data
# (simplified for illustration)
new_belief += edge_info * ones(size(edge_info, 1))
end
# In a real implementation, we would update the variable data here
println("New belief for variable $v: $new_belief")
end
end
# Run one iteration of belief update
update_beliefs!(g)
New belief for variable 2: [1.0, 1.0]
New belief for variable 1: [2.0, 2.0]