@@ -611,6 +611,56 @@ void ApproximateBoundedNormByLinearConstraints(
611611 prog->AddLinearConstraint (x (0 ) - x (1 ) + x (2 ), -sqrt3_c, sqrt3_c);
612612 prog->AddLinearConstraint (x (0 ) - x (1 ) - x (2 ), -sqrt3_c, sqrt3_c);
613613}
614+ } // namespace
615+ namespace {
616+
617+ // Helper struct containing precomputed quantities shared by
618+ // AddJointLimitConstraint() and AddJointCenteringCost().
619+ struct RevoluteJointGeometry {
620+ std::array<Eigen::Vector3d, 4 > v_samples;
621+ Eigen::Matrix3d rotmat_joint_offset;
622+ };
623+
624+ // Helper to compute v_samples and rotmat_joint_offset for a revolute joint.
625+ RevoluteJointGeometry ComputeRevoluteJointGeometry (
626+ const Eigen::Vector3d& axis_F, double angle_offset) {
627+ // First generate a vector v_C that is perpendicular to rotation
628+ // axis, in child frame.
629+ Vector3d v_C = axis_F.cross (Vector3d (1 , 0 , 0 ));
630+ double v_C_norm = v_C.norm ();
631+ if (v_C_norm < sqrt (2 ) / 2 ) {
632+ // axis_F is almost parallel to [1; 0; 0]. Try another axis [0, 1, 0]
633+ v_C = axis_F.cross (Vector3d (0 , 1 , 0 ));
634+ v_C_norm = v_C.norm ();
635+ }
636+ // Normalizes the revolute vector.
637+ v_C /= v_C_norm;
638+
639+ // The constraint would be tighter, if we choose many unit
640+ // length vector `v`, perpendicular to the joint axis, in the
641+ // joint frame. Here to balance between the size of the
642+ // optimization problem, and the tightness of the convex
643+ // relaxation, we just use four vectors in `v`. Notice that
644+ // v_basis contains the orthonormal basis of the null space
645+ // null(axis_F).
646+ std::array<Eigen::Vector3d, 2 > v_basis = {{v_C, axis_F.cross (v_C)}};
647+ v_basis[1 ] /= v_basis[1 ].norm ();
648+
649+ std::array<Eigen::Vector3d, 4 > v_samples;
650+ v_samples[0 ] = v_basis[0 ];
651+ v_samples[1 ] = v_basis[1 ];
652+ v_samples[2 ] = v_basis[0 ] + v_basis[1 ];
653+ v_samples[2 ] /= v_samples[2 ].norm ();
654+ v_samples[3 ] = v_basis[0 ] - v_basis[1 ];
655+ v_samples[3 ] /= v_samples[3 ].norm ();
656+
657+ // rotmat_joint_offset is R(k, angle_offset) explained above.
658+ const Matrix3d rotmat_joint_offset =
659+ Eigen::AngleAxisd (angle_offset, axis_F).toRotationMatrix ();
660+
661+ return {v_samples, rotmat_joint_offset};
662+ }
663+
614664} // namespace
615665
616666void GlobalInverseKinematics::AddJointLimitConstraint (
@@ -711,46 +761,16 @@ void GlobalInverseKinematics::AddJointLimitConstraint(
711761 // |R_WC*R_CJc*v - R_WP * R_PJp * R(k,(a+b)/2)*v | <= 2*sin (α / 2)
712762 // as we explained above.
713763
714- // First generate a vector v_C that is perpendicular to rotation
715- // axis, in child frame.
716- Vector3d v_C = axis_F.cross (Vector3d (1 , 0 , 0 ));
717- double v_C_norm = v_C.norm ();
718- if (v_C_norm < sqrt (2 ) / 2 ) {
719- // axis_F is almost parallel to [1; 0; 0]. Try another axis
720- // [0, 1, 0]
721- v_C = axis_F.cross (Vector3d (0 , 1 , 0 ));
722- v_C_norm = v_C.norm ();
723- }
724- // Normalizes the revolute vector.
725- v_C /= v_C_norm;
726-
727- // The constraint would be tighter, if we choose many unit
728- // length vector `v`, perpendicular to the joint axis, in the
729- // joint frame. Here to balance between the size of the
730- // optimization problem, and the tightness of the convex
731- // relaxation, we just use four vectors in `v`. Notice that
732- // v_basis contains the orthonormal basis of the null space
733- // null(axis_F).
734- std::array<Eigen::Vector3d, 2 > v_basis = {{v_C, axis_F.cross (v_C)}};
735- v_basis[1 ] /= v_basis[1 ].norm ();
736-
737- std::array<Eigen::Vector3d, 4 > v_samples;
738- v_samples[0 ] = v_basis[0 ];
739- v_samples[1 ] = v_basis[1 ];
740- v_samples[2 ] = v_basis[0 ] + v_basis[1 ];
741- v_samples[2 ] /= v_samples[2 ].norm ();
742- v_samples[3 ] = v_basis[0 ] - v_basis[1 ];
743- v_samples[3 ] /= v_samples[3 ].norm ();
744-
745- // rotmat_joint_offset is R(k, (a+b)/2) explained above.
746- const Matrix3d rotmat_joint_offset =
747- Eigen::AngleAxisd ((joint_lower_bounds_[position_idx] +
748- joint_upper_bounds_[position_idx]) /
749- 2 ,
750- axis_F)
751- .toRotationMatrix ();
752-
753- // joint_limit_expr is going to be within the Lorentz cone.
764+ const double offset_angle = (joint_lower_bounds_[position_idx] +
765+ joint_upper_bounds_[position_idx]) /
766+ 2 ;
767+ const auto joint_geometry =
768+ ComputeRevoluteJointGeometry (axis_F, offset_angle);
769+
770+ const auto & v_samples = joint_geometry.v_samples ;
771+ const auto & rotmat_joint_offset =
772+ joint_geometry.rotmat_joint_offset ;
773+
754774 Eigen::Matrix<Expression, 4 , 1 > joint_limit_expr;
755775 const double joint_limit_lorentz_rhs = 2 * sin (joint_bound / 2 );
756776 joint_limit_expr (0 ) = joint_limit_lorentz_rhs;
@@ -766,7 +786,6 @@ void GlobalInverseKinematics::AddJointLimitConstraint(
766786 ApproximateBoundedNormByLinearConstraints (
767787 joint_limit_expr.tail <3 >(), joint_limit_lorentz_rhs,
768788 &prog_);
769-
770789 } else {
771790 prog_.AddLorentzConeConstraint (joint_limit_expr);
772791 }
@@ -815,6 +834,8 @@ void GlobalInverseKinematics::AddJointLimitConstraint(
815834 R_WB_[body_index] * X_CJc.rotation ().matrix ();
816835 const double joint_bound_cos{std::cos (joint_bound)};
817836 if (!linear_constraint_approximation) {
837+ std::array<Eigen::Vector3d, 2 > v_basis = {
838+ {v_samples[0 ], (v_samples[0 ].cross (axis_F)).normalized ()}};
818839 Eigen::Matrix<double , 3 , 2 > V;
819840 V << v_basis[0 ], v_basis[1 ];
820841 const Eigen::Matrix<symbolic::Expression, 2 , 2 > M =
@@ -824,7 +845,6 @@ void GlobalInverseKinematics::AddJointLimitConstraint(
824845 prog_.AddRotatedLorentzConeConstraint (
825846 Vector3<symbolic::Expression>(M (0 , 0 ), M (1 , 1 ), M (1 , 0 )));
826847 }
827-
828848 // From Rodriguez formula, we know that -α <= β <= α implies
829849 // trace(R(k, β)) = 1 + 2 * cos(β) >= 1 + 2*cos(α)
830850 // So we can impose the constraint
@@ -880,53 +900,19 @@ void GlobalInverseKinematics::AddJointCenteringCost(BodyIndex body_index,
880900
881901 switch (joint->num_velocities ()) {
882902 case 0 : {
883- // Fixed to the parent body.
884903 throw std::runtime_error (" Cannot impose joint limits for a fixed joint." );
885904 }
886905 case 1 : {
887- // Should NOT do this evil dynamic cast here, but currently we do
888- // not have a method to tell if a joint is revolute or not.
889906 if (dynamic_cast <const RevoluteJoint<double >*>(joint) != nullptr ) {
890907 const auto * revolute_joint =
891908 dynamic_cast <const RevoluteJoint<double >*>(joint);
892- // axis_F is the vector of the rotation axis in the joint
893- // inboard/outboard frame.
894909 const Vector3d axis_F = revolute_joint->revolute_axis ();
895910
896- // First generate a vector v_C that is perpendicular to rotation
897- // axis, in child frame.
898- Vector3d v_C = axis_F.cross (Vector3d (1 , 0 , 0 ));
899- double v_C_norm = v_C.norm ();
900- if (v_C_norm < sqrt (2 ) / 2 ) {
901- // axis_F is almost parallel to [1; 0; 0]. Try another axis
902- // [0, 1, 0]
903- v_C = axis_F.cross (Vector3d (0 , 1 , 0 ));
904- v_C_norm = v_C.norm ();
905- }
906- // Normalizes the revolute vector.
907- v_C /= v_C_norm;
908-
909- // The constraint would be tighter, if we choose many unit
910- // length vector `v`, perpendicular to the joint axis, in the
911- // joint frame. Here to balance between the size of the
912- // optimization problem, and the tightness of the convex
913- // relaxation, we just use four vectors in `v`. Notice that
914- // v_basis contains the orthonormal basis of the null space
915- // null(axis_F).
916- std::array<Eigen::Vector3d, 2 > v_basis = {{v_C, axis_F.cross (v_C)}};
917- v_basis[1 ] /= v_basis[1 ].norm ();
918-
919- std::array<Eigen::Vector3d, 4 > v_samples;
920- v_samples[0 ] = v_basis[0 ];
921- v_samples[1 ] = v_basis[1 ];
922- v_samples[2 ] = v_basis[0 ] + v_basis[1 ];
923- v_samples[2 ] /= v_samples[2 ].norm ();
924- v_samples[3 ] = v_basis[0 ] - v_basis[1 ];
925- v_samples[3 ] /= v_samples[3 ].norm ();
926-
927- // rotmat_joint_offset is R(k, nominal) explained above.
928- const Matrix3d rotmat_joint_offset =
929- Eigen::AngleAxisd (nominal_value, axis_F).toRotationMatrix ();
911+ // Compute shared geometric quantities.
912+ const auto joint_geometry =
913+ ComputeRevoluteJointGeometry (axis_F, nominal_value);
914+ const auto & v_samples = joint_geometry.v_samples ;
915+ const auto & rotmat_joint_offset = joint_geometry.rotmat_joint_offset ;
930916
931917 for (const auto & v : v_samples) {
932918 Eigen::Matrix<Expression, 3 , 1 > unit_vector_difference =
@@ -942,7 +928,7 @@ void GlobalInverseKinematics::AddJointCenteringCost(BodyIndex body_index,
942928 prog_.AddLinearCost (weight * s);
943929 }
944930 } else if (norm == 2 ) {
945- // Get the union of all variables in all expressions
931+ // Get the union of all variables in all expressions.
946932 symbolic::Variables var_set;
947933 for (int i = 0 ; i < unit_vector_difference.size (); ++i) {
948934 var_set += unit_vector_difference[i].GetVariables ();
@@ -963,7 +949,6 @@ void GlobalInverseKinematics::AddJointCenteringCost(BodyIndex body_index,
963949 // Sign flip is necessary since this method adds a cost on Ax-b.
964950 prog_.Add2NormSquaredCost (A, -b, vars);
965951 } else {
966- // prog_.AddL2NormCost(A, b, vars);
967952 prog_.AddL2NormCostUsingConicConstraint (A, b, vars);
968953 }
969954 } else {
0 commit comments