Skip to content

Commit aa1f38a

Browse files
author
Thibault Poignonec
committed
prepare admittance for singularity checks
1 parent 66061bc commit aa1f38a

File tree

1 file changed

+135
-16
lines changed

1 file changed

+135
-16
lines changed

cartesian_vic_controller/src/rules/vanilla_cartesian_admittance_rule.cpp

Lines changed: 135 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ bool VanillaCartesianAdmittanceRule::compute_controls(
7171
success = false;
7272
}
7373

74+
size_t num_joints = vic_state_.input_data.joint_state_position.size();
75+
size_t dims = 6; // 6 DoF
76+
7477
// auto num_joints = vic_input_data.joint_state_position.size();
7578
// Get reference compliant frame at t_k
7679
RCLCPP_DEBUG(logger_, "Reading reference frame...");
@@ -180,9 +183,6 @@ bool VanillaCartesianAdmittanceRule::compute_controls(
180183
J_dot_
181184
);
182185

183-
RCLCPP_DEBUG(logger_, "Computing J_pinv...");
184-
J_pinv_ = (J_.transpose() * J_ + alpha_pinv_ * I_joint_space_).inverse() * J_.transpose();
185-
186186
if (!model_is_ok) {
187187
success = false;
188188
RCLCPP_ERROR(
@@ -191,6 +191,21 @@ bool VanillaCartesianAdmittanceRule::compute_controls(
191191
);
192192
}
193193

194+
195+
RCLCPP_DEBUG(logger_, "Computing J_pinv...");
196+
const Eigen::JacobiSVD<Eigen::MatrixXd> J_svd =
197+
Eigen::JacobiSVD<Eigen::MatrixXd>(J_, Eigen::ComputeThinU | Eigen::ComputeThinV);
198+
const Eigen::MatrixXd matrix_s = J_svd.singularValues().asDiagonal();
199+
if (J_svd.singularValues()(0) / J_svd.singularValues()(dims - 1) > 30) {
200+
success = false;
201+
RCLCPP_ERROR(
202+
logger_,
203+
"Jacobian singularity detected!"
204+
);
205+
}
206+
// J_pinv_ = J_svd.matrixV() * matrix_s.inverse() * J_svd.matrixU().transpose();
207+
J_pinv_ = (J_.transpose() * J_ + alpha_pinv_ * I_joint_space_).inverse() * J_.transpose();
208+
194209
// Compute desired inertia matrix and its inverse
195210
RCLCPP_DEBUG(logger_, "Computing M_inv...");
196211
Eigen::Matrix<double, 6, 6> M_inv;
@@ -214,33 +229,72 @@ bool VanillaCartesianAdmittanceRule::compute_controls(
214229
// VIC rule: M * err_p_ddot + D * err_p_dot + K * err_p = F_ext - F_ref
215230
// where err_p = p_desired - p_current
216231
//
217-
// -> commanded_acc = p_ddot_desired + inv(M) * (K * err_p + D * err_p_dot - F_ext + F_ref)
218-
// Implement "normal" impedance control
219-
RCLCPP_DEBUG(logger_, "Cmd cartesian acc...");
220-
Eigen::Matrix<double, 6, 1> commanded_cartesian_acc = reference_compliant_frame.acceleration + \
221-
M_inv * (K * error_pose + D * error_velocity - F_ext + reference_compliant_frame.wrench);
222-
223-
// TODO(tpoignonec): clip cartesian acceleration in min/max range
224-
// (and same for velocity if possible)
225232

226233
/*
227-
// Basic scheme for reference (not great): integrate cartesian velocity
228-
229-
// Compute commanded cartesian twist
230-
auto robot_command_twist = J_ * previous_jnt_cmd_velocity + commanded_cartesian_acc * dt;
234+
auto robot_command_twist = D.inverse() * (
235+
K * error_pose
236+
+ M * (reference_compliant_frame.acceleration - vic_input_data.robot_estimated_acceleration)
237+
- F_ext + reference_compliant_frame.wrench
238+
) + reference_compliant_frame.velocity;
231239
success &= dynamics_->convert_cartesian_deltas_to_joint_deltas(
232240
vic_input_data.joint_state_position,
233241
robot_command_twist,
234242
vic_input_data.control_frame,
235243
vic_command_data.joint_command_velocity
236244
);
245+
246+
// Nullspace objective for stability
247+
// ------------------------------------------------
248+
if (false){ //(vic_input_data.activate_nullspace_control) {
249+
RCLCPP_DEBUG(logger_, "Cmd nullspace joint acc...");
250+
nullspace_projection_ = I_joint_space_ - J_pinv_ * J_;
251+
M_nullspace_.diagonal() = vic_input_data.nullspace_joint_inertia;
252+
K_nullspace_.diagonal() = vic_input_data.nullspace_joint_stiffness;
253+
D_nullspace_.diagonal() = vic_input_data.nullspace_joint_damping;
254+
auto D_inv_nullspace_ = D_nullspace_;
255+
D_inv_nullspace_.diagonal() = D_nullspace_.diagonal().cwiseInverse();
256+
auto error_position_nullspace = \
257+
vic_input_data.nullspace_desired_joint_positions - vic_input_data.joint_state_position;
258+
// Add nullspace contribution to joint velocity
259+
vic_command_data.joint_command_velocity += nullspace_projection_ * D_inv_nullspace_ * (
260+
K_nullspace_ * error_position_nullspace
261+
+ external_joint_torques_);
262+
} else {
263+
// Pure (small) damping in nullspace for stability
264+
RCLCPP_WARN_THROTTLE(
265+
logger_,
266+
internal_clock_,
267+
10000, // every 10 seconds
268+
"WARNING! nullspace impedance control is disabled!"
269+
);
270+
}
271+
272+
std::cout << "ref vel: " << reference_compliant_frame.velocity.transpose() << std::endl;
273+
std::cout << "cur vel: " << vic_input_data.robot_current_velocity.transpose() << std::endl;
274+
std::cout << "ref acc: " << reference_compliant_frame.acceleration.transpose() << std::endl;
275+
std::cout << "est acc: " << vic_input_data.robot_estimated_acceleration.transpose() << std::endl;
276+
std::cout << "ref wrench: " << reference_compliant_frame.wrench.transpose() << std::endl;
277+
std::cout << "D.inv(): " << D.inverse() << std::endl;
278+
std::cout << "K: " << K << std::endl;
279+
std::cout << "M: " << M << std::endl;
280+
std::cout << "robot_command_twist: " << robot_command_twist.transpose() << std::endl;
237281
*/
238282

283+
// Alternative: use joint accelerations to integrate cartesian velocity
239284
// Compute joint command accelerations
285+
286+
// -> commanded_acc = p_ddot_desired + inv(M) * (K * err_p + D * err_p_dot - F_ext + F_ref)
287+
// Implement "normal" impedance control
288+
RCLCPP_DEBUG(logger_, "Cmd cartesian acc...");
289+
Eigen::Matrix<double, 6, 1> commanded_cartesian_acc = reference_compliant_frame.acceleration + \
290+
M_inv * (K * error_pose + D * error_velocity - F_ext + reference_compliant_frame.wrench);
291+
292+
// TODO(tpoignonec): clip cartesian acceleration in min/max range
293+
// (and same for velocity if possible)
294+
240295
RCLCPP_DEBUG(logger_, "Cmd joint acc...");
241296
vic_command_data.joint_command_acceleration = \
242297
J_pinv_ * (commanded_cartesian_acc - J_dot_ * vic_input_data.joint_state_velocity);
243-
244298
// Nullspace objective for stability
245299
// ------------------------------------------------
246300
if (vic_input_data.activate_nullspace_control) {
@@ -274,6 +328,71 @@ bool VanillaCartesianAdmittanceRule::compute_controls(
274328
vic_command_data.joint_command_velocity += \
275329
vic_command_data.joint_command_acceleration * dt;
276330

331+
// Detect and handle singularity issues
332+
/*
333+
// Singularity detection
334+
// from https://github.com/moveit/moveit2/blob/8a0c655e2ba48e1f93f551cd52fb5aa093021659/moveit_ros/moveit_servo/src/utils/common.cpp#L282
335+
double singularity_step_scale = 0.01;
336+
double hard_stop_singularity_threshold = 30.0;
337+
double lower_singularity_threshold = 17.0;
338+
double leaving_singularity_threshold_multiplier = 1.5;
339+
340+
Eigen::VectorXd vector_towards_singularity = J_svd.matrixU().col(dims - 1);
341+
const double current_condition_number = J_svd.singularValues()(0) / J_svd.singularValues()(dims - 1);
342+
const Eigen::VectorXd delta_x = vector_towards_singularity * singularity_step_scale;
343+
// Compute the new joint angles if we take the small step delta_x
344+
Eigen::VectorXd next_joint_position = vic_input_data.joint_state_position;
345+
next_joint_position += J_pinv_ * delta_x;
346+
347+
// Compute the Jacobian SVD for the new robot state.
348+
Eigen::Matrix<double, 6, Eigen::Dynamic> J_next = Eigen::Matrix<double, 6, Eigen::Dynamic>::Zero(6, dims);
349+
dynamics_->calculate_jacobian(
350+
next_joint_position,
351+
vic_input_data.control_frame,
352+
J_next
353+
);
354+
const Eigen::JacobiSVD<Eigen::MatrixXd> next_svd = Eigen::JacobiSVD<Eigen::MatrixXd>(
355+
J_next, Eigen::ComputeThinU | Eigen::ComputeThinV);
356+
357+
// Compute condition number for the new Jacobian.
358+
const double next_condition_number = next_svd.singularValues()(0) / next_svd.singularValues()(dims - 1);
359+
if (next_condition_number <= current_condition_number) {
360+
vector_towards_singularity *= -1;
361+
}
362+
// Double check the direction using dot product.
363+
const bool moving_towards_singularity = vector_towards_singularity.dot(target_delta_x) > 0;
364+
365+
// Compute upper condition variable threshold based on if we are moving towards or away from singularity.
366+
// See https://github.com/moveit/moveit2/pull/620#issuecomment-1201418258 for visual explanation.
367+
double upper_threshold;
368+
if (moving_towards_singularity)
369+
{
370+
upper_threshold = hard_stop_singularity_threshold;
371+
}
372+
else
373+
{
374+
const double threshold_size = (hard_stop_singularity_threshold - lower_singularity_threshold);
375+
upper_threshold = lower_singularity_threshold + (threshold_size * leaving_singularity_threshold_multiplier);
376+
}
377+
378+
// Compute the scale based on the current condition number.
379+
double velocity_scale = 1.0;
380+
const bool is_above_lower_limit = current_condition_number > lower_singularity_threshold;
381+
const bool is_below_hard_stop_limit = current_condition_number < hard_stop_singularity_threshold;
382+
if (is_above_lower_limit && is_below_hard_stop_limit)
383+
{
384+
velocity_scale -=
385+
(current_condition_number - lower_singularity_threshold) / (upper_threshold - lower_singularity_threshold);
386+
}
387+
// If condition number has crossed hard stop limit, halt the robot.
388+
else if (!is_below_hard_stop_limit)
389+
{
390+
success = false;
391+
velocity_scale = 0.0;
392+
}
393+
*/
394+
395+
277396
// Filter joint command velocity
278397
double cutoff_freq_cmd = parameters_.filters.command_filter_cuttoff_freq;
279398
if (cutoff_freq_cmd > 0.0) { // No smoothing otherwise

0 commit comments

Comments
 (0)