@@ -36,6 +36,59 @@ 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+
3992BOOST_AUTO_TEST_SUITE (JointEllipsoid)
4093
4194BOOST_AUTO_TEST_CASE(vsSphericalZYX)
@@ -71,31 +124,8 @@ BOOST_AUTO_TEST_CASE(vsSphericalZYX)
71124 // Extract XYZ Euler angles from the rotation matrix
72125 // For XYZ convention: R = Rx(x) * Ry(y) * Rz(z)
73126 // We need to solve for x, y, z
74-
75127 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- }
128+ q_e = R.eulerAngles (0 , 1 , 2 ); // XYZ convention
99129
100130 // Get the motion subspace matrices (which give us the Jacobians)
101131 JointModelSphericalZYX jmodel_s;
@@ -120,7 +150,7 @@ BOOST_AUTO_TEST_CASE(vsSphericalZYX)
120150 Eigen::Vector3d w_e = S_e * qd_e;
121151
122152 BOOST_CHECK (w_s.isApprox (w_e));
123-
153+
124154 // Compute forward kinematics with the converted configurations
125155 forwardKinematics (modelEllipsoid, dataEllipsoid, q_e, qd_e);
126156
@@ -291,4 +321,36 @@ BOOST_AUTO_TEST_CASE(vsCompositeTxTyTzRxRyRz)
291321 BOOST_CHECK (aAbaEllipsoid.isApprox (qddot_ellipsoid));
292322}
293323
324+ BOOST_AUTO_TEST_CASE (testSdotFiniteDifferences)
325+ {
326+ using namespace pinocchio ;
327+
328+ // Ellipsoid parameters
329+ double radius_a = 2.0 ;
330+ double radius_b = 1.5 ;
331+ double radius_c = 1.0 ;
332+
333+ JointModelEllipsoid jmodel (radius_a, radius_b, radius_c);
334+ jmodel.setIndexes (0 , 0 , 0 );
335+
336+ JointDataEllipsoid jdata = jmodel.createData ();
337+
338+ // Test configuration and velocity
339+ typedef JointModelEllipsoid::ConfigVector_t ConfigVector_t;
340+ typedef JointModelEllipsoid::TangentVector_t TangentVector_t;
341+ typedef LieGroup<JointModelEllipsoid>::type LieGroupType;
342+
343+ ConfigVector_t q = LieGroupType ().random ();
344+ TangentVector_t v = TangentVector_t::Random ();
345+
346+ // Compute analytical Sdot
347+ const double eps = 1e-8 ;
348+ jmodel.calc (jdata, q, v);
349+ const Eigen::Matrix<double , 6 , 3 > Sdot_ref = jdata.Sdot .matrix ();
350+
351+ // Compute Sdot via finite differences using helper function
352+ const Eigen::Matrix<double , 6 , 3 > Sdot_fd = finiteDiffSdot (jmodel, jdata, q, v, eps);
353+
354+ BOOST_CHECK (Sdot_ref.isApprox (Sdot_fd, sqrt (eps)));
355+ }
294356BOOST_AUTO_TEST_SUITE_END ()
0 commit comments