| 
									
										
										
										
											2009-11-24 03:57:35 +08:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * @file testSQPOptimizer.cpp | 
					
						
							|  |  |  |  * @brief tests the optimization algorithm for nonlinear graphs with nonlinear constraints | 
					
						
							|  |  |  |  * @author Alex Cunningham | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <CppUnitLite/TestHarness.h>
 | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | #include <boost/assign/std/list.hpp> // for operator +=
 | 
					
						
							|  |  |  | #include <boost/assign/std/map.hpp> // for insert
 | 
					
						
							| 
									
										
										
										
											2009-12-02 03:45:47 +08:00
										 |  |  | #include <boost/bind.hpp>
 | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | #include <simulated2D.h>
 | 
					
						
							| 
									
										
										
										
											2009-11-24 03:57:35 +08:00
										 |  |  | #include "NonlinearFactorGraph.h"
 | 
					
						
							|  |  |  | #include "NonlinearConstraint.h"
 | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | #include "NonlinearEquality.h"
 | 
					
						
							| 
									
										
										
										
											2009-11-24 03:57:35 +08:00
										 |  |  | #include "VectorConfig.h"
 | 
					
						
							|  |  |  | #include "Ordering.h"
 | 
					
						
							| 
									
										
										
										
											2009-11-29 05:00:09 +08:00
										 |  |  | #include "NonlinearOptimizer.h"
 | 
					
						
							| 
									
										
										
										
											2009-11-24 03:57:35 +08:00
										 |  |  | #include "SQPOptimizer.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // implementations
 | 
					
						
							|  |  |  | #include "NonlinearConstraint-inl.h"
 | 
					
						
							| 
									
										
										
										
											2009-11-29 05:00:09 +08:00
										 |  |  | #include "NonlinearOptimizer-inl.h"
 | 
					
						
							| 
									
										
										
										
											2009-11-24 03:57:35 +08:00
										 |  |  | #include "SQPOptimizer-inl.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | using namespace std; | 
					
						
							|  |  |  | using namespace gtsam; | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | using namespace boost::assign; | 
					
						
							| 
									
										
										
										
											2009-11-24 03:57:35 +08:00
										 |  |  | 	 | 
					
						
							|  |  |  | // typedefs
 | 
					
						
							|  |  |  | typedef boost::shared_ptr<VectorConfig> shared_config; | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | typedef NonlinearFactorGraph<VectorConfig> NLGraph; | 
					
						
							|  |  |  | typedef boost::shared_ptr<NonlinearFactor<VectorConfig> > shared; | 
					
						
							|  |  |  | typedef boost::shared_ptr<NonlinearConstraint<VectorConfig> > shared_c; | 
					
						
							| 
									
										
										
										
											2009-11-24 03:57:35 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | TEST ( SQPOptimizer, basic ) { | 
					
						
							|  |  |  | 	// create a basic optimizer
 | 
					
						
							|  |  |  | 	NLGraph graph; | 
					
						
							|  |  |  | 	Ordering ordering; | 
					
						
							|  |  |  | 	shared_config config(new VectorConfig); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	SQPOptimizer<NLGraph, VectorConfig> optimizer(graph, ordering, config); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// verify components
 | 
					
						
							|  |  |  | 	CHECK(assert_equal(graph, *(optimizer.graph()))); | 
					
						
							|  |  |  | 	CHECK(assert_equal(ordering, *(optimizer.ordering()))); | 
					
						
							|  |  |  | 	CHECK(assert_equal(*config, *(optimizer.config()))); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-24 14:39:27 +08:00
										 |  |  | /* ********************************************************************* */ | 
					
						
							|  |  |  | // Example that moves two separate maps into the same frame of reference
 | 
					
						
							|  |  |  | // Note that this is a linear example, so it should converge in one step
 | 
					
						
							|  |  |  | /* ********************************************************************* */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-24 06:10:52 +08:00
										 |  |  | namespace sqp_LinearMapWarp2 { | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | // binary constraint between landmarks
 | 
					
						
							|  |  |  | /** g(x) = x-y = 0 */ | 
					
						
							| 
									
										
										
										
											2009-11-29 03:18:02 +08:00
										 |  |  | Vector g_func(const VectorConfig& config, const list<string>& keys) { | 
					
						
							|  |  |  | 	return config[keys.front()]-config[keys.back()]; | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-12-18 11:05:47 +08:00
										 |  |  | /** jacobian at l1 */ | 
					
						
							|  |  |  | Matrix jac_g1(const VectorConfig& config, const list<string>& keys) { | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | 	return eye(2); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-12-18 11:05:47 +08:00
										 |  |  | /** jacobian at l2 */ | 
					
						
							|  |  |  | Matrix jac_g2(const VectorConfig& config, const list<string>& keys) { | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | 	return -1*eye(2); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2009-11-24 06:10:52 +08:00
										 |  |  | } // \namespace sqp_LinearMapWarp2
 | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-24 06:10:52 +08:00
										 |  |  | namespace sqp_LinearMapWarp1 { | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | // Unary Constraint on x1
 | 
					
						
							|  |  |  | /** g(x) = x -[1;1] = 0 */ | 
					
						
							| 
									
										
										
										
											2009-11-29 03:18:02 +08:00
										 |  |  | Vector g_func(const VectorConfig& config, const list<string>& keys) { | 
					
						
							|  |  |  | 	return config[keys.front()]-Vector_(2, 1.0, 1.0); | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-12-18 11:05:47 +08:00
										 |  |  | /** jacobian at x1 */ | 
					
						
							|  |  |  | Matrix jac_g(const VectorConfig& config, const list<string>& keys) { | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | 	return eye(2); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2009-11-24 06:10:52 +08:00
										 |  |  | } // \namespace sqp_LinearMapWarp12
 | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | typedef SQPOptimizer<NLGraph, VectorConfig> Optimizer; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-24 14:39:27 +08:00
										 |  |  | /**
 | 
					
						
							|  |  |  |  * Creates the graph with each robot seeing the landmark, and it is | 
					
						
							|  |  |  |  * known that it is the same landmark | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2009-11-24 06:10:52 +08:00
										 |  |  | NLGraph linearMapWarpGraph() { | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | 	// constant constraint on x1
 | 
					
						
							| 
									
										
										
										
											2009-12-18 10:39:02 +08:00
										 |  |  | 	list<string> keyx; keyx += "x1"; | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | 	boost::shared_ptr<NonlinearConstraint1<VectorConfig> > c1( | 
					
						
							|  |  |  | 			new NonlinearConstraint1<VectorConfig>( | 
					
						
							| 
									
										
										
										
											2009-12-18 10:39:02 +08:00
										 |  |  | 					boost::bind(sqp_LinearMapWarp1::g_func, _1, keyx), | 
					
						
							| 
									
										
										
										
											2009-12-18 11:05:47 +08:00
										 |  |  | 					"x1", boost::bind(sqp_LinearMapWarp1::jac_g, _1, keyx), | 
					
						
							| 
									
										
										
										
											2009-12-18 09:40:29 +08:00
										 |  |  | 					 2, "L_x1")); | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// measurement from x1 to l1
 | 
					
						
							|  |  |  | 	Vector z1 = Vector_(2, 0.0, 5.0); | 
					
						
							|  |  |  | 	double sigma1 = 0.1; | 
					
						
							| 
									
										
										
										
											2010-01-14 10:50:06 +08:00
										 |  |  | 	shared f1(new simulated2D::Measurement(z1, sigma1, "x1", "l1")); | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// measurement from x2 to l2
 | 
					
						
							|  |  |  | 	Vector z2 = Vector_(2, -4.0, 0.0); | 
					
						
							|  |  |  | 	double sigma2 = 0.1; | 
					
						
							| 
									
										
										
										
											2010-01-14 10:50:06 +08:00
										 |  |  | 	shared f2(new simulated2D::Measurement(z2, sigma2, "x2", "l2")); | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// equality constraint between l1 and l2
 | 
					
						
							| 
									
										
										
										
											2009-12-18 10:39:02 +08:00
										 |  |  | 	list<string> keys; keys += "l1", "l2"; | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | 	boost::shared_ptr<NonlinearConstraint2<VectorConfig> > c2( | 
					
						
							|  |  |  | 			new NonlinearConstraint2<VectorConfig>( | 
					
						
							| 
									
										
										
										
											2009-12-18 10:39:02 +08:00
										 |  |  | 					boost::bind(sqp_LinearMapWarp2::g_func, _1, keys), | 
					
						
							| 
									
										
										
										
											2009-12-18 11:05:47 +08:00
										 |  |  | 					"l1", boost::bind(sqp_LinearMapWarp2::jac_g1, _1, keys), | 
					
						
							|  |  |  | 					"l2", boost::bind(sqp_LinearMapWarp2::jac_g2, _1, keys), | 
					
						
							| 
									
										
										
										
											2009-12-18 09:40:29 +08:00
										 |  |  | 					 2, "L_l1l2")); | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// construct the graph
 | 
					
						
							|  |  |  | 	NLGraph graph; | 
					
						
							|  |  |  | 	graph.push_back(c1); | 
					
						
							|  |  |  | 	graph.push_back(c2); | 
					
						
							|  |  |  | 	graph.push_back(f1); | 
					
						
							|  |  |  | 	graph.push_back(f2); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-24 06:10:52 +08:00
										 |  |  | 	return graph; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* ********************************************************************* */ | 
					
						
							|  |  |  | TEST ( SQPOptimizer, map_warp_initLam ) { | 
					
						
							|  |  |  | 	bool verbose = false; | 
					
						
							|  |  |  | 	// get a graph
 | 
					
						
							|  |  |  | 	NLGraph graph = linearMapWarpGraph(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | 	// create an initial estimate
 | 
					
						
							| 
									
										
										
										
											2009-11-24 06:10:52 +08:00
										 |  |  | 	shared_config initialEstimate(new VectorConfig); | 
					
						
							|  |  |  | 	initialEstimate->insert("x1", Vector_(2, 1.0, 1.0)); | 
					
						
							|  |  |  | 	initialEstimate->insert("l1", Vector_(2, 1.0, 6.0)); | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | 	initialEstimate->insert("l2", Vector_(2, -4.0, 0.0)); // starting with a separate reference frame
 | 
					
						
							|  |  |  | 	initialEstimate->insert("x2", Vector_(2, 0.0, 0.0)); // other pose starts at origin
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// create an initial estimate for the lagrange multiplier
 | 
					
						
							|  |  |  | 	shared_config initLagrange(new VectorConfig); | 
					
						
							|  |  |  | 	initLagrange->insert("L_l1l2", Vector_(2, 1.0, 1.0)); | 
					
						
							|  |  |  | 	initLagrange->insert("L_x1", Vector_(2, 1.0, 1.0)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// create an ordering
 | 
					
						
							|  |  |  | 	Ordering ordering; | 
					
						
							|  |  |  | 	ordering += "x1", "x2", "l1", "l2", "L_l1l2", "L_x1"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// create an optimizer
 | 
					
						
							|  |  |  | 	Optimizer optimizer(graph, ordering, initialEstimate, initLagrange); | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | 	if (verbose) optimizer.print("Initialized Optimizer"); | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// perform an iteration of optimization
 | 
					
						
							|  |  |  | 	Optimizer oneIteration = optimizer.iterate(Optimizer::SILENT); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// get the config back out and verify
 | 
					
						
							|  |  |  | 	VectorConfig actual = *(oneIteration.config()); | 
					
						
							|  |  |  | 	VectorConfig expected; | 
					
						
							|  |  |  | 	expected.insert("x1", Vector_(2, 1.0, 1.0)); | 
					
						
							|  |  |  | 	expected.insert("l1", Vector_(2, 1.0, 6.0)); | 
					
						
							|  |  |  | 	expected.insert("l2", Vector_(2, 1.0, 6.0)); | 
					
						
							|  |  |  | 	expected.insert("x2", Vector_(2, 5.0, 6.0)); | 
					
						
							| 
									
										
										
										
											2009-11-29 05:00:09 +08:00
										 |  |  | 	CHECK(assert_equal(expected, actual)); | 
					
						
							| 
									
										
										
										
											2009-11-24 05:25:31 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-24 06:10:52 +08:00
										 |  |  | /* ********************************************************************* */ | 
					
						
							|  |  |  | TEST ( SQPOptimizer, map_warp ) { | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | 	bool verbose = false; | 
					
						
							| 
									
										
										
										
											2009-11-24 06:10:52 +08:00
										 |  |  | 	// get a graph
 | 
					
						
							|  |  |  | 	NLGraph graph = linearMapWarpGraph(); | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | 	if (verbose) graph.print("Initial map warp graph"); | 
					
						
							| 
									
										
										
										
											2009-11-24 06:10:52 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// create an initial estimate
 | 
					
						
							|  |  |  | 	shared_config initialEstimate(new VectorConfig); | 
					
						
							|  |  |  | 	initialEstimate->insert("x1", Vector_(2, 1.0, 1.0)); | 
					
						
							|  |  |  | 	initialEstimate->insert("l1", Vector_(2, 1.0, 6.0)); | 
					
						
							|  |  |  | 	initialEstimate->insert("l2", Vector_(2, -4.0, 0.0)); // starting with a separate reference frame
 | 
					
						
							|  |  |  | 	initialEstimate->insert("x2", Vector_(2, 0.0, 0.0)); // other pose starts at origin
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// create an ordering
 | 
					
						
							|  |  |  | 	Ordering ordering; | 
					
						
							|  |  |  | 	ordering += "x1", "x2", "l1", "l2"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// create an optimizer
 | 
					
						
							|  |  |  | 	Optimizer optimizer(graph, ordering, initialEstimate); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// perform an iteration of optimization
 | 
					
						
							|  |  |  | 	Optimizer oneIteration = optimizer.iterate(Optimizer::SILENT); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// get the config back out and verify
 | 
					
						
							|  |  |  | 	VectorConfig actual = *(oneIteration.config()); | 
					
						
							|  |  |  | 	VectorConfig expected; | 
					
						
							|  |  |  | 	expected.insert("x1", Vector_(2, 1.0, 1.0)); | 
					
						
							|  |  |  | 	expected.insert("l1", Vector_(2, 1.0, 6.0)); | 
					
						
							|  |  |  | 	expected.insert("l2", Vector_(2, 1.0, 6.0)); | 
					
						
							|  |  |  | 	expected.insert("x2", Vector_(2, 5.0, 6.0)); | 
					
						
							| 
									
										
										
										
											2009-11-29 05:00:09 +08:00
										 |  |  | 	CHECK(assert_equal(expected, actual)); | 
					
						
							| 
									
										
										
										
											2009-11-24 06:10:52 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | /* ********************************************************************* */ | 
					
						
							|  |  |  | // This is an obstacle avoidance demo, where there is a trajectory of
 | 
					
						
							|  |  |  | // three points, where there is a circular obstacle in the middle.  There
 | 
					
						
							|  |  |  | // is a binary inequality constraint connecting the obstacle to the
 | 
					
						
							|  |  |  | // states, which enforces a minimum distance.
 | 
					
						
							|  |  |  | /* ********************************************************************* */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												Large gtsam refactoring
To support faster development *and* better performance Richard and I pushed through a large refactoring of NonlinearFactors.
The following are the biggest changes:
1) NonLinearFactor1 and NonLinearFactor2 are now templated on Config, Key type, and X type, where X is the argument to the measurement function.
2) The measurement itself is no longer kept in the nonlinear factor. Instead, a derived class (see testVSLAMFactor, testNonlinearEquality, testPose3Factor etc...) has to implement a function to compute the errors, "evaluateErrors". Instead of (h(x)-z), it needs to return (z-h(x)), so Ax-b is an approximation of the error. IMPORTANT: evaluateErrors needs - if asked - *combine* the calculation of the function value h(x) and the derivatives dh(x)/dx. This was a major performance issue. To do this, boost::optional<Matrix&> arguments are provided, and tin EvaluateErrors you just  says something like
	if (H) *H = Matrix_(3,6,....);
3) We are no longer using int or strings for nonlinear factors. Instead, the preferred key type is now Symbol, defined in Key.h. This is both fast and cool: you can construct it from an int, and cast it to a strong. It also does type checking: a Symbol<Pose3,'x'> will not match a Symbol<Pose2,'x'>
4) minor: take a look at LieConfig.h: it help you avoid writing a lot of code bu automatically creating configs for a certain type. See e.g. Pose3Config.h. A "double" LieConfig is on the way - Thanks Richard and Manohar !
											
										 
											2010-01-14 06:25:03 +08:00
										 |  |  | bool vector_compare(const Vector& a, const Vector& b) { | 
					
						
							|  |  |  | 	return equal_with_abs_tol(a, b, 1e-5); | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | typedef NonlinearConstraint1<VectorConfig> NLC1; | 
					
						
							|  |  |  | typedef boost::shared_ptr<NLC1> shared_NLC1; | 
					
						
							|  |  |  | typedef NonlinearConstraint2<VectorConfig> NLC2; | 
					
						
							|  |  |  | typedef boost::shared_ptr<NLC2> shared_NLC2; | 
					
						
							| 
									
										
											  
											
												Large gtsam refactoring
To support faster development *and* better performance Richard and I pushed through a large refactoring of NonlinearFactors.
The following are the biggest changes:
1) NonLinearFactor1 and NonLinearFactor2 are now templated on Config, Key type, and X type, where X is the argument to the measurement function.
2) The measurement itself is no longer kept in the nonlinear factor. Instead, a derived class (see testVSLAMFactor, testNonlinearEquality, testPose3Factor etc...) has to implement a function to compute the errors, "evaluateErrors". Instead of (h(x)-z), it needs to return (z-h(x)), so Ax-b is an approximation of the error. IMPORTANT: evaluateErrors needs - if asked - *combine* the calculation of the function value h(x) and the derivatives dh(x)/dx. This was a major performance issue. To do this, boost::optional<Matrix&> arguments are provided, and tin EvaluateErrors you just  says something like
	if (H) *H = Matrix_(3,6,....);
3) We are no longer using int or strings for nonlinear factors. Instead, the preferred key type is now Symbol, defined in Key.h. This is both fast and cool: you can construct it from an int, and cast it to a strong. It also does type checking: a Symbol<Pose3,'x'> will not match a Symbol<Pose2,'x'>
4) minor: take a look at LieConfig.h: it help you avoid writing a lot of code bu automatically creating configs for a certain type. See e.g. Pose3Config.h. A "double" LieConfig is on the way - Thanks Richard and Manohar !
											
										 
											2010-01-14 06:25:03 +08:00
										 |  |  | typedef NonlinearEquality<VectorConfig,string,Vector> NLE; | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | typedef boost::shared_ptr<NLE> shared_NLE; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace sqp_avoid1 { | 
					
						
							|  |  |  | // avoidance radius
 | 
					
						
							|  |  |  | double radius = 1.0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // binary avoidance constraint
 | 
					
						
							| 
									
										
										
										
											2009-12-01 01:36:34 +08:00
										 |  |  | /** g(x) = ||x2-obs||^2 - radius^2 > 0 */ | 
					
						
							| 
									
										
										
										
											2009-11-29 03:18:02 +08:00
										 |  |  | Vector g_func(const VectorConfig& config, const list<string>& keys) { | 
					
						
							|  |  |  | 	Vector delta = config[keys.front()]-config[keys.back()]; | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | 	double dist2 = sum(emul(delta, delta)); | 
					
						
							|  |  |  | 	double thresh = radius*radius; | 
					
						
							| 
									
										
										
										
											2009-12-01 01:36:34 +08:00
										 |  |  | 	return Vector_(1, dist2-thresh); | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-12-18 11:05:47 +08:00
										 |  |  | /** jacobian at pose */ | 
					
						
							|  |  |  | Matrix jac_g1(const VectorConfig& config, const list<string>& keys) { | 
					
						
							| 
									
										
										
										
											2009-11-29 05:00:09 +08:00
										 |  |  | 	Vector x2 = config[keys.front()], obs = config[keys.back()]; | 
					
						
							|  |  |  | 	Vector grad = 2.0*(x2-obs); | 
					
						
							|  |  |  | 	return Matrix_(1,2, grad(0), grad(1)); | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-12-18 11:05:47 +08:00
										 |  |  | /** jacobian at obstacle */ | 
					
						
							|  |  |  | Matrix jac_g2(const VectorConfig& config, const list<string>& keys) { | 
					
						
							| 
									
										
										
										
											2009-11-29 05:00:09 +08:00
										 |  |  | 	Vector x2 = config[keys.front()], obs = config[keys.back()]; | 
					
						
							|  |  |  | 	Vector grad = -2.0*(x2-obs); | 
					
						
							|  |  |  | 	return Matrix_(1,2, grad(0), grad(1)); | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | pair<NLGraph, VectorConfig> obstacleAvoidGraph() { | 
					
						
							|  |  |  | 	// fix start, end, obstacle positions
 | 
					
						
							|  |  |  | 	VectorConfig feasible; | 
					
						
							| 
									
										
											  
											
												Large gtsam refactoring
To support faster development *and* better performance Richard and I pushed through a large refactoring of NonlinearFactors.
The following are the biggest changes:
1) NonLinearFactor1 and NonLinearFactor2 are now templated on Config, Key type, and X type, where X is the argument to the measurement function.
2) The measurement itself is no longer kept in the nonlinear factor. Instead, a derived class (see testVSLAMFactor, testNonlinearEquality, testPose3Factor etc...) has to implement a function to compute the errors, "evaluateErrors". Instead of (h(x)-z), it needs to return (z-h(x)), so Ax-b is an approximation of the error. IMPORTANT: evaluateErrors needs - if asked - *combine* the calculation of the function value h(x) and the derivatives dh(x)/dx. This was a major performance issue. To do this, boost::optional<Matrix&> arguments are provided, and tin EvaluateErrors you just  says something like
	if (H) *H = Matrix_(3,6,....);
3) We are no longer using int or strings for nonlinear factors. Instead, the preferred key type is now Symbol, defined in Key.h. This is both fast and cool: you can construct it from an int, and cast it to a strong. It also does type checking: a Symbol<Pose3,'x'> will not match a Symbol<Pose2,'x'>
4) minor: take a look at LieConfig.h: it help you avoid writing a lot of code bu automatically creating configs for a certain type. See e.g. Pose3Config.h. A "double" LieConfig is on the way - Thanks Richard and Manohar !
											
										 
											2010-01-14 06:25:03 +08:00
										 |  |  | 	Vector feas1 = Vector_(2, 0.0, 0.0), feas2 = Vector_(2, 10.0, 0.0), feas3 = | 
					
						
							|  |  |  | 			Vector_(2, 5.0, -0.5); | 
					
						
							|  |  |  | 	feasible.insert("x1", feas1); | 
					
						
							|  |  |  | 	feasible.insert("x3", feas2); | 
					
						
							|  |  |  | 	feasible.insert("obs", feas3); | 
					
						
							|  |  |  | 	shared_NLE e1(new NLE("x1", feas1, vector_compare)); | 
					
						
							|  |  |  | 	shared_NLE e2(new NLE("x3", feas2, vector_compare)); | 
					
						
							|  |  |  | 	shared_NLE e3(new NLE("obs", feas3, vector_compare)); | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// measurement from x1 to x2
 | 
					
						
							|  |  |  | 	Vector x1x2 = Vector_(2, 5.0, 0.0); | 
					
						
							|  |  |  | 	double sigma1 = 0.1; | 
					
						
							| 
									
										
										
										
											2010-01-14 10:50:06 +08:00
										 |  |  | 	shared f1(new simulated2D::Odometry(x1x2, sigma1, "x1", "x2")); | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// measurement from x2 to x3
 | 
					
						
							|  |  |  | 	Vector x2x3 = Vector_(2, 5.0, 0.0); | 
					
						
							|  |  |  | 	double sigma2 = 0.1; | 
					
						
							| 
									
										
										
										
											2010-01-14 10:50:06 +08:00
										 |  |  | 	shared f2(new simulated2D::Odometry(x2x3, sigma2, "x2", "x3")); | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// create a binary inequality constraint that forces the middle point away from
 | 
					
						
							|  |  |  | 	//  the obstacle
 | 
					
						
							| 
									
										
										
										
											2009-12-18 10:39:02 +08:00
										 |  |  | 	list<string> keys; keys += "x2", "obs"; | 
					
						
							|  |  |  | 	shared_NLC2 c1(new NLC2(boost::bind(sqp_avoid1::g_func, _1, keys), | 
					
						
							| 
									
										
										
										
											2009-12-18 11:05:47 +08:00
										 |  |  | 							"x2", boost::bind(sqp_avoid1::jac_g1, _1, keys), | 
					
						
							|  |  |  | 						    "obs",boost::bind(sqp_avoid1::jac_g2, _1, keys), | 
					
						
							| 
									
										
										
										
											2009-12-18 09:40:29 +08:00
										 |  |  | 						    1, "L_x2obs", false)); | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// construct the graph
 | 
					
						
							|  |  |  | 	NLGraph graph; | 
					
						
							|  |  |  | 	graph.push_back(e1); | 
					
						
							|  |  |  | 	graph.push_back(e2); | 
					
						
							|  |  |  | 	graph.push_back(e3); | 
					
						
							| 
									
										
										
										
											2009-11-29 05:44:07 +08:00
										 |  |  | 	graph.push_back(c1); | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | 	graph.push_back(f1); | 
					
						
							|  |  |  | 	graph.push_back(f2); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return make_pair(graph, feasible); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-29 05:00:09 +08:00
										 |  |  | /* ********************************************************************* */ | 
					
						
							| 
									
										
										
										
											2009-11-29 05:44:07 +08:00
										 |  |  | TEST ( SQPOptimizer, inequality_avoid ) { | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | 	// create the graph
 | 
					
						
							|  |  |  | 	NLGraph graph; VectorConfig feasible; | 
					
						
							|  |  |  | 	boost::tie(graph, feasible) = obstacleAvoidGraph(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// create the rest of the config
 | 
					
						
							|  |  |  | 	shared_config init(new VectorConfig(feasible)); | 
					
						
							| 
									
										
										
										
											2009-11-29 05:00:09 +08:00
										 |  |  | 	init->insert("x2", Vector_(2, 5.0, 100.0)); | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// create an ordering
 | 
					
						
							|  |  |  | 	Ordering ord; | 
					
						
							|  |  |  | 	ord += "x1", "x2", "x3", "obs"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// create an optimizer
 | 
					
						
							|  |  |  | 	Optimizer optimizer(graph, ord, init); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-29 05:44:07 +08:00
										 |  |  | 	// perform an iteration of optimization
 | 
					
						
							|  |  |  | 	// NOTE: the constraint will be inactive in the first iteration,
 | 
					
						
							|  |  |  | 	// so it will violate the constraint after one iteration
 | 
					
						
							| 
									
										
										
										
											2009-11-29 05:00:09 +08:00
										 |  |  | 	Optimizer afterOneIteration = optimizer.iterate(Optimizer::SILENT); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-29 05:44:07 +08:00
										 |  |  | 	VectorConfig exp1(feasible); | 
					
						
							|  |  |  | 	exp1.insert("x2", Vector_(2, 5.0, 0.0)); | 
					
						
							|  |  |  | 	CHECK(assert_equal(exp1, *(afterOneIteration.config()))); | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-29 05:44:07 +08:00
										 |  |  | 	// the second iteration will activate the constraint and force the
 | 
					
						
							|  |  |  | 	// config to a viable configuration.
 | 
					
						
							|  |  |  | 	Optimizer after2ndIteration = afterOneIteration.iterate(Optimizer::SILENT); | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-29 05:44:07 +08:00
										 |  |  | 	VectorConfig exp2(feasible); | 
					
						
							| 
									
										
										
										
											2009-12-18 09:24:19 +08:00
										 |  |  | 	exp2.insert("x2", Vector_(2, 5.0, 0.75)); | 
					
						
							| 
									
										
										
										
											2009-11-29 05:44:07 +08:00
										 |  |  | 	CHECK(assert_equal(exp2, *(after2ndIteration.config()))); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-29 06:49:14 +08:00
										 |  |  | /* ********************************************************************* */ | 
					
						
							|  |  |  | TEST ( SQPOptimizer, inequality_avoid_iterative ) { | 
					
						
							|  |  |  | 	// create the graph
 | 
					
						
							|  |  |  | 	NLGraph graph; VectorConfig feasible; | 
					
						
							|  |  |  | 	boost::tie(graph, feasible) = obstacleAvoidGraph(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// create the rest of the config
 | 
					
						
							|  |  |  | 	shared_config init(new VectorConfig(feasible)); | 
					
						
							|  |  |  | 	init->insert("x2", Vector_(2, 5.0, 100.0)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// create an ordering
 | 
					
						
							|  |  |  | 	Ordering ord; | 
					
						
							|  |  |  | 	ord += "x1", "x2", "x3", "obs"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// create an optimizer
 | 
					
						
							|  |  |  | 	Optimizer optimizer(graph, ord, init); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	double relThresh = 1e-5; // minimum change in error between iterations
 | 
					
						
							|  |  |  | 	double absThresh = 1e-5; // minimum error necessary to converge
 | 
					
						
							|  |  |  | 	double constraintThresh = 1e-9; // minimum constraint error to be feasible
 | 
					
						
							|  |  |  | 	Optimizer final = optimizer.iterateSolve(relThresh, absThresh, constraintThresh); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// verify
 | 
					
						
							|  |  |  | 	VectorConfig exp2(feasible); | 
					
						
							| 
									
										
										
										
											2009-12-18 09:24:19 +08:00
										 |  |  | 	exp2.insert("x2", Vector_(2, 5.0, 0.75)); | 
					
						
							| 
									
										
										
										
											2009-11-29 06:49:14 +08:00
										 |  |  | 	CHECK(assert_equal(exp2, *(final.config()))); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-12-02 03:45:47 +08:00
										 |  |  | /* ********************************************************************* */ | 
					
						
							|  |  |  | // Use boost bind to parameterize the function
 | 
					
						
							|  |  |  | namespace sqp_avoid2 { | 
					
						
							|  |  |  | // binary avoidance constraint
 | 
					
						
							|  |  |  | /** g(x) = ||x2-obs||^2 - radius^2 > 0 */ | 
					
						
							|  |  |  | Vector g_func(double radius, const VectorConfig& config, const list<string>& keys) { | 
					
						
							|  |  |  | 	Vector delta = config[keys.front()]-config[keys.back()]; | 
					
						
							|  |  |  | 	double dist2 = sum(emul(delta, delta)); | 
					
						
							|  |  |  | 	double thresh = radius*radius; | 
					
						
							|  |  |  | 	return Vector_(1, dist2-thresh); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-12-18 11:05:47 +08:00
										 |  |  | /** jacobian at pose */ | 
					
						
							|  |  |  | Matrix jac_g1(const VectorConfig& config, const list<string>& keys) { | 
					
						
							| 
									
										
										
										
											2009-12-02 03:45:47 +08:00
										 |  |  | 	Vector x2 = config[keys.front()], obs = config[keys.back()]; | 
					
						
							|  |  |  | 	Vector grad = 2.0*(x2-obs); | 
					
						
							|  |  |  | 	return Matrix_(1,2, grad(0), grad(1)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-12-18 11:05:47 +08:00
										 |  |  | /** jacobian at obstacle */ | 
					
						
							|  |  |  | Matrix jac_g2(const VectorConfig& config, const list<string>& keys) { | 
					
						
							| 
									
										
										
										
											2009-12-02 03:45:47 +08:00
										 |  |  | 	Vector x2 = config[keys.front()], obs = config[keys.back()]; | 
					
						
							|  |  |  | 	Vector grad = -2.0*(x2-obs); | 
					
						
							|  |  |  | 	return Matrix_(1,2, grad(0), grad(1)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | pair<NLGraph, VectorConfig> obstacleAvoidGraphGeneral() { | 
					
						
							|  |  |  | 	// fix start, end, obstacle positions
 | 
					
						
							|  |  |  | 	VectorConfig feasible; | 
					
						
							| 
									
										
											  
											
												Large gtsam refactoring
To support faster development *and* better performance Richard and I pushed through a large refactoring of NonlinearFactors.
The following are the biggest changes:
1) NonLinearFactor1 and NonLinearFactor2 are now templated on Config, Key type, and X type, where X is the argument to the measurement function.
2) The measurement itself is no longer kept in the nonlinear factor. Instead, a derived class (see testVSLAMFactor, testNonlinearEquality, testPose3Factor etc...) has to implement a function to compute the errors, "evaluateErrors". Instead of (h(x)-z), it needs to return (z-h(x)), so Ax-b is an approximation of the error. IMPORTANT: evaluateErrors needs - if asked - *combine* the calculation of the function value h(x) and the derivatives dh(x)/dx. This was a major performance issue. To do this, boost::optional<Matrix&> arguments are provided, and tin EvaluateErrors you just  says something like
	if (H) *H = Matrix_(3,6,....);
3) We are no longer using int or strings for nonlinear factors. Instead, the preferred key type is now Symbol, defined in Key.h. This is both fast and cool: you can construct it from an int, and cast it to a strong. It also does type checking: a Symbol<Pose3,'x'> will not match a Symbol<Pose2,'x'>
4) minor: take a look at LieConfig.h: it help you avoid writing a lot of code bu automatically creating configs for a certain type. See e.g. Pose3Config.h. A "double" LieConfig is on the way - Thanks Richard and Manohar !
											
										 
											2010-01-14 06:25:03 +08:00
										 |  |  | 	Vector feas1 = Vector_(2, 0.0, 0.0), feas2 = Vector_(2, 10.0, 0.0), feas3 = | 
					
						
							|  |  |  | 			Vector_(2, 5.0, -0.5); | 
					
						
							|  |  |  | 	feasible.insert("x1", feas1); | 
					
						
							|  |  |  | 	feasible.insert("x3", feas2); | 
					
						
							|  |  |  | 	feasible.insert("obs", feas3); | 
					
						
							|  |  |  | 	shared_NLE e1(new NLE("x1", feas1,vector_compare)); | 
					
						
							|  |  |  | 	shared_NLE e2(new NLE("x3", feas2, vector_compare)); | 
					
						
							|  |  |  | 	shared_NLE e3(new NLE("obs", feas3, vector_compare)); | 
					
						
							| 
									
										
										
										
											2009-12-02 03:45:47 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// measurement from x1 to x2
 | 
					
						
							|  |  |  | 	Vector x1x2 = Vector_(2, 5.0, 0.0); | 
					
						
							|  |  |  | 	double sigma1 = 0.1; | 
					
						
							| 
									
										
										
										
											2010-01-14 10:50:06 +08:00
										 |  |  | 	shared f1(new simulated2D::Odometry(x1x2, sigma1, "x1", "x2")); | 
					
						
							| 
									
										
										
										
											2009-12-02 03:45:47 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// measurement from x2 to x3
 | 
					
						
							|  |  |  | 	Vector x2x3 = Vector_(2, 5.0, 0.0); | 
					
						
							|  |  |  | 	double sigma2 = 0.1; | 
					
						
							| 
									
										
										
										
											2010-01-14 10:50:06 +08:00
										 |  |  | 	shared f2(new simulated2D::Odometry(x2x3, sigma2, "x2", "x3")); | 
					
						
							| 
									
										
										
										
											2009-11-29 06:49:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-12-02 03:45:47 +08:00
										 |  |  | 	double radius = 1.0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// create a binary inequality constraint that forces the middle point away from
 | 
					
						
							|  |  |  | 	//  the obstacle
 | 
					
						
							| 
									
										
										
										
											2009-12-18 10:39:02 +08:00
										 |  |  | 	list<string> keys; keys += "x2", "obs"; | 
					
						
							|  |  |  | 	shared_NLC2 c1(new NLC2(boost::bind(sqp_avoid2::g_func, radius, _1, keys), | 
					
						
							| 
									
										
										
										
											2009-12-18 11:05:47 +08:00
										 |  |  | 						    "x2", boost::bind(sqp_avoid2::jac_g1, _1, keys), | 
					
						
							|  |  |  | 						    "obs", boost::bind(sqp_avoid2::jac_g2, _1, keys), | 
					
						
							| 
									
										
										
										
											2009-12-18 09:40:29 +08:00
										 |  |  | 						    1, "L_x2obs", false)); | 
					
						
							| 
									
										
										
										
											2009-12-02 03:45:47 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// construct the graph
 | 
					
						
							|  |  |  | 	NLGraph graph; | 
					
						
							|  |  |  | 	graph.push_back(e1); | 
					
						
							|  |  |  | 	graph.push_back(e2); | 
					
						
							|  |  |  | 	graph.push_back(e3); | 
					
						
							|  |  |  | 	graph.push_back(c1); | 
					
						
							|  |  |  | 	graph.push_back(f1); | 
					
						
							|  |  |  | 	graph.push_back(f2); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return make_pair(graph, feasible); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* ********************************************************************* */ | 
					
						
							|  |  |  | TEST ( SQPOptimizer, inequality_avoid_iterative_bind ) { | 
					
						
							|  |  |  | 	// create the graph
 | 
					
						
							|  |  |  | 	NLGraph graph; VectorConfig feasible; | 
					
						
							|  |  |  | 	boost::tie(graph, feasible) = obstacleAvoidGraphGeneral(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// create the rest of the config
 | 
					
						
							|  |  |  | 	shared_config init(new VectorConfig(feasible)); | 
					
						
							|  |  |  | 	init->insert("x2", Vector_(2, 5.0, 100.0)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// create an ordering
 | 
					
						
							|  |  |  | 	Ordering ord; | 
					
						
							|  |  |  | 	ord += "x1", "x2", "x3", "obs"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// create an optimizer
 | 
					
						
							|  |  |  | 	Optimizer optimizer(graph, ord, init); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	double relThresh = 1e-5; // minimum change in error between iterations
 | 
					
						
							|  |  |  | 	double absThresh = 1e-5; // minimum error necessary to converge
 | 
					
						
							|  |  |  | 	double constraintThresh = 1e-9; // minimum constraint error to be feasible
 | 
					
						
							|  |  |  | 	Optimizer final = optimizer.iterateSolve(relThresh, absThresh, constraintThresh); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// verify
 | 
					
						
							|  |  |  | 	VectorConfig exp2(feasible); | 
					
						
							| 
									
										
										
										
											2009-12-18 09:24:19 +08:00
										 |  |  | 	exp2.insert("x2", Vector_(2, 5.0, 0.75)); | 
					
						
							| 
									
										
										
										
											2009-12-02 03:45:47 +08:00
										 |  |  | 	CHECK(assert_equal(exp2, *(final.config()))); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2009-11-29 02:35:36 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-24 03:57:35 +08:00
										 |  |  | /* ************************************************************************* */ | 
					
						
							|  |  |  | int main() { TestResult tr; return TestRegistry::runAllTests(tr); } | 
					
						
							|  |  |  | /* ************************************************************************* */ |