From cd76754789d0e0fc38aa62d89f4c0680c1b73343 Mon Sep 17 00:00:00 2001 From: dehann Date: Wed, 6 May 2026 22:28:25 -0700 Subject: [PATCH 1/8] redo HomotopyDensityDFG, creep change serialization approach --- src/DistributedFactorGraphs.jl | 1 + src/entities/HomotopyDensity.jl | 163 ++++++++++++++++++++++++++++++++ src/entities/State.jl | 161 ------------------------------- 3 files changed, 164 insertions(+), 161 deletions(-) create mode 100644 src/entities/HomotopyDensity.jl diff --git a/src/DistributedFactorGraphs.jl b/src/DistributedFactorGraphs.jl index 14c93843..4e1e92b3 100644 --- a/src/DistributedFactorGraphs.jl +++ b/src/DistributedFactorGraphs.jl @@ -403,6 +403,7 @@ include("entities/Tags.jl") include("entities/Timestamp.jl") include("entities/Agent_and_Graph.jl") include("entities/Factor.jl") +include("entities/HomotopyDensity.jl") include("entities/State.jl") include("entities/Variable.jl") include("entities/equality.jl") diff --git a/src/entities/HomotopyDensity.jl b/src/entities/HomotopyDensity.jl new file mode 100644 index 00000000..c4298a91 --- /dev/null +++ b/src/entities/HomotopyDensity.jl @@ -0,0 +1,163 @@ + + +##============================================================================== +## Abstract Types +##============================================================================== + +abstract type AbstractStateType{N} end +const StateType = AbstractStateType + +# ============================================================================== +# StoredHomotopyBelief +# ============================================================================== +""" + AbstractHomotopyTopology + +Describes the physical layout of the nodes within a `StoredHomotopyBelief`. + +Since all beliefs in the Caesar ecosystem are fundamentally Homotopy densities, +this trait acts as a lightweight dispatch hint (a "Lens Selector"). It indicates +which parts of the tree (Roots vs. Leaves) are currently populated and how they +are wired, without requiring downstream packages to inspect the underlying vectors. + +**Role of the Topology Trait:** +- **DFG:** Determines how to serialize and spatial-index the belief in the database. +- **Visualizers:** Decides how to render the data (e.g., drawing ellipses for roots vs. a point cloud for leaves). +- **IIF/AMP:** Selects the correct mathematical view to construct (e.g., `MvNormal` vs. `ManifoldKernelDensity` vs. a full `HomotopyDensity`). + +!!! note "State, not Strategy" + The trait purely describes the *current physical shape* of the data (e.g., "I currently only have Roots populated"). + It does *not* dictate the solver strategy (e.g., "You must use a parametric solver"). + The math engine is always free to convert or expand the data based on the graph's needs. + +**Extending:** +If the standard tree-based Homotopy model does not fit your specific data layout +or solver requirements, you are encouraged to extend this abstract type with your +own custom topology struct. Alternatively, if you believe your use case represents +a missing core layout, please open an issue to discuss adding it to the +foundational ecosystem. +""" +abstract type AbstractHomotopyTopology end + +# --- 1. The Roots --- +"L1 structural nodes only. No L2 samples. (Schema: `means`, `weights`, `shapes` populated. `points` empty.)" +struct RootsOnlyTopology <: AbstractHomotopyTopology end + +# --- 2. The Leaves --- +"L2 raw samples only. No L1 structure. (Schema: `points`, `bandwidths` populated. `means` empty.)" +struct LeavesOnlyTopology <: AbstractHomotopyTopology end + +# --- 3. The Full Trees --- +"Tree packed in arrays using 2i, 2i+1 math.(Schema: L1 and L2 populated. Parent arrays empty.)" +struct ImplicitTreeTopology <: AbstractHomotopyTopology end + +"Full tree using adjacency lists. (Schema: L1, L2, and Parent arrays fully populated.)" +struct ExplicitTreeTopology <: AbstractHomotopyTopology end + +function StructUtils.lower(::StructUtils.StructStyle, p::AbstractHomotopyTopology) + return StructUtils.lower(Packed(p)) +end +@choosetype AbstractHomotopyTopology resolvePackedType + +""" + StoredHomotopyBelief{T <: StateType, P} + +A multi-resolution "Grove of Trees" representing a manifold belief. +Each tree can be as deep (ExplicitTreeTopology) or as shallow (RootsOnlyTopology) +as the evidence requires, but they all speak the same language of Nodes and Parents. + +These are the internal raw beliefs and need to be viewed through a lens such as +provided by AMP for features like pdf evaluation. Organized into structural +Tree/Branch layers (L1) and empirical Leaf layers (L2). + +!!! warning "Raw Data Container" + `StoredHomotopyBelief` is the raw data schema used for database storage and serialization. + Mutating this structure in-place is discouraged. Rather, construct a new `State` object + and call `addState!` or `mergeState!`. +""" +@kwdef struct StoredHomotopyBelief{T <: StateType, P} + statekind::T = T()# NOTE duplication for serialization and self description. + """A hint for downstream solvers on how to interpret this data (The 'How')""" + topologykind::AbstractHomotopyTopology = LeavesOnlyTopology() + + # L1 Nodes + """ + [Order 0] The relative importance or probability of each node in L1. + """ + weights::Vector{Float64} = Float64[] + """ + [Order 1] The location/center of each node, stored directly on the manifold. + """ + means::Vector{P} = P[] # previously `val[1]` for Gaussian + """ + [Order 2] The spread/curvature of each node (e.g., Covariance or Precision matrix). + """ + shapes::Vector{Matrix{Float64}} = Matrix{Float64}[] # previously `covar` existed but was stored in `bw` (hacky) + + # L2 Nodes + """ + The raw empirical samples on the manifold. Used for KDE and particle representations. + """ + points::Vector{P} = P[] # previously `val` + """ + The second-order bandwidths for the non-parametric points, supports variable bandwidth kernels. + """ + bandwidths::Vector{Matrix{Float64}} = Matrix{Float64}[] #previously `bw` --- + + # --- Topology (The Hierarchy) --- + """ + L1 Internal Topology: mean_parents[i] = j means means[i] is a child of means[j]. A value of 0 indicates a Root node. + """ + mean_parents::Vector{Int} = Int[] + """ + L2-to-L1 Bridge: point_parents[i] = j means points[i] is governed by means[j]. Points are leaves. + """ + point_parents::Vector{Int} = Int[] +end + +JSON.omit_empty(::Type{<:StoredHomotopyBelief}) = true + +function StoredHomotopyBelief(T::AbstractStateType) + return StoredHomotopyBelief{typeof(T), getPointType(T)}(; statekind = T) +end + +function StoredHomotopyBelief(::LeavesOnlyTopology, T::AbstractStateType; kwargs...) + return StoredHomotopyBelief{typeof(T), getPointType(T)}(; + statekind = T, + topologykind = LeavesOnlyTopology(), + bandwidths = [zeros(getDimension(T), getDimension(T))], + kwargs..., + ) +end + +function StoredHomotopyBelief(::RootsOnlyTopology, T::AbstractStateType; kwargs...) + return StoredHomotopyBelief{typeof(T), getPointType(T)}(; + statekind = T, + topologykind = RootsOnlyTopology(), + kwargs..., + ) +end + +function StructUtils.fielddefaults( + ::StructUtils.StructStyle, + ::Type{StoredHomotopyBelief{T, P}}, +) where {T, P} + return ( + statekind = T(), + topologykind = LeavesOnlyTopology(), + means = P[], + shapes = Matrix{Float64}[], + weights = Float64[], + points = P[], + bandwidths = Matrix{Float64}[], + mean_parents = Int[], + point_parents = Int[], + ) +end + +function resolveStoredBeliefType(lazyobj) + statekind = liftStateKind(lazyobj.statekind[]) + return StoredHomotopyBelief{typeof(statekind), getPointType(statekind)} +end + +@choosetype StoredHomotopyBelief resolveStoredBeliefType \ No newline at end of file diff --git a/src/entities/State.jl b/src/entities/State.jl index 7caccdad..10131d5f 100644 --- a/src/entities/State.jl +++ b/src/entities/State.jl @@ -1,164 +1,3 @@ -##============================================================================== -## Abstract Types -##============================================================================== - -abstract type AbstractStateType{N} end -const StateType = AbstractStateType - -# ============================================================================== -# StoredHomotopyBelief -# ============================================================================== -""" - AbstractHomotopyTopology - -Describes the physical layout of the nodes within a `StoredHomotopyBelief`. - -Since all beliefs in the Caesar ecosystem are fundamentally Homotopy densities, -this trait acts as a lightweight dispatch hint (a "Lens Selector"). It indicates -which parts of the tree (Roots vs. Leaves) are currently populated and how they -are wired, without requiring downstream packages to inspect the underlying vectors. - -**Role of the Topology Trait:** -- **DFG:** Determines how to serialize and spatial-index the belief in the database. -- **Visualizers:** Decides how to render the data (e.g., drawing ellipses for roots vs. a point cloud for leaves). -- **IIF/AMP:** Selects the correct mathematical view to construct (e.g., `MvNormal` vs. `ManifoldKernelDensity` vs. a full `HomotopyDensity`). - -!!! note "State, not Strategy" - The trait purely describes the *current physical shape* of the data (e.g., "I currently only have Roots populated"). - It does *not* dictate the solver strategy (e.g., "You must use a parametric solver"). - The math engine is always free to convert or expand the data based on the graph's needs. - -**Extending:** -If the standard tree-based Homotopy model does not fit your specific data layout -or solver requirements, you are encouraged to extend this abstract type with your -own custom topology struct. Alternatively, if you believe your use case represents -a missing core layout, please open an issue to discuss adding it to the -foundational ecosystem. -""" -abstract type AbstractHomotopyTopology end - -# --- 1. The Roots --- -"L1 structural nodes only. No L2 samples. (Schema: `means`, `weights`, `shapes` populated. `points` empty.)" -struct RootsOnlyTopology <: AbstractHomotopyTopology end - -# --- 2. The Leaves --- -"L2 raw samples only. No L1 structure. (Schema: `points`, `bandwidths` populated. `means` empty.)" -struct LeavesOnlyTopology <: AbstractHomotopyTopology end - -# --- 3. The Full Trees --- -"Tree packed in arrays using 2i, 2i+1 math.(Schema: L1 and L2 populated. Parent arrays empty.)" -struct ImplicitTreeTopology <: AbstractHomotopyTopology end - -"Full tree using adjacency lists. (Schema: L1, L2, and Parent arrays fully populated.)" -struct ExplicitTreeTopology <: AbstractHomotopyTopology end - -function StructUtils.lower(::StructUtils.StructStyle, p::AbstractHomotopyTopology) - return StructUtils.lower(Packed(p)) -end -@choosetype AbstractHomotopyTopology resolvePackedType - -""" - StoredHomotopyBelief{T <: StateType, P} - -A multi-resolution "Grove of Trees" representing a manifold belief. -Each tree can be as deep (ExplicitTreeTopology) or as shallow (RootsOnlyTopology) -as the evidence requires, but they all speak the same language of Nodes and Parents. - -These are the internal raw beliefs and need to be viewed through a lens such as -provided by AMP for features like pdf evaluation. Organized into structural -Tree/Branch layers (L1) and empirical Leaf layers (L2). - -!!! warning "Raw Data Container" - `StoredHomotopyBelief` is the raw data schema used for database storage and serialization. - Mutating this structure in-place is discouraged. Rather, construct a new `State` object - and call `addState!` or `mergeState!`. -""" -@kwdef struct StoredHomotopyBelief{T <: StateType, P} - statekind::T = T()# NOTE duplication for serialization and self description. - """A hint for downstream solvers on how to interpret this data (The 'How')""" - topologykind::AbstractHomotopyTopology = LeavesOnlyTopology() - - # L1 Nodes - """ - [Order 0] The relative importance or probability of each node in L1. - """ - weights::Vector{Float64} = Float64[] - """ - [Order 1] The location/center of each node, stored directly on the manifold. - """ - means::Vector{P} = P[] # previously `val[1]` for Gaussian - """ - [Order 2] The spread/curvature of each node (e.g., Covariance or Precision matrix). - """ - shapes::Vector{Matrix{Float64}} = Matrix{Float64}[] # previously `covar` existed but was stored in `bw` (hacky) - - # L2 Nodes - """ - The raw empirical samples on the manifold. Used for KDE and particle representations. - """ - points::Vector{P} = P[] # previously `val` - """ - The second-order bandwidths for the non-parametric points, supports variable bandwidth kernels. - """ - bandwidths::Vector{Matrix{Float64}} = Matrix{Float64}[] #previously `bw` --- - - # --- Topology (The Hierarchy) --- - """ - L1 Internal Topology: mean_parents[i] = j means means[i] is a child of means[j]. A value of 0 indicates a Root node. - """ - mean_parents::Vector{Int} = Int[] - """ - L2-to-L1 Bridge: point_parents[i] = j means points[i] is governed by means[j]. Points are leaves. - """ - point_parents::Vector{Int} = Int[] -end - -JSON.omit_empty(::Type{<:StoredHomotopyBelief}) = true - -function StoredHomotopyBelief(T::AbstractStateType) - return StoredHomotopyBelief{typeof(T), getPointType(T)}(; statekind = T) -end - -function StoredHomotopyBelief(::LeavesOnlyTopology, T::AbstractStateType; kwargs...) - return StoredHomotopyBelief{typeof(T), getPointType(T)}(; - statekind = T, - topologykind = LeavesOnlyTopology(), - bandwidths = [zeros(getDimension(T), getDimension(T))], - kwargs..., - ) -end - -function StoredHomotopyBelief(::RootsOnlyTopology, T::AbstractStateType; kwargs...) - return StoredHomotopyBelief{typeof(T), getPointType(T)}(; - statekind = T, - topologykind = RootsOnlyTopology(), - kwargs..., - ) -end - -function StructUtils.fielddefaults( - ::StructUtils.StructStyle, - ::Type{StoredHomotopyBelief{T, P}}, -) where {T, P} - return ( - statekind = T(), - topologykind = LeavesOnlyTopology(), - means = P[], - shapes = Matrix{Float64}[], - weights = Float64[], - points = P[], - bandwidths = Matrix{Float64}[], - mean_parents = Int[], - point_parents = Int[], - ) -end - -function resolveStoredBeliefType(lazyobj) - statekind = liftStateKind(lazyobj.statekind[]) - return StoredHomotopyBelief{typeof(statekind), getPointType(statekind)} -end - -@choosetype StoredHomotopyBelief resolveStoredBeliefType ##============================================================================== ## State From 2eb35031f0792f30a466a0eedc2e4f898bb9c32c Mon Sep 17 00:00:00 2001 From: dehann Date: Thu, 7 May 2026 08:41:01 -0700 Subject: [PATCH 2/8] HomotopyDensityDFG DFG tests pass Co-authored-by: Johannes Terblanche --- src/Deprecated.jl | 71 ++++++++ src/Serialization/StateSerialization.jl | 10 +- src/entities/HomotopyDensity.jl | 229 ++++++++++++++++++------ src/entities/State.jl | 15 +- src/entities/equality.jl | 3 +- src/services/AbstractDFG.jl | 15 -- src/services/compare.jl | 7 +- test/affiesandbox.jl | 78 ++++++++ test/compareTests.jl | 2 + test/testSerializingVariables.jl | 14 +- 10 files changed, 350 insertions(+), 94 deletions(-) create mode 100644 test/affiesandbox.jl diff --git a/src/Deprecated.jl b/src/Deprecated.jl index 375f279e..26a58f93 100644 --- a/src/Deprecated.jl +++ b/src/Deprecated.jl @@ -1,3 +1,57 @@ + + + +refMeans(state::State) = state.belief.principal_elements +refCovariances(state::State) = state.belief.principal_details +refWeights(state::State) = state.belief.weights +refPoints(state::State) = state.belief.points +refBandwidth(state::State) = state.belief.trailing_details[1] +refBandwidths(state::State) = values(state.belief.trailing_details) # FIXME, unordered will be a problem +getTopologyKind(state::State) = state.belief.topologykind + + +# # TODO deprecate +import Base: getproperty, setproperty! + +function getproperty(obj::HomotopyDensityDFG, f::Symbol) + showerr = true + ret = if f === :means + getfield(obj, :principal_elements) + elseif f === :shapes + getfield(obj, :principal_details) + elseif f === :bandwidths + getfield(obj, :trailing_details) + else + showerr = false + getfield(obj, f) + end + + showerr && @error("HomotopyDensityDFG field access with $(f) is deprecated") + return ret +end + +function setproperty!(obj::HomotopyDensityDFG, f::Symbol, val) + showerr = true + ret = if f === :means + setfield!(obj, :principal_elements, val) + elseif f === :shapes + setfield!(obj, :principal_details, val) + elseif f === :bandwidths + setfield!(obj, :trailing_details, val) + else + showerr = false + setfield!(obj, f, val) + end + + showerr && @error("HomotopyDensityDFG field access with $(f) is deprecated") + return ret +end + + + + + + ## ================================================================================ ## Deprecated in v0.29 ##================================================================================= @@ -761,3 +815,20 @@ end @deprecate mergeStorelinks! mergeBlobproviders! @deprecate addBlob! putBlob! @deprecate LinkStore LinkBlobprovider + +""" + $(SIGNATURES) + +!!! warning "Deprecated" + `getSolverParams(dfg)` is deprecated in DFG v0.29 Pass `SolverParams` directly + to `solveTree!()` as a keyword argument instead. +""" +function getSolverParams(dfg::AbstractDFG) + #FIXME uncomment before committing only temp for spamming + # Base.depwarn( + # "getSolverParams(dfg) is deprecated. SolverParams will be removed from the DFG object. " * + # "Pass SolverParams directly to solveTree!() as a keyword argument instead.", + # :getSolverParams, + # ) + return dfg.solverParams +end \ No newline at end of file diff --git a/src/Serialization/StateSerialization.jl b/src/Serialization/StateSerialization.jl index 8001697b..82114b24 100644 --- a/src/Serialization/StateSerialization.jl +++ b/src/Serialization/StateSerialization.jl @@ -131,18 +131,18 @@ function unpackOldState(d) label = Symbol(d.solveKey) !isempty(d.covar) && error("covar field is not supported") if label == :parametric - belief = StoredHomotopyBelief( + belief = HomotopyDensityDFG( RootsOnlyTopology(), statekind; - means = vals, - shapes = [BW], + principal_elements = vals, + principal_details = [BW], ) else - belief = StoredHomotopyBelief( + belief = HomotopyDensityDFG( LeavesOnlyTopology(), statekind; points = vals, - bandwidths = [BW], + trailing_details = Dict(1 => BW), ) end return State{T, getPointType(T)}(; diff --git a/src/entities/HomotopyDensity.jl b/src/entities/HomotopyDensity.jl index c4298a91..f832dafb 100644 --- a/src/entities/HomotopyDensity.jl +++ b/src/entities/HomotopyDensity.jl @@ -8,12 +8,12 @@ abstract type AbstractStateType{N} end const StateType = AbstractStateType # ============================================================================== -# StoredHomotopyBelief +# HomotopyDensityDFG # ============================================================================== """ AbstractHomotopyTopology -Describes the physical layout of the nodes within a `StoredHomotopyBelief`. +Describes the physical layout of the nodes within a `HomotopyDensityDFG`. Since all beliefs in the Caesar ecosystem are fundamentally Homotopy densities, this trait acts as a lightweight dispatch hint (a "Lens Selector"). It indicates @@ -59,79 +59,200 @@ function StructUtils.lower(::StructUtils.StructStyle, p::AbstractHomotopyTopolog end @choosetype AbstractHomotopyTopology resolvePackedType +abstract type AbstractDensityForm end +function StructUtils.lower(::StructUtils.StructStyle, p::AbstractDensityForm) + return StructUtils.lower(Packed(p)) +end +@choosetype AbstractDensityForm resolvePackedType + +struct DefaultFormKind <: AbstractDensityForm end + +struct DefaultTopologyKind <: AbstractHomotopyTopology end + + + +# FROM AMP +# abstract type AbstractBinaryTreeDensity <: AbstractHomotopyTopology end +# const BinaryTreeDensity = AbstractBinaryTreeDensity +# struct BinaryTruncFixedDepth{N} <: AbstractBinaryTreeDensity end + + +# abstract type AbstractKernel <: AbstractDensityForm end +# @kwdef struct ConcentratedGaussianKernel{ +# partial, # partial info for compiler, usually a value e.g. nothing or (1,3) +# K <: Distributions.MvNormal, # kernel info for compiler +# T # additional parameters +# } <: AbstractKernel + + + +# Go with Option A in DFG v0.29, +# Acknowledge design compromises (previous DFG v0.29 objective was to collect breaking changes on types, get as close to DFG v1-alpha): +# - HomotopyDensityDFG/Live converters/packers (DataLevel 3.5) +# - HomotopyDensityDFG.reprkind::HomotopyReprDFG +# - HomotopyReprDFG/Live converters/packers (DataLevel 3.5) +# - HomotopyReprDFG.partial has Legacy in AMP v0.15 through Caesar that needs something better by DFG v0.30 +# - DFG v0.29 will JSON ignore HomotopyReprDFG.partial field +##OPTION A +# Pros +# - state container is type stable, +# - fields can be singleton so easier to serde +# Cons +# - not itself a singleton type (not important?) +# DOCS FORCE FIELDS TO BE SINGLETON +mutable struct HomotopyReprDFG{T <: StateType} + topologykind::AbstractHomotopyTopology # bitmap, jpeg, png + reprkind::AbstractDensityForm # RGB24, YCbCr, fullcov, uppercov, LieExpGaussianWrappedKind, ConcentrGaussKernelKind + statekind::T # Position{2} +end + +# # UX -- DataLevel 3 if OPTION A +# X1 = getState(:naive).belief ::HomotopyDensityDFG{Pose2} +# X1 = getState(:research1).belief ::HomotopyDensityDFG{Pose2} # principals look completely different +# # is this dynamic -- +# plot(X1::HomotopyDensityDFG) = plot(what_topology(X1), X1) + + +# struct HomotopyReprLive{O <: AbstractHomotopyTopology, R <: AbstractDensityForm, T <: StateType} +# ... +# end + +# struct FancyStats{S} +# x::Vector{Float64} +# y::Vector{S} +# end + +# struct SecondOrderStats{P, S} +# m0::Vector{Float64} +# m1::Vector{P} +# m2::Vector{S} +# end +# struct HomotopyDensityDFG{T <: StateType, A, B} +# principal::A +# trailing::B +# .... +# end + +# struct FancyPrincipalTrailingSecondOrder <: AbstractHomotopyTopology end +# repr = HomotopyDensityLive{HomotopyReprLive{FancyPrincipalTrailingSecondOrder, , }} +# dothis(getReprType(repr), repr) --> (prinicipal::FancyStats, trailing::SecondOrderStats) + + + +# typeof(Pose2()) = Pose2 +# getKind(...) -> Pose2() + +# getType(::Kind) = .... +# getType(ConcentratedGaussianKernelKind) = ConcentratedGaussianKernel + +# ConcentratedGaussianKernel(w...; kw...) # this calls the constructor to make a new object +# # AbstractDensityForm tells compiler what to do here +# howthis(ConcGaussKind)(w...; kw...) --> ::ConcentratedGaussianKernel{A,B,C} # this is functional programming to call the constructor +# howthis(ConcGaussKind, w...; kw...) --> ::ConcentratedGaussianKernel{A,B,C} # this is manual dispatch to create a new object + +# # + +# dothis(::ConcGaussKind, w...; kw...) --> ::ConcentratedGaussianKernel{A,B,C} # this is manual dispatch to create a new object +# dothis(repr::HomotopyReprDFG) = dothis(getReprType(repr), repr) + +# dothis(getReprType(belief), belief) +# dothis(getReprType(belief))(belief) # manual obj constructor rather than using JSON for details + +# LieExpWrappedKind <: AbstractDensityForm +# ConcGaussianKernelKind <: AbstractDensityForm +# SecondOrderStatsKind <: AbstractDensityForm + + +# # this is a good "reprtype", what Dehann is trying +# { +# "topology": "BinaryTruncFixedDepth{3}", +# "reprtype": "ConcentratedGaussianKernelKind", +# "statetype": "Pose2", +# } + + +# # this is a bad "reprtype", what Dehann already avoided +# { +# "topology": "BinaryTruncFixedDepth{3}", +# "reprtype": "ConcentratedGaussianKernel{this, that, whatever}", +# "statetype": "Pose2", +# } + + + + """ - StoredHomotopyBelief{T <: StateType, P} + HomotopyDensityDFG{H <: HomotopyRepr, P} -A multi-resolution "Grove of Trees" representing a manifold belief. -Each tree can be as deep (ExplicitTreeTopology) or as shallow (RootsOnlyTopology) -as the evidence requires, but they all speak the same language of Nodes and Parents. +Hybrid belief representation with natural transition between (non)parametric representations. -These are the internal raw beliefs and need to be viewed through a lens such as -provided by AMP for features like pdf evaluation. Organized into structural -Tree/Branch layers (L1) and empirical Leaf layers (L2). +**THIS IS IMPORTANT**: Fundamentally related to `HomotopyDensity` definition in AMP.jl. !!! warning "Raw Data Container" - `StoredHomotopyBelief` is the raw data schema used for database storage and serialization. + `HomotopyDensityDFG` is the raw data schema used for database storage and serialization. Mutating this structure in-place is discouraged. Rather, construct a new `State` object and call `addState!` or `mergeState!`. + +Notes: +- These are the internal raw beliefs and need to be viewed through a lens such as +provided by AMP for features like pdf evaluation. +- Allows partials as identified by list of coordinate dimensions via `.reprkind{...L}.partial` + - e.g. legacy `partial = [1;3] or (1,3)` + - When building a partial belief, use full points with necessary information in the specified partial coords. +- Replaces ManellicTree, ManifoldKernelDensity, KernelDensityEstimate, GaussianMixtureModel, PCA, Mixtures + - a.k.a. model order reduction given a topology selection """ -@kwdef struct StoredHomotopyBelief{T <: StateType, P} +@kwdef struct HomotopyDensityDFG{T <: StateType, P} statekind::T = T()# NOTE duplication for serialization and self description. + reprkind::HomotopyReprDFG{T} = HomotopyReprDFG{T}(DefaultTopologyKind(), DefaultFormKind(), T()) # NOTE duplication for serialization and self description. FIXME this is redundant with statekind, but we need it to be a struct for serde, so we duplicate the statekind info here for now. Future refactor could unify these concepts better. """A hint for downstream solvers on how to interpret this data (The 'How')""" topologykind::AbstractHomotopyTopology = LeavesOnlyTopology() - # L1 Nodes - """ - [Order 0] The relative importance or probability of each node in L1. - """ - weights::Vector{Float64} = Float64[] - """ - [Order 1] The location/center of each node, stored directly on the manifold. - """ - means::Vector{P} = P[] # previously `val[1]` for Gaussian - """ - [Order 2] The spread/curvature of each node (e.g., Covariance or Precision matrix). - """ - shapes::Vector{Matrix{Float64}} = Matrix{Float64}[] # previously `covar` existed but was stored in `bw` (hacky) - - # L2 Nodes - """ - The raw empirical samples on the manifold. Used for KDE and particle representations. - """ points::Vector{P} = P[] # previously `val` + weights::Vector{Float64} = Float64[] """ - The second-order bandwidths for the non-parametric points, supports variable bandwidth kernels. + In model order reduction, PCA, and modal analysis, the terms for the eigenvectors associated with the largest and smallest eigenvalues are commonly: + Major eigenvectors are often called "dominant eigenvectors," or simply "leading modes." In Principal Component Analysis (PCA), these are the "principal components." + Minor eigenvectors are sometimes called "trailing eigenvectors," or "residual modes." In PCA, these correspond to the components with the smallest variance. """ - bandwidths::Vector{Matrix{Float64}} = Matrix{Float64}[] #previously `bw` --- - - # --- Topology (The Hierarchy) --- + principal_coeffs::Vector{Float64} = Vector{Float64}() # FIXME getMajorsLength(reprkind)) + principal_elements::Vector{P} = P[] # previously `val[1]` for Gaussian + principal_details::Vector{Matrix{Float64}} = Matrix{Float64}[] # previously `covar` existed but was stored in `bw` (hacky) """ - L1 Internal Topology: mean_parents[i] = j means means[i] is a child of means[j]. A value of 0 indicates a Root node. + Store minor eigenvalue details such as leaf bandwidth or reconstruction vectors. + - When lifted for compute efficiency, this field is likely to hold something like PDMats. + - When lowered or for serde, this field is likely to hold Dict{Int, Vector{Float64}}. """ - mean_parents::Vector{Int} = Int[] + trailing_details::Dict{Int,Matrix{Float64}} = Dict( + # 1 => Matrix{Float64}(I, manifold_dimension(getManifold(statekind)), manifold_dimension(getManifold(statekind))) + ) + #Matrix{Float64}[] #previously `bw` --- """ - L2-to-L1 Bridge: point_parents[i] = j means points[i] is governed by means[j]. Points are leaves. + Geometric points permute field, allows fast binary tree operations and geometric points splits for manellic (ball) trees. + - Geometric split reqs at least 2*(N+1)-1 points -- e.g. when nodes have only right children, points=[1,2,-3]. """ - point_parents::Vector{Int} = Int[] + structure::Dict{Int,Vector{Int}} = Dict( + 1 => collect(1:length(points)) + ) end -JSON.omit_empty(::Type{<:StoredHomotopyBelief}) = true +JSON.omit_empty(::Type{<:HomotopyDensityDFG}) = true -function StoredHomotopyBelief(T::AbstractStateType) - return StoredHomotopyBelief{typeof(T), getPointType(T)}(; statekind = T) +function HomotopyDensityDFG(T::AbstractStateType) + return HomotopyDensityDFG{typeof(T), getPointType(T)}(; statekind = T) end -function StoredHomotopyBelief(::LeavesOnlyTopology, T::AbstractStateType; kwargs...) - return StoredHomotopyBelief{typeof(T), getPointType(T)}(; +function HomotopyDensityDFG(::LeavesOnlyTopology, T::AbstractStateType; kwargs...) + return HomotopyDensityDFG{typeof(T), getPointType(T)}(; statekind = T, topologykind = LeavesOnlyTopology(), - bandwidths = [zeros(getDimension(T), getDimension(T))], + trailing_details = Dict{Int, Matrix{Float64}}(), kwargs..., ) end -function StoredHomotopyBelief(::RootsOnlyTopology, T::AbstractStateType; kwargs...) - return StoredHomotopyBelief{typeof(T), getPointType(T)}(; +function HomotopyDensityDFG(::RootsOnlyTopology, T::AbstractStateType; kwargs...) + return HomotopyDensityDFG{typeof(T), getPointType(T)}(; statekind = T, topologykind = RootsOnlyTopology(), kwargs..., @@ -140,24 +261,24 @@ end function StructUtils.fielddefaults( ::StructUtils.StructStyle, - ::Type{StoredHomotopyBelief{T, P}}, + ::Type{HomotopyDensityDFG{T, P}}, ) where {T, P} return ( statekind = T(), topologykind = LeavesOnlyTopology(), - means = P[], - shapes = Matrix{Float64}[], + principal_coeffs = Float64[], + principal_elements = P[], + principal_details = Matrix{Float64}[], weights = Float64[], points = P[], - bandwidths = Matrix{Float64}[], - mean_parents = Int[], - point_parents = Int[], + trailing_details = Dict{Int, Matrix{Float64}}(), ) end -function resolveStoredBeliefType(lazyobj) +function resolveHomotopyDensityDFGType(lazyobj) statekind = liftStateKind(lazyobj.statekind[]) - return StoredHomotopyBelief{typeof(statekind), getPointType(statekind)} + return HomotopyDensityDFG{typeof(statekind), getPointType(statekind)} end -@choosetype StoredHomotopyBelief resolveStoredBeliefType \ No newline at end of file +@choosetype HomotopyDensityDFG resolveHomotopyDensityDFGType + diff --git a/src/entities/State.jl b/src/entities/State.jl index 10131d5f..01d55a70 100644 --- a/src/entities/State.jl +++ b/src/entities/State.jl @@ -21,7 +21,7 @@ $(TYPEDFIELDS) """ Generic stored belief for this state. """ - belief::StoredHomotopyBelief{T, P} = StoredHomotopyBelief{T, P}()#; statekind = T()) + belief::HomotopyDensityDFG{T, P} = HomotopyDensityDFG{T, P}()#; statekind = T()) """List of symbols for separator variables for this state, used in variable elimination and inference computations.""" separator::Vector{Symbol} = Symbol[] """False if initial numerical values are not yet available or stored values are not ready for further processing yet.""" @@ -47,7 +47,7 @@ end # ============================================================================== # FUTURE VIEW WRAPPER (Internal DFG Placeholder) # ============================================================================== -# NOTE: The `StoredHomotopyBelief` is currently expressive and fast enough that +# NOTE: The `HomotopyDensityDFG` is currently expressive and fast enough that # DFG does not need to store a resolved view next to it in memory. # # If future profiling requires it, DFG will introduce a verbose View wrapper @@ -56,7 +56,7 @@ end # abstract type AbstractHomotopyBeliefView end # # struct HomotopyBeliefView{T, P, M} <: AbstractHomotopyBeliefView -# stored::StoredHomotopyBelief{T, P} +# stored::HomotopyDensityDFG{T, P} # math_engine::M # Read-only instantiated solver object (e.g., AMP.HomotopyDensity) # end @@ -85,7 +85,7 @@ function StructUtils.fielddefaults( ::Type{State{T, P}}, ) where {T, P} return ( - belief = StoredHomotopyBelief{T, P}(; statekind = T()), + belief = HomotopyDensityDFG{T, P}(; statekind = T()), separator = Symbol[], initialized = false, observability = Float64[], @@ -95,13 +95,6 @@ function StructUtils.fielddefaults( ) end -refMeans(state::State) = state.belief.means -refCovariances(state::State) = state.belief.shapes -refWeights(state::State) = state.belief.weights -refPoints(state::State) = state.belief.points -refBandwidth(state::State) = state.belief.bandwidths[1] -refBandwidths(state::State) = state.belief.bandwidths -getTopologyKind(state::State) = state.belief.topologykind # we can also do somthing like this: function getComponent(state::State, i) diff --git a/src/entities/equality.jl b/src/entities/equality.jl index 72f38f31..5864dfd6 100644 --- a/src/entities/equality.jl +++ b/src/entities/equality.jl @@ -20,7 +20,8 @@ implement compare if needed. const GeneratedCompareUnion = Union{ Agent, Graphroot, - StoredHomotopyBelief, + HomotopyDensityDFG, + HomotopyReprDFG, State, Blobentry, Bloblet, diff --git a/src/services/AbstractDFG.jl b/src/services/AbstractDFG.jl index a429d2ce..a31297ca 100644 --- a/src/services/AbstractDFG.jl +++ b/src/services/AbstractDFG.jl @@ -24,21 +24,6 @@ function getGraph end """ getGraphLabel(dfg::AbstractDFG) = getLabel(getGraph(dfg)) -""" - $(SIGNATURES) - -!!! warning "Deprecated" - `getSolverParams(dfg)` is deprecated in DFG v0.29 Pass `SolverParams` directly - to `solveTree!()` as a keyword argument instead. -""" -function getSolverParams(dfg::AbstractDFG) - Base.depwarn( - "getSolverParams(dfg) is deprecated. SolverParams will be removed from the DFG object. " * - "Pass SolverParams directly to solveTree!() as a keyword argument instead.", - :getSolverParams, - ) - return dfg.solverParams -end """ $(SIGNATURES) diff --git a/src/services/compare.jl b/src/services/compare.jl index b8b2d3be..a1c90dde 100644 --- a/src/services/compare.jl +++ b/src/services/compare.jl @@ -155,7 +155,12 @@ end #Compare State function compare(a::State, b::State) refPoints(a) != refPoints(b) && @debug("val is not equal") === nothing && return false - refBandwidths(a) != refBandwidths(b) && + TP = true + for (k,v) in refBandwidths(a) + TP = TP && haskey(refBandwidths(b), k) + TP = TP && isapprox(v, refBandwidths(b)[k]; rtol = 1e-8) + end + !TP && @debug("bw is not equal") === nothing && return false # a.BayesNetOutVertIDs != b.BayesNetOutVertIDs && diff --git a/test/affiesandbox.jl b/test/affiesandbox.jl new file mode 100644 index 00000000..7abdbde2 --- /dev/null +++ b/test/affiesandbox.jl @@ -0,0 +1,78 @@ +julia> using JSON + +{ + "topology": { + "type": { + "pkg": "Main", + "name": "BinaryTruncFixedDepth", + "version": null + } + }, + "reprtype": { + "type": { + "pkg": "Main", + "name": "ConcentratedGaussianKernelKind", + "version": null + } + }, + "statetype": { + "pkg": "Main", + "name": "Pose{2}", + "version": null + } +} + + + + + + + + + +State{TestVariableType1, Vector{Float64}}(:default, TestVariableType1(), DistributedFactorGraphs.HomotopyDensityDFG{TestVariableType1, Vector{Float64}}(TestVariableType1(), DistributedFactorGraphs.LeavesOnlyTopology(), [[1.0]], Float64[], Float64[], Vector{Float64}[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}()), Symbol[], false, Float64[], false, 0), +State{TestVariableType1, Vector{Float64}}(:default, TestVariableType1(), DistributedFactorGraphs.HomotopyDensityDFG{TestVariableType1, Vector{Float64}}(TestVariableType1(), DistributedFactorGraphs.LeavesOnlyTopology(), [[1.0]], Float64[], Float64[], Vector{Float64}[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}()), Symbol[], false, Float64[], false, 0) + + + + +VariableDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:x1, 2026-05-07T15:07:47.542648064Z, Set([:VARIABLE]), OrderedDict{Symbol, State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}}(:default => State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:default, Pose{3}(), DistributedFactorGraphs.HomotopyDensityDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(Pose{3}(), DistributedFactorGraphs.HomotopyReprDFG{Pose{3}}(DistributedFactorGraphs.DefaultTopologyKind(), DistributedFactorGraphs.DefaultFormKind(), Pose{3}()), DistributedFactorGraphs.LeavesOnlyTopology(), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}(([0.0, 0.0, 0.0], [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0])), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}(([-0.9037181209535056, -0.8689856941604722, 0.048355945981813954], [0.8232040275412903 -0.5677257726213318 -0.004751435710420795; 0.4602571712696246 0.6624275077460037 0.5910610233100317; -0.3324130944091735 -0.4887506972710424 0.8066128504942057]))], Float64[], Float64[], ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}(), Dict(1 => [])), Symbol[], false, Float64[], false, 0), :parametric => State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:parametric, Pose{3}(), DistributedFactorGraphs.HomotopyDensityDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(Pose{3}(), DistributedFactorGraphs.HomotopyReprDFG{Pose{3}}(DistributedFactorGraphs.DefaultTopologyKind(), DistributedFactorGraphs.DefaultFormKind(), Pose{3}()), DistributedFactorGraphs.LeavesOnlyTopology(), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Float64[], Float64[], ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}(), Dict(1 => [])), Symbol[], false, Float64[], false, 0)), OrderedCollections.LittleDict{Symbol, Bloblet, Vector{Symbol}, Vector{Bloblet}}(:a => Bloblet(:a, "1"), :b => Bloblet(:b, "2")), OrderedDict{Symbol, Blobentry}(:bel => Blobentry(:bel, DistributedFactorGraphs.Multihash(UInt8[0x12, 0x20, 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14 … 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55]), 0x00000000, 0, :default, "", "", MIME type application/octet-stream, JSONText("{\"start\":54,\"N\":20,\"s\":\"test\"}"), 2026-05-07T15:07:47.542754048Z, v"0.1.0")), Base.RefValue{Int64}(1), Pose{3}(), nothing) +VariableDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:x1, 2026-05-07T15:07:47.542648064Z, Set([:VARIABLE]), OrderedDict{Symbol, State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}}(:default => State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:default, Pose{3}(), DistributedFactorGraphs.HomotopyDensityDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(Pose{3}(), DistributedFactorGraphs.HomotopyReprDFG{Pose{3}}(DistributedFactorGraphs.DefaultTopologyKind(), DistributedFactorGraphs.DefaultFormKind(), Pose{3}()), DistributedFactorGraphs.LeavesOnlyTopology(), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}(([0.0, 0.0, 0.0], [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0])), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}(([-0.9037181209535056, -0.8689856941604722, 0.048355945981813954], [0.8232040275412903 -0.5677257726213318 -0.004751435710420795; 0.4602571712696246 0.6624275077460037 0.5910610233100317; -0.3324130944091735 -0.4887506972710424 0.8066128504942057]))], Float64[], Float64[], ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}(), Dict(1 => [])), Symbol[], false, Float64[], false, 0), :parametric => State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:parametric, Pose{3}(), DistributedFactorGraphs.HomotopyDensityDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(Pose{3}(), DistributedFactorGraphs.HomotopyReprDFG{Pose{3}}(DistributedFactorGraphs.DefaultTopologyKind(), DistributedFactorGraphs.DefaultFormKind(), Pose{3}()), DistributedFactorGraphs.LeavesOnlyTopology(), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Float64[], Float64[], ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}(), Dict(1 => [])), Symbol[], false, Float64[], false, 0)), OrderedCollections.LittleDict{Symbol, Bloblet, Vector{Symbol}, Vector{Bloblet}}(:a => Bloblet(:a, "1"), :b => Bloblet(:b, "2")), OrderedDict{Symbol, Blobentry}(:bel => Blobentry(:bel, DistributedFactorGraphs.Multihash(UInt8[0x12, 0x20, 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14 … 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55]), 0x00000000, 0, :default, "", "", MIME type application/octet-stream, JSONText("{\"start\":54,\"N\":20,\"s\":\"test\"}"), 2026-05-07T15:07:47.542754048Z, v"0.1.0")), Base.RefValue{Int64}(1), Pose{3}(), nothing) + + + + + +VariableDFG parsefile round-trip: Test Failed at /home/dehann/.julia/dev/DistributedFactorGraphs/test/testSerializingVariables.jl:97 + Expression: v == parsed + Evaluated: VariableDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:x1, 2026-05-07T15:07:47.542648064Z, Set([:VARIABLE]), OrderedDict{Symbol, State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}}(:default => State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:default, Pose{3}(), DistributedFactorGraphs.HomotopyDensityDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(Pose{3}(), DistributedFactorGraphs.HomotopyReprDFG{Pose{3}}(DistributedFactorGraphs.DefaultTopologyKind(), DistributedFactorGraphs.DefaultFormKind(), Pose{3}()), DistributedFactorGraphs.LeavesOnlyTopology(), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}(([0.0, 0.0, 0.0], [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0])), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}(([-0.9037181209535056, -0.8689856941604722, 0.048355945981813954], [0.8232040275412903 -0.5677257726213318 -0.004751435710420795; 0.4602571712696246 0.6624275077460037 0.5910610233100317; -0.3324130944091735 -0.4887506972710424 0.8066128504942057]))], Float64[], Float64[], ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}(), Dict(1 => [])), Symbol[], false, Float64[], false, 0), :parametric => State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:parametric, Pose{3}(), DistributedFactorGraphs.HomotopyDensityDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(Pose{3}(), DistributedFactorGraphs.HomotopyReprDFG{Pose{3}}(DistributedFactorGraphs.DefaultTopologyKind(), DistributedFactorGraphs.DefaultFormKind(), Pose{3}()), DistributedFactorGraphs.LeavesOnlyTopology(), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Float64[], Float64[], ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}(), Dict(1 => [])), Symbol[], false, Float64[], false, 0)), OrderedCollections.LittleDict{Symbol, Bloblet, Vector{Symbol}, Vector{Bloblet}}(:a => Bloblet(:a, "1"), :b => Bloblet(:b, "2")), OrderedDict{Symbol, Blobentry}(:bel => Blobentry(:bel, DistributedFactorGraphs.Multihash(UInt8[0x12, 0x20, 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14 … 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55]), 0x00000000, 0, :default, "", "", MIME type application/octet-stream, JSONText("{\"start\":54,\"N\":20,\"s\":\"test\"}"), 2026-05-07T15:07:47.542754048Z, v"0.1.0")), Base.RefValue{Int64}(1), Pose{3}(), nothing) == VariableDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:x1, 2026-05-07T15:07:47.542648064Z, Set([:VARIABLE]), OrderedDict{Symbol, State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}}(:default => State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:default, Pose{3}(), DistributedFactorGraphs.HomotopyDensityDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(Pose{3}(), DistributedFactorGraphs.HomotopyReprDFG{Pose{3}}(DistributedFactorGraphs.DefaultTopologyKind(), DistributedFactorGraphs.DefaultFormKind(), Pose{3}()), DistributedFactorGraphs.LeavesOnlyTopology(), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}(([0.0, 0.0, 0.0], [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0])), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}(([-0.9037181209535056, -0.8689856941604722, 0.048355945981813954], [0.8232040275412903 -0.5677257726213318 -0.004751435710420795; 0.4602571712696246 0.6624275077460037 0.5910610233100317; -0.3324130944091735 -0.4887506972710424 0.8066128504942057]))], Float64[], Float64[], ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}(), Dict(1 => [])), Symbol[], false, Float64[], false, 0), :parametric => State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:parametric, Pose{3}(), DistributedFactorGraphs.HomotopyDensityDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(Pose{3}(), DistributedFactorGraphs.HomotopyReprDFG{Pose{3}}(DistributedFactorGraphs.DefaultTopologyKind(), DistributedFactorGraphs.DefaultFormKind(), Pose{3}()), DistributedFactorGraphs.LeavesOnlyTopology(), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Float64[], Float64[], ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}(), Dict(1 => [])), Symbol[], false, Float64[], false, 0)), OrderedCollections.LittleDict{Symbol, Bloblet, Vector{Symbol}, Vector{Bloblet}}(:a => Bloblet(:a, "1"), :b => Bloblet(:b, "2")), OrderedDict{Symbol, Blobentry}(:bel => Blobentry(:bel, DistributedFactorGraphs.Multihash(UInt8[0x12, 0x20, 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14 … 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55]), 0x00000000, 0, :default, "", "", MIME type application/octet-stream, JSONText("{\"start\":54,\"N\":20,\"s\":\"test\"}"), 2026-05-07T15:07:47.542754048Z, v"0.1.0")), Base.RefValue{Int64}(1), Pose{3}(), nothing) + Stacktrace: + [1] top-level scope + @ ~/.julia/dev/DistributedFactorGraphs/test/testSerializingVariables.jl:49 + [2] macro expansion + @ ~/.julia/juliaup/julia-1.13.0-rc1+0.x64.linux.gnu/share/julia/stdlib/v1.13/Test/src/Test.jl:1961 [inlined] + [3] macro expansion + @ ~/.julia/dev/DistributedFactorGraphs/test/testSerializingVariables.jl:91 [inlined] + [4] macro expansion + @ ~/.julia/juliaup/julia-1.13.0-rc1+0.x64.linux.gnu/share/julia/stdlib/v1.13/Test/src/Test.jl:1961 [inlined] + [5] macro expansion + @ ~/.julia/dev/DistributedFactorGraphs/test/testSerializingVariables.jl:97 [inlined] + [6] macro expansion + @ ~/.julia/juliaup/julia-1.13.0-rc1+0.x64.linux.gnu/share/julia/stdlib/v1.13/Test/src/Test.jl:753 [inlined] + +Complex (CircleGroup) end-to-end: Test Failed at /home/dehann/.julia/dev/DistributedFactorGraphs/test/testSerializingVariables.jl:129 + Expression: v == parsed + Evaluated: VariableDFG{CircleState, ComplexF64}(:c1, 2026-05-07T15:07:50.68094208Z, Set([:VARIABLE]), OrderedDict{Symbol, State{CircleState, ComplexF64}}(:default => State{CircleState, ComplexF64}(:default, CircleState(), DistributedFactorGraphs.HomotopyDensityDFG{CircleState, ComplexF64}(CircleState(), DistributedFactorGraphs.HomotopyReprDFG{CircleState}(DistributedFactorGraphs.DefaultTopologyKind(), DistributedFactorGraphs.DefaultFormKind(), CircleState()), DistributedFactorGraphs.LeavesOnlyTopology(), ComplexF64[1.0 + 0.0im, 0.5 + 0.866im], Float64[], Float64[], ComplexF64[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}(), Dict(1 => [])), Symbol[], false, Float64[], false, 0)), OrderedCollections.LittleDict{Symbol, Bloblet, Vector{Symbol}, Vector{Bloblet}}(:meta => Bloblet(:meta, "circle")), OrderedDict{Symbol, Blobentry}(), Base.RefValue{Int64}(1), CircleState(), nothing) == VariableDFG{CircleState, ComplexF64}(:c1, 2026-05-07T15:07:50.68094208Z, Set([:VARIABLE]), OrderedDict{Symbol, State{CircleState, ComplexF64}}(:default => State{CircleState, ComplexF64}(:default, CircleState(), DistributedFactorGraphs.HomotopyDensityDFG{CircleState, ComplexF64}(CircleState(), DistributedFactorGraphs.HomotopyReprDFG{CircleState}(DistributedFactorGraphs.DefaultTopologyKind(), DistributedFactorGraphs.DefaultFormKind(), CircleState()), DistributedFactorGraphs.LeavesOnlyTopology(), ComplexF64[1.0 + 0.0im, 0.5 + 0.866im], Float64[], Float64[], ComplexF64[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}(), Dict(1 => [])), Symbol[], false, Float64[], false, 0)), OrderedCollections.LittleDict{Symbol, Bloblet, Vector{Symbol}, Vector{Bloblet}}(:meta => Bloblet(:meta, "circle")), OrderedDict{Symbol, Blobentry}(), Base.RefValue{Int64}(1), CircleState(), nothing) + Stacktrace: + [1] top-level scope + @ ~/.julia/dev/DistributedFactorGraphs/test/testSerializingVariables.jl:49 + [2] macro expansion + @ ~/.julia/juliaup/julia-1.13.0-rc1+0.x64.linux.gnu/share/julia/stdlib/v1.13/Test/src/Test.jl:1961 [inlined] + [3] macro expansion + @ ~/.julia/dev/DistributedFactorGraphs/test/testSerializingVariables.jl:118 [inlined] + [4] macro expansion + @ ~/.julia/juliaup/julia-1.13.0-rc1+0.x64.linux.gnu/share/julia/stdlib/v1.13/Test/src/Test.jl:1961 [inlined] + [5] macro expansion + @ ~/.julia/dev/DistributedFactorGraphs/test/testSerializingVariables.jl:129 [inlined] + [6] macro expansion + @ ~/.julia/juliaup/julia-1.13.0-rc1+0.x64.linux.gnu/share/julia/stdlib/v1.13/Test/src/Test.jl:753 [inlined] diff --git a/test/compareTests.jl b/test/compareTests.jl index b9698b2c..2aa34790 100644 --- a/test/compareTests.jl +++ b/test/compareTests.jl @@ -6,6 +6,8 @@ using Dates # TestFunctorInferenceType1 # TestCCW1 +DistributedFactorGraphs.@usingDFG true + ## Generated compare functions # State vnd1 = State(:default, TestVariableType1()) diff --git a/test/testSerializingVariables.jl b/test/testSerializingVariables.jl index f4de87b5..eb3d1683 100644 --- a/test/testSerializingVariables.jl +++ b/test/testSerializingVariables.jl @@ -46,27 +46,27 @@ function make_test_variable() end @testset "Serializing Variables" begin - @testset "StoredHomotopyBelief round-trip" begin - bel = DFG.StoredHomotopyBelief(Pose{3}()) + @testset "HomotopyDensityDFG round-trip" begin + bel = DFG.HomotopyDensityDFG(Pose{3}()) push!(bel.points, DFG.getPointIdentity(Pose{3}())) G = DFG.getManifold(Pose{3}()) push!(bel.points, rand(G, ArrayPartition)) jstr = JSON.json(bel; pretty = true, style = DFG.DFGJSONStyle()) - parsed = JSON.parse(jstr, DFG.StoredHomotopyBelief; style = DFG.DFGJSONStyle()) + parsed = JSON.parse(jstr, DFG.HomotopyDensityDFG; style = DFG.DFGJSONStyle()) @test bel == parsed end @testset "RootsOnlyTopology round-trip" begin dim = DFG.getDimension(Pose{3}()) - bel = DFG.StoredHomotopyBelief( + bel = DFG.HomotopyDensityDFG( DFG.RootsOnlyTopology(), Pose{3}(); - means = [DFG.getPointIdentity(Pose{3}())], - shapes = [diagm(ones(dim))], + principal_elements = [DFG.getPointIdentity(Pose{3}())], + principal_details = [diagm(ones(dim))], ) jstr = JSON.json(bel; pretty = true, style = DFG.DFGJSONStyle()) - parsed = JSON.parse(jstr, DFG.StoredHomotopyBelief; style = DFG.DFGJSONStyle()) + parsed = JSON.parse(jstr, DFG.HomotopyDensityDFG; style = DFG.DFGJSONStyle()) @test bel == parsed end From 187cf53b735eeafd02dc5ce78a1f311cc2722ed0 Mon Sep 17 00:00:00 2001 From: dehann Date: Fri, 8 May 2026 00:07:48 -0700 Subject: [PATCH 3/8] struggling with json ignore on repr.partial --- src/Deprecated.jl | 14 ++++----- src/Serialization/StateSerialization.jl | 4 +-- src/entities/HomotopyDensity.jl | 41 +++++++++++++++++-------- test/testSerializingVariables.jl | 2 +- 4 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/Deprecated.jl b/src/Deprecated.jl index 26a58f93..0acfcb7d 100644 --- a/src/Deprecated.jl +++ b/src/Deprecated.jl @@ -2,11 +2,11 @@ refMeans(state::State) = state.belief.principal_elements -refCovariances(state::State) = state.belief.principal_details +refCovariances(state::State) = state.belief.principal_forms refWeights(state::State) = state.belief.weights refPoints(state::State) = state.belief.points -refBandwidth(state::State) = state.belief.trailing_details[1] -refBandwidths(state::State) = values(state.belief.trailing_details) # FIXME, unordered will be a problem +refBandwidth(state::State) = state.belief.trailing_forms[1] +refBandwidths(state::State) = values(state.belief.trailing_forms) # FIXME, unordered will be a problem getTopologyKind(state::State) = state.belief.topologykind @@ -18,9 +18,9 @@ function getproperty(obj::HomotopyDensityDFG, f::Symbol) ret = if f === :means getfield(obj, :principal_elements) elseif f === :shapes - getfield(obj, :principal_details) + getfield(obj, :principal_forms) elseif f === :bandwidths - getfield(obj, :trailing_details) + getfield(obj, :trailing_forms) else showerr = false getfield(obj, f) @@ -35,9 +35,9 @@ function setproperty!(obj::HomotopyDensityDFG, f::Symbol, val) ret = if f === :means setfield!(obj, :principal_elements, val) elseif f === :shapes - setfield!(obj, :principal_details, val) + setfield!(obj, :principal_forms, val) elseif f === :bandwidths - setfield!(obj, :trailing_details, val) + setfield!(obj, :trailing_forms, val) else showerr = false setfield!(obj, f, val) diff --git a/src/Serialization/StateSerialization.jl b/src/Serialization/StateSerialization.jl index 82114b24..a92eea3e 100644 --- a/src/Serialization/StateSerialization.jl +++ b/src/Serialization/StateSerialization.jl @@ -135,14 +135,14 @@ function unpackOldState(d) RootsOnlyTopology(), statekind; principal_elements = vals, - principal_details = [BW], + principal_forms = [BW], ) else belief = HomotopyDensityDFG( LeavesOnlyTopology(), statekind; points = vals, - trailing_details = Dict(1 => BW), + trailing_forms = Dict(1 => BW), ) end return State{T, getPointType(T)}(; diff --git a/src/entities/HomotopyDensity.jl b/src/entities/HomotopyDensity.jl index f832dafb..7d183034 100644 --- a/src/entities/HomotopyDensity.jl +++ b/src/entities/HomotopyDensity.jl @@ -7,9 +7,7 @@ abstract type AbstractStateType{N} end const StateType = AbstractStateType -# ============================================================================== -# HomotopyDensityDFG -# ============================================================================== + """ AbstractHomotopyTopology @@ -65,10 +63,23 @@ function StructUtils.lower(::StructUtils.StructStyle, p::AbstractDensityForm) end @choosetype AbstractDensityForm resolvePackedType +# TBD for best future looking structure here +abstract type AbstractPartialTrait end +function StructUtils.lower(::StructUtils.StructStyle, p::AbstractPartialTrait) + return StructUtils.lower(Packed(p)) +end +@choosetype AbstractPartialTrait resolvePackedType + + struct DefaultFormKind <: AbstractDensityForm end struct DefaultTopologyKind <: AbstractHomotopyTopology end +struct DefaultPartialKind <: AbstractPartialTrait end + +# ============================================================================== +# HomotopyDensityDFG +# ============================================================================== # FROM AMP @@ -100,10 +111,11 @@ struct DefaultTopologyKind <: AbstractHomotopyTopology end # Cons # - not itself a singleton type (not important?) # DOCS FORCE FIELDS TO BE SINGLETON -mutable struct HomotopyReprDFG{T <: StateType} +@tags mutable struct HomotopyReprDFG{T <: StateType} topologykind::AbstractHomotopyTopology # bitmap, jpeg, png - reprkind::AbstractDensityForm # RGB24, YCbCr, fullcov, uppercov, LieExpGaussianWrappedKind, ConcentrGaussKernelKind - statekind::T # Position{2} + reprkind::AbstractDensityForm # RGB24, YCbCr, fullcov, uppercov, LieExpGaussianWrappedKind, ConcentrGaussKernelKind + statekind::T # Position{2} + partial::AbstractPartialTrait #& (json = (ignore = true,),) # partials field needed for AMP v0.15, will be JSON ignored in DFG v0.29, needs better solution by DFG v0.30 end # # UX -- DataLevel 3 if OPTION A @@ -204,7 +216,12 @@ provided by AMP for features like pdf evaluation. """ @kwdef struct HomotopyDensityDFG{T <: StateType, P} statekind::T = T()# NOTE duplication for serialization and self description. - reprkind::HomotopyReprDFG{T} = HomotopyReprDFG{T}(DefaultTopologyKind(), DefaultFormKind(), T()) # NOTE duplication for serialization and self description. FIXME this is redundant with statekind, but we need it to be a struct for serde, so we duplicate the statekind info here for now. Future refactor could unify these concepts better. + reprkind::HomotopyReprDFG{T} = HomotopyReprDFG{T}( + DefaultTopologyKind(), + DefaultFormKind(), + T(), + DefaultPartialKind() + ) # NOTE duplication for serialization and self description. FIXME this is redundant with statekind, but we need it to be a struct for serde, so we duplicate the statekind info here for now. Future refactor could unify these concepts better. """A hint for downstream solvers on how to interpret this data (The 'How')""" topologykind::AbstractHomotopyTopology = LeavesOnlyTopology() @@ -217,13 +234,13 @@ provided by AMP for features like pdf evaluation. """ principal_coeffs::Vector{Float64} = Vector{Float64}() # FIXME getMajorsLength(reprkind)) principal_elements::Vector{P} = P[] # previously `val[1]` for Gaussian - principal_details::Vector{Matrix{Float64}} = Matrix{Float64}[] # previously `covar` existed but was stored in `bw` (hacky) + principal_forms::Vector{Matrix{Float64}} = Matrix{Float64}[] # previously `covar` existed but was stored in `bw` (hacky) """ Store minor eigenvalue details such as leaf bandwidth or reconstruction vectors. - When lifted for compute efficiency, this field is likely to hold something like PDMats. - When lowered or for serde, this field is likely to hold Dict{Int, Vector{Float64}}. """ - trailing_details::Dict{Int,Matrix{Float64}} = Dict( + trailing_forms::Dict{Int,Matrix{Float64}} = Dict( # 1 => Matrix{Float64}(I, manifold_dimension(getManifold(statekind)), manifold_dimension(getManifold(statekind))) ) #Matrix{Float64}[] #previously `bw` --- @@ -246,7 +263,7 @@ function HomotopyDensityDFG(::LeavesOnlyTopology, T::AbstractStateType; kwargs.. return HomotopyDensityDFG{typeof(T), getPointType(T)}(; statekind = T, topologykind = LeavesOnlyTopology(), - trailing_details = Dict{Int, Matrix{Float64}}(), + trailing_forms = Dict{Int, Matrix{Float64}}(), kwargs..., ) end @@ -268,10 +285,10 @@ function StructUtils.fielddefaults( topologykind = LeavesOnlyTopology(), principal_coeffs = Float64[], principal_elements = P[], - principal_details = Matrix{Float64}[], + principal_forms = Matrix{Float64}[], weights = Float64[], points = P[], - trailing_details = Dict{Int, Matrix{Float64}}(), + trailing_forms = Dict{Int, Matrix{Float64}}(), ) end diff --git a/test/testSerializingVariables.jl b/test/testSerializingVariables.jl index eb3d1683..b6f685f8 100644 --- a/test/testSerializingVariables.jl +++ b/test/testSerializingVariables.jl @@ -63,7 +63,7 @@ end DFG.RootsOnlyTopology(), Pose{3}(); principal_elements = [DFG.getPointIdentity(Pose{3}())], - principal_details = [diagm(ones(dim))], + principal_forms = [diagm(ones(dim))], ) jstr = JSON.json(bel; pretty = true, style = DFG.DFGJSONStyle()) parsed = JSON.parse(jstr, DFG.HomotopyDensityDFG; style = DFG.DFGJSONStyle()) From 07e8cc3fccec35073c8e254dc5e023de6e55be98 Mon Sep 17 00:00:00 2001 From: Johannes Terblanche <6612981+Affie@users.noreply.github.com> Date: Fri, 8 May 2026 10:22:09 +0200 Subject: [PATCH 4/8] Apply suggestions from code review formatting Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/Deprecated.jl | 64 +++++++++++++++------------------ src/entities/HomotopyDensity.jl | 42 +++++++--------------- src/entities/State.jl | 1 - src/services/AbstractDFG.jl | 1 - src/services/compare.jl | 6 ++-- 5 files changed, 42 insertions(+), 72 deletions(-) diff --git a/src/Deprecated.jl b/src/Deprecated.jl index 0acfcb7d..ab9fdc0a 100644 --- a/src/Deprecated.jl +++ b/src/Deprecated.jl @@ -1,6 +1,4 @@ - - refMeans(state::State) = state.belief.principal_elements refCovariances(state::State) = state.belief.principal_forms refWeights(state::State) = state.belief.weights @@ -9,49 +7,43 @@ refBandwidth(state::State) = state.belief.trailing_forms[1] refBandwidths(state::State) = values(state.belief.trailing_forms) # FIXME, unordered will be a problem getTopologyKind(state::State) = state.belief.topologykind - # # TODO deprecate import Base: getproperty, setproperty! function getproperty(obj::HomotopyDensityDFG, f::Symbol) - showerr = true - ret = if f === :means - getfield(obj, :principal_elements) - elseif f === :shapes - getfield(obj, :principal_forms) - elseif f === :bandwidths - getfield(obj, :trailing_forms) - else - showerr = false - getfield(obj, f) - end - - showerr && @error("HomotopyDensityDFG field access with $(f) is deprecated") - return ret + showerr = true + ret = if f === :means + getfield(obj, :principal_elements) + elseif f === :shapes + getfield(obj, :principal_forms) + elseif f === :bandwidths + getfield(obj, :trailing_forms) + else + showerr = false + getfield(obj, f) + end + + showerr && @error("HomotopyDensityDFG field access with $(f) is deprecated") + return ret end function setproperty!(obj::HomotopyDensityDFG, f::Symbol, val) - showerr = true - ret = if f === :means - setfield!(obj, :principal_elements, val) - elseif f === :shapes - setfield!(obj, :principal_forms, val) - elseif f === :bandwidths - setfield!(obj, :trailing_forms, val) - else - showerr = false - setfield!(obj, f, val) - end + showerr = true + ret = if f === :means + setfield!(obj, :principal_elements, val) + elseif f === :shapes + setfield!(obj, :principal_forms, val) + elseif f === :bandwidths + setfield!(obj, :trailing_forms, val) + else + showerr = false + setfield!(obj, f, val) + end - showerr && @error("HomotopyDensityDFG field access with $(f) is deprecated") - return ret + showerr && @error("HomotopyDensityDFG field access with $(f) is deprecated") + return ret end - - - - - ## ================================================================================ ## Deprecated in v0.29 ##================================================================================= @@ -831,4 +823,4 @@ function getSolverParams(dfg::AbstractDFG) # :getSolverParams, # ) return dfg.solverParams -end \ No newline at end of file +end diff --git a/src/entities/HomotopyDensity.jl b/src/entities/HomotopyDensity.jl index 7d183034..cbc240de 100644 --- a/src/entities/HomotopyDensity.jl +++ b/src/entities/HomotopyDensity.jl @@ -1,5 +1,4 @@ - ##============================================================================== ## Abstract Types ##============================================================================== @@ -7,7 +6,6 @@ abstract type AbstractStateType{N} end const StateType = AbstractStateType - """ AbstractHomotopyTopology @@ -70,7 +68,6 @@ function StructUtils.lower(::StructUtils.StructStyle, p::AbstractPartialTrait) end @choosetype AbstractPartialTrait resolvePackedType - struct DefaultFormKind <: AbstractDensityForm end struct DefaultTopologyKind <: AbstractHomotopyTopology end @@ -81,13 +78,11 @@ struct DefaultPartialKind <: AbstractPartialTrait end # HomotopyDensityDFG # ============================================================================== - # FROM AMP # abstract type AbstractBinaryTreeDensity <: AbstractHomotopyTopology end # const BinaryTreeDensity = AbstractBinaryTreeDensity # struct BinaryTruncFixedDepth{N} <: AbstractBinaryTreeDensity end - # abstract type AbstractKernel <: AbstractDensityForm end # @kwdef struct ConcentratedGaussianKernel{ # partial, # partial info for compiler, usually a value e.g. nothing or (1,3) @@ -95,8 +90,6 @@ struct DefaultPartialKind <: AbstractPartialTrait end # T # additional parameters # } <: AbstractKernel - - # Go with Option A in DFG v0.29, # Acknowledge design compromises (previous DFG v0.29 objective was to collect breaking changes on types, get as close to DFG v1-alpha): # - HomotopyDensityDFG/Live converters/packers (DataLevel 3.5) @@ -112,10 +105,10 @@ struct DefaultPartialKind <: AbstractPartialTrait end # - not itself a singleton type (not important?) # DOCS FORCE FIELDS TO BE SINGLETON @tags mutable struct HomotopyReprDFG{T <: StateType} - topologykind::AbstractHomotopyTopology # bitmap, jpeg, png - reprkind::AbstractDensityForm # RGB24, YCbCr, fullcov, uppercov, LieExpGaussianWrappedKind, ConcentrGaussKernelKind - statekind::T # Position{2} - partial::AbstractPartialTrait #& (json = (ignore = true,),) # partials field needed for AMP v0.15, will be JSON ignored in DFG v0.29, needs better solution by DFG v0.30 + topologykind::AbstractHomotopyTopology # bitmap, jpeg, png + reprkind::AbstractDensityForm # RGB24, YCbCr, fullcov, uppercov, LieExpGaussianWrappedKind, ConcentrGaussKernelKind + statekind::T # Position{2} + partial::AbstractPartialTrait #& (json = (ignore = true,),) # partials field needed for AMP v0.15, will be JSON ignored in DFG v0.29, needs better solution by DFG v0.30 end # # UX -- DataLevel 3 if OPTION A @@ -124,7 +117,6 @@ end # # is this dynamic -- # plot(X1::HomotopyDensityDFG) = plot(what_topology(X1), X1) - # struct HomotopyReprLive{O <: AbstractHomotopyTopology, R <: AbstractDensityForm, T <: StateType} # ... # end @@ -149,8 +141,6 @@ end # repr = HomotopyDensityLive{HomotopyReprLive{FancyPrincipalTrailingSecondOrder, , }} # dothis(getReprType(repr), repr) --> (prinicipal::FancyStats, trailing::SecondOrderStats) - - # typeof(Pose2()) = Pose2 # getKind(...) -> Pose2() @@ -174,7 +164,6 @@ end # ConcGaussianKernelKind <: AbstractDensityForm # SecondOrderStatsKind <: AbstractDensityForm - # # this is a good "reprtype", what Dehann is trying # { # "topology": "BinaryTruncFixedDepth{3}", @@ -182,7 +171,6 @@ end # "statetype": "Pose2", # } - # # this is a bad "reprtype", what Dehann already avoided # { # "topology": "BinaryTruncFixedDepth{3}", @@ -190,9 +178,6 @@ end # "statetype": "Pose2", # } - - - """ HomotopyDensityDFG{H <: HomotopyRepr, P} @@ -217,10 +202,10 @@ provided by AMP for features like pdf evaluation. @kwdef struct HomotopyDensityDFG{T <: StateType, P} statekind::T = T()# NOTE duplication for serialization and self description. reprkind::HomotopyReprDFG{T} = HomotopyReprDFG{T}( - DefaultTopologyKind(), - DefaultFormKind(), - T(), - DefaultPartialKind() + DefaultTopologyKind(), + DefaultFormKind(), + T(), + DefaultPartialKind(), ) # NOTE duplication for serialization and self description. FIXME this is redundant with statekind, but we need it to be a struct for serde, so we duplicate the statekind info here for now. Future refactor could unify these concepts better. """A hint for downstream solvers on how to interpret this data (The 'How')""" topologykind::AbstractHomotopyTopology = LeavesOnlyTopology() @@ -240,17 +225,15 @@ provided by AMP for features like pdf evaluation. - When lifted for compute efficiency, this field is likely to hold something like PDMats. - When lowered or for serde, this field is likely to hold Dict{Int, Vector{Float64}}. """ - trailing_forms::Dict{Int,Matrix{Float64}} = Dict( - # 1 => Matrix{Float64}(I, manifold_dimension(getManifold(statekind)), manifold_dimension(getManifold(statekind))) + trailing_forms::Dict{Int, Matrix{Float64}} = Dict( + # 1 => Matrix{Float64}(I, manifold_dimension(getManifold(statekind)), manifold_dimension(getManifold(statekind))) ) - #Matrix{Float64}[] #previously `bw` --- + #Matrix{Float64}[] #previously `bw` --- """ Geometric points permute field, allows fast binary tree operations and geometric points splits for manellic (ball) trees. - Geometric split reqs at least 2*(N+1)-1 points -- e.g. when nodes have only right children, points=[1,2,-3]. """ - structure::Dict{Int,Vector{Int}} = Dict( - 1 => collect(1:length(points)) - ) + structure::Dict{Int, Vector{Int}} = Dict(1 => collect(1:length(points))) end JSON.omit_empty(::Type{<:HomotopyDensityDFG}) = true @@ -298,4 +281,3 @@ function resolveHomotopyDensityDFGType(lazyobj) end @choosetype HomotopyDensityDFG resolveHomotopyDensityDFGType - diff --git a/src/entities/State.jl b/src/entities/State.jl index 01d55a70..e626fe51 100644 --- a/src/entities/State.jl +++ b/src/entities/State.jl @@ -95,7 +95,6 @@ function StructUtils.fielddefaults( ) end - # we can also do somthing like this: function getComponent(state::State, i) return ( diff --git a/src/services/AbstractDFG.jl b/src/services/AbstractDFG.jl index a31297ca..e3231f9c 100644 --- a/src/services/AbstractDFG.jl +++ b/src/services/AbstractDFG.jl @@ -24,7 +24,6 @@ function getGraph end """ getGraphLabel(dfg::AbstractDFG) = getLabel(getGraph(dfg)) - """ $(SIGNATURES) diff --git a/src/services/compare.jl b/src/services/compare.jl index a1c90dde..cf73e795 100644 --- a/src/services/compare.jl +++ b/src/services/compare.jl @@ -156,13 +156,11 @@ end function compare(a::State, b::State) refPoints(a) != refPoints(b) && @debug("val is not equal") === nothing && return false TP = true - for (k,v) in refBandwidths(a) + for (k, v) in refBandwidths(a) TP = TP && haskey(refBandwidths(b), k) TP = TP && isapprox(v, refBandwidths(b)[k]; rtol = 1e-8) end - !TP && - @debug("bw is not equal") === nothing && - return false + !TP && @debug("bw is not equal") === nothing && return false # a.BayesNetOutVertIDs != b.BayesNetOutVertIDs && # @debug("BayesNetOutVertIDs is not equal") === nothing && # return false From c94405ac9950d8f7772e768a2ecbfcf951414bd6 Mon Sep 17 00:00:00 2001 From: Johannes Terblanche Date: Sun, 10 May 2026 13:06:16 +0200 Subject: [PATCH 5/8] HomotopyDensityDFG closer to AMP type --- src/Deprecated.jl | 78 +++------ src/Serialization/DFGStructStyles.jl | 15 ++ src/Serialization/StateSerialization.jl | 10 +- src/entities/Factor.jl | 2 +- src/entities/HomotopyDensity.jl | 216 +++++------------------- src/entities/State.jl | 2 +- src/services/compare.jl | 14 +- src/services/print.jl | 2 +- test/affiesandbox.jl | 78 --------- test/testSerializingVariables.jl | 6 +- 10 files changed, 99 insertions(+), 324 deletions(-) delete mode 100644 test/affiesandbox.jl diff --git a/src/Deprecated.jl b/src/Deprecated.jl index ab9fdc0a..5c7614aa 100644 --- a/src/Deprecated.jl +++ b/src/Deprecated.jl @@ -1,49 +1,3 @@ - -refMeans(state::State) = state.belief.principal_elements -refCovariances(state::State) = state.belief.principal_forms -refWeights(state::State) = state.belief.weights -refPoints(state::State) = state.belief.points -refBandwidth(state::State) = state.belief.trailing_forms[1] -refBandwidths(state::State) = values(state.belief.trailing_forms) # FIXME, unordered will be a problem -getTopologyKind(state::State) = state.belief.topologykind - -# # TODO deprecate -import Base: getproperty, setproperty! - -function getproperty(obj::HomotopyDensityDFG, f::Symbol) - showerr = true - ret = if f === :means - getfield(obj, :principal_elements) - elseif f === :shapes - getfield(obj, :principal_forms) - elseif f === :bandwidths - getfield(obj, :trailing_forms) - else - showerr = false - getfield(obj, f) - end - - showerr && @error("HomotopyDensityDFG field access with $(f) is deprecated") - return ret -end - -function setproperty!(obj::HomotopyDensityDFG, f::Symbol, val) - showerr = true - ret = if f === :means - setfield!(obj, :principal_elements, val) - elseif f === :shapes - setfield!(obj, :principal_forms, val) - elseif f === :bandwidths - setfield!(obj, :trailing_forms, val) - else - showerr = false - setfield!(obj, f, val) - end - - showerr && @error("HomotopyDensityDFG field access with $(f) is deprecated") - return ret -end - ## ================================================================================ ## Deprecated in v0.29 ##================================================================================= @@ -816,11 +770,31 @@ end to `solveTree!()` as a keyword argument instead. """ function getSolverParams(dfg::AbstractDFG) - #FIXME uncomment before committing only temp for spamming - # Base.depwarn( - # "getSolverParams(dfg) is deprecated. SolverParams will be removed from the DFG object. " * - # "Pass SolverParams directly to solveTree!() as a keyword argument instead.", - # :getSolverParams, - # ) + Base.depwarn( + "getSolverParams(dfg) is deprecated. SolverParams will be removed from the DFG object. " * + "Pass SolverParams directly to solveTree!() as a keyword argument instead.", + :getSolverParams, + ) return dfg.solverParams end + +# TODO +# The `ref*` accessors use topology-specific lens names while the fields use topology-neutral homotopy names: +# Something like: +# refPrincipalElements(state::State) = state.belief.principal_elements +# refPrincipalForms(state::State) = state.belief.principal_forms +# refTrailingForms(state::State) = state.belief.trailing_forms +# Then IIF defines the lens-specific wrappers with topology dispatch, something like: +# refMeans(state) = refPrincipalElements(state) # RootsOnly view +# refCovariances(state) = refPrincipalForms(state) # RootsOnly view +# refBandwidth(state) = refTrailingForms(state)[1] # LeavesOnly view +# But my (JT) preference is for HomotopyBeliefDFG to contain a neutral homotopy tree of nodes +# and use tree accessors on the node level and not raw references. + +refMeans(state::State) = state.belief.principal_elements +refCovariances(state::State) = state.belief.principal_forms +refWeights(state::State) = state.belief.weights +refPoints(state::State) = state.belief.points +refBandwidth(state::State) = state.belief.trailing_forms[1] +refBandwidths(state::State) = SparseArrays.nonzeros(state.belief.trailing_forms) +getTopologyKind(state::State) = state.belief.topologykind diff --git a/src/Serialization/DFGStructStyles.jl b/src/Serialization/DFGStructStyles.jl index 00da8c3a..470cf493 100644 --- a/src/Serialization/DFGStructStyles.jl +++ b/src/Serialization/DFGStructStyles.jl @@ -53,6 +53,7 @@ function StructUtils.lift(::DFGJSONStyle, ::Type{TimeDateZone}, x::AbstractStrin end #TODO StructUtils v2.7 adds support for StaticArrays, update, test, and remove these overloads if they work as expected +# does not work in v2.7.0 without these overloads. # SArray serialization overloads StructUtils.lower(::DFGJSONStyle, x::SArray) = x @@ -106,3 +107,17 @@ end # function StructUtils.lift(::DFGJSONStyle, ::Type{T}, x::JSON.LazyValue) where T <: Complex # return T(x.re[], x.im[]), nothing # end + +# SparseVector — serialize as struct (n, nzind, nzval fields). +# Must override arraylike since SparseVector <: AbstractVector. +# lower avoids isassigned dispatch going through getindex→zero(eltype). +StructUtils.structlike(::DFGJSONStyle, ::Type{<:SparseVector}) = true +StructUtils.arraylike(::DFGJSONStyle, ::Type{<:SparseVector}) = false + +function StructUtils.lower(::DFGJSONStyle, x::SparseVector) + return ( + n = length(x), + nzind = SparseArrays.nonzeroinds(x), + nzval = SparseArrays.nonzeros(x), + ) +end diff --git a/src/Serialization/StateSerialization.jl b/src/Serialization/StateSerialization.jl index a92eea3e..95d2e6ec 100644 --- a/src/Serialization/StateSerialization.jl +++ b/src/Serialization/StateSerialization.jl @@ -131,18 +131,14 @@ function unpackOldState(d) label = Symbol(d.solveKey) !isempty(d.covar) && error("covar field is not supported") if label == :parametric - belief = HomotopyDensityDFG( - RootsOnlyTopology(), - statekind; + belief = HomotopyDensityDFG{T, getPointType(T)}(; principal_elements = vals, principal_forms = [BW], ) else - belief = HomotopyDensityDFG( - LeavesOnlyTopology(), - statekind; + belief = HomotopyDensityDFG{T, getPointType(T)}(; points = vals, - trailing_forms = Dict(1 => BW), + trailing_forms = sparsevec(Dict(1 => BW)), ) end return State{T, getPointType(T)}(; diff --git a/src/entities/Factor.jl b/src/entities/Factor.jl index 4004a474..106a5813 100644 --- a/src/entities/Factor.jl +++ b/src/entities/Factor.jl @@ -30,7 +30,7 @@ end eliminated::Bool = false potentialused::Bool = false end - +#TODO add `tension` and consider keying as per variable statelabel ##============================================================================== ## Factors ##============================================================================== diff --git a/src/entities/HomotopyDensity.jl b/src/entities/HomotopyDensity.jl index cbc240de..096c74af 100644 --- a/src/entities/HomotopyDensity.jl +++ b/src/entities/HomotopyDensity.jl @@ -6,49 +6,8 @@ abstract type AbstractStateType{N} end const StateType = AbstractStateType -""" - AbstractHomotopyTopology - -Describes the physical layout of the nodes within a `HomotopyDensityDFG`. - -Since all beliefs in the Caesar ecosystem are fundamentally Homotopy densities, -this trait acts as a lightweight dispatch hint (a "Lens Selector"). It indicates -which parts of the tree (Roots vs. Leaves) are currently populated and how they -are wired, without requiring downstream packages to inspect the underlying vectors. - -**Role of the Topology Trait:** -- **DFG:** Determines how to serialize and spatial-index the belief in the database. -- **Visualizers:** Decides how to render the data (e.g., drawing ellipses for roots vs. a point cloud for leaves). -- **IIF/AMP:** Selects the correct mathematical view to construct (e.g., `MvNormal` vs. `ManifoldKernelDensity` vs. a full `HomotopyDensity`). - -!!! note "State, not Strategy" - The trait purely describes the *current physical shape* of the data (e.g., "I currently only have Roots populated"). - It does *not* dictate the solver strategy (e.g., "You must use a parametric solver"). - The math engine is always free to convert or expand the data based on the graph's needs. - -**Extending:** -If the standard tree-based Homotopy model does not fit your specific data layout -or solver requirements, you are encouraged to extend this abstract type with your -own custom topology struct. Alternatively, if you believe your use case represents -a missing core layout, please open an issue to discuss adding it to the -foundational ecosystem. -""" abstract type AbstractHomotopyTopology end - -# --- 1. The Roots --- -"L1 structural nodes only. No L2 samples. (Schema: `means`, `weights`, `shapes` populated. `points` empty.)" -struct RootsOnlyTopology <: AbstractHomotopyTopology end - -# --- 2. The Leaves --- -"L2 raw samples only. No L1 structure. (Schema: `points`, `bandwidths` populated. `means` empty.)" -struct LeavesOnlyTopology <: AbstractHomotopyTopology end - -# --- 3. The Full Trees --- -"Tree packed in arrays using 2i, 2i+1 math.(Schema: L1 and L2 populated. Parent arrays empty.)" -struct ImplicitTreeTopology <: AbstractHomotopyTopology end - -"Full tree using adjacency lists. (Schema: L1, L2, and Parent arrays fully populated.)" -struct ExplicitTreeTopology <: AbstractHomotopyTopology end +struct DefaultTopologyKind <: AbstractHomotopyTopology end function StructUtils.lower(::StructUtils.StructStyle, p::AbstractHomotopyTopology) return StructUtils.lower(Packed(p)) @@ -56,6 +15,7 @@ end @choosetype AbstractHomotopyTopology resolvePackedType abstract type AbstractDensityForm end +struct DefaultFormKind <: AbstractDensityForm end function StructUtils.lower(::StructUtils.StructStyle, p::AbstractDensityForm) return StructUtils.lower(Packed(p)) end @@ -68,115 +28,32 @@ function StructUtils.lower(::StructUtils.StructStyle, p::AbstractPartialTrait) end @choosetype AbstractPartialTrait resolvePackedType -struct DefaultFormKind <: AbstractDensityForm end - -struct DefaultTopologyKind <: AbstractHomotopyTopology end - -struct DefaultPartialKind <: AbstractPartialTrait end - # ============================================================================== # HomotopyDensityDFG # ============================================================================== - -# FROM AMP -# abstract type AbstractBinaryTreeDensity <: AbstractHomotopyTopology end -# const BinaryTreeDensity = AbstractBinaryTreeDensity -# struct BinaryTruncFixedDepth{N} <: AbstractBinaryTreeDensity end - -# abstract type AbstractKernel <: AbstractDensityForm end -# @kwdef struct ConcentratedGaussianKernel{ -# partial, # partial info for compiler, usually a value e.g. nothing or (1,3) -# K <: Distributions.MvNormal, # kernel info for compiler -# T # additional parameters -# } <: AbstractKernel - -# Go with Option A in DFG v0.29, -# Acknowledge design compromises (previous DFG v0.29 objective was to collect breaking changes on types, get as close to DFG v1-alpha): -# - HomotopyDensityDFG/Live converters/packers (DataLevel 3.5) -# - HomotopyDensityDFG.reprkind::HomotopyReprDFG -# - HomotopyReprDFG/Live converters/packers (DataLevel 3.5) -# - HomotopyReprDFG.partial has Legacy in AMP v0.15 through Caesar that needs something better by DFG v0.30 -# - DFG v0.29 will JSON ignore HomotopyReprDFG.partial field -##OPTION A -# Pros -# - state container is type stable, -# - fields can be singleton so easier to serde -# Cons -# - not itself a singleton type (not important?) -# DOCS FORCE FIELDS TO BE SINGLETON -@tags mutable struct HomotopyReprDFG{T <: StateType} - topologykind::AbstractHomotopyTopology # bitmap, jpeg, png - reprkind::AbstractDensityForm # RGB24, YCbCr, fullcov, uppercov, LieExpGaussianWrappedKind, ConcentrGaussKernelKind - statekind::T # Position{2} - partial::AbstractPartialTrait #& (json = (ignore = true,),) # partials field needed for AMP v0.15, will be JSON ignored in DFG v0.29, needs better solution by DFG v0.30 +# @kwarg struct HomotopyReprDFG{ +@defaults struct HomotopyReprDFG{ + T <: StateType, + HT <: AbstractHomotopyTopology, + RF <: AbstractDensityForm, + PT <: Union{Nothing, <:AbstractPartialTrait}, +} + topologykind::HT + formkind::RF + statekind::T + partial::PT = nothing & (ignore = true,) #TODO deprecated field, still needed for AMP. + # partials field needed for AMP v0.15, will be JSON ignored in DFG v0.29, needs better solution by DFG v0.30 end -# # UX -- DataLevel 3 if OPTION A -# X1 = getState(:naive).belief ::HomotopyDensityDFG{Pose2} -# X1 = getState(:research1).belief ::HomotopyDensityDFG{Pose2} # principals look completely different -# # is this dynamic -- -# plot(X1::HomotopyDensityDFG) = plot(what_topology(X1), X1) - -# struct HomotopyReprLive{O <: AbstractHomotopyTopology, R <: AbstractDensityForm, T <: StateType} -# ... -# end +StructUtils.structlike(::StructUtils.StructStyle, ::Type{<:HomotopyReprDFG}) = true -# struct FancyStats{S} -# x::Vector{Float64} -# y::Vector{S} -# end - -# struct SecondOrderStats{P, S} -# m0::Vector{Float64} -# m1::Vector{P} -# m2::Vector{S} -# end -# struct HomotopyDensityDFG{T <: StateType, A, B} -# principal::A -# trailing::B -# .... -# end - -# struct FancyPrincipalTrailingSecondOrder <: AbstractHomotopyTopology end -# repr = HomotopyDensityLive{HomotopyReprLive{FancyPrincipalTrailingSecondOrder, , }} -# dothis(getReprType(repr), repr) --> (prinicipal::FancyStats, trailing::SecondOrderStats) - -# typeof(Pose2()) = Pose2 -# getKind(...) -> Pose2() - -# getType(::Kind) = .... -# getType(ConcentratedGaussianKernelKind) = ConcentratedGaussianKernel - -# ConcentratedGaussianKernel(w...; kw...) # this calls the constructor to make a new object -# # AbstractDensityForm tells compiler what to do here -# howthis(ConcGaussKind)(w...; kw...) --> ::ConcentratedGaussianKernel{A,B,C} # this is functional programming to call the constructor -# howthis(ConcGaussKind, w...; kw...) --> ::ConcentratedGaussianKernel{A,B,C} # this is manual dispatch to create a new object - -# # - -# dothis(::ConcGaussKind, w...; kw...) --> ::ConcentratedGaussianKernel{A,B,C} # this is manual dispatch to create a new object -# dothis(repr::HomotopyReprDFG) = dothis(getReprType(repr), repr) - -# dothis(getReprType(belief), belief) -# dothis(getReprType(belief))(belief) # manual obj constructor rather than using JSON for details - -# LieExpWrappedKind <: AbstractDensityForm -# ConcGaussianKernelKind <: AbstractDensityForm -# SecondOrderStatsKind <: AbstractDensityForm - -# # this is a good "reprtype", what Dehann is trying -# { -# "topology": "BinaryTruncFixedDepth{3}", -# "reprtype": "ConcentratedGaussianKernelKind", -# "statetype": "Pose2", -# } - -# # this is a bad "reprtype", what Dehann already avoided -# { -# "topology": "BinaryTruncFixedDepth{3}", -# "reprtype": "ConcentratedGaussianKernel{this, that, whatever}", -# "statetype": "Pose2", -# } +function resolveHomotopyReprDFGType(lazyobj) + topo = resolveType(lazyobj.topologykind[]) + form = resolveType(lazyobj.formkind[]) + statekind = liftStateKind(lazyobj.statekind[]) + return HomotopyReprDFG{typeof(statekind), topo, form, Nothing} +end +@choosetype HomotopyReprDFG resolveHomotopyReprDFGType """ HomotopyDensityDFG{H <: HomotopyRepr, P} @@ -200,15 +77,10 @@ provided by AMP for features like pdf evaluation. - a.k.a. model order reduction given a topology selection """ @kwdef struct HomotopyDensityDFG{T <: StateType, P} - statekind::T = T()# NOTE duplication for serialization and self description. - reprkind::HomotopyReprDFG{T} = HomotopyReprDFG{T}( - DefaultTopologyKind(), - DefaultFormKind(), - T(), - DefaultPartialKind(), - ) # NOTE duplication for serialization and self description. FIXME this is redundant with statekind, but we need it to be a struct for serde, so we duplicate the statekind info here for now. Future refactor could unify these concepts better. + reprkind::HomotopyReprDFG{T} = + HomotopyReprDFG(DefaultTopologyKind(), DefaultFormKind(), T(), nothing) """A hint for downstream solvers on how to interpret this data (The 'How')""" - topologykind::AbstractHomotopyTopology = LeavesOnlyTopology() + topologykind::AbstractHomotopyTopology = DefaultTopologyKind() points::Vector{P} = P[] # previously `val` weights::Vector{Float64} = Float64[] @@ -225,36 +97,27 @@ provided by AMP for features like pdf evaluation. - When lifted for compute efficiency, this field is likely to hold something like PDMats. - When lowered or for serde, this field is likely to hold Dict{Int, Vector{Float64}}. """ - trailing_forms::Dict{Int, Matrix{Float64}} = Dict( - # 1 => Matrix{Float64}(I, manifold_dimension(getManifold(statekind)), manifold_dimension(getManifold(statekind))) - ) - #Matrix{Float64}[] #previously `bw` --- + trailing_forms::SparseVector{Matrix{Float64}, Int} = spzeros(Matrix{Float64}, 1) #previously `bw` """ Geometric points permute field, allows fast binary tree operations and geometric points splits for manellic (ball) trees. - Geometric split reqs at least 2*(N+1)-1 points -- e.g. when nodes have only right children, points=[1,2,-3]. """ - structure::Dict{Int, Vector{Int}} = Dict(1 => collect(1:length(points))) + structure::SparseVector{Vector{Int}, Int} = spzeros(Vector{Int}, 1) end JSON.omit_empty(::Type{<:HomotopyDensityDFG}) = true function HomotopyDensityDFG(T::AbstractStateType) - return HomotopyDensityDFG{typeof(T), getPointType(T)}(; statekind = T) + return HomotopyDensityDFG{typeof(T), getPointType(T)}() end -function HomotopyDensityDFG(::LeavesOnlyTopology, T::AbstractStateType; kwargs...) +function HomotopyDensityDFG( + topology::AbstractHomotopyTopology, + T::AbstractStateType; + kwargs..., +) return HomotopyDensityDFG{typeof(T), getPointType(T)}(; - statekind = T, - topologykind = LeavesOnlyTopology(), - trailing_forms = Dict{Int, Matrix{Float64}}(), - kwargs..., - ) -end - -function HomotopyDensityDFG(::RootsOnlyTopology, T::AbstractStateType; kwargs...) - return HomotopyDensityDFG{typeof(T), getPointType(T)}(; - statekind = T, - topologykind = RootsOnlyTopology(), + topologykind = topology, kwargs..., ) end @@ -264,19 +127,22 @@ function StructUtils.fielddefaults( ::Type{HomotopyDensityDFG{T, P}}, ) where {T, P} return ( - statekind = T(), - topologykind = LeavesOnlyTopology(), + topologykind = DefaultTopologyKind(), principal_coeffs = Float64[], principal_elements = P[], principal_forms = Matrix{Float64}[], weights = Float64[], points = P[], - trailing_forms = Dict{Int, Matrix{Float64}}(), + trailing_forms = sparsevec(Int[], Matrix{Float64}[]), ) end +function StructUtils.fieldtags(::StructUtils.StructStyle, ::Type{<:HomotopyDensityDFG}) + return (reprkind = (choosetype = resolveHomotopyReprDFGType,),) +end + function resolveHomotopyDensityDFGType(lazyobj) - statekind = liftStateKind(lazyobj.statekind[]) + statekind = liftStateKind(lazyobj.reprkind.statekind[]) return HomotopyDensityDFG{typeof(statekind), getPointType(statekind)} end diff --git a/src/entities/State.jl b/src/entities/State.jl index e626fe51..f783728f 100644 --- a/src/entities/State.jl +++ b/src/entities/State.jl @@ -85,7 +85,7 @@ function StructUtils.fielddefaults( ::Type{State{T, P}}, ) where {T, P} return ( - belief = HomotopyDensityDFG{T, P}(; statekind = T()), + belief = HomotopyDensityDFG{T, P}(), separator = Symbol[], initialized = false, observability = Float64[], diff --git a/src/services/compare.jl b/src/services/compare.jl index cf73e795..bae84288 100644 --- a/src/services/compare.jl +++ b/src/services/compare.jl @@ -155,12 +155,16 @@ end #Compare State function compare(a::State, b::State) refPoints(a) != refPoints(b) && @debug("val is not equal") === nothing && return false - TP = true - for (k, v) in refBandwidths(a) - TP = TP && haskey(refBandwidths(b), k) - TP = TP && isapprox(v, refBandwidths(b)[k]; rtol = 1e-8) + bwa = a.belief.trailing_forms + bwb = b.belief.trailing_forms + SparseArrays.nonzeroinds(bwa) != SparseArrays.nonzeroinds(bwb) && + @debug("bw indices not equal") === nothing && + return false + for (va, vb) in zip(SparseArrays.nonzeros(bwa), SparseArrays.nonzeros(bwb)) + !isapprox(va, vb; rtol = 1e-8) && + @debug("bw is not equal") === nothing && + return false end - !TP && @debug("bw is not equal") === nothing && return false # a.BayesNetOutVertIDs != b.BayesNetOutVertIDs && # @debug("BayesNetOutVertIDs is not equal") === nothing && # return false diff --git a/src/services/print.jl b/src/services/print.jl index 9dc61a81..dc99213f 100644 --- a/src/services/print.jl +++ b/src/services/print.jl @@ -61,7 +61,7 @@ function printVariable( bws = refBandwidths(vnd) if !isempty(bws) print(ioc, " kde bandwidths: ") - println(ioc, round.(bws[1]; digits = 4)) + println(ioc, round.(first(bws); digits = 4)) end printstyled(ioc, " VNDs: "; bold = true) println(ioc, solk[smsk], 4 < lsolk ? "..." : "") diff --git a/test/affiesandbox.jl b/test/affiesandbox.jl deleted file mode 100644 index 7abdbde2..00000000 --- a/test/affiesandbox.jl +++ /dev/null @@ -1,78 +0,0 @@ -julia> using JSON - -{ - "topology": { - "type": { - "pkg": "Main", - "name": "BinaryTruncFixedDepth", - "version": null - } - }, - "reprtype": { - "type": { - "pkg": "Main", - "name": "ConcentratedGaussianKernelKind", - "version": null - } - }, - "statetype": { - "pkg": "Main", - "name": "Pose{2}", - "version": null - } -} - - - - - - - - - -State{TestVariableType1, Vector{Float64}}(:default, TestVariableType1(), DistributedFactorGraphs.HomotopyDensityDFG{TestVariableType1, Vector{Float64}}(TestVariableType1(), DistributedFactorGraphs.LeavesOnlyTopology(), [[1.0]], Float64[], Float64[], Vector{Float64}[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}()), Symbol[], false, Float64[], false, 0), -State{TestVariableType1, Vector{Float64}}(:default, TestVariableType1(), DistributedFactorGraphs.HomotopyDensityDFG{TestVariableType1, Vector{Float64}}(TestVariableType1(), DistributedFactorGraphs.LeavesOnlyTopology(), [[1.0]], Float64[], Float64[], Vector{Float64}[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}()), Symbol[], false, Float64[], false, 0) - - - - -VariableDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:x1, 2026-05-07T15:07:47.542648064Z, Set([:VARIABLE]), OrderedDict{Symbol, State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}}(:default => State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:default, Pose{3}(), DistributedFactorGraphs.HomotopyDensityDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(Pose{3}(), DistributedFactorGraphs.HomotopyReprDFG{Pose{3}}(DistributedFactorGraphs.DefaultTopologyKind(), DistributedFactorGraphs.DefaultFormKind(), Pose{3}()), DistributedFactorGraphs.LeavesOnlyTopology(), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}(([0.0, 0.0, 0.0], [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0])), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}(([-0.9037181209535056, -0.8689856941604722, 0.048355945981813954], [0.8232040275412903 -0.5677257726213318 -0.004751435710420795; 0.4602571712696246 0.6624275077460037 0.5910610233100317; -0.3324130944091735 -0.4887506972710424 0.8066128504942057]))], Float64[], Float64[], ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}(), Dict(1 => [])), Symbol[], false, Float64[], false, 0), :parametric => State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:parametric, Pose{3}(), DistributedFactorGraphs.HomotopyDensityDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(Pose{3}(), DistributedFactorGraphs.HomotopyReprDFG{Pose{3}}(DistributedFactorGraphs.DefaultTopologyKind(), DistributedFactorGraphs.DefaultFormKind(), Pose{3}()), DistributedFactorGraphs.LeavesOnlyTopology(), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Float64[], Float64[], ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}(), Dict(1 => [])), Symbol[], false, Float64[], false, 0)), OrderedCollections.LittleDict{Symbol, Bloblet, Vector{Symbol}, Vector{Bloblet}}(:a => Bloblet(:a, "1"), :b => Bloblet(:b, "2")), OrderedDict{Symbol, Blobentry}(:bel => Blobentry(:bel, DistributedFactorGraphs.Multihash(UInt8[0x12, 0x20, 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14 … 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55]), 0x00000000, 0, :default, "", "", MIME type application/octet-stream, JSONText("{\"start\":54,\"N\":20,\"s\":\"test\"}"), 2026-05-07T15:07:47.542754048Z, v"0.1.0")), Base.RefValue{Int64}(1), Pose{3}(), nothing) -VariableDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:x1, 2026-05-07T15:07:47.542648064Z, Set([:VARIABLE]), OrderedDict{Symbol, State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}}(:default => State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:default, Pose{3}(), DistributedFactorGraphs.HomotopyDensityDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(Pose{3}(), DistributedFactorGraphs.HomotopyReprDFG{Pose{3}}(DistributedFactorGraphs.DefaultTopologyKind(), DistributedFactorGraphs.DefaultFormKind(), Pose{3}()), DistributedFactorGraphs.LeavesOnlyTopology(), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}(([0.0, 0.0, 0.0], [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0])), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}(([-0.9037181209535056, -0.8689856941604722, 0.048355945981813954], [0.8232040275412903 -0.5677257726213318 -0.004751435710420795; 0.4602571712696246 0.6624275077460037 0.5910610233100317; -0.3324130944091735 -0.4887506972710424 0.8066128504942057]))], Float64[], Float64[], ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}(), Dict(1 => [])), Symbol[], false, Float64[], false, 0), :parametric => State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:parametric, Pose{3}(), DistributedFactorGraphs.HomotopyDensityDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(Pose{3}(), DistributedFactorGraphs.HomotopyReprDFG{Pose{3}}(DistributedFactorGraphs.DefaultTopologyKind(), DistributedFactorGraphs.DefaultFormKind(), Pose{3}()), DistributedFactorGraphs.LeavesOnlyTopology(), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Float64[], Float64[], ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}(), Dict(1 => [])), Symbol[], false, Float64[], false, 0)), OrderedCollections.LittleDict{Symbol, Bloblet, Vector{Symbol}, Vector{Bloblet}}(:a => Bloblet(:a, "1"), :b => Bloblet(:b, "2")), OrderedDict{Symbol, Blobentry}(:bel => Blobentry(:bel, DistributedFactorGraphs.Multihash(UInt8[0x12, 0x20, 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14 … 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55]), 0x00000000, 0, :default, "", "", MIME type application/octet-stream, JSONText("{\"start\":54,\"N\":20,\"s\":\"test\"}"), 2026-05-07T15:07:47.542754048Z, v"0.1.0")), Base.RefValue{Int64}(1), Pose{3}(), nothing) - - - - - -VariableDFG parsefile round-trip: Test Failed at /home/dehann/.julia/dev/DistributedFactorGraphs/test/testSerializingVariables.jl:97 - Expression: v == parsed - Evaluated: VariableDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:x1, 2026-05-07T15:07:47.542648064Z, Set([:VARIABLE]), OrderedDict{Symbol, State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}}(:default => State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:default, Pose{3}(), DistributedFactorGraphs.HomotopyDensityDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(Pose{3}(), DistributedFactorGraphs.HomotopyReprDFG{Pose{3}}(DistributedFactorGraphs.DefaultTopologyKind(), DistributedFactorGraphs.DefaultFormKind(), Pose{3}()), DistributedFactorGraphs.LeavesOnlyTopology(), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}(([0.0, 0.0, 0.0], [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0])), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}(([-0.9037181209535056, -0.8689856941604722, 0.048355945981813954], [0.8232040275412903 -0.5677257726213318 -0.004751435710420795; 0.4602571712696246 0.6624275077460037 0.5910610233100317; -0.3324130944091735 -0.4887506972710424 0.8066128504942057]))], Float64[], Float64[], ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}(), Dict(1 => [])), Symbol[], false, Float64[], false, 0), :parametric => State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:parametric, Pose{3}(), DistributedFactorGraphs.HomotopyDensityDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(Pose{3}(), DistributedFactorGraphs.HomotopyReprDFG{Pose{3}}(DistributedFactorGraphs.DefaultTopologyKind(), DistributedFactorGraphs.DefaultFormKind(), Pose{3}()), DistributedFactorGraphs.LeavesOnlyTopology(), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Float64[], Float64[], ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}(), Dict(1 => [])), Symbol[], false, Float64[], false, 0)), OrderedCollections.LittleDict{Symbol, Bloblet, Vector{Symbol}, Vector{Bloblet}}(:a => Bloblet(:a, "1"), :b => Bloblet(:b, "2")), OrderedDict{Symbol, Blobentry}(:bel => Blobentry(:bel, DistributedFactorGraphs.Multihash(UInt8[0x12, 0x20, 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14 … 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55]), 0x00000000, 0, :default, "", "", MIME type application/octet-stream, JSONText("{\"start\":54,\"N\":20,\"s\":\"test\"}"), 2026-05-07T15:07:47.542754048Z, v"0.1.0")), Base.RefValue{Int64}(1), Pose{3}(), nothing) == VariableDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:x1, 2026-05-07T15:07:47.542648064Z, Set([:VARIABLE]), OrderedDict{Symbol, State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}}(:default => State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:default, Pose{3}(), DistributedFactorGraphs.HomotopyDensityDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(Pose{3}(), DistributedFactorGraphs.HomotopyReprDFG{Pose{3}}(DistributedFactorGraphs.DefaultTopologyKind(), DistributedFactorGraphs.DefaultFormKind(), Pose{3}()), DistributedFactorGraphs.LeavesOnlyTopology(), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}(([0.0, 0.0, 0.0], [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0])), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}(([-0.9037181209535056, -0.8689856941604722, 0.048355945981813954], [0.8232040275412903 -0.5677257726213318 -0.004751435710420795; 0.4602571712696246 0.6624275077460037 0.5910610233100317; -0.3324130944091735 -0.4887506972710424 0.8066128504942057]))], Float64[], Float64[], ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}(), Dict(1 => [])), Symbol[], false, Float64[], false, 0), :parametric => State{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(:parametric, Pose{3}(), DistributedFactorGraphs.HomotopyDensityDFG{Pose{3}, ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}}(Pose{3}(), DistributedFactorGraphs.HomotopyReprDFG{Pose{3}}(DistributedFactorGraphs.DefaultTopologyKind(), DistributedFactorGraphs.DefaultFormKind(), Pose{3}()), DistributedFactorGraphs.LeavesOnlyTopology(), ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Float64[], Float64[], ArrayPartition{Float64, Tuple{SVector{3, Float64}, SMatrix{3, 3, Float64, 9}}}[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}(), Dict(1 => [])), Symbol[], false, Float64[], false, 0)), OrderedCollections.LittleDict{Symbol, Bloblet, Vector{Symbol}, Vector{Bloblet}}(:a => Bloblet(:a, "1"), :b => Bloblet(:b, "2")), OrderedDict{Symbol, Blobentry}(:bel => Blobentry(:bel, DistributedFactorGraphs.Multihash(UInt8[0x12, 0x20, 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14 … 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55]), 0x00000000, 0, :default, "", "", MIME type application/octet-stream, JSONText("{\"start\":54,\"N\":20,\"s\":\"test\"}"), 2026-05-07T15:07:47.542754048Z, v"0.1.0")), Base.RefValue{Int64}(1), Pose{3}(), nothing) - Stacktrace: - [1] top-level scope - @ ~/.julia/dev/DistributedFactorGraphs/test/testSerializingVariables.jl:49 - [2] macro expansion - @ ~/.julia/juliaup/julia-1.13.0-rc1+0.x64.linux.gnu/share/julia/stdlib/v1.13/Test/src/Test.jl:1961 [inlined] - [3] macro expansion - @ ~/.julia/dev/DistributedFactorGraphs/test/testSerializingVariables.jl:91 [inlined] - [4] macro expansion - @ ~/.julia/juliaup/julia-1.13.0-rc1+0.x64.linux.gnu/share/julia/stdlib/v1.13/Test/src/Test.jl:1961 [inlined] - [5] macro expansion - @ ~/.julia/dev/DistributedFactorGraphs/test/testSerializingVariables.jl:97 [inlined] - [6] macro expansion - @ ~/.julia/juliaup/julia-1.13.0-rc1+0.x64.linux.gnu/share/julia/stdlib/v1.13/Test/src/Test.jl:753 [inlined] - -Complex (CircleGroup) end-to-end: Test Failed at /home/dehann/.julia/dev/DistributedFactorGraphs/test/testSerializingVariables.jl:129 - Expression: v == parsed - Evaluated: VariableDFG{CircleState, ComplexF64}(:c1, 2026-05-07T15:07:50.68094208Z, Set([:VARIABLE]), OrderedDict{Symbol, State{CircleState, ComplexF64}}(:default => State{CircleState, ComplexF64}(:default, CircleState(), DistributedFactorGraphs.HomotopyDensityDFG{CircleState, ComplexF64}(CircleState(), DistributedFactorGraphs.HomotopyReprDFG{CircleState}(DistributedFactorGraphs.DefaultTopologyKind(), DistributedFactorGraphs.DefaultFormKind(), CircleState()), DistributedFactorGraphs.LeavesOnlyTopology(), ComplexF64[1.0 + 0.0im, 0.5 + 0.866im], Float64[], Float64[], ComplexF64[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}(), Dict(1 => [])), Symbol[], false, Float64[], false, 0)), OrderedCollections.LittleDict{Symbol, Bloblet, Vector{Symbol}, Vector{Bloblet}}(:meta => Bloblet(:meta, "circle")), OrderedDict{Symbol, Blobentry}(), Base.RefValue{Int64}(1), CircleState(), nothing) == VariableDFG{CircleState, ComplexF64}(:c1, 2026-05-07T15:07:50.68094208Z, Set([:VARIABLE]), OrderedDict{Symbol, State{CircleState, ComplexF64}}(:default => State{CircleState, ComplexF64}(:default, CircleState(), DistributedFactorGraphs.HomotopyDensityDFG{CircleState, ComplexF64}(CircleState(), DistributedFactorGraphs.HomotopyReprDFG{CircleState}(DistributedFactorGraphs.DefaultTopologyKind(), DistributedFactorGraphs.DefaultFormKind(), CircleState()), DistributedFactorGraphs.LeavesOnlyTopology(), ComplexF64[1.0 + 0.0im, 0.5 + 0.866im], Float64[], Float64[], ComplexF64[], Matrix{Float64}[], Dict{Int64, Matrix{Float64}}(), Dict(1 => [])), Symbol[], false, Float64[], false, 0)), OrderedCollections.LittleDict{Symbol, Bloblet, Vector{Symbol}, Vector{Bloblet}}(:meta => Bloblet(:meta, "circle")), OrderedDict{Symbol, Blobentry}(), Base.RefValue{Int64}(1), CircleState(), nothing) - Stacktrace: - [1] top-level scope - @ ~/.julia/dev/DistributedFactorGraphs/test/testSerializingVariables.jl:49 - [2] macro expansion - @ ~/.julia/juliaup/julia-1.13.0-rc1+0.x64.linux.gnu/share/julia/stdlib/v1.13/Test/src/Test.jl:1961 [inlined] - [3] macro expansion - @ ~/.julia/dev/DistributedFactorGraphs/test/testSerializingVariables.jl:118 [inlined] - [4] macro expansion - @ ~/.julia/juliaup/julia-1.13.0-rc1+0.x64.linux.gnu/share/julia/stdlib/v1.13/Test/src/Test.jl:1961 [inlined] - [5] macro expansion - @ ~/.julia/dev/DistributedFactorGraphs/test/testSerializingVariables.jl:129 [inlined] - [6] macro expansion - @ ~/.julia/juliaup/julia-1.13.0-rc1+0.x64.linux.gnu/share/julia/stdlib/v1.13/Test/src/Test.jl:753 [inlined] diff --git a/test/testSerializingVariables.jl b/test/testSerializingVariables.jl index b6f685f8..639abf22 100644 --- a/test/testSerializingVariables.jl +++ b/test/testSerializingVariables.jl @@ -59,9 +59,7 @@ end @testset "RootsOnlyTopology round-trip" begin dim = DFG.getDimension(Pose{3}()) - bel = DFG.HomotopyDensityDFG( - DFG.RootsOnlyTopology(), - Pose{3}(); + bel = DFG.HomotopyDensityDFG{typeof(Pose{3}()), DFG.getPointType(Pose{3}())}(; principal_elements = [DFG.getPointIdentity(Pose{3}())], principal_forms = [diagm(ones(dim))], ) @@ -100,7 +98,7 @@ end @testset "VariableSummary from VariableDFG" begin v = make_test_variable() - vs = VariableSummary(v) + vs = DFG.VariableSummary(v) @test vs.label == :x1 @test vs.tags == v.tags @test vs.solvable[] == v.solvable[] From 615ff50569bf571c145e332b47ccb0138b825b18 Mon Sep 17 00:00:00 2001 From: Johannes Terblanche Date: Sun, 10 May 2026 13:21:14 +0200 Subject: [PATCH 6/8] update news --- NEWS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS.md b/NEWS.md index 978cc919..e0189097 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,9 @@ Listing news on any major breaking changes in DFG. For regular changes, see integrated Github.com project milestones for DFG. # v0.29 +- Breaking: Serialized types finalized toward DFG v1, see #1177 and sub-issues. + Serialization is breaking from v0.28 towards a version to remain stable within v1. + `State.belief` fields and related abstract types are still considered **unstable** and will be finalized in a future releases. - `AbstractPointParametricEst` (`MeanMaxPPE`) and related `PPE` functions are obsolete, see #1133. - Rename filter keyword arguments to `where`-prefixed form: - `solvableFilter` -> `whereSolvable` From 001660d2b2004febd03a6b3fa7c23ea734b29900 Mon Sep 17 00:00:00 2001 From: Johannes Terblanche <6612981+Affie@users.noreply.github.com> Date: Mon, 11 May 2026 13:45:47 +0200 Subject: [PATCH 7/8] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Johannes Terblanche <6612981+Affie@users.noreply.github.com> --- src/entities/HomotopyDensity.jl | 2 +- test/testSerializingVariables.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/entities/HomotopyDensity.jl b/src/entities/HomotopyDensity.jl index 096c74af..45804d71 100644 --- a/src/entities/HomotopyDensity.jl +++ b/src/entities/HomotopyDensity.jl @@ -56,7 +56,7 @@ end @choosetype HomotopyReprDFG resolveHomotopyReprDFGType """ - HomotopyDensityDFG{H <: HomotopyRepr, P} + HomotopyDensityDFG{T <: StateType, P} Hybrid belief representation with natural transition between (non)parametric representations. diff --git a/test/testSerializingVariables.jl b/test/testSerializingVariables.jl index 639abf22..cc702fc1 100644 --- a/test/testSerializingVariables.jl +++ b/test/testSerializingVariables.jl @@ -57,7 +57,7 @@ end @test bel == parsed end - @testset "RootsOnlyTopology round-trip" begin + @testset "HomotopyDensityDFG round-trip with roots only" begin dim = DFG.getDimension(Pose{3}()) bel = DFG.HomotopyDensityDFG{typeof(Pose{3}()), DFG.getPointType(Pose{3}())}(; principal_elements = [DFG.getPointIdentity(Pose{3}())], From 6cc4f42ad664d92489a12a6d2c6530ca612301c0 Mon Sep 17 00:00:00 2001 From: Johannes Terblanche Date: Mon, 11 May 2026 17:42:04 +0200 Subject: [PATCH 8/8] rm unused HomotopyDensityDFG constructor --- src/entities/HomotopyDensity.jl | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/entities/HomotopyDensity.jl b/src/entities/HomotopyDensity.jl index 45804d71..d1c3742a 100644 --- a/src/entities/HomotopyDensity.jl +++ b/src/entities/HomotopyDensity.jl @@ -21,12 +21,8 @@ function StructUtils.lower(::StructUtils.StructStyle, p::AbstractDensityForm) end @choosetype AbstractDensityForm resolvePackedType -# TBD for best future looking structure here +# TBD for best future looking structure here. ignore = true, so no serialization abstract type AbstractPartialTrait end -function StructUtils.lower(::StructUtils.StructStyle, p::AbstractPartialTrait) - return StructUtils.lower(Packed(p)) -end -@choosetype AbstractPartialTrait resolvePackedType # ============================================================================== # HomotopyDensityDFG @@ -111,17 +107,6 @@ function HomotopyDensityDFG(T::AbstractStateType) return HomotopyDensityDFG{typeof(T), getPointType(T)}() end -function HomotopyDensityDFG( - topology::AbstractHomotopyTopology, - T::AbstractStateType; - kwargs..., -) - return HomotopyDensityDFG{typeof(T), getPointType(T)}(; - topologykind = topology, - kwargs..., - ) -end - function StructUtils.fielddefaults( ::StructUtils.StructStyle, ::Type{HomotopyDensityDFG{T, P}},