diff --git a/src/GraphsDFG/services/graph_ops.jl b/src/GraphsDFG/services/graph_ops.jl index a191ad79..19dc4237 100644 --- a/src/GraphsDFG/services/graph_ops.jl +++ b/src/GraphsDFG/services/graph_ops.jl @@ -74,18 +74,42 @@ function DFG.listNeighborhood( whereSolvable = >=(solvable) end - # find neighbors at distance to add - nbhood = Int[] - - for l in variableFactorLabels - union!(nbhood, neighborhood(dfg.g, dfg.g.labels[l], distance)) + # Filter-aware BFS: listNeighbors applies filterDFG! so filtered-out nodes are + # never added to the frontier, consistently breaking the traversal chain. + # Fast path (no filters): neighborhood() from the graph library is used directly. + allvarfacs = if isnothing(whereSolvable) && isnothing(whereTags) + nbhood = Int[] + for l in variableFactorLabels + union!(nbhood, neighborhood(dfg.g, dfg.g.labels[l], distance)) + end + [dfg.g.labels[id] for id in nbhood] + else + visited = Set{Symbol}(variableFactorLabels) + included = copy(variableFactorLabels) + frontier = Symbol[] + # Seed frontier with starting nodes that pass the filter (they are always + # expansion roots regardless, but only passed to listNeighbors if they exist). + filterDFG!(included, whereSolvable, l -> getSolvable(dfg, l)) + filterDFG!(included, whereTags, l -> listTags(dfg, l)) + append!(frontier, included) + + for _ = 1:distance + isempty(frontier) && break + next_frontier = Symbol[] + for node in frontier + for nl in listNeighbors(dfg, node; whereSolvable, whereTags) + if !(nl in visited) + push!(visited, nl) + push!(included, nl) + push!(next_frontier, nl) + end + end + end + frontier = next_frontier + end + included end - allvarfacs = [dfg.g.labels[id] for id in nbhood] - - filterDFG!(allvarfacs, whereSolvable, l -> getSolvable(dfg, l)) - filterDFG!(allvarfacs, whereTags, l -> listTags(dfg, l)) - variableLabels = intersect(listVariables(dfg), allvarfacs) factorLabels = intersect(listFactors(dfg), allvarfacs) diff --git a/src/services/discovery.jl b/src/services/discovery.jl index 6891521b..682f1adf 100644 --- a/src/services/discovery.jl +++ b/src/services/discovery.jl @@ -118,10 +118,11 @@ end """ $(SIGNATURES) Build a list of all unique neighbors inside 'distance'. Neighbors can be filtered by using keyword arguments, eg. [`whereTags`] and [`whereSolvable`]. -Filters are applied to final neighborhood result. +Filters break the traversal chain: nodes that do not pass the filter are excluded from the result and their neighbors are not explored further. Notes - Returns a tuple `(variableLabels, factorLabels)`, where each element is a `Vector{Symbol}`. +- The starting `label` itself is subject to filtering. Related: - [`getSubgraph`](@ref) @@ -134,7 +135,9 @@ function listNeighborhood(dfg::AbstractDFG, label::Symbol, distance::Int; filter for dist = 1:distance newNeighbors = Set{Symbol}() for node in curList - neighbors = listNeighbors(dfg, node) + # Apply filters during traversal so that filtered-out nodes break the chain: + # only neighbors passing the filter are added to the result and frontier. + neighbors = listNeighbors(dfg, node; filters...) union!(neighborList, neighbors) union!(newNeighbors, neighbors) end diff --git a/test/testBlocks.jl b/test/testBlocks.jl index ceaeeb1b..b6a01cdd 100644 --- a/test/testBlocks.jl +++ b/test/testBlocks.jl @@ -1541,6 +1541,16 @@ function GettingNeighbors(testDFGAPI; VARTYPE = VariableDFG, FACTYPE = FactorDFG # Solvable #TODO if not a GraphsDFG with and summary or skeleton + if VARTYPE == VariableDFG + # Chain-breaking: x7x8f1 is solvable=0; x8x9f1 is only reachable through x8 (solvable=0). + # With the filter applied during traversal, x8x9f1 must NOT appear in the result. + varnh, facnh = listNeighborhood(dfg, [:x7], 4; whereSolvable = >=(1)) + @test issetequal(varnh, [:x5, :x6, :x7]) + @test issetequal(facnh, [:x5x6f1, :x6x7f1]) + @test :x8x9f1 ∉ facnh # chain-breaking: not reachable through solvable=0 x7x8f1/x8 + @test :x8 ∉ varnh + end + if VARTYPE == VariableDFG @test listNeighbors(dfg, :x5; whereSolvable = >=(2)) == Symbol[] @test issetequal(listNeighbors(dfg, :x5; whereSolvable = >=(0)), [:x4x5f1, :x5x6f1])