// Test of kinematic consistency: check if finite differences of velocities, accelerations // match positions #include #include #include #include #include #include "Bullet3Common/b3Random.h" #include "CloneTreeCreator.hpp" #include "CoilCreator.hpp" #include "DillCreator.hpp" #include "RandomTreeCreator.hpp" #include "BulletInverseDynamics/MultiBodyTree.hpp" #include "MultiBodyTreeDebugGraph.hpp" using namespace btInverseDynamics; #if (defined BT_ID_HAVE_MAT3X) && (defined BT_ID_WITH_JACOBIANS) // minimal smart pointer to make this work for c++2003 template class ptr { ptr(); ptr(const ptr&); public: ptr(T* p) : m_p(p){}; ~ptr() { delete m_p; } T& operator*() { return *m_p; } T* operator->() { return m_p; } T* get() { return m_p; } const T* get() const { return m_p; } friend bool operator==(const ptr& lhs, const ptr& rhs) { return rhs.m_p == lhs.m_p; } friend bool operator!=(const ptr& lhs, const ptr& rhs) { return !(rhs.m_p == lhs.m_p); } private: T* m_p; }; void calculateDotJacUError(const MultiBodyTreeCreator& creator, const int nloops, double* max_error) { // tree1 is used as reference to compute dot(Jacobian)*u from acceleration(dot(u)=0) ptr tree1(CreateMultiBodyTree(creator)); ASSERT_TRUE(0x0 != tree1); CloneTreeCreator clone(tree1.get()); // tree2 is used to compute dot(Jacobian)*u using the calculateJacobian function ptr tree2(CreateMultiBodyTree(clone)); ASSERT_TRUE(0x0 != tree2); const int ndofs = tree1->numDoFs(); const int nbodies = tree1->numBodies(); if (ndofs <= 0) { *max_error = 0; return; } vecx q(ndofs); vecx u(ndofs); vecx dot_u(ndofs); vecx zero(ndofs); setZero(zero); double max_lin_error = 0; double max_ang_error = 0; for (int loop = 0; loop < nloops; loop++) { for (int i = 0; i < q.size(); i++) { q(i) = b3RandRange(-B3_PI, B3_PI); u(i) = b3RandRange(-B3_PI, B3_PI); } EXPECT_EQ(0, tree1->calculateKinematics(q, u, zero)); EXPECT_EQ(0, tree2->calculatePositionAndVelocityKinematics(q, u)); EXPECT_EQ(0, tree2->calculateJacobians(q, u)); for (int idx = 0; idx < nbodies; idx++) { vec3 tmp1, tmp2; vec3 diff; EXPECT_EQ(0, tree1->getBodyLinearAcceleration(idx, &tmp1)); EXPECT_EQ(0, tree2->getBodyDotJacobianTransU(idx, &tmp2)); diff = tmp1 - tmp2; double lin_error = maxAbs(diff); if (lin_error > max_lin_error) { max_lin_error = lin_error; } EXPECT_EQ(0, tree1->getBodyAngularAcceleration(idx, &tmp1)); EXPECT_EQ(0, tree2->getBodyDotJacobianRotU(idx, &tmp2)); diff = tmp1 - tmp2; double ang_error = maxAbs(diff); if (ang_error > max_ang_error) { max_ang_error = ang_error; } } } *max_error = max_ang_error > max_lin_error ? max_ang_error : max_lin_error; } void calculateJacobianError(const MultiBodyTreeCreator& creator, const int nloops, double* max_error) { // tree1 is used as reference to compute the Jacobian from velocities with unit u vectors. ptr tree1(CreateMultiBodyTree(creator)); ASSERT_TRUE(0x0 != tree1); // tree2 is used to compute the Jacobians using the calculateJacobian function CloneTreeCreator clone(tree1.get()); ptr tree2(CreateMultiBodyTree(clone)); ASSERT_TRUE(0x0 != tree2); const int ndofs = tree1->numDoFs(); const int nbodies = tree1->numBodies(); if (ndofs <= 0) { *max_error = 0; return; } vecx q(ndofs); vecx zero(ndofs); setZero(zero); vecx one(ndofs); double max_lin_error = 0; double max_ang_error = 0; for (int loop = 0; loop < nloops; loop++) { for (int i = 0; i < q.size(); i++) { q(i) = b3RandRange(-B3_PI, B3_PI); } EXPECT_EQ(0, tree2->calculatePositionKinematics(q)); EXPECT_EQ(0, tree2->calculateJacobians(q)); for (int idx = 0; idx < nbodies; idx++) { mat3x ref_jac_r(3, ndofs); mat3x ref_jac_t(3, ndofs); ref_jac_r.setZero(); ref_jac_t.setZero(); // this re-computes all jacobians for every body ... // but avoids std::vector issues for (int col = 0; col < ndofs; col++) { setZero(one); one(col) = 1.0; EXPECT_EQ(0, tree1->calculatePositionAndVelocityKinematics(q, one)); vec3 vel, omg; EXPECT_EQ(0, tree1->getBodyLinearVelocity(idx, &vel)); EXPECT_EQ(0, tree1->getBodyAngularVelocity(idx, &omg)); setMat3xElem(0, col, omg(0), &ref_jac_r); setMat3xElem(1, col, omg(1), &ref_jac_r); setMat3xElem(2, col, omg(2), &ref_jac_r); setMat3xElem(0, col, vel(0), &ref_jac_t); setMat3xElem(1, col, vel(1), &ref_jac_t); setMat3xElem(2, col, vel(2), &ref_jac_t); } mat3x jac_r(3, ndofs); mat3x jac_t(3, ndofs); mat3x diff(3, ndofs); EXPECT_EQ(0, tree2->getBodyJacobianTrans(idx, &jac_t)); EXPECT_EQ(0, tree2->getBodyJacobianRot(idx, &jac_r)); sub(ref_jac_t, jac_t, &diff); double lin_error = maxAbsMat3x(diff); if (lin_error > max_lin_error) { max_lin_error = lin_error; } sub(ref_jac_r, jac_r, &diff); double ang_error = maxAbsMat3x(diff); if (ang_error > max_ang_error) { max_ang_error = ang_error; } } } *max_error = max_ang_error > max_lin_error ? max_ang_error : max_lin_error; } void calculateVelocityJacobianError(const MultiBodyTreeCreator& creator, const int nloops, double* max_error) { // tree1 is used as reference to compute the velocities directly ptr tree1(CreateMultiBodyTree(creator)); ASSERT_TRUE(0x0 != tree1); // tree2 is used to compute the velocities via jacobians CloneTreeCreator clone(tree1.get()); ptr tree2(CreateMultiBodyTree(clone)); ASSERT_TRUE(0x0 != tree2); const int ndofs = tree1->numDoFs(); const int nbodies = tree1->numBodies(); if (ndofs <= 0) { *max_error = 0; return; } vecx q(ndofs); vecx u(ndofs); double max_lin_error = 0; double max_ang_error = 0; for (int loop = 0; loop < nloops; loop++) { for (int i = 0; i < q.size(); i++) { q(i) = b3RandRange(-B3_PI, B3_PI); u(i) = b3RandRange(-B3_PI, B3_PI); } EXPECT_EQ(0, tree1->calculatePositionAndVelocityKinematics(q, u)); EXPECT_EQ(0, tree2->calculatePositionKinematics(q)); EXPECT_EQ(0, tree2->calculateJacobians(q)); for (int idx = 0; idx < nbodies; idx++) { vec3 vel1; vec3 omg1; vec3 vel2; vec3 omg2; mat3x jac_r2(3, ndofs); mat3x jac_t2(3, ndofs); EXPECT_EQ(0, tree1->getBodyLinearVelocity(idx, &vel1)); EXPECT_EQ(0, tree1->getBodyAngularVelocity(idx, &omg1)); EXPECT_EQ(0, tree2->getBodyJacobianTrans(idx, &jac_t2)); EXPECT_EQ(0, tree2->getBodyJacobianRot(idx, &jac_r2)); omg2 = jac_r2 * u; vel2 = jac_t2 * u; double lin_error = maxAbs(vel1 - vel2); if (lin_error > max_lin_error) { max_lin_error = lin_error; } double ang_error = maxAbs(omg1 - omg2); if (ang_error > max_ang_error) { max_ang_error = ang_error; } } } *max_error = max_ang_error > max_lin_error ? max_ang_error : max_lin_error; } // test nonlinear terms: dot(Jacobian)*u (linear and angular acceleration for dot_u ==0) // from Jacobian calculation method and pseudo-numerically using via the kinematics method. TEST(InvDynJacobians, JacDotJacU) { const int kNumLevels = 5; #ifdef B3_USE_DOUBLE_PRECISION const double kMaxError = 1e-12; #else const double kMaxError = 5e-5; #endif const int kNumLoops = 20; for (int level = 0; level < kNumLevels; level++) { const int nbodies = BT_ID_POW(2, level); CoilCreator coil(nbodies); double error; calculateDotJacUError(coil, kNumLoops, &error); EXPECT_GT(kMaxError, error); DillCreator dill(level); calculateDotJacUError(dill, kNumLoops, &error); EXPECT_GT(kMaxError, error); } const int kRandomLoops = 100; const int kMaxRandomBodies = 128; for (int loop = 0; loop < kRandomLoops; loop++) { RandomTreeCreator random(kMaxRandomBodies); double error; calculateDotJacUError(random, kNumLoops, &error); EXPECT_GT(kMaxError, error); } } // Jacobians: linear and angular acceleration for dot_u ==0 // from Jacobian calculation method and pseudo-numerically using via the kinematics method. TEST(InvDynJacobians, Jacobians) { const int kNumLevels = 5; #ifdef B3_USE_DOUBLE_PRECISION const double kMaxError = 1e-12; #else const double kMaxError = 5e-5; #endif const int kNumLoops = 20; for (int level = 0; level < kNumLevels; level++) { const int nbodies = BT_ID_POW(2, level); CoilCreator coil(nbodies); double error; calculateJacobianError(coil, kNumLoops, &error); EXPECT_GT(kMaxError, error); DillCreator dill(level); calculateDotJacUError(dill, kNumLoops, &error); EXPECT_GT(kMaxError, error); } const int kRandomLoops = 20; const int kMaxRandomBodies = 16; for (int loop = 0; loop < kRandomLoops; loop++) { RandomTreeCreator random(kMaxRandomBodies); double error; calculateJacobianError(random, kNumLoops, &error); EXPECT_GT(kMaxError, error); } } // test for jacobian*u == velocity TEST(InvDynJacobians, VelocitiesFromJacobians) { const int kRandomLoops = 20; const int kMaxRandomBodies = 16; const int kNumLoops = 20; #ifdef B3_USE_DOUBLE_PRECISION const double kMaxError = 1e-12; #else const double kMaxError = 5e-5; #endif for (int loop = 0; loop < kRandomLoops; loop++) { RandomTreeCreator random(kMaxRandomBodies); double error; calculateVelocityJacobianError(random, kNumLoops, &error); EXPECT_GT(kMaxError, error); } } #endif int main(int argc, char** argv) { b3Srand(1234); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }