@@ -36,6 +36,63 @@ void addJointAndBody(
3636 model.appendBodyToJoint (idx, Y);
3737}
3838
39+ // / \brief Compute Sdot (motion subspace derivative) via finite differences
40+ template <typename JointModel>
41+ Eigen::Matrix<double , 6 , JointModel::NV> finiteDiffSdot (
42+ const JointModel & jmodel,
43+ typename JointModel::JointDataDerived & jdata,
44+ const typename JointModel::ConfigVector_t & q,
45+ const typename JointModel::TangentVector_t & v,
46+ double eps = 1e-8 )
47+ {
48+ typedef typename LieGroup<JointModel>::type LieGroupType;
49+ typedef typename JointModel::ConfigVector_t ConfigVector_t;
50+ typedef typename JointModel::TangentVector_t TangentVector_t;
51+
52+ const Eigen::DenseIndex nv = jmodel.nv ();
53+
54+ Eigen::Matrix<double , 6 , JointModel::NV> Sdot_fd;
55+ Sdot_fd.setZero ();
56+
57+ ConfigVector_t q_integrated (q);
58+ TangentVector_t v_integrate (nv);
59+ v_integrate.setZero ();
60+
61+ for (Eigen::DenseIndex k = 0 ; k < nv; ++k)
62+ {
63+ // Integrate along kth direction
64+ v_integrate[k] = eps;
65+ q_integrated = LieGroupType ().integrate (q, v_integrate);
66+
67+ // Compute S at q + eps * e_k
68+ jmodel.calc (jdata, q_integrated);
69+ const Eigen::Matrix<double , 6 , JointModel::NV> S_plus = jdata.S .matrix ();
70+
71+ // Integrate in negative direction
72+ v_integrate[k] = -eps;
73+ q_integrated = LieGroupType ().integrate (q, v_integrate);
74+
75+ // Compute S at q - eps * e_k
76+ jmodel.calc (jdata, q_integrated);
77+ const Eigen::Matrix<double , 6 , JointModel::NV> S_minus = jdata.S .matrix ();
78+
79+ // Compute dS/dq_k via central differences
80+ Eigen::Matrix<double , 6 , JointModel::NV> dS_dqk = (S_plus - S_minus) / (2.0 * eps);
81+
82+ // Accumulate: Sdot += (dS/dq_k) * v_k
83+ Sdot_fd += dS_dqk * v[k];
84+
85+ // Reset
86+ v_integrate[k] = 0 .;
87+ }
88+
89+ return Sdot_fd;
90+ }
91+
92+ // ============================================================================
93+ // Test Suite
94+ // ============================================================================
95+
3996BOOST_AUTO_TEST_SUITE (JointEllipsoid)
4097
4198BOOST_AUTO_TEST_CASE(vsSphericalZYX)
@@ -71,31 +128,8 @@ BOOST_AUTO_TEST_CASE(vsSphericalZYX)
71128 // Extract XYZ Euler angles from the rotation matrix
72129 // For XYZ convention: R = Rx(x) * Ry(y) * Rz(z)
73130 // We need to solve for x, y, z
74-
75131 Eigen::Vector3d q_e;
76-
77- // From the Ellipsoid rotation matrix structure:
78- // R(0,2) = sin(y)
79- // R(1,2) = -sin(x)*cos(y)
80- // R(2,2) = cos(x)*cos(y)
81-
82- double sy = R (0 , 2 );
83- q_e (1 ) = std::asin (sy); // y angle
84-
85- double cy = std::cos (q_e (1 ));
86-
87- if (std::abs (cy) > 1e-6 )
88- {
89- // Not at singularity
90- q_e (0 ) = std::atan2 (-R (1 , 2 ) / cy, R (2 , 2 ) / cy); // x angle
91- q_e (2 ) = std::atan2 (-R (0 , 1 ) / cy, R (0 , 0 ) / cy); // z angle
92- }
93- else
94- {
95- // Gimbal lock - choose x = 0
96- q_e (0 ) = 0.0 ;
97- q_e (2 ) = std::atan2 (R (1 , 0 ), R (1 , 1 ));
98- }
132+ q_e = R.eulerAngles (0 , 1 , 2 ); // XYZ convention
99133
100134 // Get the motion subspace matrices (which give us the Jacobians)
101135 JointModelSphericalZYX jmodel_s;
@@ -291,4 +325,36 @@ BOOST_AUTO_TEST_CASE(vsCompositeTxTyTzRxRyRz)
291325 BOOST_CHECK (aAbaEllipsoid.isApprox (qddot_ellipsoid));
292326}
293327
328+ BOOST_AUTO_TEST_CASE (testSdotFiniteDifferences)
329+ {
330+ using namespace pinocchio ;
331+
332+ // Ellipsoid parameters
333+ double radius_a = 2.0 ;
334+ double radius_b = 1.5 ;
335+ double radius_c = 1.0 ;
336+
337+ JointModelEllipsoid jmodel (radius_a, radius_b, radius_c);
338+ jmodel.setIndexes (0 , 0 , 0 );
339+
340+ JointDataEllipsoid jdata = jmodel.createData ();
341+
342+ // Test configuration and velocity
343+ typedef JointModelEllipsoid::ConfigVector_t ConfigVector_t;
344+ typedef JointModelEllipsoid::TangentVector_t TangentVector_t;
345+ typedef LieGroup<JointModelEllipsoid>::type LieGroupType;
346+
347+ ConfigVector_t q = LieGroupType ().random ();
348+ TangentVector_t v = TangentVector_t::Random ();
349+
350+ // Compute analytical Sdot
351+ const double eps = 1e-8 ;
352+ jmodel.calc (jdata, q, v);
353+ const Eigen::Matrix<double , 6 , 3 > Sdot_ref = jdata.Sdot .matrix ();
354+
355+ // Compute Sdot via finite differences using helper function
356+ const Eigen::Matrix<double , 6 , 3 > Sdot_fd = finiteDiffSdot (jmodel, jdata, q, v, eps);
357+
358+ BOOST_CHECK (Sdot_ref.isApprox (Sdot_fd, sqrt (eps)));
359+ }
294360BOOST_AUTO_TEST_SUITE_END ()
0 commit comments