Skip to content

Commit cf1e85e

Browse files
authored
Refactor path functions to findPath[s] (#1214)
* Refactor path functions to getPath[s] * Changes from review and finish changing build -> getSubgraph (probably still wrong verbNoun) * find is the better verb as the path is still an index to the graph * missing functions TODO --------- Co-authored-by: Johannes Terblanche <Affie@users.noreply.github.com>
1 parent 5f896ed commit cf1e85e

File tree

9 files changed

+358
-112
lines changed

9 files changed

+358
-112
lines changed

src/Deprecated.jl

Lines changed: 62 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ end
436436
# - Orphaned factors (where the subgraph does not contain all the related variables) are not included.
437437
# Related:
438438
# - [`copyGraph!`](@ref)
439-
# - [`buildSubgraph`](@ref)
439+
# - [`getSubgraph`](@ref)
440440
# - [`listNeighborhood`](@ref)
441441
# - [`deepcopyGraph`](@ref)
442442
# """
@@ -455,7 +455,7 @@ NOTE: `copyGraphMetadata` is deprecated – use agent/graph Bloblets instead.
455455
Related:
456456
- [`deepcopyGraph`](@ref)
457457
- [`deepcopyGraph!`](@ref)
458-
- [`buildSubgraph`](@ref)
458+
- [`getSubgraph`](@ref)
459459
- [`listNeighborhood`](@ref)
460460
- [`mergeGraph!`](@ref)
461461
"""
@@ -528,7 +528,7 @@ Copy nodes from one graph into another graph by making deepcopies.
528528
see [`copyGraph!`](@ref) for more detail.
529529
Related:
530530
- [`deepcopyGraph`](@ref)
531-
- [`buildSubgraph`](@ref)
531+
- [`getSubgraph`](@ref)
532532
- [`listNeighborhood`](@ref)
533533
- [`mergeGraph!`](@ref)
534534
"""
@@ -556,7 +556,7 @@ Copy nodes from one graph into a new graph by making deepcopies.
556556
see [`copyGraph!`](@ref) for more detail.
557557
Related:
558558
- [`deepcopyGraph!`](@ref)
559-
- [`buildSubgraph`](@ref)
559+
- [`getSubgraph`](@ref)
560560
- [`listNeighborhood`](@ref)
561561
- [`mergeGraph!`](@ref)
562562
"""
@@ -634,40 +634,9 @@ end
634634
# - A better noun is maybe Path or simply listFactors with a fancy filter, something like:
635635
# - [list/get]Path(dfg, from, to; algorithm...)
636636
# the `search` verb can also come ito play, but it is more for knn search type functions.
637-
"""
638-
$SIGNATURES
639-
640-
Relatively naive function counting linearly from-to
641-
642-
DevNotes
643-
- Convert to using Graphs shortest path methods instead.
644-
"""
645-
#
646-
function findFactorsBetweenNaive(
647-
dfg::AbstractDFG,
648-
from::Symbol,
649-
to::Symbol,
650-
assertSingles::Bool = false,
651-
)
652-
#
653-
@info "findFactorsBetweenNaive is naive linear number method -- improvements welcome"
654-
SRT = getVariableLabelNumber(from)
655-
STP = getVariableLabelNumber(to)
656-
prefix = string(from)[1]
657-
@assert prefix == string(to)[1] "from-to prefixes must match, one is $prefix, other $(string(to)[1])"
658-
prev = from
659-
fctlist = Symbol[]
660-
for num = (SRT + 1):STP
661-
next = Symbol(prefix, num)
662-
fct = intersect(ls(dfg, prev), ls(dfg, next))
663-
if assertSingles
664-
@assert length(fct) == 1 "assertSingles=true, won't return multiple factors joining variables at this time"
665-
end
666-
union!(fctlist, fct)
667-
prev = next
668-
end
669637

670-
return fctlist
638+
function findFactorsBetweenNaive(args...)
639+
return error("findFactorsBetweenNaive is obsolete, use DFG.findPath[s] instead.")
671640
end
672641

673642
#TODO deprecate `is` is the correct verb, but rather isHomogeneous(path::Path) the form is isAdjective
@@ -690,3 +659,59 @@ function isPathFactorsHomogeneous(dfg::AbstractDFG, from::Symbol, to::Symbol)
690659
utyp = unique(types)
691660
return (length(utyp) == 1), utyp
692661
end
662+
663+
# deprecated use filter and path separately.
664+
function findShortestPathDijkstra(
665+
dfg::GraphsDFG,
666+
from::Symbol,
667+
to::Symbol;
668+
labelFilterVariables::Union{Function, Nothing} = nothing,
669+
labelFilterFactors::Union{Function, Nothing} = nothing,
670+
tagsFilterVariables::Union{Function, Nothing} = nothing,
671+
tagsFilterFactors::Union{Function, Nothing} = nothing,
672+
typeFilterVariables::Union{Function, Nothing} = nothing,
673+
typeFilterFactors::Union{Function, Nothing} = nothing,
674+
solvableFilter::Union{Function, Nothing} = nothing,
675+
initialized::Union{Nothing, Bool} = nothing,
676+
)
677+
Base.depwarn(
678+
"findShortestPathDijkstra is deprecated, use findPath with `variableLabels`/`factorLabels` kwargs instead.",
679+
:findShortestPathDijkstra,
680+
)
681+
any_active_filters = any(
682+
.!isnothing.([labelFilterVariables, labelFilterFactors, tagsFilterVariables, tagsFilterFactors, typeFilterVariables, typeFilterFactors, initialized, solvableFilter]),
683+
)
684+
685+
if any_active_filters
686+
varList = listVariables(
687+
dfg;
688+
labelFilter = labelFilterVariables,
689+
tagsFilter = tagsFilterVariables,
690+
typeFilter = typeFilterVariables,
691+
solvableFilter,
692+
)
693+
fctList = listFactors(
694+
dfg;
695+
labelFilter = labelFilterFactors,
696+
tagsFilter = tagsFilterFactors,
697+
typeFilter = typeFilterFactors,
698+
solvableFilter,
699+
)
700+
701+
varList = if initialized !== nothing
702+
initmask = isInitialized.(dfg, varList) .== initialized
703+
varList[initmask]
704+
else
705+
varList
706+
end
707+
restrict_labels = vcat(varList, fctList)
708+
subdfg = DFG.getSubgraph(
709+
GraphsDFG{NoSolverParams, VariableSkeleton, FactorSkeleton},
710+
dfg,
711+
restrict_labels,
712+
)
713+
return findPath(subdfg, from, to).path
714+
else
715+
return findPath(dfg, from, to).path
716+
end
717+
end

src/DistributedFactorGraphs.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,8 @@ const unstable_functions::Vector{Symbol} = [
422422
:FactorSummary,
423423
:listNeighborhood,
424424
:listNeighbors,
425+
:findPath,
426+
:findPaths,
425427
:InMemoryBlobstore,
426428
:exists,
427429
:compare,
@@ -442,7 +444,7 @@ const unstable_functions::Vector{Symbol} = [
442444
:findClosestTimestamp,
443445
:findVariablesNearTimestamp,
444446
:findShortestPathDijkstra,
445-
:findFactorsBetweenNaive,
447+
:findFactorsBetweenNaive, # TODO not really used
446448
:getAgentLabel, #TODO check and mark as public
447449
:getGraphLabel, #TODO check and mark as public
448450
:getDescription,

src/GraphsDFG/GraphsDFG.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ import ...DistributedFactorGraphs:
5454
lsf,
5555
isConnected,
5656
listNeighbors,
57-
buildSubgraph,
57+
findPaths,
58+
findPath,
59+
getSubgraph,
5860
getBiadjacencyMatrix,
5961
toDot,
6062
toDotFile,

src/GraphsDFG/services/GraphsDFG.jl

Lines changed: 102 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -358,74 +358,118 @@ function toDot(dfg::GraphsDFG)
358358
return String(data)
359359
end
360360

361-
function findShortestPathDijkstra(
361+
#API design NOTE:
362+
# Do not create new Verbs or Nouns for metric vs. topological pathfinding. findPaths is the universal router... findPaths(..., metric)
363+
# for now we only look at topological paths.
364+
365+
function findPaths(::typeof(all_simple_paths), dfg, from::Symbol, to::Symbol; kwargs...)
366+
gpaths = Graphs.all_simple_paths(dfg.g, dfg.g.labels[from], dfg.g.labels[to]; kwargs...)
367+
return map(p -> (path = map(i -> dfg.g.labels[i], p), dist = length(p) - 1), gpaths)
368+
end
369+
370+
function findPaths(
371+
::typeof(yen_k_shortest_paths),
362372
dfg::GraphsDFG,
363373
from::Symbol,
364-
to::Symbol;
365-
labelFilterVariables::Union{Function, Nothing} = nothing,
366-
labelFilterFactors::Union{Function, Nothing} = nothing,
367-
tagsFilterVariables::Union{Function, Nothing} = nothing,
368-
tagsFilterFactors::Union{Function, Nothing} = nothing,
369-
typeFilterVariables::Union{Function, Nothing} = nothing,
370-
typeFilterFactors::Union{Function, Nothing} = nothing,
371-
solvableFilter::Union{Function, Nothing} = nothing,
372-
initialized::Union{Nothing, Bool} = nothing,
374+
to::Symbol,
375+
k::Int;
376+
distmx = weights(dfg.g),
377+
kwargs...,
373378
)
374-
duplicate =
375-
!isnothing(labelFilterVariables) ||
376-
!isnothing(labelFilterFactors) ||
377-
!isnothing(tagsFilterVariables) ||
378-
!isnothing(tagsFilterFactors) ||
379-
!isnothing(typeFilterVariables) ||
380-
!isnothing(typeFilterFactors) ||
381-
!isnothing(initialized) ||
382-
!isnothing(solvableFilter)
383-
384-
dfg_ = if duplicate
385-
# use copy if filter is being applied
386-
varList = ls(
387-
dfg;
388-
labelFilter = labelFilterVariables,
389-
tagsFilter = tagsFilterVariables,
390-
typeFilter = typeFilterVariables,
391-
solvableFilter,
392-
)
393-
fctList = lsf(
394-
dfg;
395-
labelFilter = labelFilterFactors,
396-
tagsFilter = tagsFilterFactors,
397-
typeFilter = typeFilterFactors,
398-
solvableFilter,
399-
)
379+
(; paths, dists) = Graphs.yen_k_shortest_paths(
380+
dfg.g,
381+
dfg.g.labels[from],
382+
dfg.g.labels[to],
383+
distmx,
384+
k;
385+
kwargs...,
386+
)
387+
return map(zip(paths, dists)) do (path, dist)
388+
return (path = map(i -> dfg.g.labels[i], path), dist = dist)
389+
end
390+
end
400391

401-
varList = if initialized !== nothing
402-
initmask = isInitialized.(dfg, varList) .== initialized
403-
varList[initmask]
392+
# note with default heuristic this is just dijkstra's algorithm
393+
function findPaths(
394+
::typeof(a_star),
395+
dfg::GraphsDFG,
396+
from::Symbol,
397+
to::Symbol;
398+
distmx::AbstractMatrix{T} = weights(dfg.g),
399+
heuristic = nothing,
400+
) where {T}
401+
#TODO make it easier to use label in the heuristic
402+
heuristic = something(heuristic, (n) -> zero(T))
403+
edgepath = Graphs.a_star(dfg.g, dfg.g.labels[from], dfg.g.labels[to], distmx, heuristic)
404+
405+
if isempty(edgepath)
406+
return @NamedTuple{path::Vector{Symbol}, dist::T}[]
407+
end
408+
409+
path = [dfg.g.labels[edgepath[1].src]]
410+
dist = zero(T)
411+
for (; dst, src) in edgepath
412+
push!(path, dfg.g.labels[dst])
413+
dist += distmx[src, dst]
414+
end
415+
416+
return [(path = path, dist = dist)]
417+
end
418+
419+
#TODO Move findPaths and findPath to AbstractDFG services as default implementations.
420+
function findPaths(
421+
dfg::AbstractDFG,
422+
from::Symbol,
423+
to::Symbol,
424+
k::Int;
425+
variableLabels::Union{Nothing, Vector{Symbol}} = nothing,
426+
factorLabels::Union{Nothing, Vector{Symbol}} = nothing,
427+
kwargs...,
428+
)
429+
# If the user provided restricted lists, build the subgraph automatically
430+
active_dfg =
431+
if isa(dfg, GraphsDFG) && isnothing(variableLabels) && isnothing(factorLabels)
432+
dfg
404433
else
405-
varList
434+
vlabels = something(variableLabels, listVariables(dfg))
435+
flabels = something(factorLabels, listFactors(dfg))
436+
labels = vcat(vlabels, flabels)
437+
DFG.getSubgraph(
438+
GraphsDFG{NoSolverParams, VariableSkeleton, FactorSkeleton},
439+
dfg,
440+
labels,
441+
)
406442
end
407-
DFG.deepcopyGraph(typeof(dfg), dfg, varList, fctList)
443+
!hasVariable(active_dfg, from) &&
444+
!hasFactor(active_dfg, from) &&
445+
throw(DFG.LabelNotFoundError(from))
446+
!hasVariable(active_dfg, to) &&
447+
!hasFactor(active_dfg, to) &&
448+
throw(DFG.LabelNotFoundError(to))
449+
450+
# optimization for k=1 since A* is more efficient than Yen's for single shortest path
451+
if k == 1
452+
return findPaths(a_star, active_dfg, from, to; kwargs...)
408453
else
409-
# no filter can be used directly
410-
dfg
411-
end
412-
413-
if !(hasVariable(dfg_, from) || hasFactor(dfg_, from)) ||
414-
!(hasVariable(dfg_, to) || hasFactor(dfg_, to))
415-
# assume filters excluded either `to` or `from` and hence no shortest path
416-
return Symbol[]
454+
return findPaths(yen_k_shortest_paths, active_dfg, from, to, k; kwargs...)
417455
end
418-
# GraphsDFG internally uses Integers
419-
frI = dfg_.g.labels[from]
420-
toI = dfg_.g.labels[to]
456+
end
421457

422-
# get shortest path from graph provider
423-
path_state = Graphs.dijkstra_shortest_paths(dfg_.g.graph, [frI;])
424-
path = Graphs.enumerate_paths(path_state, toI)
425-
dijkpath = map(x -> dfg_.g.labels[x], path)
458+
function findPath(
459+
dfg::AbstractDFG,
460+
from::Symbol,
461+
to::Symbol;
462+
variableLabels::Union{Nothing, Vector{Symbol}} = nothing,
463+
factorLabels::Union{Nothing, Vector{Symbol}} = nothing,
464+
kwargs...,
465+
)
466+
paths = findPaths(dfg, from, to, 1; variableLabels, factorLabels, kwargs...)
426467

427-
# return the list of symbols
428-
return dijkpath
468+
if isempty(paths)
469+
return nothing
470+
else
471+
return first(paths)
472+
end
429473
end
430474

431475
export bfs_tree

src/entities/DFGVariable.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ end
2727
# TODO naming? Density, DensityRepresentation, BeliefRepresentation, BeliefState, etc?
2828
# TODO flatten in State? likeley not for easier serialization of points.
2929
@kwdef struct BeliefRepresentation{T <: StateType, P}
30-
statekind::T = T()# NOTE duplication for serialization, TODO maybe only in State and therefore belief cannot deserialize seperately.
30+
statekind::T = T()# NOTE duplication for serialization, TODO maybe only in State and therefore belief cannot deserialize separately.
3131
"""Discriminator for which representation is active."""
3232
densitykind::AbstractDensityKind = NonparametricDensityKind()
3333

0 commit comments

Comments
 (0)