Moved TransformConstraint into gtsam, cleaned up includes

release/4.3a0
Alex Cunningham 2010-08-10 14:30:41 +00:00
parent 4f9a60d41c
commit 1c72d92365
9 changed files with 454 additions and 10 deletions

View File

@ -45,6 +45,7 @@ public:
*/
NonlinearConstraint(size_t dim, double mu = 1000.0):
Base(noiseModel::Constrained::All(dim)), mu_(fabs(mu)), dim_(dim) {}
virtual ~NonlinearConstraint() {}
/** returns the gain mu */
double mu() const { return mu_; }
@ -116,6 +117,7 @@ public:
: Base(dim, mu), key_(key) {
this->keys_.push_back(key);
}
virtual ~NonlinearConstraint1() {}
/* print */
void print(const std::string& s = "") const {
@ -173,6 +175,7 @@ protected:
public:
NonlinearEqualityConstraint1(const Key& key, size_t dim, double mu = 1000.0)
: Base(key, dim, mu) {}
virtual ~NonlinearEqualityConstraint1() {}
/** Always active, so fixed value for active() */
virtual bool active(const Config& c) const { return true; }
@ -206,6 +209,7 @@ public:
this->keys_.push_back(key1);
this->keys_.push_back(key2);
}
virtual ~NonlinearConstraint2() {}
/* print */
void print(const std::string& s = "") const {
@ -267,11 +271,119 @@ protected:
public:
NonlinearEqualityConstraint2(const Key1& key1, const Key2& key2, size_t dim, double mu = 1000.0)
: Base(key1, key2, dim, mu) {}
virtual ~NonlinearEqualityConstraint2() {}
/** Always active, so fixed value for active() */
virtual bool active(const Config& c) const { return true; }
};
/**
* A ternary constraint
*/
template <class Config, class Key1, class X1, class Key2, class X2, class Key3, class X3>
class NonlinearConstraint3 : public NonlinearConstraint<Config> {
protected:
typedef NonlinearConstraint3<Config,Key1,X1,Key2,X2,Key3,X3> This;
typedef NonlinearConstraint<Config> Base;
/** keys for the constrained variables */
Key1 key1_;
Key2 key2_;
Key3 key3_;
public:
/**
* Basic constructor
* @param key1 is the identifier for the first variable constrained
* @param key2 is the identifier for the second variable constrained
* @param key3 is the identifier for the second variable constrained
* @param dim is the size of the constraint (p)
* @param mu is the gain for the factor
*/
NonlinearConstraint3(const Key1& key1, const Key2& key2, const Key3& key3,
size_t dim, double mu = 1000.0) :
Base(dim, mu), key1_(key1), key2_(key2), key3_(key3) {
this->keys_.push_back(key1);
this->keys_.push_back(key2);
this->keys_.push_back(key3);
}
virtual ~NonlinearConstraint3() {}
/* print */
void print(const std::string& s = "") const {
std::cout << "NonlinearConstraint3 " << s << std::endl;
std::cout << "key1: " << (std::string) key1_ << std::endl;
std::cout << "key2: " << (std::string) key2_ << std::endl;
std::cout << "key3: " << (std::string) key3_ << std::endl;
std::cout << "mu: " << this->mu_ << std::endl;
}
/** Check if two factors are equal. Note type is Factor and needs cast. */
virtual bool equals(const Factor<Config>& f, double tol = 1e-9) const {
const This* p = dynamic_cast<const This*> (&f);
if (p == NULL) return false;
return Base::equals(*p, tol) && (key1_ == p->key1_) && (key2_ == p->key2_) && (key3_ == p->key3_);
}
/** error function g(x), switched depending on whether the constraint is active */
inline Vector unwhitenedError(const Config& x) const {
if (!active(x)) {
return zero(this->dim());
}
const Key1& j1 = key1_;
const Key2& j2 = key2_;
const Key3& j3 = key3_;
const X1& xj1 = x[j1];
const X2& xj2 = x[j2];
const X3& xj3 = x[j3];
return evaluateError(xj1, xj2, xj3);
}
/** Linearize from config */
boost::shared_ptr<GaussianFactor> linearize(const Config& c) const {
if (!active(c)) {
boost::shared_ptr<GaussianFactor> factor;
return factor;
}
const Key1& j1 = key1_; const Key2& j2 = key2_; const Key3& j3 = key3_;
const X1& x1 = c[j1]; const X2& x2 = c[j2]; const X3& x3 = c[j3];
Matrix grad1, grad2, grad3;
Vector g = -1.0 * evaluateError(x1, x2, x3, grad1, grad2, grad3);
SharedDiagonal model = noiseModel::Constrained::All(this->dim());
return GaussianFactor::shared_ptr(new GaussianFactor(j1, grad1, j2, grad2, j3, grad3, g, model));
}
/** g(x) with optional derivative3 - does not depend on active */
virtual Vector evaluateError(const X1& x1, const X2& x2, const X3& x3,
boost::optional<Matrix&> H1 = boost::none,
boost::optional<Matrix&> H2 = boost::none,
boost::optional<Matrix&> H3 = boost::none) const = 0;
};
/**
* Ternary Equality constraint - simply forces the value of active() to true
*/
template <class Config, class Key1, class X1, class Key2, class X2, class Key3, class X3>
class NonlinearEqualityConstraint3 : public NonlinearConstraint3<Config, Key1, X1, Key2, X2, Key3, X3> {
protected:
typedef NonlinearEqualityConstraint3<Config,Key1,X1,Key2,X2,Key3,X3> This;
typedef NonlinearConstraint3<Config,Key1,X1,Key2,X2,Key3,X3> Base;
public:
NonlinearEqualityConstraint3(const Key1& key1, const Key2& key2, const Key3& key3,
size_t dim, double mu = 1000.0)
: Base(key1, key2, key3, dim, mu) {}
virtual ~NonlinearEqualityConstraint3() {}
/** Always active, so fixed value for active() */
virtual bool active(const Config& c) const { return true; }
};
/**
* Simple unary equality constraint - fixes a value for a variable
*/
@ -288,6 +400,7 @@ public:
NonlinearEquality1(const X& value, const Key& key1, double mu = 1000.0)
: Base(key1, X::dim(), mu), value_(value) {}
virtual ~NonlinearEquality1() {}
/** g(x) with optional derivative */
Vector evaluateError(const X& x1, boost::optional<Matrix&> H1 = boost::none) const {
@ -313,6 +426,7 @@ public:
NonlinearEquality2(const Key& key1, const Key& key2, double mu = 1000.0)
: Base(key1, key2, X::dim(), mu) {}
virtual ~NonlinearEquality2() {}
/** g(x) with optional derivative2 */
Vector evaluateError(const X& x1, const X& x2,

View File

@ -388,7 +388,7 @@ namespace gtsam {
/** Print */
void print(const std::string& s = "") const {
std::cout << "NonlinearFactor2 " << s << std::endl;
std::cout << "NonlinearFactor3 " << s << std::endl;
std::cout << "key1: " << (std::string) key1_ << std::endl;
std::cout << "key2: " << (std::string) key2_ << std::endl;
std::cout << "key3: " << (std::string) key3_ << std::endl;
@ -419,7 +419,7 @@ namespace gtsam {
boost::shared_ptr<GaussianFactor> linearize(const Config& c) const {
const X1& x1 = c[key1_];
const X2& x2 = c[key2_];
const X2& x3 = c[key3_];
const X3& x3 = c[key3_];
Matrix A1, A2, A3;
Vector b = -evaluateError(x1, x2, x3, A1, A2, A3);
// TODO pass unwhitened + noise model to Gaussian factor

View File

@ -6,7 +6,7 @@
#pragma once
#include <NonlinearConstraint.h>
#include "NonlinearConstraint.h"
namespace gtsam {

View File

@ -6,8 +6,8 @@
#pragma once
#include <Lie.h>
#include <NonlinearConstraint.h>
#include "Lie.h"
#include "NonlinearConstraint.h"
namespace gtsam {

View File

@ -28,7 +28,7 @@ check_PROGRAMS += tests/testSimulated3D
headers += BetweenFactor.h PriorFactor.h
# General constraints
headers += BetweenConstraint.h BoundingConstraint.h
headers += BetweenConstraint.h BoundingConstraint.h TransformConstraint.h
# 2D Pose SLAM
sources += pose2SLAM.cpp Pose2SLAMOptimizer.cpp dataset.cpp

View File

@ -0,0 +1,66 @@
/*
* @file TransformConstraint.h
* @brief A constraint for combining graphs by common landmarks and a transform node
* @author Alex Cunningham
*/
#pragma once
#include "NonlinearConstraint.h"
namespace gtsam {
/**
* A constraint between two landmarks in separate maps
* Templated on:
* Config : The overall config
* PKey : Key of landmark being constrained
* Point : Type of landmark
* TKey : Key of transform used
* Transform : Transform variable class
*
* The Point and Transform concepts must be Lie types, and the transform
* relationship "Point = transform_from(Transform, Point)" must exist.
*
* This base class should be specialized to implement the cost function for
* specific classes of landmarks
*/
template<class Config, class PKey, class Point, class TKey, class Transform>
class TransformConstraint : public NonlinearEqualityConstraint3<Config, PKey, Point, TKey, Transform, PKey, Point> {
public:
typedef NonlinearEqualityConstraint3<Config, PKey, Point, TKey, Transform, PKey, Point> Base;
typedef TransformConstraint<Config, PKey, Point, TKey, Transform> This;
/**
* General constructor with all of the keys to variables in the map
* Extracts everything necessary from the key types
*/
TransformConstraint(const PKey& foreignKey, const TKey& transKey, const PKey& localKey, double mu = 1000.0)
: Base(foreignKey, transKey, localKey, Point().dim(), mu) {}
virtual ~TransformConstraint(){}
/** Combined cost and derivative function using boost::optional */
virtual Vector evaluateError(const Point& foreign, const Transform& trans, const Point& local,
boost::optional<Matrix&> Dforeign = boost::none,
boost::optional<Matrix&> Dtrans = boost::none,
boost::optional<Matrix&> Dlocal = boost::none) const {
if (Dforeign) {
Matrix Af;
transform_from(trans, foreign, boost::none, Af);
*Dforeign = Af;
}
if (Dtrans) {
Matrix At;
transform_from(trans, foreign, At, boost::none);
*Dtrans = At;
}
if (Dlocal) {
Point dummy;
*Dlocal = -1* eye(dummy.dim());
}
return gtsam::logmap(gtsam::between<Point>(local, transform_from(trans, foreign)));
}
};
} // \namespace gtsam

View File

@ -10,10 +10,10 @@
#include <numericalDerivative.h> // TODO: remove
#include <NonlinearConstraint.h>
#include <BetweenConstraint.h>
#include <BoundingConstraint.h>
#include <simulated2D.h>
#include "NonlinearConstraint.h"
#include "BetweenConstraint.h"
#include "BoundingConstraint.h"
#include "simulated2D.h"
// \namespace

View File

@ -11,6 +11,7 @@ check_PROGRAMS += testNonlinearEquality testNonlinearFactor testNonlinearFactorG
check_PROGRAMS += testNonlinearOptimizer testSubgraphPreconditioner
check_PROGRAMS += testSymbolicBayesNet testSymbolicFactorGraph testTupleConfig
check_PROGRAMS += testNonlinearEqualityConstraint testBoundingConstraint
check_PROGRAMS += testTransformConstraint
if USE_LDL
check_PROGRAMS += testConstraintOptimizer

View File

@ -0,0 +1,263 @@
/*
* @file testTransformConstraint.cpp
* @author Alex Cunningham
*/
#include <iostream>
#include <boost/bind.hpp>
#include <CppUnitLite/TestHarness.h>
#include <Ordering.h>
#include <Key.h>
#include <numericalDerivative.h>
#include <Pose2.h>
#include <Point2.h>
#include <TransformConstraint.h>
#include <NonlinearEquality.h>
// implementations
#include <LieConfig-inl.h>
#include <TupleConfig-inl.h>
#include <NonlinearFactorGraph-inl.h>
#include <NonlinearOptimizer-inl.h>
using namespace std;
using namespace gtsam;
using namespace boost;
typedef TypedSymbol<Pose2, 'x'> PoseKey;
typedef TypedSymbol<Point2, 'l'> PointKey;
typedef TypedSymbol<Pose2, 'T'> TransformKey;
typedef LieConfig<PoseKey, Pose2> PoseConfig;
typedef LieConfig<PointKey, Point2> PointConfig;
typedef LieConfig<TransformKey, Pose2> TransformConfig;
typedef TupleConfig3< PoseConfig, PointConfig, TransformConfig > DDFConfig;
typedef NonlinearFactorGraph<DDFConfig> DDFGraph;
typedef NonlinearOptimizer<DDFGraph, DDFConfig> Optimizer;
typedef NonlinearEquality<DDFConfig, PoseKey, Pose2> PoseConstraint;
typedef NonlinearEquality<DDFConfig, PointKey, Point2> PointConstraint;
typedef NonlinearEquality<DDFConfig, TransformKey, Pose2> TransformPriorConstraint;
typedef TransformConstraint<DDFConfig, PointKey, Point2, TransformKey, Pose2> PointTransformConstraint;
PointKey lA1(1), lA2(2), lB1(3);
TransformKey t1(1);
/* ************************************************************************* */
TEST( TransformConstraint, equals ) {
PointTransformConstraint c1(lB1, t1, lA1),
c2(lB1, t1, lA1),
c3(lB1, t1, lA2);
CHECK(assert_equal(c1, c1));
CHECK(assert_equal(c1, c2));
CHECK(!c1.equals(c3));
}
/* ************************************************************************* */
TEST( TransformConstraint, jacobians ) {
// from examples below
Point2 local(2.0, 3.0), global(-1.0, 2.0);
Pose2 trans(1.5, 2.5, 0.3);
PointTransformConstraint tc(lA1, t1, lB1);
Matrix actualDT, actualDL, actualDF;
tc.evaluateError(global, trans, local, actualDF, actualDT, actualDL);
Matrix numericalDT, numericalDL, numericalDF;
numericalDF = numericalDerivative31<Vector, Point2, Pose2, Point2>(
boost::bind(&PointTransformConstraint::evaluateError, ref(tc), _1, _2, _3, boost::none, boost::none, boost::none),
global, trans, local, 1e-5);
numericalDT = numericalDerivative32<Vector, Point2, Pose2, Point2>(
boost::bind(&PointTransformConstraint::evaluateError, ref(tc), _1, _2, _3, boost::none, boost::none, boost::none),
global, trans, local, 1e-5);
numericalDL = numericalDerivative33<Vector, Point2, Pose2, Point2>(
boost::bind(&PointTransformConstraint::evaluateError, ref(tc), _1, _2, _3, boost::none, boost::none, boost::none),
global, trans, local, 1e-5);
CHECK(assert_equal(numericalDF, actualDF));
CHECK(assert_equal(numericalDL, actualDL));
CHECK(assert_equal(numericalDT, actualDT));
}
/* ************************************************************************* */
TEST( TransformConstraint, jacobians_zero ) {
// get values that are ideal
Pose2 trans(2.0, 3.0, 0.0);
Point2 global(5.0, 6.0);
Point2 local = transform_from(trans, global);
PointTransformConstraint tc(lA1, t1, lB1);
Vector actCost = tc.evaluateError(global, trans, local),
expCost = zero(2);
CHECK(assert_equal(expCost, actCost, 1e-5));
Matrix actualDT, actualDL, actualDF;
tc.evaluateError(global, trans, local, actualDF, actualDT, actualDL);
Matrix numericalDT, numericalDL, numericalDF;
numericalDF = numericalDerivative31<Vector, Point2, Pose2, Point2>(
boost::bind(&PointTransformConstraint::evaluateError, ref(tc), _1, _2, _3, boost::none, boost::none, boost::none),
global, trans, local, 1e-5);
numericalDT = numericalDerivative32<Vector, Point2, Pose2, Point2>(
boost::bind(&PointTransformConstraint::evaluateError, ref(tc), _1, _2, _3, boost::none, boost::none, boost::none),
global, trans, local, 1e-5);
numericalDL = numericalDerivative33<Vector, Point2, Pose2, Point2>(
boost::bind(&PointTransformConstraint::evaluateError, ref(tc), _1, _2, _3, boost::none, boost::none, boost::none),
global, trans, local, 1e-5);
CHECK(assert_equal(numericalDF, actualDF));
CHECK(assert_equal(numericalDL, actualDL));
CHECK(assert_equal(numericalDT, actualDT));
}
/* ************************************************************************* */
TEST( TransformConstraint, converge_trans ) {
// initial points
Point2 local1(2.0, 2.0), local2(4.0, 5.0),
global1(-1.0, 5.0), global2(2.0, 3.0);
Pose2 transIdeal(7.0, 3.0, M_PI/2);
// verify direction
CHECK(assert_equal(local1, transform_from(transIdeal, global1)));
CHECK(assert_equal(local2, transform_from(transIdeal, global2)));
// choose transform
// Pose2 trans = transIdeal; // ideal - works
// Pose2 trans = transIdeal * Pose2(0.1, 1.0, 0.00); // translation - works
// Pose2 trans = transIdeal * Pose2(10.1, 1.0, 0.00); // large translation - works
// Pose2 trans = transIdeal * Pose2(0.0, 0.0, 0.1); // small rotation - works
Pose2 trans = transIdeal * Pose2(-200.0, 100.0, 1.3); // combined - works
// Pose2 trans = transIdeal * Pose2(-200.0, 100.0, 2.0); // beyond pi/2 - fails
// keys
PointKey localK1(1), localK2(2),
globalK1(3), globalK2(4);
TransformKey transK(1);
DDFGraph graph;
graph.add(PointTransformConstraint(globalK1, transK, localK1));
graph.add(PointTransformConstraint(globalK2, transK, localK2));
// hard constraints on points
double error_gain = 1000.0;
graph.add(PointConstraint(localK1, local1, error_gain));
graph.add(PointConstraint(localK2, local2, error_gain));
graph.add(PointConstraint(globalK1, global1, error_gain));
graph.add(PointConstraint(globalK2, global2, error_gain));
// create initial estimate
DDFConfig init;
init.insert(localK1, local1);
init.insert(localK2, local2);
init.insert(globalK1, global1);
init.insert(globalK2, global2);
init.insert(transK, trans);
// optimize
Optimizer::shared_config actual = Optimizer::optimizeLM(graph, init);
DDFConfig expected;
expected.insert(localK1, local1);
expected.insert(localK2, local2);
expected.insert(globalK1, global1);
expected.insert(globalK2, global2);
expected.insert(transK, transIdeal);
CHECK(assert_equal(expected, *actual, 1e-4));
}
/* ************************************************************************* */
TEST( TransformConstraint, converge_local ) {
// initial points
Point2 global(-1.0, 2.0);
// Pose2 trans(1.5, 2.5, 0.3); // original
// Pose2 trans(1.5, 2.5, 1.0); // larger rotation
Pose2 trans(1.5, 2.5, 3.1); // significant rotation
Point2 idealLocal = transform_from(trans, global);
// perturb the initial estimate
// Point2 local = idealLocal; // Ideal case - works
// Point2 local = idealLocal + Point2(1.0, 0.0); // works
Point2 local = idealLocal + Point2(-10.0, 10.0); // works
// keys
PointKey localK(1), globalK(2);
TransformKey transK(1);
DDFGraph graph;
double error_gain = 1000.0;
graph.add(PointTransformConstraint(globalK, transK, localK));
graph.add(PointConstraint(globalK, global, error_gain));
graph.add(TransformPriorConstraint(transK, trans, error_gain));
// create initial estimate
DDFConfig init;
init.insert(localK, local);
init.insert(globalK, global);
init.insert(transK, trans);
// optimize
Optimizer::shared_config actual = Optimizer::optimizeLM(graph, init);
CHECK(assert_equal(idealLocal, actual->at(localK), 1e-5));
}
/* ************************************************************************* */
TEST( TransformConstraint, converge_global ) {
// initial points
Point2 local(2.0, 3.0);
// Pose2 trans(1.5, 2.5, 0.3); // original
// Pose2 trans(1.5, 2.5, 1.0); // larger rotation
Pose2 trans(1.5, 2.5, 3.1); // significant rotation
Point2 idealForeign = transform_from(inverse(trans), local);
// perturb the initial estimate
// Point2 global = idealForeign; // Ideal - works
// Point2 global = idealForeign + Point2(1.0, 0.0); // simple - works
Point2 global = idealForeign + Point2(10.0, -10.0); // larger - works
// keys
PointKey localK(1), globalK(2);
TransformKey transK(1);
DDFGraph graph;
double error_gain = 1000.0;
graph.add(PointTransformConstraint(globalK, transK, localK));
graph.add(PointConstraint(localK, local, error_gain));
graph.add(TransformPriorConstraint(transK, trans, error_gain));
// create initial estimate
DDFConfig init;
init.insert(localK, local);
init.insert(globalK, global);
init.insert(transK, trans);
// optimize
Optimizer::shared_config actual = Optimizer::optimizeLM(graph, init);
// verify
CHECK(assert_equal(idealForeign, actual->at(globalK), 1e-5));
}
/* ************************************************************************* */
int main() { TestResult tr; return TestRegistry::runAllTests(tr); }
/* ************************************************************************* */