Skip to content

Steiner trees for Covariance Recovery#2476

Merged
dellaert merged 12 commits intodevelopfrom
feature/SteinerTrees
Mar 27, 2026
Merged

Steiner trees for Covariance Recovery#2476
dellaert merged 12 commits intodevelopfrom
feature/SteinerTrees

Conversation

@dellaert
Copy link
Copy Markdown
Member

This PR contributes two main contributions:

  1. a paper describing the existing covariance recovery mechanisms in the Bayes tree;
  2. two improvements suggested by Codex 5.4, namely (a) generalizing multivariable queries using Steiner trees, and (b) a partial covariance block recovery rather than a full inversion of a clique's covariance matrix.

@dellaert dellaert requested a review from Copilot March 23, 2026 04:50
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds benchmarking and plotting utilities for Bayes-tree covariance recovery while extending core GTSAM APIs to support multi-variable joint extraction (via Steiner-tree style compression) and more efficient covariance recovery (triangular solves / partial block extraction).

Changes:

  • Added a new benchmark executable + CSV writers for multiple covariance recovery variants and query families.
  • Added plotting + “visuals” export tooling for paper-ready figures.
  • Extended BayesTree with multi-key joint() / jointBayesNet() and added Marginals::crossCovariance() plus updated covariance recovery implementation and tests.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
timing/timeBayesTreeCovariance.cpp New benchmark executable measuring legacy vs Steiner localization + dense inversion vs solve-based covariance recovery.
timing/plot_bayes_tree_covariance.py New script to turn summary CSV output into paper-ready plots and optional visual-data figures.
timing/exportBayesTreeCovarianceVisuals.cpp New exporter producing query selections + covariances for illustrative w100 figures.
timing/README.md Documentation for building/running the benchmark, exporting visuals, and generating plots.
tests/testMarginals.cpp Adds tests for ordering-stable joint/cross covariance behavior and agreement with legacy computation.
tests/testGaussianBayesTreeB.cpp Adds tests that multi-key joint() matches existing pairwise and legacy multifrontal marginalization.
gtsam/nonlinear/Marginals.h Adds crossCovariance(left, right) API.
gtsam/nonlinear/Marginals.cpp Implements cross-covariance block recovery via selected-column triangular solves; updates marginal/joint covariance routines.
gtsam/linear/GaussianBayesTree.cpp Updates marginal covariance computation to avoid direct matrix inversion and handle non-finite information.
gtsam/inference/BayesTree.h Adds multi-key overloads for joint() and jointBayesNet().
gtsam/inference/BayesTree-inst.h Implements multi-key joint extraction using compressed support (Steiner-tree style).

@github-actions
Copy link
Copy Markdown

timeSFMBAL benchmark

  • Head: 441899ada415753fd6b5b198cda9e2f2eb992980
  • Base: 3ace3618fd227331ff5c4a6ca299104cc3e0ddd7
Runner Metric Base (s) Head (s) Delta (s) Change
linux-arm64-tbbOFF timeSFMBAL/dubrovnik-135-90642-pre.txt/MultifrontalCholesky 9.587152 9.373499 -0.213654 -2.23%
linux-arm64-tbbOFF timeSFMBAL/dubrovnik-135-90642-pre.txt/MultifrontalSolver 12.619615 12.635559 +0.015944 +0.13%
linux-arm64-tbbON timeSFMBAL/dubrovnik-135-90642-pre.txt/MultifrontalCholesky 5.596846 5.391615 -0.205231 -3.67%
linux-arm64-tbbON timeSFMBAL/dubrovnik-135-90642-pre.txt/MultifrontalSolver 9.483817 9.349318 -0.134498 -1.42%
linux-x64-tbbOFF timeSFMBAL/dubrovnik-135-90642-pre.txt/MultifrontalCholesky 11.314487 11.294456 -0.020031 -0.18%
linux-x64-tbbOFF timeSFMBAL/dubrovnik-135-90642-pre.txt/MultifrontalSolver 18.066797 18.002419 -0.064379 -0.36%
linux-x64-tbbON timeSFMBAL/dubrovnik-135-90642-pre.txt/MultifrontalCholesky 7.215864 7.298690 +0.082826 +1.15%
linux-x64-tbbON timeSFMBAL/dubrovnik-135-90642-pre.txt/MultifrontalSolver 15.169765 15.168655 -0.001110 -0.01%
macos-arm64-tbbOFF timeSFMBAL/dubrovnik-135-90642-pre.txt/MultifrontalCholesky 41.025142 30.574164 -10.450978 -25.47%
macos-arm64-tbbOFF timeSFMBAL/dubrovnik-135-90642-pre.txt/MultifrontalSolver 23.586273 18.135836 -5.450437 -23.11%
macos-arm64-tbbON timeSFMBAL/dubrovnik-135-90642-pre.txt/MultifrontalCholesky 27.046401 46.928741 +19.882341 +73.51%
macos-arm64-tbbON timeSFMBAL/dubrovnik-135-90642-pre.txt/MultifrontalSolver 16.278504 18.626741 +2.348237 +14.43%

Worker runs

Role Runner SHA Conclusion
head linux-x64 441899ada415753fd6b5b198cda9e2f2eb992980 success
base linux-x64 3ace3618fd227331ff5c4a6ca299104cc3e0ddd7 success
head linux-arm64 441899ada415753fd6b5b198cda9e2f2eb992980 success
base linux-arm64 3ace3618fd227331ff5c4a6ca299104cc3e0ddd7 success
head macos-arm64 441899ada415753fd6b5b198cda9e2f2eb992980 success
base macos-arm64 3ace3618fd227331ff5c4a6ca299104cc3e0ddd7 success

@dellaert dellaert marked this pull request as ready for review March 23, 2026 23:09
@dellaert dellaert requested a review from ProfFan March 23, 2026 23:10
Copy link
Copy Markdown
Collaborator

@ProfFan ProfFan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great to me :shipit: I think the only problem is the templated magic should have more documentation, but that can be a future improvement.

}
const Matrix identity =
Matrix::Identity(information.rows(), information.cols());
return information.selfadjointView<Eigen::Upper>().llt().solve(identity);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use solveInPlace here? This is LAPACK's dpotri.


const Matrix identity =
Matrix::Identity(information.rows(), information.cols());
return information.selfadjointView<Eigen::Upper>().llt().solve(identity);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Matrix intermediate =
R.transpose().triangularView<Eigen::Lower>().solve(selectors);
return R.triangularView<Eigen::Upper>().solve(intermediate);
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Copy Markdown

@Ovardo Ovardo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ref. recent GTSAM forum thread.

I’m still not fully convinced about the treatment of the queried-clique conditional in CovarianceRecovery.pdf.

Unless I’m misunderstanding something, my reasoning is the following:

From Eqs. (17)–(18), the interface sets A_{u,b} and B_{u,b} are constructed from separator variables along the path. Under the usual Bayes tree construction, the frontal variables of the queried clique u do not appear in ancestor cliques, so it seems that F_u = F_{d_0} is not contained in A_{u,b} \cup B_{u,b}.

If that is right, then from Eq. (19), F_u \subseteq Z_{u,b}. But Eq. (20) integrates over Z_{u,b}, so it appears that the frontal variables of the queried clique are integrated out in the shortcut \chi_{u->b}.

That is where I get confused about Eq. (21): the final integral seems to treat q_1 and q_2 as variables still present in the integrand, but if they are frontal variables of the queried cliques, it looks like they would already have been integrated out by Eq. (20).

So I may be missing something in the intended role of the descendant-side interface B_{u,b} in Eq. (18), but at the moment I don’t see how the queried clique’s frontal variables are retained.

Feel free to let me know where my reasoning goes wrong.

@dellaert
Copy link
Copy Markdown
Member Author

@Ovardo I had another look and you're right! I updated the PDF with an example, and what I think are now the correct definitions for A and B. Let me know what you think. I'm actually on travel so I'm doing this quickly in between activities - so they could still be wrong :-/ I appreciate your help!

@Ovardo
Copy link
Copy Markdown

Ovardo commented Mar 27, 2026

I've had a look, and to me it now looks consistent.

One could perhaps mention that in the edge case(s) of one or both of the queried cliques themselves being the lowest common ancestor, then one should not include the respective clique potentials in the final pairwise assembly, as they are already embedded in the ancestor clique marginal.

Thanks again for updating this — I really appreciate it.

@dellaert dellaert merged commit 5723155 into develop Mar 27, 2026
33 of 42 checks passed
@dellaert dellaert deleted the feature/SteinerTrees branch March 27, 2026 20:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants