Skip to content

Commit 3ec069c

Browse files
[SymForce] Calculate hessian & rhs in one place
We allow construction of sym::Factor objects from functions which only calculate the residual and jacobian. There are many ways this could happen: 1) The user could construct it from a function calculating fixed sized jacobians. 2) The user could construct it from a function calculating dynamically sized jacobians. 3) The user could construct it from a function of a sym::Values object. In each of these scenarios, the only difference is in how you get a hold of the jacobian and residual from which to calculate the hessian and rhs. Once you have them, you follow the same steps. Unfortunately, we calculated the same results 3 different times (one for each scenario). Not only is this more verbose, but it can also be easy to forget to update one of the scenarios. For example, branch 3 still used an old method of calculating the hessian from the jacobian. This was an oversight that was made easy by the repition. To address this issue, this commit factors out the calculation into a single generic function which is called in each of the 3 locations. Topic: 3_fold_hessian GitOrigin-RevId: 5a77ee6cf475a495b168c59052f51444dc274106
1 parent 236e27f commit 3ec069c

File tree

2 files changed

+37
-49
lines changed

2 files changed

+37
-49
lines changed

symforce/opt/factor.cc

+8-14
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <fmt/ranges.h>
1313

1414
#include "./assert.h"
15+
#include "./internal/factor_utils.h"
1516

1617
namespace sym {
1718

@@ -23,22 +24,15 @@ Factor<Scalar> Factor<Scalar>::Jacobian(const JacobianFunc& jacobian_func,
2324
[jacobian_func](const Values<Scalar>& values, const std::vector<index_entry_t>& keys_to_func,
2425
VectorX<Scalar>* residual, MatrixX<Scalar>* jacobian,
2526
MatrixX<Scalar>* hessian, VectorX<Scalar>* rhs) {
26-
SYM_ASSERT(residual != nullptr);
2727
jacobian_func(values, keys_to_func, residual, jacobian);
28-
SYM_ASSERT(jacobian == nullptr || residual->rows() == jacobian->rows());
29-
30-
// Compute the lower triangle of the hessian if needed
31-
if (hessian != nullptr) {
32-
SYM_ASSERT(jacobian != nullptr);
33-
hessian->resize(jacobian->cols(), jacobian->cols());
34-
hessian->template triangularView<Eigen::Lower>().setZero();
35-
hessian->template selfadjointView<Eigen::Lower>().rankUpdate(jacobian->transpose());
36-
}
3728

38-
// Compute RHS if needed
39-
if (rhs != nullptr) {
40-
SYM_ASSERT(jacobian != nullptr);
41-
(*rhs) = jacobian->transpose() * (*residual);
29+
SYM_ASSERT(residual != nullptr);
30+
if (jacobian == nullptr) {
31+
SYM_ASSERT(hessian == nullptr);
32+
SYM_ASSERT(rhs == nullptr);
33+
} else {
34+
SYM_ASSERT(residual->rows() == jacobian->rows());
35+
internal::CalculateHessianRhs(*residual, *jacobian, hessian, rhs);
4236
}
4337
},
4438
keys_to_func, keys_to_optimize);

symforce/opt/internal/factor_utils.h

+29-35
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ struct JacobianFuncValuesExtractor {
8080
// Factor::Jacobian constructor support for dynamic matrices
8181
// ----------------------------------------------------------------------------
8282

83-
template <typename MatrixType>
83+
template <typename HMatrixType, typename JMatrixType = HMatrixType>
8484
struct RankUpdateHelper {
85-
void operator()(MatrixType& hessian, const MatrixType& jacobian) const {
85+
void operator()(HMatrixType& hessian, const JMatrixType& jacobian) const {
8686
// NOTE(aaron): Testing seemed to show that this matrix multiply is faster than
8787
// rankUpdate, at least for smallish jacobians that are still large enough for Eigen to
8888
// not inline the whole multiplication. More investigation would be necessary to figure
@@ -101,6 +101,25 @@ struct RankUpdateHelper<Eigen::SparseMatrix<Scalar>> {
101101
}
102102
};
103103

104+
/**
105+
* Precondition: residual and jacobian have the same number of rows
106+
*/
107+
template <typename RVecType, typename JMatrixType, typename HMatrixType, typename Scalar>
108+
void CalculateHessianRhs(const RVecType& residual, const JMatrixType& jacobian,
109+
HMatrixType* hessian, VectorX<Scalar>* rhs) {
110+
// Compute the lower triangle of the hessian if needed
111+
if (hessian != nullptr) {
112+
hessian->resize(jacobian.cols(), jacobian.cols());
113+
114+
RankUpdateHelper<HMatrixType, JMatrixType>{}(*hessian, jacobian);
115+
}
116+
117+
// Compute RHS if needed
118+
if (rhs != nullptr) {
119+
(*rhs) = jacobian.transpose() * residual;
120+
}
121+
}
122+
104123
template <typename Scalar, typename Functor>
105124
Factor<Scalar> JacobianDynamic(Functor func, const std::vector<Key>& keys_to_func,
106125
const std::vector<Key>& keys_to_optimize) {
@@ -109,23 +128,15 @@ Factor<Scalar> JacobianDynamic(Functor func, const std::vector<Key>& keys_to_fun
109128
return Factor<Scalar>(
110129
[func](const Values<Scalar>& values, const std::vector<index_entry_t>& keys_to_func,
111130
VectorX<Scalar>* residual, Mat* jacobian, Mat* hessian, VectorX<Scalar>* rhs) {
112-
SYM_ASSERT(residual != nullptr);
113131
JacobianFuncValuesExtractor<Scalar, Functor>::Invoke(func, values, keys_to_func, residual,
114132
jacobian);
115-
SYM_ASSERT(jacobian == nullptr || residual->rows() == jacobian->rows());
116-
117-
// Compute the lower triangle of the hessian if needed
118-
if (hessian != nullptr) {
119-
SYM_ASSERT(jacobian);
120-
hessian->resize(jacobian->cols(), jacobian->cols());
121-
122-
RankUpdateHelper<Mat>{}(*hessian, *jacobian);
123-
}
124-
125-
// Compute RHS if needed
126-
if (rhs != nullptr) {
127-
SYM_ASSERT(jacobian);
128-
(*rhs) = jacobian->transpose() * (*residual);
133+
SYM_ASSERT(residual != nullptr);
134+
if (jacobian == nullptr) {
135+
SYM_ASSERT(hessian == nullptr);
136+
SYM_ASSERT(rhs == nullptr);
137+
} else {
138+
SYM_ASSERT(residual->rows() == jacobian->rows());
139+
CalculateHessianRhs(*residual, *jacobian, hessian, rhs);
129140
}
130141
},
131142
keys_to_func, keys_to_optimize);
@@ -176,24 +187,7 @@ Factor<Scalar> JacobianFixed(Functor func, const std::vector<Key>& keys_to_func,
176187
JacobianFuncValuesExtractor<Scalar, Functor>::Invoke(func, values, keys_to_func,
177188
&residual_fixed, &jacobian_fixed);
178189
(*jacobian) = jacobian_fixed;
179-
180-
// Compute the lower triangle of the hessian if needed
181-
if (hessian != nullptr) {
182-
hessian->resize(N, N);
183-
184-
// NOTE(aaron): Testing seemed to show that this matrix multiply is faster than
185-
// rankUpdate, at least for smallish jacobians that are still large enough for Eigen to
186-
// not inline the whole multiplication. More investigation would be necessary to figure
187-
// out why, and if it's expected to be faster for large jacobians
188-
hessian->template triangularView<Eigen::Lower>() =
189-
(jacobian_fixed.transpose() * jacobian_fixed)
190-
.template triangularView<Eigen::Lower>();
191-
}
192-
193-
// Compute RHS if needed
194-
if (rhs != nullptr) {
195-
(*rhs) = jacobian_fixed.transpose() * residual_fixed;
196-
}
190+
CalculateHessianRhs(residual_fixed, jacobian_fixed, hessian, rhs);
197191
} else {
198192
// jacobian not requested
199193
Eigen::Matrix<Scalar, M, N>* const jacobian_invoke_arg = nullptr;

0 commit comments

Comments
 (0)