17 enum class ExitCode :
int { kNoError = 0, kInequalityConstraintViolation, kQPSolverError };
22 math::SE3::MatrixType T{math::SE3::Identity()};
23 double weight_position{0.};
24 double weight_orientation{0.};
29 Eigen::Vector3d com{Eigen::Vector3d::Zero()};
35 q = Eigen::Vector<double, DOF>::Zero(dof);
36 weight = Eigen::Vector<double, DOF>::Zero(dof);
40 if constexpr (DOF > 0) {
44 static_assert(DOF > 0,
"JointAngleTarget() is only available for fixed-size DOF");
48 Eigen::Vector<double, DOF> q;
49 Eigen::Vector<double, DOF> weight;
54 q = Eigen::Vector<double, DOF>::Zero(dof);
55 weight = Eigen::Vector<double, DOF>::Zero(dof);
59 if constexpr (DOF > 0) {
63 static_assert(DOF > 0,
"NullspaceJointTarget() is only available for fixed-size DOF");
67 Eigen::Vector<double, DOF> q;
68 Eigen::Vector<double, DOF> weight;
71 double cost_weight{1e-3};
75 std::optional<std::vector<LinkTarget>> link_targets;
76 std::optional<COMTarget> com_target;
77 std::optional<JointAngleTarget> q_target;
78 std::optional<NullspaceJointTarget> nullspace_q_target;
82 const std::vector<unsigned int>& joint_idx,
83 bool nullspace_mapping =
true,
84 bool soft_position_boundary =
false
86 : robot_(std::move(robot)),
87 joint_idx_(joint_idx),
88 dof_(robot_->GetDOF()),
89 n_joints_((int)joint_idx.size()),
90 nullspace_mapping_(nullspace_mapping),
91 soft_position_boundary_(soft_position_boundary) {
92 n_vars_ = soft_position_boundary_ ? 3 * n_joints_ : n_joints_;
93 n_consts_ = soft_position_boundary_ ? 5 * n_joints_ : n_joints_;
94 qp_solver_.Setup(n_vars_, n_consts_);
96 std::vector<bool> is_selected(dof_,
false);
97 for (
const auto& i : joint_idx_) {
99 is_selected[i] =
true;
102 unselected_joint_idx_.reserve(dof_ - n_joints_);
103 for (
int i = 0; i < dof_; i++) {
104 if (!is_selected[i]) {
105 unselected_joint_idx_.push_back(i);
110 std::optional<Eigen::VectorXd> Solve(Input in,
111 std::shared_ptr<dyn::State<DOF>> state,
113 double error_scaling,
114 const Eigen::Vector<double, DOF>& position_lower_limit,
115 const Eigen::Vector<double, DOF>& position_upper_limit,
116 const Eigen::Vector<double, DOF>& velocity_limit,
117 const Eigen::Vector<double, DOF>& acceleration_limit,
118 bool need_forward_kinematics =
false
120 exit_code_ = ExitCode::kNoError;
124 double max_cond = 1.0;
126 dt = std::max(dt, 1.e-6);
128 if (need_forward_kinematics) {
129 robot_->ComputeForwardKinematics(state);
132 qp_solver_.InitFunction();
134 if (in.link_targets.has_value()) {
135 AddLinkTargetCosts(in.link_targets.value(), state, dt, error_scaling, err_sum, max_cond);
138 if (in.com_target.has_value()) {
139 AddCOMTargetCost(in.com_target.value(), state, dt, error_scaling, err_sum, max_cond);
142 if (in.q_target.has_value()) {
143 AddJointTargetCost(in.q_target.value(), state, dt, error_scaling, err_sum);
147 const Eigen::MatrixXd A_cost_primary = qp_solver_.GetACost();
148 const Eigen::MatrixXd J_primary = A_cost_primary.leftCols(n_joints_);
150 if (in.nullspace_q_target) {
151 AddNullspaceCostTowardQTarget_Projected(*in.nullspace_q_target, state, dt, J_primary);
154 if (nullspace_mapping_) {
155 AddNullspaceCost_Projected(1e-4, J_primary);
158 if (soft_position_boundary_) {
159 const double weight = 1.;
160 const double safety_margin = 5 * math::kDeg2Rad;
162 Eigen::VectorXd q = state->GetQ();
164 Eigen::MatrixXd upper_penalty = Eigen::MatrixXd::Zero(n_joints_, n_vars_);
165 Eigen::MatrixXd lower_penalty = Eigen::MatrixXd::Zero(n_joints_, n_vars_);
166 for (
int i = 0; i < n_joints_; i++) {
167 if (q(joint_idx_[i]) > position_upper_limit(joint_idx_[i]) - safety_margin) {
168 const double alpha = std::clamp(
169 (safety_margin - (position_upper_limit(joint_idx_[i]) - q(joint_idx_[i]))) / safety_margin, 1.e-6, 1.0);
170 upper_penalty(i, n_joints_ + i) = weight * alpha;
172 upper_penalty(i, n_joints_ + i) = 1.e-6;
174 if (q(joint_idx_[i]) < position_lower_limit(joint_idx_[i]) + safety_margin) {
175 const double alpha = std::clamp(
176 (safety_margin - (q(joint_idx_[i]) - position_lower_limit(joint_idx_[i]))) / safety_margin, 1.e-6, 1.0);
177 lower_penalty(i, 2 * n_joints_ + i) = weight * alpha;
179 lower_penalty(i, 2 * n_joints_ + i) = 1.e-6;
182 qp_solver_.AddCostFunction(upper_penalty, Eigen::VectorXd::Zero(n_joints_));
183 qp_solver_.AddCostFunction(lower_penalty, Eigen::VectorXd::Zero(n_joints_));
186 qp_solver_.SetCostFunction(qp_solver_.GetACost(), qp_solver_.GetBCost());
188 if (!SetInequalityConstraints(state->GetQ()(joint_idx_),
189 state->GetQdot()(joint_idx_),
190 position_lower_limit(joint_idx_),
191 position_upper_limit(joint_idx_),
192 velocity_limit(joint_idx_),
193 acceleration_limit(joint_idx_),
199 Eigen::VectorXd primal_variable = Eigen::VectorXd::Zero(n_vars_);
200 primal_variable.head(n_joints_) = state->GetQdot()(joint_idx_);
201 qp_solver_.SetPrimalVariable(primal_variable);
204 Eigen::VectorXd solution = state->GetQdot();
205 const auto& result = qp_solver_.
Solve();
206 solution(joint_idx_) = result.head(n_joints_);
207 err_ = std::sqrt(err_sum);
208 manipulability_ = max_cond;
210 }
catch (math::QPSolverException& e) {
211 std::stringstream ss;
212 ss <<
"[OptimalControl::Solve] OSQP Error: " << e.what();
213 exit_code_ = ExitCode::kQPSolverError;
214 exit_code_msg_ = ss.str();
219 std::optional<Eigen::VectorXd> Solve(Input in,
220 std::shared_ptr<dyn::State<DOF>> state,
222 double error_scaling,
223 const Eigen::Vector<double, DOF>& velocity_limit,
224 const Eigen::Vector<double, DOF>& acceleration_limit,
225 bool need_forward_kinematics =
false
227 return Solve(in, state, dt, error_scaling, robot_->GetLimitQLower(state), robot_->GetLimitQUpper(state),
228 velocity_limit, acceleration_limit, need_forward_kinematics);
231 ExitCode GetExitCode()
const {
return exit_code_; }
233 std::string GetExitCodeMessage()
const {
return exit_code_msg_; }
235 double GetError()
const {
return err_; }
237 double GetManipulability()
const {
return manipulability_; }
241 double CalculateConditionNumber(
const Eigen::Matrix<double, N, N>& A) {
242 constexpr double kEpsilon = 1e-6;
244 Eigen::JacobiSVD<Eigen::Matrix<double, N, N>> svd(A);
245 const auto& singular_values = svd.singularValues();
247 double max_sv = singular_values.maxCoeff();
248 double min_sv = singular_values.minCoeff();
250 if (std::abs(min_sv) <= kEpsilon) {
251 return std::numeric_limits<double>::infinity();
253 return max_sv / min_sv;
256 Eigen::MatrixXd ComputeNullspaceProjection(
const Eigen::MatrixXd& J) {
257 Eigen::MatrixXd JJt = J * J.transpose();
258 Eigen::MatrixXd pinv = J.transpose() * JJt.completeOrthogonalDecomposition().pseudoInverse();
260 Eigen::MatrixXd I = Eigen::MatrixXd::Identity(J.cols(), J.cols());
264 void AddLinkTargetCosts(
const std::vector<LinkTarget>& link_targets,
265 const std::shared_ptr<dyn::State<DOF>>& state,
267 double error_scaling,
271 using namespace math;
273 Eigen::Vector<double, DOF> qdot = state->GetQdot();
274 qdot(unselected_joint_idx_).setZero();
276 for (
const auto& link_target : link_targets) {
277 Eigen::Matrix4d T_cur = robot_->ComputeTransformation(state, link_target.ref_link_index, link_target.link_index);
278 Eigen::Matrix4d T_err = T_cur.inverse() * link_target.T;
280 Eigen::Matrix<double, 6, DOF> J_full =
281 robot_->ComputeBodyJacobian(state, link_target.ref_link_index, link_target.link_index);
282 J_full(Eigen::all, unselected_joint_idx_).setZero();
284 Eigen::Vector<double, 6> w;
285 w.head<3>().fill(link_target.weight_orientation < 1e-6 ? 0. : link_target.weight_orientation);
286 w.tail<3>().fill(link_target.weight_position < 1e-6 ? 0. : link_target.weight_position);
288 Eigen::Matrix<double, 6, 6> JJt = J_full(Eigen::all, joint_idx_) * J_full(Eigen::all, joint_idx_).transpose();
289 max_cond = std::max(max_cond, CalculateConditionNumber(JJt));
291 Eigen::Vector<double, 6> err = w.asDiagonal() * (SE3::Log(T_err) / dt * error_scaling - J_full * qdot / 2);
292 Eigen::Matrix<double, 6, DOF> J_weighted = w.asDiagonal() * J_full / 2;
293 err_sum += err.squaredNorm();
295 Eigen::MatrixXd A{Eigen::MatrixXd::Zero(6, n_vars_)};
296 A.block(0, 0, 6, n_joints_) = J_weighted(Eigen::all, joint_idx_);
298 qp_solver_.AddCostFunction(A, err);
302 void AddCOMTargetCost(
const COMTarget& target,
303 const std::shared_ptr<dyn::State<DOF>>& state,
305 double error_scaling,
309 Eigen::Vector<double, DOF> qdot = state->GetQdot();
310 qdot(unselected_joint_idx_).setZero();
312 Eigen::Vector3d com = robot_->ComputeCenterOfMass(state, target.ref_link_index);
313 Eigen::Matrix<double, 3, DOF> J_com = robot_->ComputeCenterOfMassJacobian(state, target.ref_link_index);
314 J_com(Eigen::all, unselected_joint_idx_).setZero();
316 Eigen::Vector<double, 3> w;
317 w.fill(target.weight < 1e-6 ? 0. : target.weight);
319 Eigen::Matrix3d JJt = J_com * J_com.transpose();
320 max_cond = std::max(max_cond, CalculateConditionNumber(JJt));
322 Eigen::Vector3d err = w.asDiagonal() * ((target.com - com) / dt * error_scaling - J_com * qdot / 2);
323 Eigen::Matrix<double, 3, DOF> J_com_weighted = w.asDiagonal() * J_com / 2;
324 err_sum += err.squaredNorm();
326 Eigen::MatrixXd A{Eigen::MatrixXd::Zero(3, n_vars_)};
327 A.block(0, 0, 3, n_joints_) = J_com_weighted(Eigen::all, joint_idx_);
329 qp_solver_.AddCostFunction(A, err);
332 void AddJointTargetCost(
const JointAngleTarget& target,
333 const std::shared_ptr<dyn::State<DOF>>& state,
335 double error_scaling,
338 Eigen::VectorXd q = state->GetQ()(joint_idx_);
339 Eigen::VectorXd qdot = state->GetQdot()(joint_idx_);
341 Eigen::MatrixXd J_full = Eigen::MatrixXd::Identity(n_joints_, n_joints_);
343 Eigen::MatrixXd w = Eigen::MatrixXd::Zero(n_joints_, n_joints_);
344 for (
int i = 0; i < n_joints_; i++) {
345 if (target.weight[joint_idx_[i]] > 1e-6) {
346 w(i, i) = target.weight[joint_idx_[i]];
350 Eigen::VectorXd err = w * ((target.q(joint_idx_) - q) / dt * error_scaling - qdot / 2);
351 Eigen::MatrixXd J_weighted = w * J_full / 2;
352 err_sum += err.squaredNorm();
354 Eigen::MatrixXd A{Eigen::MatrixXd::Zero(n_joints_, n_vars_)};
355 A.block(0, 0, n_joints_, n_joints_) = J_weighted;
357 qp_solver_.AddCostFunction(A, err);
360 void AddNullspaceCost_Projected(
double weight,
const Eigen::MatrixXd& J_primary) {
361 Eigen::MatrixXd P = ComputeNullspaceProjection(J_primary);
362 Eigen::MatrixXd A = Eigen::MatrixXd::Zero(n_joints_, n_vars_);
363 A.block(0, 0, n_joints_, n_joints_) = weight * P;
364 qp_solver_.AddCostFunction(A, Eigen::VectorXd::Zero(n_joints_));
367 void AddNullspaceCostToward_Projected(
const Eigen::Vector<double, DOF>& v_des_full,
double weight,
368 const Eigen::MatrixXd& J_primary) {
369 const Eigen::MatrixXd P = ComputeNullspaceProjection(J_primary);
370 Eigen::VectorXd v_des = v_des_full(joint_idx_);
371 Eigen::MatrixXd A = Eigen::MatrixXd::Zero(n_joints_, n_vars_);
372 A.block(0, 0, n_joints_, n_joints_) = weight * P;
373 Eigen::VectorXd b = weight * P * v_des;
374 qp_solver_.AddCostFunction(A, b);
377 void AddNullspaceCostTowardQTarget_Projected(
const NullspaceJointTarget& target,
378 const std::shared_ptr<dyn::State<DOF>>& state,
double dt,
379 const Eigen::MatrixXd& J_primary) {
380 const double h = std::max(dt, 1e-6);
381 const Eigen::VectorXd q = state->GetQ();
382 const Eigen::VectorXd qdot = state->GetQdot();
384 Eigen::Vector<double, DOF> v_des;
385 if constexpr (DOF > 0) {
388 v_des = Eigen::Vector<double, DOF>::Zero(q.size());
390 v_des(joint_idx_) = target.k_p * (target.q(joint_idx_) - q(joint_idx_)) / h - target.k_d * qdot(joint_idx_);
392 const Eigen::MatrixXd P = ComputeNullspaceProjection(J_primary);
394 if ((target.weight.array() > 1e-6).any()) {
395 Eigen::VectorXd w = target.weight(joint_idx_).cwiseMax(0.0);
396 Eigen::MatrixXd W = w.asDiagonal();
397 Eigen::MatrixXd A = Eigen::MatrixXd::Zero(n_joints_, n_vars_);
398 A.block(0, 0, n_joints_, n_joints_) = target.cost_weight * (W * P);
399 Eigen::VectorXd b = target.cost_weight * (W * P) * v_des(joint_idx_);
400 qp_solver_.AddCostFunction(A, b);
402 AddNullspaceCostToward_Projected(v_des, target.cost_weight, J_primary);
406 bool SetInequalityConstraints(
const Eigen::VectorXd& q,
407 const Eigen::VectorXd& qdot,
408 const Eigen::VectorXd& q_lb,
409 const Eigen::VectorXd& q_ub,
410 const Eigen::VectorXd& velocity_limit,
411 const Eigen::VectorXd& acceleration_limit,
414 Eigen::MatrixXd A_const = Eigen::MatrixXd::Zero(n_consts_, n_vars_);
415 A_const.block(0, 0, n_joints_, n_joints_).setIdentity();
416 if (soft_position_boundary_) {
417 A_const.block(n_joints_, 0, n_joints_, n_joints_) = Eigen::VectorXd::Constant(n_joints_, -1).asDiagonal();
418 A_const.block(n_joints_, n_joints_, n_joints_, n_joints_).setIdentity();
419 A_const.block(2 * n_joints_, n_joints_, n_joints_, n_joints_).setIdentity();
420 A_const.block(3 * n_joints_, 0, n_joints_, n_joints_).setIdentity();
421 A_const.block(3 * n_joints_, 2 * n_joints_, n_joints_, n_joints_).setIdentity();
422 A_const.block(4 * n_joints_, 2 * n_joints_, n_joints_, n_joints_).setIdentity();
425 Eigen::VectorXd qdot_lb = -velocity_limit;
426 Eigen::VectorXd qdot_ub = velocity_limit;
427 qdot_lb = qdot_lb.cwiseMax(qdot - acceleration_limit * dt);
428 qdot_ub = qdot_ub.cwiseMin(qdot + acceleration_limit * dt);
429 qdot_lb = qdot_lb.cwiseMax(2 * (q_lb - q) / dt - qdot);
430 qdot_ub = qdot_ub.cwiseMin(2 * (q_ub - q) / dt - qdot);
432 if ((qdot_lb.array() <= qdot_ub.array()).all()) {
433 Eigen::VectorXd lb = Eigen::VectorXd::Zero(n_consts_);
434 Eigen::VectorXd ub = Eigen::VectorXd::Zero(n_consts_);
436 lb.head(n_joints_) = qdot_lb;
437 ub.head(n_joints_) = qdot_ub;
438 lb.tail(n_consts_ - n_joints_).setZero();
439 ub.tail(n_consts_ - n_joints_).setConstant(1e6);
441 qp_solver_.SetConstraintsFunction(A_const, lb, ub);
446 Eigen::IOFormat eigen_fmt(Eigen::StreamPrecision, Eigen::DontAlignCols,
", ",
", ",
"",
"",
"",
"");
447 std::stringstream ss;
448 ss <<
"Inequality constraint violation - qdot_lb: [" << qdot_lb.format(eigen_fmt) <<
"], qdot_ub: ["
449 << qdot_ub.format(eigen_fmt) <<
"]";
450 exit_code_ = ExitCode::kInequalityConstraintViolation;
451 exit_code_msg_ = ss.str();
456 ExitCode exit_code_{ExitCode::kNoError};
457 std::string exit_code_msg_{};
460 double manipulability_{};
462 math::QPSolver qp_solver_;
464 std::shared_ptr<dyn::Robot<DOF>> robot_;
469 bool nullspace_mapping_{
false};
470 bool soft_position_boundary_{
false};
472 std::vector<unsigned int> joint_idx_;
473 std::vector<unsigned int> unselected_joint_idx_;