Skip to content

Commit e7252bf

Browse files
authored
Improve State.belief semantics and add explicit topology; mark getSolverParams as deprecated (#1227)
* Improve State.belief semantics and add explicit topology; mark SolverParams as deprecated * only print bw when not empty * Fix bugs in deprecated functions IIF still use --------- Co-authored-by: Johannes Terblanche <Affie@users.noreply.github.com>
1 parent 9cb713e commit e7252bf

10 files changed

Lines changed: 186 additions & 95 deletions

File tree

src/Deprecated.jl

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -569,8 +569,10 @@ function deepcopyGraph(
569569
graphLabel::Symbol = Symbol(getGraphLabel(sourceDFG), "_cp_$(string(uuid4())[1:6])"),
570570
kwargs...,
571571
) where {T <: AbstractDFG}
572+
sp = getSolverParams(sourceDFG)
573+
sp_kw = sp isa fieldtype(T, :solverParams) ? (; solverParams = sp) : (;)
572574
destDFG = T(;
573-
solverParams = getSolverParams(sourceDFG),
575+
sp_kw...,
574576
graph = sourceDFG.graph,
575577
agents = deepcopy(sourceDFG.agents),
576578
graphLabel,
@@ -710,9 +712,15 @@ function findShortestPathDijkstra(
710712
dfg,
711713
restrict_labels,
712714
)
713-
return findPath(subdfg, from, to).path
715+
result = try
716+
findPath(subdfg, from, to)
717+
catch ex
718+
ex isa DFG.LabelNotFoundError ? nothing : rethrow()
719+
end
720+
return result === nothing ? Symbol[] : result.path
714721
else
715-
return findPath(dfg, from, to).path
722+
result = findPath(dfg, from, to)
723+
return result === nothing ? Symbol[] : result.path
716724
end
717725
end
718726

src/DistributedFactorGraphs.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,13 @@ using StructUtils: @kwarg, @tags
6161
export StructUtils # export for use in macros
6262
export AbstractManifold
6363
export ArrayPartition
64-
public @format_str # from FileIO
64+
export @format_str # from FileIO
6565

6666
# DFG exports
6767
const DFG = DistributedFactorGraphs
6868
export DFG # module alias for DistributedFactorGraphs
6969
export getLabel #TODO move
70-
public @defStateType # macro to define custom variable types
70+
export @defStateType # macro to define custom variable types
7171

7272
# ------------------------------------------------------------------------------
7373
# Types

src/Serialization/StateSerialization.jl

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,18 @@ function unpackOldState(d)
131131
label = Symbol(d.solveKey)
132132
!isempty(d.covar) && error("covar field is not supported")
133133
if label == :parametric
134-
belief =
135-
StoredBelief(GaussianDensityKind(), statekind; means = vals, covariances = [BW])
134+
belief = StoredHomotopyBelief(
135+
RootsOnlyTopology(),
136+
statekind;
137+
means = vals,
138+
shapes = [BW],
139+
)
136140
else
137-
belief = StoredBelief(
138-
NonparametricDensityKind(),
141+
belief = StoredHomotopyBelief(
142+
LeavesOnlyTopology(),
139143
statekind;
140144
points = vals,
141-
bandwidth = BW,
145+
bandwidths = [BW],
142146
)
143147
end
144148
return State{T, getPointType(T)}(;

src/entities/State.jl

Lines changed: 137 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -5,99 +5,160 @@
55
abstract type AbstractStateType{N} end
66
const StateType = AbstractStateType
77

8-
##==============================================================================
9-
## StoredBelief
10-
##==============================================================================
11-
abstract type AbstractDensityKind end
8+
# ==============================================================================
9+
# StoredHomotopyBelief
10+
# ==============================================================================
11+
"""
12+
AbstractHomotopyTopology
13+
14+
Describes the physical layout of the nodes within a `StoredHomotopyBelief`.
15+
16+
Since all beliefs in the Caesar ecosystem are fundamentally Homotopy densities,
17+
this trait acts as a lightweight dispatch hint (a "Lens Selector"). It indicates
18+
which parts of the tree (Roots vs. Leaves) are currently populated and how they
19+
are wired, without requiring downstream packages to inspect the underlying vectors.
20+
21+
**Role of the Topology Trait:**
22+
- **DFG:** Determines how to serialize and spatial-index the belief in the database.
23+
- **Visualizers:** Decides how to render the data (e.g., drawing ellipses for roots vs. a point cloud for leaves).
24+
- **IIF/AMP:** Selects the correct mathematical view to construct (e.g., `MvNormal` vs. `ManifoldKernelDensity` vs. a full `HomotopyDensity`).
25+
26+
!!! note "State, not Strategy"
27+
The trait purely describes the *current physical shape* of the data (e.g., "I currently only have Roots populated").
28+
It does *not* dictate the solver strategy (e.g., "You must use a parametric solver").
29+
The math engine is always free to convert or expand the data based on the graph's needs.
30+
31+
**Extending:**
32+
If the standard tree-based Homotopy model does not fit your specific data layout
33+
or solver requirements, you are encouraged to extend this abstract type with your
34+
own custom topology struct. Alternatively, if you believe your use case represents
35+
a missing core layout, please open an issue to discuss adding it to the
36+
foundational ecosystem.
37+
"""
38+
abstract type AbstractHomotopyTopology end
39+
40+
# --- 1. The Roots ---
41+
"L1 structural nodes only. No L2 samples. (Schema: `means`, `weights`, `shapes` populated. `points` empty.)"
42+
struct RootsOnlyTopology <: AbstractHomotopyTopology end
1243

13-
"""Single Gaussian (mean + covariance)."""
14-
struct GaussianDensityKind <: AbstractDensityKind end
44+
# --- 2. The Leaves ---
45+
"L2 raw samples only. No L1 structure. (Schema: `points`, `bandwidths` populated. `means` empty.)"
46+
struct LeavesOnlyTopology <: AbstractHomotopyTopology end
1547

16-
"""Kernel density / particle-based (points + shared bandwidth)."""
17-
struct NonparametricDensityKind <: AbstractDensityKind end
48+
# --- 3. The Full Trees ---
49+
"Tree packed in arrays using 2i, 2i+1 math.(Schema: L1 and L2 populated. Parent arrays empty.)"
50+
struct ImplicitTreeTopology <: AbstractHomotopyTopology end
1851

19-
"""Homotopy between particles and Gaussian."""
20-
struct HomotopyDensityKind <: AbstractDensityKind end
52+
"Full tree using adjacency lists. (Schema: L1, L2, and Parent arrays fully populated.)"
53+
struct ExplicitTreeTopology <: AbstractHomotopyTopology end
2154

22-
function StructUtils.lower(::StructUtils.StructStyle, p::AbstractDensityKind)
55+
function StructUtils.lower(::StructUtils.StructStyle, p::AbstractHomotopyTopology)
2356
return StructUtils.lower(Packed(p))
2457
end
25-
@choosetype AbstractDensityKind resolvePackedType
26-
27-
# TODO naming? Density, DensityRepresentation, StoredBelief, BeliefState, etc?
28-
# TODO flatten in State? likeley not for easier serialization of points.
29-
@kwdef struct StoredBelief{T <: StateType, P}
30-
statekind::T = T()# NOTE duplication for serialization, TODO maybe only in State and therefore belief cannot deserialize separately.
31-
"""Discriminator for which representation is active."""
32-
densitykind::AbstractDensityKind = NonparametricDensityKind()
33-
34-
#--- Parametric fields (Gaussian / GMM / Homotopy leading modes) ---
35-
"""On-manifold component means.
36-
Gaussian: length 1. Homotopy: leading (tree_kernel) means."""
37-
means::Vector{P} = P[] # previously `val[1]` for Gaussian
38-
"""Component covariances, matching `means`."""
39-
covariances::Vector{Matrix{Float64}} = Matrix{Float64}[] # previously `covar` existed but was stored in `bw` (hacky)
40-
"Component weights, matching `means`."
58+
@choosetype AbstractHomotopyTopology resolvePackedType
59+
60+
"""
61+
StoredHomotopyBelief{T <: StateType, P}
62+
63+
A multi-resolution "Grove of Trees" representing a manifold belief.
64+
Each tree can be as deep (ExplicitTreeTopology) or as shallow (RootsOnlyTopology)
65+
as the evidence requires, but they all speak the same language of Nodes and Parents.
66+
67+
These are the internal raw beliefs and need to be viewed through a lens such as
68+
provided by AMP for features like pdf evaluation. Organized into structural
69+
Tree/Branch layers (L1) and empirical Leaf layers (L2).
70+
71+
!!! warning "Raw Data Container"
72+
`StoredHomotopyBelief` is the raw data schema used for database storage and serialization.
73+
Mutating this structure in-place is discouraged. Rather, construct a new `State` object
74+
and call `addState!` or `mergeState!`.
75+
"""
76+
@kwdef struct StoredHomotopyBelief{T <: StateType, P}
77+
statekind::T = T()# NOTE duplication for serialization and self description.
78+
"""A hint for downstream solvers on how to interpret this data (The 'How')"""
79+
topologykind::AbstractHomotopyTopology = LeavesOnlyTopology()
80+
81+
# L1 Nodes
82+
"""
83+
[Order 0] The relative importance or probability of each node in L1.
84+
"""
4185
weights::Vector{Float64} = Float64[]
86+
"""
87+
[Order 1] The location/center of each node, stored directly on the manifold.
88+
"""
89+
means::Vector{P} = P[] # previously `val[1]` for Gaussian
90+
"""
91+
[Order 2] The spread/curvature of each node (e.g., Covariance or Precision matrix).
92+
"""
93+
shapes::Vector{Matrix{Float64}} = Matrix{Float64}[] # previously `covar` existed but was stored in `bw` (hacky)
4294

43-
#--- Non-parametric / Homotopy leaves ---
44-
"""On-manifold sample points. For KDE/HomotopyDensity, these are the leaf kernel means."""
95+
# L2 Nodes
96+
"""
97+
The raw empirical samples on the manifold. Used for KDE and particle representations.
98+
"""
4599
points::Vector{P} = P[] # previously `val`
46-
"""Shared kernel bandwidth matrix used with ManifoldKernelDensity, see field `covar` for the parametric covariance"""
47-
bandwidth::Union{Nothing, Matrix{Float64}} = zeros(getDimension(T), getDimension(T)) #previously `bw` ---
48-
# bandwidth::Matrix{Float64} = zeros(getDimension(T), getDimension(T))
49-
# TODO is bandwidth[s] matrix or vector or ::Vector{Matrix{Float64} or ::Vector{Vector{Float64}?
50-
# JSON.parse(JSON.json(zeros(0, 0)), Matrix{Float64}) errors, so trying with nothing union
51-
end
100+
"""
101+
The second-order bandwidths for the non-parametric points, supports variable bandwidth kernels.
102+
"""
103+
bandwidths::Vector{Matrix{Float64}} = Matrix{Float64}[] #previously `bw` ---
52104

53-
#FIXME remove old name before v0.29
54-
const BeliefRepresentation = StoredBelief
105+
# --- Topology (The Hierarchy) ---
106+
"""
107+
L1 Internal Topology: mean_parents[i] = j means means[i] is a child of means[j]. A value of 0 indicates a Root node.
108+
"""
109+
mean_parents::Vector{Int} = Int[]
110+
"""
111+
L2-to-L1 Bridge: point_parents[i] = j means points[i] is governed by means[j]. Points are leaves.
112+
"""
113+
point_parents::Vector{Int} = Int[]
114+
end
55115

56-
JSON.omit_empty(::Type{<:StoredBelief}) = true
116+
JSON.omit_empty(::Type{<:StoredHomotopyBelief}) = true
57117

58-
function StoredBelief(T::AbstractStateType)
59-
return StoredBelief{typeof(T), getPointType(T)}(; statekind = T)
118+
function StoredHomotopyBelief(T::AbstractStateType)
119+
return StoredHomotopyBelief{typeof(T), getPointType(T)}(; statekind = T)
60120
end
61121

62-
function StoredBelief(::NonparametricDensityKind, T::AbstractStateType; kwargs...)
63-
return StoredBelief{typeof(T), getPointType(T)}(;
122+
function StoredHomotopyBelief(::LeavesOnlyTopology, T::AbstractStateType; kwargs...)
123+
return StoredHomotopyBelief{typeof(T), getPointType(T)}(;
64124
statekind = T,
65-
densitykind = NonparametricDensityKind(),
66-
bandwidth = zeros(getDimension(T), getDimension(T)),
125+
topologykind = LeavesOnlyTopology(),
126+
bandwidths = [zeros(getDimension(T), getDimension(T))],
67127
kwargs...,
68128
)
69129
end
70130

71-
function StoredBelief(::GaussianDensityKind, T::AbstractStateType; kwargs...)
72-
return StoredBelief{typeof(T), getPointType(T)}(;
131+
function StoredHomotopyBelief(::RootsOnlyTopology, T::AbstractStateType; kwargs...)
132+
return StoredHomotopyBelief{typeof(T), getPointType(T)}(;
73133
statekind = T,
74-
densitykind = GaussianDensityKind(),
75-
bandwidth = nothing,
134+
topologykind = RootsOnlyTopology(),
76135
kwargs...,
77136
)
78137
end
79138

80139
function StructUtils.fielddefaults(
81140
::StructUtils.StructStyle,
82-
::Type{StoredBelief{T, P}},
141+
::Type{StoredHomotopyBelief{T, P}},
83142
) where {T, P}
84143
return (
85144
statekind = T(),
86-
densitykind = NonparametricDensityKind(),
145+
topologykind = LeavesOnlyTopology(),
87146
means = P[],
88-
covariances = Matrix{Float64}[],
147+
shapes = Matrix{Float64}[],
89148
weights = Float64[],
90149
points = P[],
91-
bandwidth = nothing,
150+
bandwidths = Matrix{Float64}[],
151+
mean_parents = Int[],
152+
point_parents = Int[],
92153
)
93154
end
94155

95156
function resolveStoredBeliefType(lazyobj)
96157
statekind = liftStateKind(lazyobj.statekind[])
97-
return StoredBelief{typeof(statekind), getPointType(statekind)}
158+
return StoredHomotopyBelief{typeof(statekind), getPointType(statekind)}
98159
end
99160

100-
@choosetype StoredBelief resolveStoredBeliefType
161+
@choosetype StoredHomotopyBelief resolveStoredBeliefType
101162

102163
##==============================================================================
103164
## State
@@ -121,7 +182,7 @@ $(TYPEDFIELDS)
121182
"""
122183
Generic stored belief for this state.
123184
"""
124-
belief::StoredBelief{T, P} = StoredBelief{T, P}()#; statekind = T())
185+
belief::StoredHomotopyBelief{T, P} = StoredHomotopyBelief{T, P}()#; statekind = T())
125186
"""List of symbols for separator variables for this state, used in variable elimination and inference computations."""
126187
separator::Vector{Symbol} = Symbol[]
127188
"""False if initial numerical values are not yet available or stored values are not ready for further processing yet."""
@@ -132,16 +193,7 @@ $(TYPEDFIELDS)
132193
marginalized::Bool = false #TODO renamed from ismargin v0.29
133194
"""How many times has a solver updated this state estimate."""
134195
solves::Int = 0 # TODO renamed from solvedCount v0.29
135-
136-
#TODO belief container that can be used for active solver beliefs such as a HomotopyDensity
137-
# The type is defined by a trait saved in the StoredBelief and
138-
# verbs such as `hydrate!(state)` `persist!(state)` can be used at data at checkpoints.
139-
# Forcing an explicit `persist!` acts as a state checkpoint,
140-
#ensuring the graph only ever stores fully committed solver results rather than half-computed intermediate math.
141-
# abstract type AbstractActiveBelief end
142-
# active_belief::Base.RefValue{<:AbstractActiveBelief} = Ref{AbstractActiveBelief}() & (ignore = true,)
143196
end
144-
145197
# OLD deprecated fields, removed in v0.29, kept here for reference during transition
146198
# val::Vector{P} = Vector{P}()
147199
# bw::Matrix{Float64} = zeros(0, 0)
@@ -153,6 +205,22 @@ end
153205
# events::Dict{Symbol, Threads.Condition} = Dict{Symbol, Threads.Condition}()
154206
# dontmargin::Bool = false
155207

208+
# ==============================================================================
209+
# FUTURE VIEW WRAPPER (Internal DFG Placeholder)
210+
# ==============================================================================
211+
# NOTE: The `StoredHomotopyBelief` is currently expressive and fast enough that
212+
# DFG does not need to store a resolved view next to it in memory.
213+
#
214+
# If future profiling requires it, DFG will introduce a verbose View wrapper
215+
# to hold the raw data alongside the instantiated read-only math object.
216+
#
217+
# abstract type AbstractHomotopyBeliefView end
218+
#
219+
# struct HomotopyBeliefView{T, P, M} <: AbstractHomotopyBeliefView
220+
# stored::StoredHomotopyBelief{T, P}
221+
# math_engine::M # Read-only instantiated solver object (e.g., AMP.HomotopyDensity)
222+
# end
223+
156224
##------------------------------------------------------------------------------
157225
## Constructors
158226
function State{T}(; kwargs...) where {T <: StateType}
@@ -178,7 +246,7 @@ function StructUtils.fielddefaults(
178246
::Type{State{T, P}},
179247
) where {T, P}
180248
return (
181-
belief = StoredBelief{T, P}(; statekind = T()),
249+
belief = StoredHomotopyBelief{T, P}(; statekind = T()),
182250
separator = Symbol[],
183251
initialized = false,
184252
observability = Float64[],
@@ -189,12 +257,12 @@ function StructUtils.fielddefaults(
189257
end
190258

191259
refMeans(state::State) = state.belief.means
192-
refCovariances(state::State) = state.belief.covariances
260+
refCovariances(state::State) = state.belief.shapes
193261
refWeights(state::State) = state.belief.weights
194262
refPoints(state::State) = state.belief.points
195-
refBandwidth(state::State) = state.belief.bandwidth
196-
197-
getDensityKind(state::State) = state.belief.densitykind
263+
refBandwidth(state::State) = state.belief.bandwidths[1]
264+
refBandwidths(state::State) = state.belief.bandwidths
265+
getTopologyKind(state::State) = state.belief.topologykind
198266

199267
# we can also do somthing like this:
200268
function getComponent(state::State, i)

src/entities/equality.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ implement compare if needed.
2020
const GeneratedCompareUnion = Union{
2121
Agent,
2222
Graphroot,
23-
StoredBelief,
23+
StoredHomotopyBelief,
2424
State,
2525
Blobentry,
2626
Bloblet,

src/services/AbstractDFG.jl

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,19 @@ getGraphLabel(dfg::AbstractDFG) = getLabel(getGraph(dfg))
2626

2727
"""
2828
$(SIGNATURES)
29+
30+
!!! warning "Deprecated"
31+
`getSolverParams(dfg)` is deprecated in DFG v0.29 Pass `SolverParams` directly
32+
to `solveTree!()` as a keyword argument instead.
2933
"""
30-
getSolverParams(dfg::AbstractDFG) = dfg.solverParams
34+
function getSolverParams(dfg::AbstractDFG)
35+
Base.depwarn(
36+
"getSolverParams(dfg) is deprecated. SolverParams will be removed from the DFG object. " *
37+
"Pass SolverParams directly to solveTree!() as a keyword argument instead.",
38+
:getSolverParams,
39+
)
40+
return dfg.solverParams
41+
end
3142

3243
"""
3344
$(SIGNATURES)

src/services/compare.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ end
155155
#Compare State
156156
function compare(a::State, b::State)
157157
refPoints(a) != refPoints(b) && @debug("val is not equal") === nothing && return false
158-
refBandwidth(a) != refBandwidth(b) &&
158+
refBandwidths(a) != refBandwidths(b) &&
159159
@debug("bw is not equal") === nothing &&
160160
return false
161161
# a.BayesNetOutVertIDs != b.BayesNetOutVertIDs &&

0 commit comments

Comments
 (0)