Skip to content

Commit 19bf514

Browse files
committed
Document/refine
1 parent cbad629 commit 19bf514

File tree

5 files changed

+103
-62
lines changed

5 files changed

+103
-62
lines changed

gtsam/linear/MultifrontalClique.cpp

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <gtsam/linear/MultifrontalClique.h>
2121

2222
#include <algorithm>
23+
#include <cassert>
2324
#include <iostream>
2425
#include <set>
2526
#include <stdexcept>
@@ -172,7 +173,6 @@ std::vector<size_t> MultifrontalClique::parentIndicesFor(
172173
throw std::runtime_error(
173174
"MultifrontalSolver: separator key not found in parent clique");
174175
}
175-
indices.push_back(parent.frontals().size() + parent.separatorKeys_.size());
176176
return indices;
177177
}
178178

@@ -184,7 +184,6 @@ void MultifrontalClique::initializeMatrices(
184184
}
185185

186186
void MultifrontalClique::fillAb(const GaussianFactorGraph& graph) {
187-
sbm_.blockStart() = 0;
188187
sbm_.setZero();
189188

190189
// We only overwrite the fixed sparsity pattern, so Ab must be zeroed once in
@@ -222,30 +221,40 @@ void MultifrontalClique::fillAb(const GaussianFactorGraph& graph) {
222221
}
223222
}
224223

225-
void MultifrontalClique::eliminateClique() {
224+
void MultifrontalClique::eliminate() {
225+
// Update SBM with the local factors, Ab^T * Ab
226226
sbm_.selfadjointView().rankUpdate(Ab_.matrix().transpose());
227+
228+
// Form normal equations and factor the frontal block (Schur complement step).
227229
sbm_.choleskyPartial(frontals().size());
228230

229231
auto parent = parent_.lock();
230232
if (!parent) return;
231-
const size_t nFrontals = frontals().size();
232-
const size_t nSeparatorBlocks = separatorKeys_.size();
233-
for (size_t i = 0; i <= nSeparatorBlocks; ++i) {
234-
size_t p_i = parentIndices_[i];
235-
parent->sbm_.updateDiagonalBlock(p_i, sbm_.diagonalBlock(nFrontals + i));
236-
for (size_t j = i + 1; j <= nSeparatorBlocks; ++j) {
237-
size_t p_j = parentIndices_[j];
238-
parent->sbm_.updateOffDiagonalBlock(
239-
p_i, p_j, sbm_.aboveDiagonalBlock(nFrontals + i, nFrontals + j));
233+
// Expose only the separator+RHS view when contributing to the parent.
234+
sbm_.blockStart() = frontals().size();
235+
parent->updateWith(sbm_, parentIndices_);
236+
sbm_.blockStart() = 0;
237+
}
238+
239+
void MultifrontalClique::updateWith(const SymmetricBlockMatrix& separator,
240+
const std::vector<size_t>& indices) {
241+
const size_t numBlocks = indices.size();
242+
assert(separator.nBlocks() == numBlocks + 1);
243+
const size_t rhsBlock = sbm_.nBlocks() - 1;
244+
245+
for (size_t i = 0; i <= numBlocks; ++i) {
246+
const size_t p_i = (i < numBlocks) ? indices[i] : rhsBlock;
247+
sbm_.updateDiagonalBlock(p_i, separator.diagonalBlock(i));
248+
for (size_t j = i + 1; j <= numBlocks; ++j) {
249+
const size_t p_j = (j < numBlocks) ? indices[j] : rhsBlock;
250+
sbm_.updateOffDiagonalBlock(p_i, p_j, separator.aboveDiagonalBlock(i, j));
240251
}
241252
}
242253
}
243254

244-
void MultifrontalClique::solveClique() const {
255+
void MultifrontalClique::solve() const {
245256
// Solve with block back-substitution on the Cholesky-stored SBM, avoiding
246257
// materializing an explicit R matrix or split representation.
247-
const auto oldStart = sbm_.blockStart();
248-
sbm_.blockStart() = 0;
249258
const size_t nFrontals = frontalPtrs_.size();
250259
const size_t nSeparators = separatorPtrs_.size();
251260

@@ -280,7 +289,6 @@ void MultifrontalClique::solveClique() const {
280289
values->noalias() = rhsScratch_.segment(offset, dim);
281290
offset += dim;
282291
}
283-
sbm_.blockStart() = oldStart;
284292
}
285293

286294
void MultifrontalClique::print(const std::string& s,

gtsam/linear/MultifrontalClique.h

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class GTSAM_EXPORT MultifrontalClique {
7272

7373
/// Compute parent indices for all children after separators are finalized.
7474
void assignParentIndicesForChildren();
75+
7576
/// Cache pointers to frontal and separator update vectors.
7677
void cacheValuePointers(VectorValues* delta);
7778

@@ -90,7 +91,7 @@ class GTSAM_EXPORT MultifrontalClique {
9091
void fillAb(const GaussianFactorGraph& graph);
9192
/// @}
9293

93-
/// @name Read-only accessors
94+
/// @name Read-only methods
9495
/// @{
9596

9697
/// Get the frontal keys for this clique.
@@ -120,41 +121,71 @@ class GTSAM_EXPORT MultifrontalClique {
120121
/// Get the symmetric block matrix (const).
121122
const SymmetricBlockMatrix& sbm() const { return sbm_; }
122123

123-
/// Get the parent indices for scatter operations.
124-
const std::vector<size_t>& parentIndices() const { return parentIndices_; }
125-
/// @}
126-
127-
/// @name Solve (non-const)
128-
/// @{
129-
130-
/// Eliminate this clique and propagate to its parent.
131-
void eliminateClique();
132-
133-
/// Solve for the variables in this clique and update the solution vector.
134-
void solveClique() const;
135-
/// @}
136124

137-
/// Compute block dimensions from variable dimensions (excluding RHS).
138-
/// @param dims Variable dimensions.
139-
/// @return Block dimensions for this clique.
125+
/**
126+
* Compute block dimensions from variable dimensions (excluding RHS).
127+
* @param dims Variable dimensions.
128+
* @return Block dimensions for this clique.
129+
*/
140130
std::vector<size_t> blockDims(const std::map<Key, size_t>& dims) const;
141131

142-
/// Count rows needed for the vertical block matrix.
143-
/// @param graph The factor graph.
144-
/// @return Total number of rows.
132+
/**
133+
* Count rows needed for the vertical block matrix.
134+
* @param graph The factor graph.
135+
* @return Total number of rows.
136+
*/
145137
size_t countRows(const GaussianFactorGraph& graph) const;
146138

147-
/// Compute parent scatter indices for this clique.
148-
/// @param parent The parent clique.
149-
/// @return Parent indices for scatter operations.
139+
/**
140+
* Compute parent scatter indices for this clique.
141+
* @param parent The parent clique.
142+
* @return Parent indices for separator blocks (excluding RHS).
143+
*/
150144
std::vector<size_t> parentIndicesFor(const MultifrontalClique& parent) const;
151145

152-
/// Print this clique.
153-
/// @param s Optional string prefix.
154-
/// @param keyFormatter Key formatter for printing.
146+
/**
147+
* Print this clique.
148+
* @param s Optional string prefix.
149+
* @param keyFormatter Key formatter for printing.
150+
*/
155151
void print(const std::string& s = "",
156152
const KeyFormatter& keyFormatter = DefaultKeyFormatter) const;
157153

154+
/// @}
155+
156+
/// @name Solve (non-const)
157+
/// @{
158+
159+
/**
160+
* Eliminate this clique and propagate its separator contribution upward.
161+
*
162+
* Computes the local normal equations (SBM) from the stacked Jacobian (Ab),
163+
* performs partial Cholesky on the frontal blocks, and then updates the
164+
* parent's SBM using only the separator view (plus RHS) of this clique.
165+
* Requires parent indices to be precomputed.
166+
*/
167+
void eliminate();
168+
169+
/**
170+
* Update this clique using a child's contribution.
171+
* @param separator Child clique's SBM restricted to its separator blocks,
172+
* with the RHS block appended as the last block.
173+
* @param indices Mapping from the child's separator blocks into this
174+
* parent's SBM block indices (RHS is implicit).
175+
*/
176+
void updateWith(const SymmetricBlockMatrix& separator,
177+
const std::vector<size_t>& indices);
178+
179+
/**
180+
* Solve for this clique's frontal variables and write them back to the
181+
* cached solution vectors.
182+
*
183+
* Uses block back-substitution using the upper triangular-part of the
184+
* Cholesky-stored SBM, solving the triangular system for the frontal blocks.
185+
*/
186+
void solve() const;
187+
/// @}
188+
158189
private:
159190
void setParentIndices(const std::vector<size_t>& indices) {
160191
parentIndices_ = indices;

gtsam/linear/MultifrontalSolver.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,14 +142,14 @@ void MultifrontalSolver::load(const GaussianFactorGraph& graph) {
142142
/* ************************************************************************* */
143143
void MultifrontalSolver::eliminate() {
144144
for (auto& clique : postOrderCliques_) {
145-
clique->eliminateClique();
145+
clique->eliminate();
146146
}
147147
}
148148

149149
/* ************************************************************************* */
150150
const VectorValues& MultifrontalSolver::solve() const {
151151
for (const auto& clique : cliques_) {
152-
clique->solveClique();
152+
clique->solve();
153153
}
154154
return solution_;
155155
}

gtsam/linear/VectorValues.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,11 +289,11 @@ namespace gtsam {
289289
Vector vector(const CONTAINER& keys) const {
290290
DenseIndex totalDim = 0;
291291
FastVector<const Vector*> items;
292-
items.reserve(keys.end() - keys.begin());
292+
items.reserve(keys.size());
293293
for (Key key : keys) {
294294
const Vector* v = &at(key);
295295
totalDim += v->size();
296-
items.push_back(v);
296+
items.emplace_back(v);
297297
}
298298

299299
Vector result(totalDim);

timing/timeMultifrontalSolver.cpp

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,26 +50,28 @@ static void runMultifrontalSolver(const GaussianFactorGraph& smoother,
5050
}
5151

5252
int main() {
53-
const size_t T = 500;
54-
GaussianFactorGraph smoother = createSmoother(T);
55-
const Ordering ordering = Ordering::Metis(smoother);
53+
const std::vector<size_t> T_values = {10, 50, 100, 500, 1000, 5000};
5654
const size_t iterations = 1000;
5755

58-
auto start = std::chrono::high_resolution_clock::now();
59-
runStandardSolver(smoother, ordering, iterations);
60-
auto end = std::chrono::high_resolution_clock::now();
61-
std::chrono::duration<double> t_standard = end - start;
56+
for (size_t T : T_values) {
57+
GaussianFactorGraph smoother = createSmoother(T);
58+
const Ordering ordering = Ordering::Metis(smoother);
6259

63-
start = std::chrono::high_resolution_clock::now();
64-
runMultifrontalSolver(smoother, ordering, iterations);
65-
end = std::chrono::high_resolution_clock::now();
66-
std::chrono::duration<double> t_imperative = end - start;
60+
auto start = std::chrono::high_resolution_clock::now();
61+
runStandardSolver(smoother, ordering, iterations);
62+
auto end = std::chrono::high_resolution_clock::now();
63+
std::chrono::duration<double> t_standard = end - start;
6764

68-
cout << "\nBenchmark (T=" << T << ", iterations=" << iterations << "):\n";
69-
cout << " Standard GTSAM: " << t_standard.count() << " s\n";
70-
cout << " MultifrontalSolver: " << t_imperative.count() << " s\n";
71-
cout << " Speedup: " << t_standard.count() / t_imperative.count()
72-
<< "x\n";
65+
start = std::chrono::high_resolution_clock::now();
66+
runMultifrontalSolver(smoother, ordering, iterations);
67+
end = std::chrono::high_resolution_clock::now();
68+
std::chrono::duration<double> t_imperative = end - start;
7369

70+
cout << "\nBenchmark (T=" << T << ", iterations=" << iterations << "):\n";
71+
cout << " Standard GTSAM: " << t_standard.count() << " s\n";
72+
cout << " MultifrontalSolver: " << t_imperative.count() << " s\n";
73+
cout << " Speedup: "
74+
<< t_standard.count() / t_imperative.count() << "x\n";
75+
}
7476
return 0;
7577
}

0 commit comments

Comments
 (0)