diff --git a/CMakeLists.txt b/CMakeLists.txt index d6e5ff92f90119f6feca6ddf1f95065bf04beb8b..5ef5a85e083f1ba5443b4363cc15bf61aa4034c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,7 @@ SET(${PROJECT_NAME}_HEADERS algorithm/jacobian.hpp algorithm/cholesky.hpp algorithm/kinematics.hpp + algorithm/center-of-mass.hpp ) MAKE_DIRECTORY("${${PROJECT_NAME}_BINARY_DIR}/include/pinocchio") @@ -111,6 +112,9 @@ PKG_CONFIG_USE_DEPENDENCY(timings urdfdom) ADD_EXECUTABLE(jacobian EXCLUDE_FROM_ALL unittest/jacobian.cpp) PKG_CONFIG_USE_DEPENDENCY(jacobian eigenpy) +ADD_EXECUTABLE(com EXCLUDE_FROM_ALL unittest/com.cpp) +PKG_CONFIG_USE_DEPENDENCY(com eigenpy) + ADD_EXECUTABLE(cholesky EXCLUDE_FROM_ALL unittest/cholesky.cpp) PKG_CONFIG_USE_DEPENDENCY(cholesky eigenpy) PKG_CONFIG_USE_DEPENDENCY(cholesky urdfdom) diff --git a/src/algorithm/center-of-mass.hpp b/src/algorithm/center-of-mass.hpp new file mode 100644 index 0000000000000000000000000000000000000000..41a1a1831e9bf43854c38a84ead5d505e1cb5db9 --- /dev/null +++ b/src/algorithm/center-of-mass.hpp @@ -0,0 +1,207 @@ +#ifndef __se3_center_of_mass_hpp__ +#define __se3_center_of_mass_hpp__ + +#include "pinocchio/multibody/visitor.hpp" +#include "pinocchio/multibody/model.hpp" +#include <iostream> + +namespace se3 +{ + // inline const Eigen::MatrixXd& + // centerOfMass(const Model & model, + // Data& data, + // const Eigen::VectorXd & q); + +} // namespace se3 + +/* --- Details -------------------------------------------------------------------- */ +namespace se3 +{ + + struct CenterOfMassForwardStep : public fusion::JointVisitor<CenterOfMassForwardStep> + { + typedef boost::fusion::vector< const se3::Model&, + se3::Data&, + const Eigen::VectorXd &, + const bool & + > ArgsType; + + JOINT_VISITOR_INIT(CenterOfMassForwardStep); + + template<typename JointModel> + static void algo(const se3::JointModelBase<JointModel> & jmodel, + se3::JointDataBase<typename JointModel::JointData> & jdata, + const se3::Model& model, + se3::Data& data, + const Eigen::VectorXd & q, + const bool & computeSubtreeComs) + { + using namespace Eigen; + using namespace se3; + + const Model::Index & i = jmodel.id(); + const Model::Index & parent = model.parents[i]; + + jmodel.calc(jdata.derived(),q); + + data.liMi[i] = model.jointPlacements[i]*jdata.M(); + data.com[parent] += (data.liMi[i].rotation()*data.com[i] + +data.mass[i]*data.liMi[i].translation()); + data.mass[parent] += data.mass[i]; + + if( computeSubtreeComs ) + data.com[i] /= data.mass[i]; + } + + }; + + /* Compute the centerOfMass in the local frame. */ + const Eigen::Vector3d & + centerOfMass(const Model & model, Data& data, + const Eigen::VectorXd & q, + bool computeSubtreeComs = true) + { + data.mass[0] = 0; + data.com[0] = Eigen::Vector3d::Zero(); + + for( int i=1;i<model.nbody;++i ) + { + data.com[i] = model.inertias[i].mass()*model.inertias[i].lever(); + data.mass[i] = model.inertias[i].mass(); + } + + for( int i=model.nbody-1;i>0;--i ) + { + CenterOfMassForwardStep + ::run(model.joints[i],data.joints[i], + CenterOfMassForwardStep::ArgsType(model,data,q,computeSubtreeComs)); + } + data.com[0] /= data.mass[0]; + + return data.com[0]; + } + + const Eigen::Vector3d & getComFromCrba(const Model & , Data& data) + { + return data.Ycrb[1].lever(); + } + + /* --- JACOBIAN ---------------------------------------------------------- */ + /* --- JACOBIAN ---------------------------------------------------------- */ + /* --- JACOBIAN ---------------------------------------------------------- */ + struct JacobianCenterOfMassForwardStep + : public fusion::JointVisitor<JacobianCenterOfMassForwardStep> + { + typedef boost::fusion::vector< const se3::Model&, + se3::Data&, + const Eigen::VectorXd & + > ArgsType; + + JOINT_VISITOR_INIT(JacobianCenterOfMassForwardStep); + + template<typename JointModel> + static void algo(const se3::JointModelBase<JointModel> & jmodel, + se3::JointDataBase<typename JointModel::JointData> & jdata, + const se3::Model& model, + se3::Data& data, + const Eigen::VectorXd & q) + { + using namespace Eigen; + using namespace se3; + + const Model::Index & i = jmodel.id(); + const Model::Index & parent = model.parents[i]; + + jmodel.calc(jdata.derived(),q); + + data.liMi[i] = model.jointPlacements[i]*jdata.M(); + if(parent>0) data.oMi[i] = data.oMi[parent]*data.liMi[i]; + else data.oMi[i] = data.liMi[i]; + + data.com[i] = model.inertias[i].mass()*data.oMi[i].act(model.inertias[i].lever()); + data.mass[i] = model.inertias[i].mass(); + } + + }; + + struct JacobianCenterOfMassBackwardStep + : public fusion::JointVisitor<JacobianCenterOfMassBackwardStep> + { + typedef boost::fusion::vector< const se3::Model&, + se3::Data&, + const bool & + > ArgsType; + + JOINT_VISITOR_INIT(JacobianCenterOfMassBackwardStep); + + template<typename JointModel> + static void algo(const se3::JointModelBase<JointModel> & jmodel, + se3::JointDataBase<typename JointModel::JointData> & jdata, + const se3::Model& model, + se3::Data& data, + const bool & computeSubtreeComs ) + { + using namespace Eigen; + using namespace se3; + + const Model::Index & i = jmodel.id(); + const Model::Index & parent = model.parents[i]; + + data.com[parent] += data.com[i]; + data.mass[parent] += data.mass[i]; + + const Eigen::Matrix<double,6,JointModel::NV> & oSk + = data.oMi[i].act(jdata.S()); + + if( JointModel::NV==1 ) + data.Jcom.col(jmodel.idx_v()) // Using head and tail would confuse g++ + = data.mass[i]*oSk.template topLeftCorner<3,1>() + - data.com[i].cross(oSk.template bottomLeftCorner<3,1>()) ; + else + data.Jcom.template block<3,JointModel::NV>(0,jmodel.idx_v()) + = data.mass[i]*oSk.template topRows<3>() + - skew(data.com[i]) * oSk.template bottomRows<3>() ; + + if(computeSubtreeComs) + data.com[i] /= data.mass[i]; + } + + }; + + /* Compute the centerOfMass in the local frame. */ + const Eigen::Vector3d & + jacobianCenterOfMass(const Model & model, Data& data, + const Eigen::VectorXd & q, + const bool & computeSubtreeComs = true) + { + data.com[0] = Eigen::Vector3d::Zero(); + data.mass[0] = 0; + for( int i=1;i<model.nbody;++i ) + { + JacobianCenterOfMassForwardStep + ::run(model.joints[i],data.joints[i], + JacobianCenterOfMassForwardStep::ArgsType(model,data,q)); + } + for( int i=model.nbody-1;i>0;--i ) + { + JacobianCenterOfMassBackwardStep + ::run(model.joints[i],data.joints[i], + JacobianCenterOfMassBackwardStep::ArgsType(model,data,computeSubtreeComs)); + } + + data.com[0] /= data.mass[0]; + data.Jcom /= data.mass[0]; + return data.com[0]; + } + + const Eigen::Vector3d & getJacobianComFromCrba(const Model & , Data& data) + { + return data.Jcom = data.M.topRows<3>()/data.M(0,0); + } + + + +} // namespace se3 + +#endif // ifndef __se3_center_of_mass_hpp__ + diff --git a/src/multibody/model.hpp b/src/multibody/model.hpp index d6d3eb12e3730ae6f4a184dd2fb1ecca196e6c8b..ea09740b82d6425e0f4edf0125b960555cd34c6a 100644 --- a/src/multibody/model.hpp +++ b/src/multibody/model.hpp @@ -90,6 +90,11 @@ namespace se3 std::vector<int> nvSubtree_fromRow; // Eigen::MatrixXd J; // Jacobian of joint placement + std::vector<SE3> iMf; // Body placement wrt to algorithm end effector. + + std::vector<Eigen::Vector3d> com; // Subtree com position. + std::vector<double> mass; // Subtree total mass. + Eigen::Matrix<double,3,Eigen::Dynamic> Jcom; // Jacobian of center of mass. Data( const Model& ref ); @@ -195,6 +200,10 @@ namespace se3 ,parents_fromRow(ref.nv) ,nvSubtree_fromRow(ref.nv) ,J(6,ref.nv) + ,iMf(ref.nbody) + ,com(ref.nbody) + ,mass(ref.nbody) + ,Jcom(3,ref.nv) { for(int i=0;i<model.nbody;++i) joints.push_back(CreateJointData::run(model.joints[i])); diff --git a/unittest/com.cpp b/unittest/com.cpp new file mode 100644 index 0000000000000000000000000000000000000000..79e0e801efbee8ea4a630f8cbde11aef031ac48f --- /dev/null +++ b/unittest/com.cpp @@ -0,0 +1,108 @@ +#ifdef NDEBUG +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#endif + +#include "pinocchio/multibody/model.hpp" +#include "pinocchio/algorithm/jacobian.hpp" +#include "pinocchio/algorithm/crba.hpp" +#include "pinocchio/algorithm/center-of-mass.hpp" +#include "pinocchio/tools/timer.hpp" +#include "pinocchio/multibody/parser/sample-models.hpp" + +#include <iostream> +#include <boost/utility/binary.hpp> + +void timings(const se3::Model & model, se3::Data& data, long flag) +{ + using namespace se3; + StackTicToc timer(StackTicToc::US); +#ifdef NDEBUG + const int NBT = 1000*1000; +#else + const int NBT = 1; +#endif + + bool verbose = flag & (flag-1) ; // True is two or more binaries of the flag are 1. + if(verbose) std::cout <<"--" << std::endl; + Eigen::VectorXd q = Eigen::VectorXd::Zero(model.nq); + + if( flag >> 0 & 1 ) + { + timer.tic(); + SMOOTH(NBT) + { + centerOfMass(model,data,q); + } + if(verbose) std::cout << "COM =\t"; + timer.toc(std::cout,NBT); + } + if( flag >> 1 & 1 ) + { + timer.tic(); + SMOOTH(NBT) + { + centerOfMass(model,data,q,false); + } + if(verbose) std::cout << "Wo stree =\t"; + timer.toc(std::cout,NBT); + } + if( flag >> 2 & 1 ) + { + timer.tic(); + SMOOTH(NBT) + { + jacobianCenterOfMass(model,data,q); + } + if(verbose) std::cout << "Jcom =\t"; + timer.toc(std::cout,NBT); + } +} + +void assertValues(const se3::Model & model, se3::Data& data) +{ + using namespace Eigen; + using namespace se3; + + VectorXd q = VectorXd::Zero(model.nq); + crba(model,data,q); + + { /* Test COM against CRBA*/ + centerOfMass(model,data,q); + assert( data.com[0].isApprox( getComFromCrba(model,data) )); + + } + + { /* Test COM against Jcom (both use different way of compute the COM. */ + Vector3d com = data.com[0]; + data.M.fill(0); crba(model,data,q); + jacobianCenterOfMass(model,data,q); + assert(com.isApprox(jacobianCenterOfMass(model,data,q))); + } + + { /* Test Jcom against CRBA */ + assert(data.Jcom.isApprox(data.M.block(0,0,3,model.nv)/data.mass[0] )); + } + + std::cout << "com = [ " << data.com[0].transpose() << " ];" << std::endl; + std::cout << "mass = [ " << data.mass[0] << " ];" << std::endl; + std::cout << "Jcom = [ " << data.Jcom << " ];" << std::endl; + std::cout << "M3 = [ " << data.M.topRows<3>() << " ];" << std::endl; +} + +int main() +{ + using namespace Eigen; + using namespace se3; + + se3::Model model; + se3::buildModels::humanoidSimple(model); + se3::Data data(model); + +#ifndef NDEBUG + assertValues(model,data); +#else + timings(model,data,BOOST_BINARY(101)); +#endif + + return 0; +}