Skip to content

Commit

Permalink
I dont think this works, test still fails
Browse files Browse the repository at this point in the history
  • Loading branch information
naseweisssss committed Nov 6, 2024
1 parent 503910f commit 593d18e
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 3 deletions.
84 changes: 83 additions & 1 deletion src/experimental/ProbabilisticGraphicalModels/bayesnet.jl
Original file line number Diff line number Diff line change
Expand Up @@ -199,12 +199,94 @@ If Z is provided, the conditioning information in `bn` will be ignored.
function is_conditionally_independent end

function is_conditionally_independent(bn::BayesianNetwork{V}, X::V, Y::V) where {V}
Z = bn.names[findall(bn.is_observed)]
# Use currently observed variables as Z
Z = V[v for (v, is_obs) in zip(bn.names, bn.is_observed) if is_obs]
return is_conditionally_independent(bn, X, Y, Z)
end

function is_conditionally_independent(
bn::BayesianNetwork{V}, X::V, Y::V, Z::Vector{V}
) where {V}
# Get vertex IDs
x_id = bn.names_to_ids[X]
y_id = bn.names_to_ids[Y]
z_ids = Set([bn.names_to_ids[z] for z in Z])

# Track visited nodes and their states
n_vertices = nv(bn.graph)
visited = falses(n_vertices)

# Queue entries are (node_id, from_parent)
queue = Tuple{Int, Bool}[]

# Start from X, can go both up and down initially
push!(queue, (x_id, true)) # As if coming from a parent

while !isempty(queue)
current_id, from_parent = popfirst!(queue)

if visited[current_id]
continue
end
visited[current_id] = true

# If we reached Y, path is active
if current_id == y_id
return false
end

is_conditioned = current_id in z_ids

# Get neighbors
parents = inneighbors(bn.graph, current_id)
children = outneighbors(bn.graph, current_id)

# Rule 1: If coming from parent and not conditioned, can go to children
if from_parent && !is_conditioned
append!(queue, [(child, true) for child in children])
end

# Rule 2: If coming from child and not conditioned, can go to parents
if !from_parent && !is_conditioned
append!(queue, [(parent, false) for parent in parents])
end

# Rule 3: If at a collider (or descendant of collider) and it's conditioned,
# can go up to parents
if !from_parent && (is_conditioned || has_conditioned_descendant(bn, current_id, z_ids))
if length(parents) > 1 # Is a collider
append!(queue, [(parent, false) for parent in parents])
end
end
end

return true
end

function has_conditioned_descendant(bn::BayesianNetwork, node_id::Int, z_ids::Set{Int})
visited = falses(nv(bn.graph))
queue = Int[node_id]

while !isempty(queue)
current = popfirst!(queue)

if visited[current]
continue
end
visited[current] = true

# Check if current node is conditioned
if current in z_ids
return true
end

# Add all unvisited children to queue
for child in outneighbors(bn.graph, current)
if !visited[child]
push!(queue, child)
end
end
end

return false
end
96 changes: 94 additions & 2 deletions test/experimental/ProbabilisticGraphicalModels/bayesnet.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ using JuliaBUGS.ProbabilisticGraphicalModels:
add_edge!,
condition,
decondition,
ancestral_sampling
ancestral_sampling,
is_conditionally_independent
@testset "BayesianNetwork" begin
@testset "Adding vertices" begin
bn = BayesianNetwork{Symbol}()
Expand Down Expand Up @@ -165,5 +166,96 @@ using JuliaBUGS.ProbabilisticGraphicalModels:
@test std(means) > 0
end

@testset "Bayes Ball" begin end
@testset "Bayes Ball" begin
@testset "Chain Structure (A → B → C)" begin
bn = BayesianNetwork{Symbol}()

add_stochastic_vertex!(bn, :A, Normal(), false)
add_stochastic_vertex!(bn, :B, Normal(), false)
add_stochastic_vertex!(bn, :C, Normal(), false)

add_edge!(bn, :A, :B)
add_edge!(bn, :B, :C)

@test is_conditionally_independent(bn, :A, :C, [:B])
@test !is_conditionally_independent(bn, :A, :C, Symbol[])
end

@testset "Fork Structure (A ← B → C)" begin
bn = BayesianNetwork{Symbol}()

add_stochastic_vertex!(bn, :A, Normal(), false)
add_stochastic_vertex!(bn, :B, Normal(), false)
add_stochastic_vertex!(bn, :C, Normal(), false)

add_edge!(bn, :B, :A)
add_edge!(bn, :B, :C)

@test is_conditionally_independent(bn, :A, :C, [:B])
@test !is_conditionally_independent(bn, :A, :C, Symbol[])
end

@testset "Collider Structure (A → B ← C)" begin
bn = BayesianNetwork{Symbol}()

add_stochastic_vertex!(bn, :A, Normal(), false)
add_stochastic_vertex!(bn, :B, Normal(), false)
add_stochastic_vertex!(bn, :C, Normal(), false)

add_edge!(bn, :A, :B)
add_edge!(bn, :C, :B)

@test is_conditionally_independent(bn, :A, :C, Symbol[])
@test !is_conditionally_independent(bn, :A, :C, [:B])
end

@testset "Complex Structure" begin
bn = BayesianNetwork{Symbol}()

for v in [:A, :B, :C, :D, :E]
add_stochastic_vertex!(bn, v, Normal(), false)
end

# Create structure:
# A → B → D
# ↓ ↑
# C → E
add_edge!(bn, :A, :B)
add_edge!(bn, :B, :C)
add_edge!(bn, :B, :D)
add_edge!(bn, :C, :E)
add_edge!(bn, :E, :D)

@test is_conditionally_independent(bn, :A, :E, [:B, :C])
@test !is_conditionally_independent(bn, :A, :E, [:B])
@test !is_conditionally_independent(bn, :A, :E, Symbol[])
end

@testset "Using Observed Variables" begin
bn = BayesianNetwork{Symbol}()

add_stochastic_vertex!(bn, :A, Normal(), false)
add_stochastic_vertex!(bn, :B, Normal(), true) # B is observed
add_stochastic_vertex!(bn, :C, Normal(), false)

add_edge!(bn, :A, :B)
add_edge!(bn, :B, :C)

@test is_conditionally_independent(bn, :A, :C)

bn_decond = decondition(bn)
@test !is_conditionally_independent(bn_decond, :A, :C)
end

@testset "Error Handling" begin
bn = BayesianNetwork{Symbol}()

add_stochastic_vertex!(bn, :A, Normal(), false)
add_stochastic_vertex!(bn, :B, Normal(), false)

@test_throws KeyError is_conditionally_independent(bn, :A, :NonExistent)
@test_throws KeyError is_conditionally_independent(bn, :NonExistent, :B)
@test_throws KeyError is_conditionally_independent(bn, :A, :B, [:NonExistent])
end
end
end

0 comments on commit 593d18e

Please sign in to comment.