Skip to content

Commit 5f45723

Browse files
committed
Bring back MultiplyWithInverse
1 parent 65094b6 commit 5f45723

File tree

2 files changed

+116
-0
lines changed

2 files changed

+116
-0
lines changed

gtsam/base/Matrix.h

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,75 @@ GTSAM_EXPORT Matrix expm(const Matrix& A, size_t K=7);
347347

348348
std::string formatMatrixIndented(const std::string& label, const Matrix& matrix, bool makeVectorHorizontal = false);
349349

350+
/**
351+
* Functor that implements multiplication of a vector b with the inverse of a
352+
* matrix A. The derivatives are inspired by Mike Giles' "An extended collection
353+
* of matrix derivative results for forward and reverse mode algorithmic
354+
* differentiation", at https://people.maths.ox.ac.uk/gilesm/files/NA-08-01.pdf
355+
*/
356+
template <int N>
357+
struct MultiplyWithInverse {
358+
typedef Eigen::Matrix<double, N, 1> VectorN;
359+
typedef Eigen::Matrix<double, N, N> MatrixN;
360+
361+
/// A.inverse() * b, with optional derivatives
362+
VectorN operator()(const MatrixN& A, const VectorN& b,
363+
OptionalJacobian<N, N* N> H1 = {},
364+
OptionalJacobian<N, N> H2 = {}) const {
365+
const MatrixN invA = A.inverse();
366+
const VectorN c = invA * b;
367+
// The derivative in A is just -[c[0]*invA c[1]*invA ... c[N-1]*invA]
368+
if (H1)
369+
for (size_t j = 0; j < N; j++)
370+
H1->template middleCols<N>(N * j) = -c[j] * invA;
371+
// The derivative in b is easy, as invA*b is just a linear map:
372+
if (H2) *H2 = invA;
373+
return c;
374+
}
375+
};
376+
377+
/**
378+
* Functor that implements multiplication with the inverse of a matrix, itself
379+
* the result of a function f. It turn out we only need the derivatives of the
380+
* operator phi(a): b -> f(a) * b
381+
*/
382+
template <typename T, int N>
383+
struct MultiplyWithInverseFunction {
384+
inline constexpr static auto M = traits<T>::dimension;
385+
typedef Eigen::Matrix<double, N, 1> VectorN;
386+
typedef Eigen::Matrix<double, N, N> MatrixN;
387+
388+
// The function phi should calculate f(a)*b, with derivatives in a and b.
389+
// Naturally, the derivative in b is f(a).
390+
typedef std::function<VectorN(
391+
const T&, const VectorN&, OptionalJacobian<N, M>, OptionalJacobian<N, N>)>
392+
Operator;
393+
394+
/// Construct with function as explained above
395+
MultiplyWithInverseFunction(const Operator& phi) : phi_(phi) {}
396+
397+
/// f(a).inverse() * b, with optional derivatives
398+
VectorN operator()(const T& a, const VectorN& b,
399+
OptionalJacobian<N, M> H1 = {},
400+
OptionalJacobian<N, N> H2 = {}) const {
401+
MatrixN A;
402+
phi_(a, b, {}, A); // get A = f(a) by calling f once
403+
const MatrixN invA = A.inverse();
404+
const VectorN c = invA * b;
405+
406+
if (H1) {
407+
Eigen::Matrix<double, N, M> H;
408+
phi_(a, c, H, {}); // get derivative H of forward mapping
409+
*H1 = -invA* H;
410+
}
411+
if (H2) *H2 = invA;
412+
return c;
413+
}
414+
415+
private:
416+
const Operator phi_;
417+
};
418+
350419
GTSAM_EXPORT Matrix LLt(const Matrix& A);
351420

352421
GTSAM_EXPORT Matrix RtR(const Matrix& A);

tests/testExpressionFactor.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,26 @@ TEST(Expression, testMultipleCompositions2) {
565565
EXPECT_CORRECT_EXPRESSION_JACOBIANS(sum4_, values, fd_step, tolerance)
566566
}
567567

568+
/* ************************************************************************* */
569+
// Test multiplication with the inverse of a matrix
570+
TEST(ExpressionFactor, MultiplyWithInverse) {
571+
auto model = noiseModel::Isotropic::Sigma(3, 1);
572+
573+
// Create expression
574+
Vector3_ f_expr(MultiplyWithInverse<3>(), Expression<Matrix3>(0), Vector3_(1));
575+
576+
// Check derivatives
577+
Values values;
578+
Matrix3 A = Vector3(1, 2, 3).asDiagonal();
579+
A(0, 1) = 0.1;
580+
A(0, 2) = 0.1;
581+
const Vector3 b(0.1, 0.2, 0.3);
582+
values.insert<Matrix3>(0, A);
583+
values.insert<Vector3>(1, b);
584+
ExpressionFactor<Vector3> factor(model, Vector3::Zero(), f_expr);
585+
EXPECT_CORRECT_FACTOR_JACOBIANS(factor, values, 1e-5, 1e-5)
586+
}
587+
568588
/* ************************************************************************* */
569589
// Test multiplication with the inverse of a matrix function
570590
namespace test_operator {
@@ -580,6 +600,33 @@ Vector3 f(const Point2& a, const Vector3& b, OptionalJacobian<3, 2> H1,
580600
}
581601
}
582602

603+
TEST(ExpressionFactor, MultiplyWithInverseFunction) {
604+
auto model = noiseModel::Isotropic::Sigma(3, 1);
605+
606+
using test_operator::f;
607+
Vector3_ f_expr(MultiplyWithInverseFunction<Point2, 3>(f),
608+
Expression<Point2>(0), Vector3_(1));
609+
610+
// Check derivatives
611+
Point2 a(1, 2);
612+
const Vector3 b(0.1, 0.2, 0.3);
613+
Matrix32 H1;
614+
Matrix3 A;
615+
const Vector Ab = f(a, b, H1, A);
616+
CHECK(assert_equal(A * b, Ab))
617+
CHECK(assert_equal(
618+
numericalDerivative11<Vector3, Point2>(
619+
[&](const Point2& a) { return f(a, b, {}, {}); }, a),
620+
H1))
621+
622+
Values values;
623+
values.insert<Point2>(0, a);
624+
values.insert<Vector3>(1, b);
625+
ExpressionFactor<Vector3> factor(model, Vector3::Zero(), f_expr);
626+
EXPECT_CORRECT_FACTOR_JACOBIANS(factor, values, 1e-5, 1e-5)
627+
}
628+
629+
583630
/* ************************************************************************* */
584631
// Test N-ary variadic template
585632
class TestNaryFactor

0 commit comments

Comments
 (0)