moveit2
The MoveIt Motion Planning Framework for ROS 2.
Loading...
Searching...
No Matches
acceleration_filter.cpp
Go to the documentation of this file.
1/*********************************************************************
2 * Software License Agreement (BSD License)
3 *
4 * Copyright (c) 2024, PickNik Inc.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * * Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
17 * * Neither the name of PickNik Inc. nor the names of its
18 * contributors may be used to endorse or promote products derived
19 * from this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 * POSSIBILITY OF SUCH DAMAGE.
33 *********************************************************************/
34
36#include <rclcpp/logging.hpp>
37
38// Disable -Wold-style-cast because all _THROTTLE macros trigger this
39#pragma GCC diagnostic ignored "-Wold-style-cast"
40
42{
43#if !MOVEIT_OSQP_V1
44// v0.6.x doesn't expose these names; alias them to the equivalent v0.6 types so
45// the rest of the file can be written in the v1.0 naming without duplication.
46using OSQPInt = c_int;
47using OSQPCscMatrix = csc;
48#endif
49
50rclcpp::Logger getLogger()
51{
52 return moveit::getLogger("moveit.core.acceleration_limited_plugin");
53}
54
55// The threshold below which any velocity or position difference is considered zero (rad and rad/s).
56constexpr double COMMAND_DIFFERENCE_THRESHOLD = 1E-4;
57// The scaling parameter alpha between the current point and commanded point must be less than 1.0
58constexpr double ALPHA_UPPER_BOUND = 1.0;
59// The scaling parameter alpha must also be greater than 0.0
60constexpr double ALPHA_LOWER_BOUND = 0.0;
61
64{
66 std::vector<OSQPInt> row_indices;
68 std::vector<OSQPInt> column_pointers;
70 std::vector<double> elements;
73
74 CSCWrapper(Eigen::SparseMatrix<double>& M)
75 {
76 M.makeCompressed();
77
78 csc_sparse_matrix.n = M.cols();
79 csc_sparse_matrix.m = M.rows();
80 row_indices.assign(M.innerSize(), 0);
82 column_pointers.assign(M.outerSize() + 1, 0);
84 csc_sparse_matrix.nzmax = M.nonZeros();
85 csc_sparse_matrix.nz = -1;
86 elements.assign(M.nonZeros(), 0.0);
87 csc_sparse_matrix.x = elements.data();
88
89 update(M);
90 }
91
93 void update(Eigen::SparseMatrix<double>& M)
94 {
95 for (size_t ind = 0; ind < row_indices.size(); ++ind)
96 {
97 row_indices[ind] = M.innerIndexPtr()[ind];
98 }
99
100 for (size_t ind = 0; ind < column_pointers.size(); ++ind)
101 {
102 column_pointers[ind] = M.outerIndexPtr()[ind];
103 }
104 for (size_t ind = 0; ind < elements.size(); ++ind)
105 {
106 elements[ind] = M.data().at(ind);
107 }
108 }
109};
110
111MOVEIT_STRUCT_FORWARD(OSQPDataWrapper);
112
121{
122 OSQPDataWrapper(Eigen::SparseMatrix<double>& objective_sparse, Eigen::SparseMatrix<double>& constraints_sparse)
123 : P{ objective_sparse }
124 , A{ constraints_sparse }
125 , q{ Eigen::VectorXd::Zero(objective_sparse.rows()) }
126 , l{ Eigen::VectorXd::Zero(constraints_sparse.rows()) }
127 , u{ Eigen::VectorXd::Zero(constraints_sparse.rows()) }
128 {
129#if !MOVEIT_OSQP_V1
130 // v0.6.x: populate the OSQPData aggregate that osqp_setup expects.
131 data.n = objective_sparse.rows();
132 data.m = constraints_sparse.rows();
133 data.P = &P.csc_sparse_matrix;
134 data.q = q.data();
135 data.A = &A.csc_sparse_matrix;
136 data.l = l.data();
137 data.u = u.data();
138#endif
139 }
140
142#if MOVEIT_OSQP_V1
143 void updateA(OSQPSolver* solver, Eigen::SparseMatrix<double>& constraints_sparse)
144 {
145 constraints_sparse.makeCompressed();
146 A.update(constraints_sparse);
147 // v1.0: osqp_update_data_mat covers both P and A; we pass nullptrs for the
148 // P side to say "don't update P."
149 osqp_update_data_mat(solver, nullptr, nullptr, 0, A.elements.data(), nullptr,
150 static_cast<OSQPInt>(A.elements.size()));
151 }
152#else
153 void updateA(OSQPWorkspace* work, Eigen::SparseMatrix<double>& constraints_sparse)
154 {
155 constraints_sparse.makeCompressed();
156 A.update(constraints_sparse);
157 osqp_update_A(work, A.elements.data(), OSQP_NULL, A.elements.size());
158 }
159#endif
160
163 Eigen::VectorXd q;
164 Eigen::VectorXd l;
165 Eigen::VectorXd u;
166#if !MOVEIT_OSQP_V1
167 OSQPData data{};
168#endif
169};
170
171bool AccelerationLimitedPlugin::initialize(rclcpp::Node::SharedPtr node, moveit::core::RobotModelConstPtr robot_model,
172 size_t num_joints)
173{
174 // copy inputs into member variables
175 node_ = node;
176 num_joints_ = num_joints;
177 robot_model_ = robot_model;
178 cur_acceleration_ = Eigen::VectorXd::Zero(num_joints);
179
180 // get node parameters and store in member variables
181 auto param_listener = online_signal_smoothing::ParamListener(node_);
182 params_ = param_listener.get_params();
183
184 // get robot acceleration limits and store in member variables
185 auto joint_model_group = robot_model_->getJointModelGroup(params_.planning_group_name);
186 auto joint_bounds = joint_model_group->getActiveJointModelsBounds();
187 min_acceleration_limits_ = Eigen::VectorXd::Zero(num_joints);
188 max_acceleration_limits_ = Eigen::VectorXd::Zero(num_joints);
189 size_t ind = 0;
190 for (const auto& joint_bound : joint_bounds)
191 {
192 for (const auto& variable_bound : *joint_bound)
193 {
194 if (variable_bound.acceleration_bounded_)
195 {
196 min_acceleration_limits_[ind] = variable_bound.min_acceleration_;
197 max_acceleration_limits_[ind] = variable_bound.max_acceleration_;
198 }
199 else
200 {
201 RCLCPP_ERROR(getLogger(), "The robot must have acceleration joint limits specified for all joints to "
202 "use AccelerationLimitedPlugin.");
203 return false;
204 }
205 }
206 ind++;
207 }
208
209 // setup osqp optimization problem
210 Eigen::SparseMatrix<double> objective_sparse(1, 1);
211 objective_sparse.insert(0, 0) = 1.0;
212 size_t num_constraints = num_joints + 1;
213 constraints_sparse_ = Eigen::SparseMatrix<double>(num_constraints, 1);
214 for (size_t i = 0; i < num_constraints - 1; ++i)
215 {
216 constraints_sparse_.insert(i, 0) = 0;
217 }
218 constraints_sparse_.insert(num_constraints - 1, 0) = 0;
219 osqp_set_default_settings(&osqp_settings_);
220#if MOVEIT_OSQP_V1
221 // v1.0 renamed OSQPSettings.warm_start -> warm_starting.
222 osqp_settings_.warm_starting = 0;
223#else
224 osqp_settings_.warm_start = 0;
225#endif
226 osqp_settings_.verbose = 0;
227 osqp_data_ = std::make_shared<OSQPDataWrapper>(objective_sparse, constraints_sparse_);
228 osqp_data_->q[0] = 0;
229
230#if MOVEIT_OSQP_V1
231 // v1.0: no OSQPData aggregate — matrices/vectors go directly to osqp_setup.
232 if (osqp_setup(&osqp_solver_, &osqp_data_->P.csc_sparse_matrix, osqp_data_->q.data(), &osqp_data_->A.csc_sparse_matrix,
233 osqp_data_->l.data(), osqp_data_->u.data(), static_cast<OSQPInt>(osqp_data_->A.csc_sparse_matrix.m),
234 static_cast<OSQPInt>(osqp_data_->P.csc_sparse_matrix.n), &osqp_settings_) != 0)
235 {
236 osqp_settings_.verbose = 1;
237 // call setup again with verbose enabled to trigger error message printing
238 osqp_setup(&osqp_solver_, &osqp_data_->P.csc_sparse_matrix, osqp_data_->q.data(), &osqp_data_->A.csc_sparse_matrix,
239 osqp_data_->l.data(), osqp_data_->u.data(), static_cast<OSQPInt>(osqp_data_->A.csc_sparse_matrix.m),
240 static_cast<OSQPInt>(osqp_data_->P.csc_sparse_matrix.n), &osqp_settings_);
241 RCLCPP_ERROR(getLogger(), "Failed to initialize osqp problem.");
242 return false;
243 }
244#else
245 if (osqp_setup(&osqp_solver_, &osqp_data_->data, &osqp_settings_) != 0)
246 {
247 osqp_settings_.verbose = 1;
248 // call setup again with verbose enabled to trigger error message printing
249 osqp_setup(&osqp_solver_, &osqp_data_->data, &osqp_settings_);
250 RCLCPP_ERROR(getLogger(), "Failed to initialize osqp problem.");
251 return false;
252 }
253#endif
254
255 return true;
256}
257
258double jointLimitAccelerationScalingFactor(const Eigen::VectorXd& accelerations,
259 const moveit::core::JointBoundsVector& joint_bounds)
260{
261 double min_scaling_factor = 1.0;
262
263 // Now get the scaling factor from joint limits.
264 size_t idx = 0;
265 for (const auto& joint_bound : joint_bounds)
266 {
267 for (const auto& variable_bound : *joint_bound)
268 {
269 const auto& target_accel = accelerations(idx);
270 if (variable_bound.acceleration_bounded_ && target_accel != 0.0)
271 {
272 // Find the ratio of clamped acceleration to original acceleration
273 const auto bounded_vel =
274 std::clamp(target_accel, variable_bound.min_acceleration_, variable_bound.max_acceleration_);
275 double joint_scaling_factor = bounded_vel / target_accel;
276 min_scaling_factor = std::min(min_scaling_factor, joint_scaling_factor);
277 }
278 ++idx;
279 }
280 }
281
282 return min_scaling_factor;
283}
284
285#if MOVEIT_OSQP_V1
286inline bool updateData(const OSQPDataWrapperPtr& data, OSQPSolver* solver,
287 Eigen::SparseMatrix<double>& constraints_sparse, const Eigen::VectorXd& lower_bound,
288 const Eigen::VectorXd& upper_bound)
289#else
290inline bool updateData(const OSQPDataWrapperPtr& data, OSQPWorkspace* solver,
291 Eigen::SparseMatrix<double>& constraints_sparse, const Eigen::VectorXd& lower_bound,
292 const Eigen::VectorXd& upper_bound)
293#endif
294{
295 data->updateA(solver, constraints_sparse);
296 size_t num_constraints = constraints_sparse.rows();
297 data->u.block(0, 0, num_constraints - 1, 1) = upper_bound;
298 data->l.block(0, 0, num_constraints - 1, 1) = lower_bound;
299 data->u[num_constraints - 1] = ALPHA_UPPER_BOUND;
300 data->l[num_constraints - 1] = ALPHA_LOWER_BOUND;
301#if MOVEIT_OSQP_V1
302 // v1.0 replaces osqp_update_bounds; nullptr for q means "do not update q."
303 return 0 == osqp_update_data_vec(solver, nullptr, data->l.data(), data->u.data());
304#else
305 return 0 == osqp_update_bounds(solver, data->l.data(), data->u.data());
306#endif
307}
308
309bool AccelerationLimitedPlugin::doSmoothing(Eigen::VectorXd& positions, Eigen::VectorXd& velocities,
310 Eigen::VectorXd& /* unused */)
311{
312 const size_t num_positions = velocities.size();
313 if (num_positions != num_joints_)
314 {
315 RCLCPP_ERROR_THROTTLE(
316 getLogger(), *node_->get_clock(), 1000,
317 "The length of the joint positions parameter is not equal to the number of joints, expected %zu got %zu.",
318 num_joints_, num_positions);
319 return false;
320 }
321 else if (last_positions_.size() != positions.size())
322 {
323 RCLCPP_ERROR_THROTTLE(getLogger(), *node_->get_clock(), 1000,
324 "The length of the last joint positions not equal to the current, expected %zu got %zu. Make "
325 "sure the reset was called.",
326 last_positions_.size(), positions.size());
327 return false;
328 }
329
330 // formulate a quadratic program to find the best new reference point subject to the robot's acceleration limits
331 // p_c: robot's current position
332 // v_c: robot's current velocity
333 // p_t: robot's target position
334 // acc: acceleration to be applied
335 // p_n: next position
336 // dt: time step
337 // p_n_hat: parameterize solution to be along the line from p_c to p_t
338 // p_n_hat = p_t*alpha + p_c*(1-alpha)
339 // define constraints
340 // p_c + v_c*dt + acc_min*dt^2 < p_n_hat < p_c + v_c*dt + acc_max*dt^2
341 // p_c + v_c*dt -p_t + acc_min*dt^2 < (p_c-p_t)alpha < p_c + v_c*dt -p_t + acc_max*dt^2
342 // 0 < alpha < 1
343 // define optimization
344 // opt ||alpha||
345 // s.t. constraints
346 // p_n = p_t*alpha + p_c*(1-alpha)
347
348 double& update_period = params_.update_period;
349 size_t num_constraints = num_joints_ + 1;
350 positions_offset_ = last_positions_ - positions;
351 velocities_offset_ = last_velocities_ - velocities;
352 for (size_t i = 0; i < num_constraints - 1; ++i)
353 {
354 constraints_sparse_.coeffRef(i, 0) = positions_offset_[i];
355 }
356 constraints_sparse_.coeffRef(num_constraints - 1, 0) = 1;
357 Eigen::VectorXd vel_point = last_positions_ + last_velocities_ * update_period;
358 Eigen::VectorXd upper_bound = vel_point - positions + max_acceleration_limits_ * (update_period * update_period);
359 Eigen::VectorXd lower_bound = vel_point - positions + min_acceleration_limits_ * (update_period * update_period);
360 if (!updateData(osqp_data_, osqp_solver_, constraints_sparse_, lower_bound, upper_bound))
361 {
362 RCLCPP_ERROR_THROTTLE(getLogger(), *node_->get_clock(), 1000,
363 "failed to set osqp constraint bounds. Make sure the robot's acceleration limits are valid");
364 return false;
365 }
366
367 if (positions_offset_.norm() < COMMAND_DIFFERENCE_THRESHOLD &&
368 velocities_offset_.norm() < COMMAND_DIFFERENCE_THRESHOLD)
369 {
370 positions = last_positions_;
371 velocities = last_velocities_;
372 }
373 else if (osqp_solve(osqp_solver_) == 0 &&
374 osqp_solver_->solution->x[0] >= ALPHA_LOWER_BOUND - osqp_settings_.eps_abs &&
375 osqp_solver_->solution->x[0] <= ALPHA_UPPER_BOUND + osqp_settings_.eps_abs)
376 {
377 double alpha = osqp_solver_->solution->x[0];
378 positions = alpha * last_positions_ + (1.0 - alpha) * positions.eval();
379 velocities = (positions - last_positions_) / update_period;
380 }
381 else
382 {
383 auto joint_model_group = robot_model_->getJointModelGroup(params_.planning_group_name);
384 auto joint_bounds = joint_model_group->getActiveJointModelsBounds();
385 cur_acceleration_ = -(last_velocities_) / update_period;
386 cur_acceleration_ *= jointLimitAccelerationScalingFactor(cur_acceleration_, joint_bounds);
387 velocities = last_velocities_ + cur_acceleration_ * update_period;
388 positions = last_positions_ + velocities * update_period;
389 }
390
391 last_velocities_ = velocities;
392 last_positions_ = positions;
393
394 return true;
395}
396
397bool AccelerationLimitedPlugin::reset(const Eigen::VectorXd& positions, const Eigen::VectorXd& velocities,
398 const Eigen::VectorXd& /* unused */)
399{
400 last_velocities_ = velocities;
401 last_positions_ = positions;
402 cur_acceleration_ = Eigen::VectorXd::Zero(num_joints_);
403
404 return true;
405}
406
407} // namespace online_signal_smoothing
408
409#include <pluginlib/class_list_macros.hpp>
PLUGINLIB_EXPORT_CLASS(cached_ik_kinematics_plugin::CachedIKKinematicsPlugin< kdl_kinematics_plugin::KDLKinematicsPlugin >, kinematics::KinematicsBase)
#define MOVEIT_STRUCT_FORWARD(C)
bool initialize(rclcpp::Node::SharedPtr node, moveit::core::RobotModelConstPtr robot_model, size_t num_joints) override
bool reset(const Eigen::VectorXd &positions, const Eigen::VectorXd &velocities, const Eigen::VectorXd &accelerations) override
bool doSmoothing(Eigen::VectorXd &positions, Eigen::VectorXd &velocities, Eigen::VectorXd &accelerations) override
std::vector< const JointModel::Bounds * > JointBoundsVector
rclcpp::Logger getLogger(const std::string &name)
Creates a namespaced logger.
Definition logger.cpp:79
double jointLimitAccelerationScalingFactor(const Eigen::VectorXd &accelerations, const moveit::core::JointBoundsVector &joint_bounds)
bool updateData(const OSQPDataWrapperPtr &data, OSQPWorkspace *solver, Eigen::SparseMatrix< double > &constraints_sparse, const Eigen::VectorXd &lower_bound, const Eigen::VectorXd &upper_bound)
constexpr double COMMAND_DIFFERENCE_THRESHOLD
Wrapper struct to make memory management easier for using osqp's C sparse_matrix types.
OSQPCscMatrix csc_sparse_matrix
osqp C sparse_matrix type
std::vector< double > elements
holds the non-zero values in Compressed Sparse Column (CSC) form
std::vector< OSQPInt > column_pointers
column pointers (size n+1); col indices (size nzmax)
void update(Eigen::SparseMatrix< double > &M)
Update the the data point to by sparse_matrix without reallocating memory.
CSCWrapper(Eigen::SparseMatrix< double > &M)
std::vector< OSQPInt > row_indices
row indices, size nzmax starting from 0
void updateA(OSQPWorkspace *work, Eigen::SparseMatrix< double > &constraints_sparse)
Update the constraint matrix A without reallocating memory.
OSQPDataWrapper(Eigen::SparseMatrix< double > &objective_sparse, Eigen::SparseMatrix< double > &constraints_sparse)