diff --git a/CMakeLists.txt b/CMakeLists.txt
index ea3a6375087fe20477b5a22563da0545965bb86e..9caa4619d4e70ff6e624c51750c2836769e3d42c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -32,6 +32,7 @@ ADD_REQUIRED_DEPENDENCY("urdfdom >= v0.3.0")
 # ----------------------------------------------------
 SET(${PROJECT_NAME}_HEADERS
   exception.hpp
+  spatial/symmetric3.hpp
   spatial/se3.hpp
   spatial/motion.hpp
   spatial/force.hpp
@@ -86,6 +87,9 @@ ENDFOREACH(header)
 ADD_EXECUTABLE(tspatial EXCLUDE_FROM_ALL unittest/tspatial.cpp)
 PKG_CONFIG_USE_DEPENDENCY(tspatial eigenpy)
 
+ADD_EXECUTABLE(symmetric EXCLUDE_FROM_ALL unittest/symmetric.cpp)
+PKG_CONFIG_USE_DEPENDENCY(symmetric eigenpy)
+
 ADD_EXECUTABLE(constraint EXCLUDE_FROM_ALL unittest/constraint.cpp)
 PKG_CONFIG_USE_DEPENDENCY(constraint eigenpy)
 
diff --git a/src/spatial/symmetric3.hpp b/src/spatial/symmetric3.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f6845d1ed06eff6f2e91e7df054f20a4f61f15b3
--- /dev/null
+++ b/src/spatial/symmetric3.hpp
@@ -0,0 +1,241 @@
+/* Copyright LAAS-CNRS, 2014
+ *
+ * This file is originally copied from metapod/tools/spatial/lti.hh.
+ * Authors: Olivier Stasse (LAAS, CNRS) and Sébastien Barthélémy (Aldebaran Robotics)
+ * The file was modified in pinocchio by Nicolas Mansard (LAAS, CNRS)
+ *
+ * metapod is free software, distributed 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.
+ *
+ */
+
+#ifndef __se3__symmetric3_hpp__
+#define __se3__symmetric3_hpp__
+
+#include <ostream>
+
+namespace se3
+{
+
+  template<typename _Scalar, int _Options>
+  class Symmetric3Tpl
+  {
+  public:
+    typedef _Scalar Scalar;
+    enum { Options = _Options };
+    typedef Eigen::Matrix<Scalar,3,1,Options> Vector3;
+    typedef Eigen::Matrix<Scalar,6,1,Options> Vector6;
+    typedef Eigen::Matrix<Scalar,3,3,Options> Matrix3;
+    typedef Eigen::Matrix<Scalar,2,2,Options> Matrix2;
+    typedef Eigen::Matrix<Scalar,3,2,Options> Matrix32;
+
+  public:    
+    Symmetric3Tpl(): data() {}
+    Symmetric3Tpl(const Matrix3 &I) 
+    {
+      assert( (I-I.transpose()).isMuchSmallerThan(I) );
+      data(0) = I(0,0);
+      data(1) = I(1,0); data(2) = I(1,1);
+      data(3) = I(2,0); data(4) = I(2,1); data(5) = I(2,2);
+    }
+    Symmetric3Tpl(const Vector6 &I) : data(I) {}
+    Symmetric3Tpl(const double & a0,const double & a1,const double & a2,
+		  const double & a3,const double & a4,const double & a5)
+    { data << a0,a1,a2,a3,a4,a5; }
+
+    static Symmetric3Tpl Zero()     { return Symmetric3Tpl(Vector6::Zero()  );  }
+    static Symmetric3Tpl Random()   { return Symmetric3Tpl(Vector6::Random().eval());  }
+    static Symmetric3Tpl Identity() { return Symmetric3Tpl( 1, 0, 1, 0, 0, 1);  }
+    static Symmetric3Tpl SkewSq( const Vector3 & v )
+    { 
+      const double & x = v[0], & y = v[1], & z = v[2]; 
+      return Symmetric3Tpl( -y*y-z*z,
+			     x*y    ,  -x*x-z*z, 
+			     x*z    ,   y*z    ,  -x*x-y*y );
+    }
+    /* Shoot a positive definite matrix. */
+    static Symmetric3Tpl RandomPositive() 
+    { 
+      double 
+	a = double(std::rand())/RAND_MAX*2.0-1.0,
+	b = double(std::rand())/RAND_MAX*2.0-1.0,
+	c = double(std::rand())/RAND_MAX*2.0-1.0,
+	d = double(std::rand())/RAND_MAX*2.0-1.0,
+	e = double(std::rand())/RAND_MAX*2.0-1.0,
+	f = double(std::rand())/RAND_MAX*2.0-1.0;
+      return Symmetric3Tpl(a*a+b*b+d*d,
+			   a*b+b*c+d*e, b*b+c*c+e*e,
+			   a*d+b*e+d*f, b*d+c*e+e*f,  d*d+e*e+f*f );
+    }
+
+    Matrix3 matrix() const
+    {
+      Matrix3 res;
+      res(0,0) = data(0); res(0,1) = data(1); res(0,2) = data(3);
+      res(1,0) = data(1); res(1,1) = data(2); res(1,2) = data(4);
+      res(2,0) = data(3); res(2,1) = data(4); res(2,2) = data(5);
+      return res;
+    }
+    operator Matrix3 () const { return matrix(); }
+
+    Symmetric3Tpl operator+(const Symmetric3Tpl & s2) const
+    {
+      return Symmetric3Tpl(data+s2.data);
+    }
+
+    Symmetric3Tpl & operator+=(const Symmetric3Tpl & s2)
+    {
+      data += s2.data; return *this;
+    }
+
+    Vector3 operator*(const Vector3 &v) const
+    {
+      return Vector3(
+		     data(0) * v(0) + data(1) * v(1) + data(3) * v(2),
+		     data(1) * v(0) + data(2) * v(1) + data(4) * v(2),
+		     data(3) * v(0) + data(4) * v(1) + data(5) * v(2)
+		     );		     
+    }
+
+    // Matrix3 operator*(const Matrix3 &a) const
+    // {
+    //   Matrix3 r;
+    //   for(unsigned int i=0; i<3; ++i)
+    //     {
+    //       r(0,i) = data(0) * a(0,i) + data(1) * a(1,i) + data(3) * a(2,i);
+    //       r(1,i) = data(1) * a(0,i) + data(2) * a(1,i) + data(4) * a(2,i);
+    //       r(2,i) = data(3) * a(0,i) + data(4) * a(1,i) + data(5) * a(2,i);
+    //     }
+    //   return r;
+    // }
+
+    const Scalar& operator()(const int &i,const int &j) const
+    {
+      return ((i!=2)&&(j!=2)) ? data[i+j] : data[i+j+1];
+    }
+
+    Symmetric3Tpl operator-(const Matrix3 &S) const
+    {
+      assert( (S-S.transpose()).isMuchSmallerThan(S) );
+      return Symmetric3Tpl( data(0)-S(0,0),
+			    data(1)-S(1,0), data(2)-S(1,1),
+			    data(3)-S(2,0), data(4)-S(2,1), data(5)-S(2,2) );
+    }
+
+    Symmetric3Tpl operator+(const Matrix3 &S) const
+    {
+      assert( (S-S.transpose()).isMuchSmallerThan(S) );
+      return Symmetric3Tpl( data(0)+S(0,0),
+			    data(1)+S(1,0), data(2)+S(1,1),
+			    data(3)+S(2,0), data(4)+S(2,1), data(5)+S(2,2) );
+    }
+
+    /* --- Symmetric R*S*R' and R'*S*R products --- */
+  public: //private:
+    
+    /** \brief Computes L for a symmetric matrix A.
+     */
+    Matrix32  decomposeltI() const
+    {
+      Matrix32 L;
+      L << 
+	data(0) - data(5),    data(1),
+	data(1),              data(2) - data(5),
+	2*data(3),            data(4) + data(4);
+      return L;
+    }
+
+
+    /* R*S*R' */
+    Symmetric3Tpl rotate(const Matrix3 & R) const
+    {
+      assert( (R.cols()==3) && (R.rows()==3) );
+      assert( (R.transpose()*R).isApprox(Matrix3::Identity()) );
+
+	Symmetric3Tpl Sres;
+	Matrix2 Y;
+	Matrix32 L;
+
+	// 4 a
+	L = decomposeltI();
+	
+	// Y = R' L   ===> (12 m + 8 a)
+	Y = R.template block<2,3>(1,0) * L;
+
+	// Sres= Y R  ===> (16 m + 8a)
+	Sres.data(1) = Y(0,0)*R(0,0) + Y(0,1)*R(0,1);
+	Sres.data(2) = Y(0,0)*R(1,0) + Y(0,1)*R(1,1);
+	Sres.data(3) = Y(1,0)*R(0,0) + Y(1,1)*R(0,1);
+	Sres.data(4) = Y(1,0)*R(1,0) + Y(1,1)*R(1,1);
+	Sres.data(5) = Y(1,0)*R(2,0) + Y(1,1)*R(2,1);
+
+	// r=R' v ( 6m + 3a)
+	 const Vector3 r( -R(0,0)*data(4) + R(0,1)*data(3),
+			  -R(1,0)*data(4) + R(1,1)*data(3),
+			  -R(2,0)*data(4) + R(2,1)*data(3) );
+
+	// Sres_11 (3a)
+	Sres.data(0) = L(0,0) + L(1,1) - Sres.data(2) - Sres.data(5);
+	
+	// Sres + D + (Ev)x ( 9a)
+	Sres.data(0) += data(5); 
+	Sres.data(1) += r(2);    Sres.data(2)+= data(5); 
+	Sres.data(3) +=-r(1);    Sres.data(4)+=    r(0); Sres.data(5) += data(5);
+
+	return Sres;
+    }
+
+    /* R*S*R' */
+    template<typename D>
+    Symmetric3Tpl rotateTpl(const Eigen::MatrixBase<D> & R) const
+    {
+      assert( (R.cols()==3) && (R.rows()==3) );
+      assert( (R.transpose()*R).isApprox(Matrix3::Identity()) );
+
+	Symmetric3Tpl Sres;
+	Matrix2 Y;
+	Matrix32 L;
+
+	// 4 a
+	L = decomposeltI();
+	
+	// Y = R' L   ===> (12 m + 8 a)
+	Y = R.template block<2,3>(1,0) * L;
+
+	// Sres= Y R  ===> (16 m + 8a)
+	Sres.data(1) = Y(0,0)*R(0,0) + Y(0,1)*R(0,1);
+	Sres.data(2) = Y(0,0)*R(1,0) + Y(0,1)*R(1,1);
+	Sres.data(3) = Y(1,0)*R(0,0) + Y(1,1)*R(0,1);
+	Sres.data(4) = Y(1,0)*R(1,0) + Y(1,1)*R(1,1);
+	Sres.data(5) = Y(1,0)*R(2,0) + Y(1,1)*R(2,1);
+
+	// r=R' v ( 6m + 3a)
+	 const Vector3 r( -R(0,0)*data(4) + R(0,1)*data(3),
+			  -R(1,0)*data(4) + R(1,1)*data(3),
+			  -R(2,0)*data(4) + R(2,1)*data(3) );
+
+	// Sres_11 (3a)
+	Sres.data(0) = L(0,0) + L(1,1) - Sres.data(2) - Sres.data(5);
+	
+	// Sres + D + (Ev)x ( 9a)
+	Sres.data(0) += data(5); 
+	Sres.data(1) += r(2);    Sres.data(2)+= data(5); 
+	Sres.data(3) +=-r(1);    Sres.data(4)+=    r(0); Sres.data(5) += data(5);
+
+	return Sres;
+    }
+
+
+  public: //todo: make private
+
+    Vector6 data; 
+  };
+
+  typedef Symmetric3Tpl<double,0> Symmetric3;
+
+} // namespace se3
+
+#endif // ifndef __se3__symmetric3_hpp__
+
diff --git a/unittest/symmetric.cpp b/unittest/symmetric.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ebce9e22b9cb99bd99f59ed8f54729024ef039d1
--- /dev/null
+++ b/unittest/symmetric.cpp
@@ -0,0 +1,228 @@
+#include "pinocchio/spatial/fwd.hpp"
+#include "pinocchio/spatial/se3.hpp"
+#include "pinocchio/spatial/inertia.hpp"
+#include "pinocchio/tools/timer.hpp"
+
+#include <boost/random.hpp>
+#include <assert.h>
+
+#include "pinocchio/spatial/symmetric3.hpp"
+
+#include <Eigen/StdVector>
+EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION( Eigen::Matrix3d );
+EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION(se3::Symmetric3);
+
+void timeSym3(const se3::Symmetric3 & S,
+	      const se3::Symmetric3::Matrix3 & R,
+	      se3::Symmetric3 & res)
+{
+  res = S.rotate(R);
+}
+
+void testSym3()
+{
+  using namespace se3;
+  typedef Symmetric3::Matrix3 Matrix3;
+  typedef Symmetric3::Vector3 Vector3;
+  
+  { 
+    // op(Matrix3)
+    {
+      Matrix3 M = Matrix3::Random(); M = M*M.transpose();
+      Symmetric3 S(M);
+      assert( S.matrix().isApprox(M) );
+    }
+
+    // S += S
+    {
+      Symmetric3
+	S = Symmetric3::Random(),
+	S2 = Symmetric3::Random();
+      Symmetric3 Scopy = S;
+      S+=S2;
+      assert( S.matrix().isApprox( S2.matrix()+Scopy.matrix()) );
+    }
+
+    // S + M
+    {
+      Symmetric3 S = Symmetric3::Random();
+      Matrix3 M = Matrix3::Random(); M = M*M.transpose();
+
+      Symmetric3 S2 = S + M;
+      assert( S2.matrix().isApprox( S.matrix()+M ));
+
+      S2 = S - M;
+      assert( S2.matrix().isApprox( S.matrix()-M ));
+    }
+
+    // S*v
+    {
+      Symmetric3 S = Symmetric3::Random();
+      Vector3 v = Vector3::Random(); 
+      Vector3 Sv = S*v;
+      assert( Sv.isApprox( S.matrix()*v ));
+    }
+
+    // Random
+    for(int i=0;i<100;++i )
+      {
+	Matrix3 M = Matrix3::Random(); M = M*M.transpose();
+	Symmetric3 S = Symmetric3::RandomPositive();
+	Vector3 v = Vector3::Random();
+	assert( (v.transpose()*(S*v))[0] > 0);
+      }
+
+    // Identity
+    { 
+      assert( Symmetric3::Identity().matrix().isApprox( Matrix3::Identity() ) );
+    }
+
+    // Skew2
+    {
+      Vector3 v = Vector3::Random();
+      Symmetric3 vxvx = Symmetric3::SkewSq(v);
+
+      Vector3 p = Vector3::UnitX();
+      assert( (vxvx*p).isApprox( v.cross(v.cross(p)) ));
+      p = Vector3::UnitY();
+      assert( (vxvx*p).isApprox( v.cross(v.cross(p)) ));
+      p = Vector3::UnitZ();
+      assert( (vxvx*p).isApprox( v.cross(v.cross(p)) ));
+    }
+
+    // (i,j)
+    {
+	Matrix3 M = Matrix3::Random(); M = M*M.transpose();
+	M << 1,2,4,2,3,5,4,5,6 ;
+	Symmetric3 S(M); // = Symmetric3::RandomPositive();
+	for(int i=0;i<3;++i)
+	  for(int j=0;j<3;++j)
+	    assert( S(i,j) == M(i,j) );
+    }
+  }
+
+  // SRS
+  {
+    Symmetric3 S = Symmetric3::RandomPositive();
+    Matrix3 R = (Eigen::Quaterniond(Eigen::Matrix<double,4,1>::Random())).normalized().matrix();
+    
+    Symmetric3 RSRt = S.rotate(R);
+    assert( RSRt.matrix().isApprox( R*S.matrix()*R.transpose() ));
+
+    Symmetric3 RtSR = S.rotate(R.transpose());
+    assert( RtSR.matrix().isApprox( R.transpose()*S.matrix()*R ));
+
+  }
+
+  // Time test 
+  {
+    const int NBT = 100000;
+    Symmetric3 S = Symmetric3::RandomPositive();
+
+    std::vector<Symmetric3> Sres (NBT);
+    std::vector<Matrix3> Rs (NBT);
+    for(int i=0;i<NBT;++i) 
+      Rs[i] = (Eigen::Quaterniond(Eigen::Matrix<double,4,1>::Random())).normalized().matrix();
+
+    StackTicToc timer(StackTicToc::US); timer.tic();
+    SMOOTH(NBT)
+      {
+	timeSym3(S,Rs[_smooth],Sres[_smooth]);
+      }
+    timer.toc(std::cout,NBT);
+  }
+}
+/* --- METAPOD ---------------------------------------------- */
+#include <metapod/tools/spatial/lti.hh>
+#include <metapod/tools/spatial/rm-general.hh>
+EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION(metapod::Spatial::ltI<double>);
+EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION(metapod::Spatial::RotationMatrixTpl<double>);
+
+void timeLTI(const metapod::Spatial::ltI<double>& S,
+	     const metapod::Spatial::RotationMatrixTpl<double>& R, 
+	     metapod::Spatial::ltI<double> & res)
+{
+  res = R.rotSymmetricMatrix(S);
+}
+
+void testLTI()
+{
+  using namespace metapod::Spatial;
+
+  typedef ltI<double> Sym3;
+  typedef Eigen::Matrix3d Matrix3;
+  typedef RotationMatrixTpl<double> R3;
+
+  Matrix3 M = Matrix3::Random();
+  Sym3 S(M),S2;
+
+  R3 R; R.randomInit();
+
+  R.rotTSymmetricMatrix(S);
+  timeLTI(S,R,S2);
+
+  assert( S2.toMatrix().isApprox( R.toMatrix().transpose()*S.toMatrix()*R.toMatrix()) );
+  
+  const int NBT = 100000;
+  std::vector<Sym3> Sres (NBT);
+  std::vector<R3> Rs (NBT);
+  for(int i=0;i<NBT;++i) 
+    Rs[i].randomInit();
+  
+  StackTicToc timer(StackTicToc::US); timer.tic();
+  SMOOTH(NBT)
+    {
+      timeLTI(S,Rs[_smooth],Sres[_smooth]);
+    }
+  timer.toc(std::cout,NBT);
+  //std::cout << Rs[std::rand() % NBT] << std::endl;
+  
+}
+
+void timeSelfAdj( const Eigen::Matrix3d & A,
+		  const Eigen::Matrix3d & Sdense,
+		  Eigen::Matrix3d & ASA )
+{
+  typedef se3::Inertia::Symmetric3 Sym3;
+  Sym3 S(Sdense);
+  ASA.triangularView<Eigen::Upper>()
+    = A * S * A.transpose();
+}
+
+void testSelfAdj()
+{
+  using namespace se3;
+  typedef Inertia::Matrix3 Matrix3;
+  typedef Inertia::Symmetric3 Sym3;
+
+  Matrix3 M = Inertia::Matrix3::Random();
+  Sym3 S(M);
+
+  Matrix3 M2 = Inertia::Matrix3::Random();
+  M.triangularView<Eigen::Upper>() = M2;
+
+  Matrix3 A = Matrix3::Random(), ASA1, ASA2;
+  ASA1.triangularView<Eigen::Upper>() = A * S * A.transpose();
+  timeSelfAdj(A,M,ASA2);
+  assert(ASA1.isApprox(ASA2));
+
+  StackTicToc timer(StackTicToc::US); timer.tic();
+  SMOOTH(1000000)
+    {
+      timeSelfAdj(A,M,ASA2);
+    }
+  timer.toc(std::cout,1000000);
+}
+
+
+int main()
+{
+  //testSelfAdj();
+  testLTI();
+  testSym3();
+  testSym3();
+  testLTI();
+  
+  std::cout << std::endl;
+  return 0;
+}