diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ab62046732338a9a8107ecaf3baf04917de33be..fdfa254a8af06fe498c333785777ff966048d377 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,6 +191,9 @@ SET(${PROJECT_NAME}_ALGORITHM_HEADERS algorithm/frames.hpp algorithm/compute-all-terms.hpp algorithm/copy.hpp + algorithm/check.hpp + algorithm/check.hxx + algorithm/default-check.hpp ) SET(${PROJECT_NAME}_PARSERS_HEADERS diff --git a/src/algorithm/aba.hpp b/src/algorithm/aba.hpp index cb5314ffe933df4c45c9dfff7dd25d7e77528b48..77b4dc6f3022eb61192693d27d7baf78dfcfc70e 100644 --- a/src/algorithm/aba.hpp +++ b/src/algorithm/aba.hpp @@ -19,6 +19,7 @@ #define __se3_aba_hpp__ #include "pinocchio/multibody/model.hpp" +#include "pinocchio/algorithm/check.hpp" namespace se3 { @@ -40,6 +41,8 @@ namespace se3 const Eigen::VectorXd & v, const Eigen::VectorXd & tau); + DEFINE_ALGO_CHECKER(ABA); + } // namespace se3 /* --- Details -------------------------------------------------------------------- */ diff --git a/src/algorithm/aba.hxx b/src/algorithm/aba.hxx index e0d175532dcee8a80913342ad64d386aaaa4d961..6b49a33a4ac8db993b8704474a41b0cd6230b043 100644 --- a/src/algorithm/aba.hxx +++ b/src/algorithm/aba.hxx @@ -217,6 +217,24 @@ namespace se3 return data.ddq; } + + // --- CHECKER --------------------------------------------------------------- + // --- CHECKER --------------------------------------------------------------- + // --- CHECKER --------------------------------------------------------------- + + // Check whether all masses are nonzero and diagonal of inertia is nonzero + // The second test is overconstraining. + inline bool ABAChecker::checkModel_impl( const Model& model ) const + { + for(JointIndex j=1;int(j)<model.njoint;j++) + if( (model.inertias[j].mass () < 1e-5) + || (model.inertias[j].inertia().data()[0] < 1e-5) + || (model.inertias[j].inertia().data()[3] < 1e-5) + || (model.inertias[j].inertia().data()[5] < 1e-5) ) + return false; + return true; + } + } // namespace se3 /// @endcond diff --git a/src/algorithm/check.hpp b/src/algorithm/check.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a4a2dca2ac0b9ae0734e1997f788bd0b79effe80 --- /dev/null +++ b/src/algorithm/check.hpp @@ -0,0 +1,58 @@ +// +// Copyright (c) 2016 CNRS +// +// This file is part of Pinocchio +// Pinocchio is free software: you can redistribute it +// and/or modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation, either version +// 3 of the License, or (at your option) any later version. +// +// Pinocchio is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Lesser Public License for more details. You should have +// received a copy of the GNU Lesser General Public License along with +// Pinocchio If not, see +// <http://www.gnu.org/licenses/>. + +#ifndef __se3_check_hpp__ +#define __se3_check_hpp__ + +namespace se3 +{ + + /// CRTP class describing the API of the checkers + template<typename AlgorithmCheckerDerived> + struct AlgorithmCheckerBase + { + inline AlgorithmCheckerDerived& derived() + { return *static_cast< AlgorithmCheckerDerived*>(this); } + + inline const AlgorithmCheckerDerived& derived() const + { return *static_cast<const AlgorithmCheckerDerived*>(this); } + + inline bool checkModel(const Model & model) const { return derived().checkModel_impl(model); } + }; + +#define DEFINE_ALGO_CHECKER(NAME) \ + struct NAME##Checker : public AlgorithmCheckerBase<NAME##Checker> \ + { \ + inline bool checkModel_impl( const Model& ) const; \ + } + + /// Simple model checker, that assert that model.parents is indeed a tree. + DEFINE_ALGO_CHECKER(Parent); + + /// Check the validity of data wrt to model, in particular if model has been modified. + /// + /// \param[in] model reference model + /// \param[in] data corresponding data + inline bool checkData(const Model & model, const Data & data); + +} // namespace se3 + + + /* --- Details -------------------------------------------------------------------- */ +#include "pinocchio/algorithm/check.hxx" + +#endif // ifndef __se3_check_hpp__ diff --git a/src/algorithm/check.hxx b/src/algorithm/check.hxx new file mode 100644 index 0000000000000000000000000000000000000000..b029859e079ffec30632d7b7ebc527001deef79f --- /dev/null +++ b/src/algorithm/check.hxx @@ -0,0 +1,139 @@ +// +// Copyright (c) 2016 CNRS +// +// This file is part of Pinocchio +// Pinocchio is free software: you can redistribute it +// and/or modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation, either version +// 3 of the License, or (at your option) any later version. +// +// Pinocchio is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Lesser Public License for more details. You should have +// received a copy of the GNU Lesser General Public License along with +// Pinocchio If not, see +// <http://www.gnu.org/licenses/>. + +#ifndef __se3_check_hxx__ +#define __se3_check_hxx__ + +#include <boost/fusion/algorithm.hpp> +#include <boost/foreach.hpp> + +namespace se3 +{ + namespace internal + { + // Dedicated structure for the fusion::accumulate algorithm: validate the check-algorithm + // for all elements in a fusion list of AlgoCheckers. + struct AlgoFusionChecker + { + typedef bool result_type; + const Model& model; + + AlgoFusionChecker(const Model&model) : model(model) {} + + template<typename T> + inline bool operator()(const bool& accumul, const T& t) const + { return accumul && t.checkModel(model); } + }; + } // namespace internal + + // Calls model.check for each checker in the fusion::list. + // Each list element is supposed to implement the AlgorithmCheckerBase API. + template<class T1,class T2,class T3,class T4,class T5, + class T6,class T7,class T8,class T9,class T10> + inline bool Model::check( const boost::fusion::list<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10> & checkerList ) const + { return boost::fusion::accumulate(checkerList,true,internal::AlgoFusionChecker(*this)); } + + // Check the validity of the kinematic tree defined by parents. + inline bool ParentChecker::checkModel_impl( const Model& model ) const + { + for( JointIndex j=1;(int)j<model.njoint;++j ) + if( model.parents[j]>=j ) return false; + + return true; + } + + inline bool checkData(const Model & model, const Data & data) + { +#define CHECK_DATA(a) if(!(a)) return false; + + // TODO JMinvJt,sDUiJt are never explicitly initialized. + // TODO impulse_c + // They are not check neither + + CHECK_DATA( (int)data.joints.size() == model.njoint ); + CHECK_DATA( (int)data.a.size() == model.njoint ); + CHECK_DATA( (int)data.a_gf.size() == model.njoint ); + CHECK_DATA( (int)data.v.size() == model.njoint ); + CHECK_DATA( (int)data.f.size() == model.njoint ); + CHECK_DATA( (int)data.oMi.size() == model.njoint ); + CHECK_DATA( (int)data.liMi.size() == model.njoint ); + CHECK_DATA( (int)data.Ycrb.size() == model.njoint ); + CHECK_DATA( (int)data.Yaba.size() == model.njoint ); + CHECK_DATA( (int)data.Fcrb.size() == model.njoint ); + BOOST_FOREACH(const Data::Matrix6x & F,data.Fcrb) CHECK_DATA( F.cols() == model.nv ); + CHECK_DATA( (int)data.iMf.size() == model.njoint ); + CHECK_DATA( (int)data.iMf.size() == model.njoint ); + CHECK_DATA( (int)data.com.size() == model.njoint ); + CHECK_DATA( (int)data.vcom.size() == model.njoint ); + CHECK_DATA( (int)data.acom.size() == model.njoint ); + CHECK_DATA( (int)data.mass.size() == model.njoint ); + + CHECK_DATA( data.tau.size() == model.nv ); + CHECK_DATA( data.nle.size() == model.nv ); + CHECK_DATA( data.ddq.size() == model.nv ); + CHECK_DATA( data.u.size() == model.nv ); + CHECK_DATA( data.M.rows() == model.nv ); + CHECK_DATA( data.M.cols() == model.nv ); + CHECK_DATA( data.Ag.cols() == model.nv ); + CHECK_DATA( data.U.cols() == model.nv ); + CHECK_DATA( data.U.rows() == model.nv ); + CHECK_DATA( data.D.size() == model.nv ); + CHECK_DATA( data.tmp.size() == model.nv ); + CHECK_DATA( data.J.cols() == model.nv ); + CHECK_DATA( data.Jcom.cols() == model.nv ); + CHECK_DATA( data.torque_residual.size() == model.nv ); + CHECK_DATA( data.dq_after.size() == model.nv ); + //CHECK_DATA( data.impulse_c.size()== model.nv ); + + CHECK_DATA( (int)data.oMf.size() == model.nFrames ); + + CHECK_DATA( (int)data.lastChild.size() == model.njoint ); + CHECK_DATA( (int)data.nvSubtree.size() == model.njoint ); + CHECK_DATA( (int)data.parents_fromRow.size() == model.nv ); + CHECK_DATA( (int)data.nvSubtree_fromRow.size() == model.nv ); + + for( JointIndex j=1;int(j)<model.njoint;++j ) + { + JointIndex c = data.lastChild[j]; + CHECK_DATA((int)c<model.njoint); + int nv=model.joints[j].nv(); + for( JointIndex d=j+1;d<=c;++d ) // explore all descendant + { + CHECK_DATA( model.parents[d]>=j ); + nv+=model.joints[d].nv(); + } + CHECK_DATA(nv==data.nvSubtree[j]); + + for( JointIndex d=c+1;(int)d<model.njoint;++d) + CHECK_DATA( (model.parents[d]<j)||(model.parents[d]>c) ); + + int row = model.joints[j].idx_v(); + CHECK_DATA(data.nvSubtree[j] == data.nvSubtree_fromRow[row]); + + const JointModel & jparent = model.joints[model.parents[j]]; + if(row==0) { CHECK_DATA(data.parents_fromRow[row]==-1); } + else { CHECK_DATA(jparent.idx_v()+jparent.nv()-1 == data.parents_fromRow[row]); } + } + +#undef CHECK_DATA + return true; + } + + +} // namespace se3 + +#endif // ifndef __se3_check_hxx__ diff --git a/src/algorithm/crba.hpp b/src/algorithm/crba.hpp index 642cdcfbc1cefcfc6b764ee2951d29b2344fdb51..b708c68c9d9eb7b2e0425165c22320e891c15059 100644 --- a/src/algorithm/crba.hpp +++ b/src/algorithm/crba.hpp @@ -19,6 +19,7 @@ #define __se3_crba_hpp__ #include "pinocchio/multibody/model.hpp" +#include "pinocchio/algorithm/check.hpp" namespace se3 { @@ -64,6 +65,8 @@ namespace se3 const Eigen::VectorXd & q, const Eigen::VectorXd & v); + DEFINE_ALGO_CHECKER(CRBA); + } // namespace se3 /* --- Details -------------------------------------------------------------------- */ diff --git a/src/algorithm/crba.hxx b/src/algorithm/crba.hxx index eb9511711fb208c3067ad9cdbe9c41949960cf6f..c84190f7ff2d68ae473b9f51445d4b7f0e5b71a9 100644 --- a/src/algorithm/crba.hxx +++ b/src/algorithm/crba.hxx @@ -209,6 +209,38 @@ namespace se3 return data.Ag; } + + // --- CHECKER --------------------------------------------------------------- + // --- CHECKER --------------------------------------------------------------- + // --- CHECKER --------------------------------------------------------------- + + namespace internal + { + inline bool isDescendant(const Model& model, const JointIndex j, const JointIndex root) + { + if(int(j)>=model.njoint) return false; + if(j==0) return root==0; + return (j==root) || isDescendant(model,model.parents[j],root); + } + } + + inline bool CRBAChecker::checkModel_impl( const Model& model ) const + { + // For CRBA, the tree must be "compact", i.e. all descendants of a node i are stored + // immediately after i in the "parents" map, i.e. forall joint i, the interval i+1..n-1 + // can be separated in two intervals [i+1..k] and [k+1..n-1], where any [i+1..k] is a descendant + // of i and none of [k+1..n-1] is a descendant of i. + for( JointIndex i=1; int(i)<model.njoint-1; ++i ) // no need to check joints 0 and N-1 + { + JointIndex k=i+1; + while(internal::isDescendant(model,k,i)) ++k; + for( ; int(k)<model.njoint; ++k ) + if( internal::isDescendant(model,k,i) ) return false; + } + return true; + } + + } // namespace se3 /// @endcond diff --git a/src/algorithm/default-check.hpp b/src/algorithm/default-check.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d6859efc684bfc310c1a3e140991742e67611bcb --- /dev/null +++ b/src/algorithm/default-check.hpp @@ -0,0 +1,39 @@ +// +// Copyright (c) 2016 CNRS +// +// This file is part of Pinocchio +// Pinocchio is free software: you can redistribute it +// and/or modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation, either version +// 3 of the License, or (at your option) any later version. +// +// Pinocchio is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Lesser Public License for more details. You should have +// received a copy of the GNU Lesser General Public License along with +// Pinocchio If not, see +// <http://www.gnu.org/licenses/>. + +#ifndef __se3_default_check_hpp__ +#define __se3_default_check_hpp__ + +#include <pinocchio/algorithm/check.hpp> +#include <pinocchio/algorithm/aba.hpp> +#include <pinocchio/algorithm/crba.hpp> + +namespace se3 +{ + /// Default checker-list, used as the default argument in Model::check(). + inline boost::fusion::list<ParentChecker,CRBAChecker,ABAChecker> makeDefaultCheckerList() + { return boost::fusion::make_list(ParentChecker(),CRBAChecker(),ABAChecker()); } + +#define DEFAULT_CHECKERS makeDefaultCheckerList() + + bool Model::check() const { this->check(DEFAULT_CHECKERS); } + +} // namespace se3 + +#endif // ifndef __se3_default_check_hpp__ + + diff --git a/src/multibody/fwd.hpp b/src/multibody/fwd.hpp index 24aa801cd7a43b46c2e5cff4e9087f19612e2b53..5d9a3032c6cbe81412d2753a54a07e8c3327583e 100644 --- a/src/multibody/fwd.hpp +++ b/src/multibody/fwd.hpp @@ -32,6 +32,10 @@ namespace se3 struct GeometryData; struct JointModel; struct JointData; + + // Forward declaration needed for Model::check + template<class D> struct AlgorithmCheckerBase; + } // namespace se3 #endif // #ifndef __se3_multibody_fwd_hpp__ diff --git a/src/multibody/model.hpp b/src/multibody/model.hpp index 4fdebcb73411ed8dd177e53f3c25ea6bd0eee28d..8e38896c6bccaafe4278f5ca088f422822008077 100644 --- a/src/multibody/model.hpp +++ b/src/multibody/model.hpp @@ -341,6 +341,32 @@ namespace se3 /// PINOCCHIO_DEPRECATED bool addFrame(const std::string & name, const JointIndex parent, const SE3 & placement, const FrameType type = OP_FRAME); + /// Check the validity of the attributes of Model with respect to the specification of some + /// algorithms. + /// + /// The method is a template so that the checkers can be defined in each algorithms. + /// \param[in] checker a class, typically defined in the algorithm module, that + /// validates the attributes of model. + /// \return true if the Model is valid, false otherwise. + template<typename D> + inline bool check(const AlgorithmCheckerBase<D> & checker = AlgorithmCheckerBase<D>()) const + { return checker.checkModel(*this); } + + /// Multiple checks for a fusion::vector of AlgorithmCheckerBase. + /// + /// Run the check test for several conditons. + /// \param[in] v fusion::vector of algo checkers. The param is typically initialize with + /// boost::fusion::make_list( AlgoChecker1(), AlgoChecker2(), ...) + /// make_list is defined in #include <boost/fusion/include/make_list.hpp> + /// \warning no more than 10 checkers can be added (or Model API should be extended). + /// \note This method is implemented in src/algo/check.hxx. + template<class T1,class T2,class T3,class T4,class T5, + class T6,class T7,class T8,class T9,class T10> + bool check( const boost::fusion::list<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10> & checkerList ) const; + + /// Run check(fusion::list) with DEFAULT_CHECKERS as argument. + bool check() const; + protected: /// \brief Add the joint_id to its parent subtrees. @@ -359,7 +385,8 @@ namespace se3 typedef Eigen::Matrix<double,3,Eigen::Dynamic> Matrix3x; typedef SE3::Vector3 Vector3; - /// \brief Vector of se3::JointData associated to the se3::JointModel stored in model, encapsulated in JointDataAccessor. + /// \brief Vector of se3::JointData associated to the se3::JointModel stored in model, + /// encapsulated in JointDataAccessor. JointDataVector joints; /// \brief Vector of joint accelerations. diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index 329a780b5f26c9a0074a2558fbd37f697b9f746c..023d1b3db9153233922030630e9554e9a3d798a4 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -114,3 +114,4 @@ ADD_UNIT_TEST(joint eigen3) ADD_UNIT_TEST(explog eigen3) ADD_UNIT_TEST(finite-differences eigen3) ADD_UNIT_TEST(visitor eigen3) +ADD_UNIT_TEST(algo-check eigen3) diff --git a/unittest/algo-check.cpp b/unittest/algo-check.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9fafdc7167169dba4c9a567b601c75b88b826351 --- /dev/null +++ b/unittest/algo-check.cpp @@ -0,0 +1,47 @@ + +#include <boost/fusion/container/generation/make_list.hpp> +#include <pinocchio/multibody/model.hpp> +#include "pinocchio/parsers/sample-models.hpp" +#include <pinocchio/algorithm/crba.hpp> +#include <pinocchio/algorithm/aba.hpp> +#include <pinocchio/algorithm/check.hpp> +#include <pinocchio/algorithm/default-check.hpp> +#include <iostream> + +using namespace se3; + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE AlgoCheckTest +#include <boost/test/unit_test.hpp> +#include <boost/utility/binary.hpp> + +// Dummy checker. +struct Check1 : public AlgorithmCheckerBase<Check1> +{ + bool checkModel_impl( const Model& ) const { return true; } +}; + +BOOST_AUTO_TEST_SUITE ( AlgoCheck ) + +BOOST_AUTO_TEST_CASE ( test_check ) +{ + using namespace boost::fusion; + + se3::Model model; buildModels::humanoidSimple(model); + + BOOST_CHECK(model.check (Check1())); + BOOST_CHECK(model.check (CRBAChecker())); + BOOST_CHECK(! model.check (ABAChecker())); // some inertias are negative ... check fail. + BOOST_FOREACH(Inertia& Y,model.inertias) + Y.mass() = Y.inertia().data()[0] = Y.inertia().data()[3] = Y.inertia().data()[5] = 1.0 ; + BOOST_CHECK(model.check (ABAChecker())); // some inertias are negative ... check fail. + + BOOST_CHECK(model.check(boost::fusion::make_list(Check1(),ParentChecker(),CRBAChecker()) )); + BOOST_CHECK(model.check(DEFAULT_CHECKERS)); + + se3::Data data(model); + BOOST_CHECK(checkData(model,data)); +} + +BOOST_AUTO_TEST_SUITE_END () +