moveit2
The MoveIt Motion Planning Framework for ROS 2.
time_optimal_trajectory_generation.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2011, Georgia Tech Research Corporation
3  * All rights reserved.
4  *
5  * Author: Tobias Kunz <tobias@gatech.edu>
6  * Date: 05/2012
7  *
8  * Humanoid Robotics Lab Georgia Institute of Technology
9  * Director: Mike Stilman http://www.golems.org
10  *
11  * Algorithm details and publications:
12  * http://www.golems.org/node/1570
13  *
14  * This file is provided under the following "BSD-style" License:
15  * Redistribution and use in source and binary forms, with or
16  * without modification, are permitted provided that the following
17  * conditions are met:
18  * * Redistributions of source code must retain the above copyright
19  * notice, this list of conditions and the following disclaimer.
20  * * Redistributions in binary form must reproduce the above
21  * copyright notice, this list of conditions and the following
22  * disclaimer in the documentation and/or other materials provided
23  * with the distribution.
24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
25  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
26  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
27  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
28  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
29  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
32  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
33  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
35  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  */
38 
39 #include <rclcpp/logger.hpp>
40 #include <rclcpp/logging.hpp>
41 #include <limits>
42 #include <Eigen/Geometry>
43 #include <algorithm>
44 #include <cmath>
46 #include <vector>
47 #include <moveit/utils/logger.hpp>
48 
49 namespace trajectory_processing
50 {
51 namespace
52 {
53 constexpr double DEFAULT_TIMESTEP = 1e-3;
54 constexpr double EPS = 1e-6;
55 constexpr double DEFAULT_SCALING_FACTOR = 1.0;
56 
57 rclcpp::Logger getLogger()
58 {
59  return moveit::getLogger("time_optimal_trajectory_generation");
60 }
61 } // namespace
62 
64 {
65 public:
66  LinearPathSegment(const Eigen::VectorXd& start, const Eigen::VectorXd& end)
67  : PathSegment((end - start).norm()), end_(end), start_(start)
68  {
69  }
70 
71  Eigen::VectorXd getConfig(double s) const override
72  {
73  s /= length_;
74  s = std::max(0.0, std::min(1.0, s));
75  return (1.0 - s) * start_ + s * end_;
76  }
77 
78  Eigen::VectorXd getTangent(double /* s */) const override
79  {
80  return (end_ - start_) / length_;
81  }
82 
83  Eigen::VectorXd getCurvature(double /* s */) const override
84  {
85  return Eigen::VectorXd::Zero(start_.size());
86  }
87 
88  std::list<double> getSwitchingPoints() const override
89  {
90  return std::list<double>();
91  }
92 
93  LinearPathSegment* clone() const override
94  {
95  return new LinearPathSegment(*this);
96  }
97 
98 private:
99  Eigen::VectorXd end_;
100  Eigen::VectorXd start_;
101 };
102 
104 {
105 public:
106  CircularPathSegment(const Eigen::VectorXd& start, const Eigen::VectorXd& intersection, const Eigen::VectorXd& end,
107  double max_deviation)
108  {
109  if ((intersection - start).norm() < 0.000001 || (end - intersection).norm() < 0.000001)
110  {
111  length_ = 0.0;
112  radius_ = 1.0;
113  center_ = intersection;
114  x_ = Eigen::VectorXd::Zero(start.size());
115  y_ = Eigen::VectorXd::Zero(start.size());
116  return;
117  }
118 
119  const Eigen::VectorXd start_direction = (intersection - start).normalized();
120  const Eigen::VectorXd end_direction = (end - intersection).normalized();
121  const double start_dot_end = start_direction.dot(end_direction);
122 
123  // catch division by 0 in computations below
124  if (start_dot_end > 0.999999 || start_dot_end < -0.999999)
125  {
126  length_ = 0.0;
127  radius_ = 1.0;
128  center_ = intersection;
129  x_ = Eigen::VectorXd::Zero(start.size());
130  y_ = Eigen::VectorXd::Zero(start.size());
131  return;
132  }
133 
134  const double angle = acos(start_dot_end);
135  const double start_distance = (start - intersection).norm();
136  const double end_distance = (end - intersection).norm();
137 
138  // enforce max deviation
139  double distance = std::min(start_distance, end_distance);
140  distance = std::min(distance, max_deviation * sin(0.5 * angle) / (1.0 - cos(0.5 * angle)));
141 
142  radius_ = distance / tan(0.5 * angle);
143  length_ = angle * radius_;
144 
145  center_ = intersection + (end_direction - start_direction).normalized() * radius_ / cos(0.5 * angle);
146  x_ = (intersection - distance * start_direction - center_).normalized();
147  y_ = start_direction;
148  }
149 
150  Eigen::VectorXd getConfig(double s) const override
151  {
152  const double angle = s / radius_;
153  return center_ + radius_ * (x_ * cos(angle) + y_ * sin(angle));
154  }
155 
156  Eigen::VectorXd getTangent(double s) const override
157  {
158  const double angle = s / radius_;
159  return -x_ * sin(angle) + y_ * cos(angle);
160  }
161 
162  Eigen::VectorXd getCurvature(double s) const override
163  {
164  const double angle = s / radius_;
165  return -1.0 / radius_ * (x_ * cos(angle) + y_ * sin(angle));
166  }
167 
168  std::list<double> getSwitchingPoints() const override
169  {
170  std::list<double> switching_points;
171  const double dim = x_.size();
172  for (unsigned int i = 0; i < dim; ++i)
173  {
174  double switching_angle = atan2(y_[i], x_[i]);
175  if (switching_angle < 0.0)
176  {
177  switching_angle += M_PI;
178  }
179  const double switching_point = switching_angle * radius_;
180  if (switching_point < length_)
181  {
182  switching_points.push_back(switching_point);
183  }
184  }
185  switching_points.sort();
186  return switching_points;
187  }
188 
189  CircularPathSegment* clone() const override
190  {
191  return new CircularPathSegment(*this);
192  }
193 
194 private:
195  double radius_;
196  Eigen::VectorXd center_;
197  Eigen::VectorXd x_;
198  Eigen::VectorXd y_;
199 };
200 
201 std::optional<Path> Path::create(const std::vector<Eigen::VectorXd>& waypoints, double max_deviation)
202 {
203  if (waypoints.size() < 2)
204  {
205  RCLCPP_ERROR(getLogger(), "A path needs at least 2 waypoints.");
206  return std::nullopt;
207  }
208  if (max_deviation <= 0.0)
209  {
210  RCLCPP_ERROR(getLogger(), "Path max_deviation must be greater than 0.0.");
211  return std::nullopt;
212  }
213 
214  // waypoints_iterator1, waypoints_iterator2 and waypoints_iterator3 point to three consecutive waypoints of the input
215  // path. The algorithm creates a LinearPathSegment starting at waypoints_iterator1, connected to CircularPathSegment
216  // at waypoints_iterator2, connected to another LinearPathSegment towards waypoints_iterator3.
217  // It does this iteratively for each three consecutive waypoints, therefore applying a blending of 'max_deviation' at
218  // the intermediate waypoints.
219  Path path;
220  std::vector<Eigen::VectorXd>::const_iterator waypoints_iterator1 = waypoints.begin();
221  std::vector<Eigen::VectorXd>::const_iterator waypoints_iterator2 = waypoints_iterator1;
222  ++waypoints_iterator2;
223  std::vector<Eigen::VectorXd>::const_iterator waypoints_iterator3;
224  Eigen::VectorXd start_config = *waypoints_iterator1;
225  while (waypoints_iterator2 != waypoints.end())
226  {
227  waypoints_iterator3 = waypoints_iterator2;
228  ++waypoints_iterator3;
229  if (waypoints_iterator3 != waypoints.end())
230  {
231  // Check that the path is not making a 180 deg. turn, which is not supported by the current implementation.
232  Eigen::VectorXd incoming_vector = *waypoints_iterator2 - *waypoints_iterator1;
233  Eigen::VectorXd outcoming_vector = *waypoints_iterator3 - *waypoints_iterator2;
234  double incoming_vector_norm = incoming_vector.norm();
235  double outcoming_vector_norm = outcoming_vector.norm();
236  if (incoming_vector_norm > std::numeric_limits<double>::epsilon() &&
237  outcoming_vector_norm > std::numeric_limits<double>::epsilon())
238  {
239  double cos_angle = incoming_vector.dot(outcoming_vector) / (incoming_vector_norm * outcoming_vector_norm);
240  constexpr double angle_tolerance = 1e-05;
241  if (cos_angle <= -1.0 + angle_tolerance)
242  {
243  RCLCPP_ERROR(getLogger(),
244  "The path requires a 180 deg. turn, which is not supported by the current implementation.");
245  return std::nullopt;
246  }
247  }
248  }
249  if (max_deviation > 0.0 && waypoints_iterator3 != waypoints.end())
250  {
251  CircularPathSegment* blend_segment =
252  new CircularPathSegment(0.5 * (*waypoints_iterator1 + *waypoints_iterator2), *waypoints_iterator2,
253  0.5 * (*waypoints_iterator2 + *waypoints_iterator3), max_deviation);
254  Eigen::VectorXd end_config = blend_segment->getConfig(0.0);
255  if ((end_config - start_config).norm() > 0.000001)
256  {
257  path.path_segments_.push_back(std::make_unique<LinearPathSegment>(start_config, end_config));
258  }
259  path.path_segments_.emplace_back(blend_segment);
260 
261  start_config = blend_segment->getConfig(blend_segment->getLength());
262  }
263  else
264  {
265  path.path_segments_.push_back(std::make_unique<LinearPathSegment>(start_config, *waypoints_iterator2));
266  start_config = *waypoints_iterator2;
267  }
268  waypoints_iterator1 = waypoints_iterator2;
269  ++waypoints_iterator2;
270  }
271 
272  // Create list of switching point candidates, calculate total path length and
273  // absolute positions of path segments
274  for (std::unique_ptr<PathSegment>& path_segment : path.path_segments_)
275  {
276  path_segment->position_ = path.length_;
277  std::list<double> local_switching_points = path_segment->getSwitchingPoints();
278  for (std::list<double>::const_iterator point = local_switching_points.begin();
279  point != local_switching_points.end(); ++point)
280  {
281  path.switching_points_.push_back(std::make_pair(path.length_ + *point, false));
282  }
283  path.length_ += path_segment->getLength();
284  while (!path.switching_points_.empty() && path.switching_points_.back().first >= path.length_)
285  path.switching_points_.pop_back();
286  path.switching_points_.push_back(std::make_pair(path.length_, true));
287  }
288  path.switching_points_.pop_back();
289  return path;
290 }
291 
292 Path::Path(const Path& path) : length_(path.length_), switching_points_(path.switching_points_)
293 {
294  for (const std::unique_ptr<PathSegment>& path_segment : path.path_segments_)
295  {
296  path_segments_.emplace_back(path_segment->clone());
297  }
298 }
299 
300 double Path::getLength() const
301 {
302  return length_;
303 }
304 
305 PathSegment* Path::getPathSegment(double& s) const
306 {
307  std::list<std::unique_ptr<PathSegment>>::const_iterator it = path_segments_.begin();
308  std::list<std::unique_ptr<PathSegment>>::const_iterator next = it;
309  ++next;
310  while (next != path_segments_.end() && s >= (*next)->position_)
311  {
312  it = next;
313  ++next;
314  }
315  s -= (*it)->position_;
316  return (*it).get();
317 }
318 
319 Eigen::VectorXd Path::getConfig(double s) const
320 {
321  const PathSegment* path_segment = getPathSegment(s);
322  return path_segment->getConfig(s);
323 }
324 
325 Eigen::VectorXd Path::getTangent(double s) const
326 {
327  const PathSegment* path_segment = getPathSegment(s);
328  return path_segment->getTangent(s);
329 }
330 
331 Eigen::VectorXd Path::getCurvature(double s) const
332 {
333  const PathSegment* path_segment = getPathSegment(s);
334  return path_segment->getCurvature(s);
335 }
336 
337 double Path::getNextSwitchingPoint(double s, bool& discontinuity) const
338 {
339  std::list<std::pair<double, bool>>::const_iterator it = switching_points_.begin();
340  while (it != switching_points_.end() && it->first <= s)
341  {
342  ++it;
343  }
344  if (it == switching_points_.end())
345  {
346  discontinuity = true;
347  return length_;
348  }
349  discontinuity = it->second;
350  return it->first;
351 }
352 
353 std::list<std::pair<double, bool>> Path::getSwitchingPoints() const
354 {
355  return switching_points_;
356 }
357 
358 std::optional<Trajectory> Trajectory::create(const Path& path, const Eigen::VectorXd& max_velocity,
359  const Eigen::VectorXd& max_acceleration, double time_step)
360 {
361  if (time_step <= 0)
362  {
363  RCLCPP_ERROR(getLogger(), "The trajectory is invalid because the time step is <= 0.0.");
364  return std::nullopt;
365  }
366 
367  Trajectory output(path, max_velocity, max_acceleration, time_step);
368  output.trajectory_.push_back(TrajectoryStep(0.0, 0.0));
369  double after_acceleration = output.getMinMaxPathAcceleration(0.0, 0.0, true);
370  while (output.valid_ && !output.integrateForward(output.trajectory_, after_acceleration) && output.valid_)
371  {
372  double before_acceleration;
373  TrajectoryStep switching_point;
374  if (output.getNextSwitchingPoint(output.trajectory_.back().path_pos_, switching_point, before_acceleration,
375  after_acceleration))
376  {
377  break;
378  }
379  output.integrateBackward(output.trajectory_, switching_point.path_pos_, switching_point.path_vel_,
380  before_acceleration);
381  }
382 
383  if (!output.valid_)
384  {
385  RCLCPP_ERROR(getLogger(), "Trajectory not valid after integrateForward and integrateBackward.");
386  return std::nullopt;
387  }
388 
389  double before_acceleration = output.getMinMaxPathAcceleration(output.path_.getLength(), 0.0, false);
390  output.integrateBackward(output.trajectory_, output.path_.getLength(), 0.0, before_acceleration);
391 
392  if (!output.valid_)
393  {
394  RCLCPP_ERROR(getLogger(), "Trajectory not valid after the second integrateBackward pass.");
395  return std::nullopt;
396  }
397 
398  // Calculate timing.
399  std::list<TrajectoryStep>::iterator previous = output.trajectory_.begin();
400  std::list<TrajectoryStep>::iterator it = previous;
401  it->time_ = 0.0;
402  ++it;
403  while (it != output.trajectory_.end())
404  {
405  it->time_ = previous->time_ + (it->path_pos_ - previous->path_pos_) / ((it->path_vel_ + previous->path_vel_) / 2.0);
406  previous = it;
407  ++it;
408  }
409 
410  return output;
411 }
412 
413 Trajectory::Trajectory(const Path& path, const Eigen::VectorXd& max_velocity, const Eigen::VectorXd& max_acceleration,
414  double time_step)
415  : path_(path), max_velocity_(max_velocity), max_acceleration_(max_acceleration), time_step_(time_step)
416 {
417  joint_num_ = max_velocity.size();
418 }
419 
420 // Returns true if end of path is reached.
421 bool Trajectory::getNextSwitchingPoint(double path_pos, TrajectoryStep& next_switching_point,
422  double& before_acceleration, double& after_acceleration)
423 {
424  TrajectoryStep acceleration_switching_point(path_pos, 0.0);
425  double acceleration_before_acceleration, acceleration_after_acceleration;
426  bool acceleration_reached_end;
427  do
428  {
429  acceleration_reached_end =
430  getNextAccelerationSwitchingPoint(acceleration_switching_point.path_pos_, acceleration_switching_point,
431  acceleration_before_acceleration, acceleration_after_acceleration);
432  } while (!acceleration_reached_end &&
433  acceleration_switching_point.path_vel_ > getVelocityMaxPathVelocity(acceleration_switching_point.path_pos_));
434 
435  TrajectoryStep velocity_switching_point(path_pos, 0.0);
436  double velocity_before_acceleration, velocity_after_acceleration;
437  bool velocity_reached_end;
438  do
439  {
440  velocity_reached_end = getNextVelocitySwitchingPoint(velocity_switching_point.path_pos_, velocity_switching_point,
441  velocity_before_acceleration, velocity_after_acceleration);
442  } while (
443  !velocity_reached_end && velocity_switching_point.path_pos_ <= acceleration_switching_point.path_pos_ &&
444  (velocity_switching_point.path_vel_ > getAccelerationMaxPathVelocity(velocity_switching_point.path_pos_ - EPS) ||
445  velocity_switching_point.path_vel_ > getAccelerationMaxPathVelocity(velocity_switching_point.path_pos_ + EPS)));
446 
447  if (acceleration_reached_end && velocity_reached_end)
448  {
449  return true;
450  }
451  else if (!acceleration_reached_end &&
452  (velocity_reached_end || acceleration_switching_point.path_pos_ <= velocity_switching_point.path_pos_))
453  {
454  next_switching_point = acceleration_switching_point;
455  before_acceleration = acceleration_before_acceleration;
456  after_acceleration = acceleration_after_acceleration;
457  return false;
458  }
459  else
460  {
461  next_switching_point = velocity_switching_point;
462  before_acceleration = velocity_before_acceleration;
463  after_acceleration = velocity_after_acceleration;
464  return false;
465  }
466 }
467 
468 bool Trajectory::getNextAccelerationSwitchingPoint(double path_pos, TrajectoryStep& next_switching_point,
469  double& before_acceleration, double& after_acceleration)
470 {
471  double switching_path_pos = path_pos;
472  double switching_path_vel;
473  while (true)
474  {
475  bool discontinuity;
476  switching_path_pos = path_.getNextSwitchingPoint(switching_path_pos, discontinuity);
477 
478  if (switching_path_pos > path_.getLength() - EPS)
479  {
480  return true;
481  }
482 
483  if (discontinuity)
484  {
485  const double before_path_vel = getAccelerationMaxPathVelocity(switching_path_pos - EPS);
486  const double after_path_vel = getAccelerationMaxPathVelocity(switching_path_pos + EPS);
487  switching_path_vel = std::min(before_path_vel, after_path_vel);
488  before_acceleration = getMinMaxPathAcceleration(switching_path_pos - EPS, switching_path_vel, false);
489  after_acceleration = getMinMaxPathAcceleration(switching_path_pos + EPS, switching_path_vel, true);
490 
491  if ((before_path_vel > after_path_vel ||
492  getMinMaxPhaseSlope(switching_path_pos - EPS, switching_path_vel, false) >
493  getAccelerationMaxPathVelocityDeriv(switching_path_pos - 2.0 * EPS)) &&
494  (before_path_vel < after_path_vel || getMinMaxPhaseSlope(switching_path_pos + EPS, switching_path_vel, true) <
495  getAccelerationMaxPathVelocityDeriv(switching_path_pos + 2.0 * EPS)))
496  {
497  break;
498  }
499  }
500  else
501  {
502  switching_path_vel = getAccelerationMaxPathVelocity(switching_path_pos);
503  before_acceleration = 0.0;
504  after_acceleration = 0.0;
505 
506  if (getAccelerationMaxPathVelocityDeriv(switching_path_pos - EPS) < 0.0 &&
507  getAccelerationMaxPathVelocityDeriv(switching_path_pos + EPS) > 0.0)
508  {
509  break;
510  }
511  }
512  }
513 
514  next_switching_point = TrajectoryStep(switching_path_pos, switching_path_vel);
515  return false;
516 }
517 
518 bool Trajectory::getNextVelocitySwitchingPoint(double path_pos, TrajectoryStep& next_switching_point,
519  double& before_acceleration, double& after_acceleration)
520 {
521  bool start = false;
522  path_pos -= DEFAULT_TIMESTEP;
523  do
524  {
525  path_pos += DEFAULT_TIMESTEP;
526 
527  if (getMinMaxPhaseSlope(path_pos, getVelocityMaxPathVelocity(path_pos), false) >=
528  getVelocityMaxPathVelocityDeriv(path_pos))
529  {
530  start = true;
531  }
532  } while ((!start || getMinMaxPhaseSlope(path_pos, getVelocityMaxPathVelocity(path_pos), false) >
533  getVelocityMaxPathVelocityDeriv(path_pos)) &&
534  path_pos < path_.getLength());
535 
536  if (path_pos >= path_.getLength())
537  {
538  return true; // end of trajectory reached
539  }
540 
541  double before_path_pos = path_pos - DEFAULT_TIMESTEP;
542  double after_path_pos = path_pos;
543  while (after_path_pos - before_path_pos > EPS)
544  {
545  path_pos = (before_path_pos + after_path_pos) / 2.0;
546  if (getMinMaxPhaseSlope(path_pos, getVelocityMaxPathVelocity(path_pos), false) >
547  getVelocityMaxPathVelocityDeriv(path_pos))
548  {
549  before_path_pos = path_pos;
550  }
551  else
552  {
553  after_path_pos = path_pos;
554  }
555  }
556 
557  before_acceleration = getMinMaxPathAcceleration(before_path_pos, getVelocityMaxPathVelocity(before_path_pos), false);
558  after_acceleration = getMinMaxPathAcceleration(after_path_pos, getVelocityMaxPathVelocity(after_path_pos), true);
559  next_switching_point = TrajectoryStep(after_path_pos, getVelocityMaxPathVelocity(after_path_pos));
560  return false;
561 }
562 
563 // Returns true if end of path is reached
564 bool Trajectory::integrateForward(std::list<TrajectoryStep>& trajectory, double acceleration)
565 {
566  double path_pos = trajectory.back().path_pos_;
567  double path_vel = trajectory.back().path_vel_;
568 
569  std::list<std::pair<double, bool>> switching_points = path_.getSwitchingPoints();
570  std::list<std::pair<double, bool>>::iterator next_discontinuity = switching_points.begin();
571 
572  while (true)
573  {
574  while ((next_discontinuity != switching_points.end()) &&
575  (next_discontinuity->first <= path_pos || !next_discontinuity->second))
576  {
577  ++next_discontinuity;
578  }
579 
580  double old_path_pos = path_pos;
581  double old_path_vel = path_vel;
582 
583  path_vel += time_step_ * acceleration;
584  path_pos += time_step_ * 0.5 * (old_path_vel + path_vel);
585 
586  if (next_discontinuity != switching_points.end() && path_pos > next_discontinuity->first)
587  {
588  // Avoid having a TrajectoryStep with path_pos near a switching point which will cause an almost identical
589  // TrajectoryStep get added in the next run (https://github.com/ros-planning/moveit/issues/1665)
590  if (path_pos - next_discontinuity->first < EPS)
591  {
592  continue;
593  }
594  path_vel = old_path_vel +
595  (next_discontinuity->first - old_path_pos) * (path_vel - old_path_vel) / (path_pos - old_path_pos);
596  path_pos = next_discontinuity->first;
597  }
598 
599  if (path_pos > path_.getLength())
600  {
601  trajectory.push_back(TrajectoryStep(path_pos, path_vel));
602  return true;
603  }
604  else if (path_vel < 0.0)
605  {
606  valid_ = false;
607  RCLCPP_ERROR(getLogger(), "Error while integrating forward: Negative path velocity");
608  return true;
609  }
610 
611  if (path_vel > getVelocityMaxPathVelocity(path_pos) &&
612  getMinMaxPhaseSlope(old_path_pos, getVelocityMaxPathVelocity(old_path_pos), false) <=
613  getVelocityMaxPathVelocityDeriv(old_path_pos))
614  {
615  path_vel = getVelocityMaxPathVelocity(path_pos);
616  }
617 
618  trajectory.push_back(TrajectoryStep(path_pos, path_vel));
619  acceleration = getMinMaxPathAcceleration(path_pos, path_vel, true);
620 
621  if (path_vel == 0 && acceleration == 0)
622  {
623  // The position will never change if velocity and acceleration are zero.
624  // The loop will spin indefinitely as no exit condition is met.
625  valid_ = false;
626  RCLCPP_ERROR(getLogger(), "Error while integrating forward: zero acceleration and velocity. Are any relevant "
627  "acceleration components limited to zero?");
628  return true;
629  }
630 
631  if (path_vel > getAccelerationMaxPathVelocity(path_pos) || path_vel > getVelocityMaxPathVelocity(path_pos))
632  {
633  // Find more accurate intersection with max-velocity curve using bisection
634  TrajectoryStep overshoot = trajectory.back();
635  trajectory.pop_back();
636  double before = trajectory.back().path_pos_;
637  double before_path_vel = trajectory.back().path_vel_;
638  double after = overshoot.path_pos_;
639  double after_path_vel = overshoot.path_vel_;
640  while (after - before > EPS)
641  {
642  const double midpoint = 0.5 * (before + after);
643  double midpoint_path_vel = 0.5 * (before_path_vel + after_path_vel);
644 
645  if (midpoint_path_vel > getVelocityMaxPathVelocity(midpoint) &&
646  getMinMaxPhaseSlope(before, getVelocityMaxPathVelocity(before), false) <=
647  getVelocityMaxPathVelocityDeriv(before))
648  {
649  midpoint_path_vel = getVelocityMaxPathVelocity(midpoint);
650  }
651 
652  if (midpoint_path_vel > getAccelerationMaxPathVelocity(midpoint) ||
653  midpoint_path_vel > getVelocityMaxPathVelocity(midpoint))
654  {
655  after = midpoint;
656  after_path_vel = midpoint_path_vel;
657  }
658  else
659  {
660  before = midpoint;
661  before_path_vel = midpoint_path_vel;
662  }
663  }
664  trajectory.push_back(TrajectoryStep(before, before_path_vel));
665 
666  if (getAccelerationMaxPathVelocity(after) < getVelocityMaxPathVelocity(after))
667  {
668  if (after > next_discontinuity->first)
669  {
670  return false;
671  }
672  else if (getMinMaxPhaseSlope(trajectory.back().path_pos_, trajectory.back().path_vel_, true) >
673  getAccelerationMaxPathVelocityDeriv(trajectory.back().path_pos_))
674  {
675  return false;
676  }
677  }
678  else
679  {
680  if (getMinMaxPhaseSlope(trajectory.back().path_pos_, trajectory_.back().path_vel_, false) >
681  getVelocityMaxPathVelocityDeriv(trajectory_.back().path_pos_))
682  {
683  return false;
684  }
685  }
686  }
687  }
688 }
689 
690 void Trajectory::integrateBackward(std::list<TrajectoryStep>& start_trajectory, double path_pos, double path_vel,
691  double acceleration)
692 {
693  std::list<TrajectoryStep>::iterator start2 = start_trajectory.end();
694  --start2;
695  std::list<TrajectoryStep>::iterator start1 = start2;
696  --start1;
697  std::list<TrajectoryStep> trajectory;
698  double slope;
699  assert(start1->path_pos_ <= path_pos);
700 
701  while (start1 != start_trajectory.begin() || path_pos >= 0.0)
702  {
703  if (start1->path_pos_ <= path_pos)
704  {
705  trajectory.push_front(TrajectoryStep(path_pos, path_vel));
706  path_vel -= time_step_ * acceleration;
707  path_pos -= time_step_ * 0.5 * (path_vel + trajectory.front().path_vel_);
708  acceleration = getMinMaxPathAcceleration(path_pos, path_vel, false);
709  slope = (trajectory.front().path_vel_ - path_vel) / (trajectory.front().path_pos_ - path_pos);
710 
711  if (path_vel < 0.0)
712  {
713  valid_ = false;
714  RCLCPP_ERROR(getLogger(), "Error while integrating backward: Negative path velocity");
715  end_trajectory_ = trajectory;
716  return;
717  }
718  }
719  else
720  {
721  --start1;
722  --start2;
723  }
724 
725  // Check for intersection between current start trajectory and backward
726  // trajectory segments
727  const double start_slope = (start2->path_vel_ - start1->path_vel_) / (start2->path_pos_ - start1->path_pos_);
728  const double intersection_path_pos =
729  (start1->path_vel_ - path_vel + slope * path_pos - start_slope * start1->path_pos_) / (slope - start_slope);
730  if (std::max(start1->path_pos_, path_pos) - EPS <= intersection_path_pos &&
731  intersection_path_pos <= EPS + std::min(start2->path_pos_, trajectory.front().path_pos_))
732  {
733  const double intersection_path_vel =
734  start1->path_vel_ + start_slope * (intersection_path_pos - start1->path_pos_);
735  start_trajectory.erase(start2, start_trajectory.end());
736  start_trajectory.push_back(TrajectoryStep(intersection_path_pos, intersection_path_vel));
737  start_trajectory.splice(start_trajectory.end(), trajectory);
738  return;
739  }
740  }
741 
742  valid_ = false;
743  RCLCPP_ERROR(getLogger(), "Error while integrating backward: Did not hit start trajectory");
744  end_trajectory_ = trajectory;
745 }
746 
747 double Trajectory::getMinMaxPathAcceleration(double path_pos, double path_vel, bool max)
748 {
749  Eigen::VectorXd config_deriv = path_.getTangent(path_pos);
750  Eigen::VectorXd config_deriv2 = path_.getCurvature(path_pos);
751  double factor = max ? 1.0 : -1.0;
752  double max_path_acceleration = std::numeric_limits<double>::max();
753  for (unsigned int i = 0; i < joint_num_; ++i)
754  {
755  if (config_deriv[i] != 0.0)
756  {
757  max_path_acceleration =
758  std::min(max_path_acceleration, max_acceleration_[i] / std::abs(config_deriv[i]) -
759  factor * config_deriv2[i] * path_vel * path_vel / config_deriv[i]);
760  }
761  }
762  return factor * max_path_acceleration;
763 }
764 
765 double Trajectory::getMinMaxPhaseSlope(double path_pos, double path_vel, bool max)
766 {
767  return getMinMaxPathAcceleration(path_pos, path_vel, max) / path_vel;
768 }
769 
770 double Trajectory::getAccelerationMaxPathVelocity(double path_pos) const
771 {
772  double max_path_velocity = std::numeric_limits<double>::infinity();
773  const Eigen::VectorXd config_deriv = path_.getTangent(path_pos);
774  const Eigen::VectorXd config_deriv2 = path_.getCurvature(path_pos);
775  for (unsigned int i = 0; i < joint_num_; ++i)
776  {
777  if (config_deriv[i] != 0.0)
778  {
779  for (unsigned int j = i + 1; j < joint_num_; ++j)
780  {
781  if (config_deriv[j] != 0.0)
782  {
783  double a_ij = config_deriv2[i] / config_deriv[i] - config_deriv2[j] / config_deriv[j];
784  if (a_ij != 0.0)
785  {
786  max_path_velocity = std::min(max_path_velocity, sqrt((max_acceleration_[i] / std::abs(config_deriv[i]) +
787  max_acceleration_[j] / std::abs(config_deriv[j])) /
788  std::abs(a_ij)));
789  }
790  }
791  }
792  }
793  else if (config_deriv2[i] != 0.0)
794  {
795  max_path_velocity = std::min(max_path_velocity, sqrt(max_acceleration_[i] / std::abs(config_deriv2[i])));
796  }
797  }
798  return max_path_velocity;
799 }
800 
801 double Trajectory::getVelocityMaxPathVelocity(double path_pos) const
802 {
803  const Eigen::VectorXd tangent = path_.getTangent(path_pos);
804  double max_path_velocity = std::numeric_limits<double>::max();
805  for (unsigned int i = 0; i < joint_num_; ++i)
806  {
807  max_path_velocity = std::min(max_path_velocity, max_velocity_[i] / std::abs(tangent[i]));
808  }
809  return max_path_velocity;
810 }
811 
812 double Trajectory::getAccelerationMaxPathVelocityDeriv(double path_pos)
813 {
814  return (getAccelerationMaxPathVelocity(path_pos + EPS) - getAccelerationMaxPathVelocity(path_pos - EPS)) /
815  (2.0 * EPS);
816 }
817 
818 double Trajectory::getVelocityMaxPathVelocityDeriv(double path_pos)
819 {
820  const Eigen::VectorXd tangent = path_.getTangent(path_pos);
821  double max_path_velocity = std::numeric_limits<double>::max();
822  unsigned int active_constraint;
823  for (unsigned int i = 0; i < joint_num_; ++i)
824  {
825  const double this_max_path_velocity = max_velocity_[i] / std::abs(tangent[i]);
826  if (this_max_path_velocity < max_path_velocity)
827  {
828  max_path_velocity = this_max_path_velocity;
829  active_constraint = i;
830  }
831  }
832  return -(max_velocity_[active_constraint] * path_.getCurvature(path_pos)[active_constraint]) /
833  (tangent[active_constraint] * std::abs(tangent[active_constraint]));
834 }
835 
837 {
838  return trajectory_.back().time_;
839 }
840 
841 std::list<Trajectory::TrajectoryStep>::const_iterator Trajectory::getTrajectorySegment(double time) const
842 {
843  if (time >= trajectory_.back().time_)
844  {
845  std::list<TrajectoryStep>::const_iterator last = trajectory_.end();
846  last--;
847  return last;
848  }
849  else
850  {
851  if (time < cached_time_)
852  {
853  cached_trajectory_segment_ = trajectory_.begin();
854  }
855  while (time >= cached_trajectory_segment_->time_)
856  {
857  ++cached_trajectory_segment_;
858  }
859  cached_time_ = time;
860  return cached_trajectory_segment_;
861  }
862 }
863 
864 Eigen::VectorXd Trajectory::getPosition(double time) const
865 {
866  std::list<TrajectoryStep>::const_iterator it = getTrajectorySegment(time);
867  std::list<TrajectoryStep>::const_iterator previous = it;
868  previous--;
869 
870  double time_step = it->time_ - previous->time_;
871  const double acceleration =
872  2.0 * (it->path_pos_ - previous->path_pos_ - time_step * previous->path_vel_) / (time_step * time_step);
873 
874  time_step = time - previous->time_;
875  const double path_pos =
876  previous->path_pos_ + time_step * previous->path_vel_ + 0.5 * time_step * time_step * acceleration;
877 
878  return path_.getConfig(path_pos);
879 }
880 
881 Eigen::VectorXd Trajectory::getVelocity(double time) const
882 {
883  std::list<TrajectoryStep>::const_iterator it = getTrajectorySegment(time);
884  std::list<TrajectoryStep>::const_iterator previous = it;
885  previous--;
886 
887  double time_step = it->time_ - previous->time_;
888  const double acceleration =
889  2.0 * (it->path_pos_ - previous->path_pos_ - time_step * previous->path_vel_) / (time_step * time_step);
890 
891  const double path_pos =
892  previous->path_pos_ + time_step * previous->path_vel_ + 0.5 * time_step * time_step * acceleration;
893  const double path_vel = previous->path_vel_ + time_step * acceleration;
894 
895  return path_.getTangent(path_pos) * path_vel;
896 }
897 
898 Eigen::VectorXd Trajectory::getAcceleration(double time) const
899 {
900  std::list<TrajectoryStep>::const_iterator it = getTrajectorySegment(time);
901  std::list<TrajectoryStep>::const_iterator previous = it;
902  previous--;
903 
904  double time_step = it->time_ - previous->time_;
905  const double acceleration =
906  2.0 * (it->path_pos_ - previous->path_pos_ - time_step * previous->path_vel_) / (time_step * time_step);
907 
908  const double path_pos =
909  previous->path_pos_ + time_step * previous->path_vel_ + 0.5 * time_step * time_step * acceleration;
910  const double path_vel = previous->path_vel_ + time_step * acceleration;
911  Eigen::VectorXd path_acc =
912  (path_.getTangent(path_pos) * path_vel - path_.getTangent(previous->path_pos_) * previous->path_vel_);
913  if (time_step > 0.0)
914  path_acc /= time_step;
915  return path_acc;
916 }
917 
918 TimeOptimalTrajectoryGeneration::TimeOptimalTrajectoryGeneration(const double path_tolerance, const double resample_dt,
919  const double min_angle_change)
920  : path_tolerance_(path_tolerance), resample_dt_(resample_dt), min_angle_change_(min_angle_change)
921 {
922 }
923 
925  const double max_velocity_scaling_factor,
926  const double max_acceleration_scaling_factor) const
927 {
928  if (trajectory.empty())
929  return true;
930 
931  const moveit::core::JointModelGroup* group = trajectory.getGroup();
932  if (!group)
933  {
934  RCLCPP_ERROR(getLogger(), "It looks like the planner did not set the group the plan was computed for");
935  return false;
936  }
937 
938  // Validate scaling
939  double velocity_scaling_factor = verifyScalingFactor(max_velocity_scaling_factor, VELOCITY);
940  double acceleration_scaling_factor = verifyScalingFactor(max_acceleration_scaling_factor, ACCELERATION);
941 
942  // Get the velocity and acceleration limits for all active joints
943  const moveit::core::RobotModel& rmodel = group->getParentModel();
944  const std::vector<std::string>& vars = group->getVariableNames();
945  std::vector<size_t> active_joint_indices;
946  if (!group->computeJointVariableIndices(group->getActiveJointModelNames(), active_joint_indices))
947  {
948  RCLCPP_ERROR(getLogger(), "Failed to get active variable indices.");
949  }
950 
951  const size_t num_active_joints = active_joint_indices.size();
952  Eigen::VectorXd max_velocity(num_active_joints);
953  Eigen::VectorXd max_acceleration(num_active_joints);
954  for (size_t idx = 0; idx < num_active_joints; ++idx)
955  {
956  // For active joints only (skip mimic joints and other types)
957  const moveit::core::VariableBounds& bounds = rmodel.getVariableBounds(vars[active_joint_indices[idx]]);
958 
959  // Limits need to be non-zero, otherwise we never exit
960  if (bounds.velocity_bounded_)
961  {
962  if (bounds.max_velocity_ <= 0.0)
963  {
964  RCLCPP_ERROR(getLogger(), "Invalid max_velocity %f specified for '%s', must be greater than 0.0",
965  bounds.max_velocity_, vars[idx].c_str());
966  return false;
967  }
968  max_velocity[idx] =
969  std::min(std::fabs(bounds.max_velocity_), std::fabs(bounds.min_velocity_)) * velocity_scaling_factor;
970  }
971  else
972  {
973  RCLCPP_ERROR_STREAM(getLogger(), "No velocity limit was defined for joint " << vars[idx].c_str()
974  << "! You have to define velocity "
975  "limits "
976  "in the URDF or "
977  "joint_limits.yaml");
978  return false;
979  }
980 
981  if (bounds.acceleration_bounded_)
982  {
983  if (bounds.max_acceleration_ < 0.0)
984  {
985  RCLCPP_ERROR(getLogger(), "Invalid max_acceleration %f specified for '%s', must be greater than 0.0",
986  bounds.max_acceleration_, vars[idx].c_str());
987  return false;
988  }
989  max_acceleration[idx] = std::min(std::fabs(bounds.max_acceleration_), std::fabs(bounds.min_acceleration_)) *
990  acceleration_scaling_factor;
991  }
992  else
993  {
994  RCLCPP_ERROR_STREAM(getLogger(), "No acceleration limit was defined for joint " << vars[idx].c_str()
995  << "! You have to define "
996  "acceleration "
997  "limits in the URDF or "
998  "joint_limits.yaml");
999  return false;
1000  }
1001  }
1002 
1003  return doTimeParameterizationCalculations(trajectory, max_velocity, max_acceleration);
1004 }
1005 
1007  const std::vector<moveit_msgs::msg::JointLimits>& joint_limits,
1008  const double max_velocity_scaling_factor,
1009  const double max_acceleration_scaling_factor) const
1010 {
1011  std::unordered_map<std::string, double> velocity_limits;
1012  std::unordered_map<std::string, double> acceleration_limits;
1013  for (const auto& limit : joint_limits)
1014  {
1015  // If custom limits are not defined here, they will be supplied from getRobotModelBounds() later
1016  if (limit.has_velocity_limits)
1017  {
1018  velocity_limits[limit.joint_name] = limit.max_velocity;
1019  }
1020  if (limit.has_acceleration_limits)
1021  {
1022  acceleration_limits[limit.joint_name] = limit.max_acceleration;
1023  }
1024  }
1025  return computeTimeStamps(trajectory, velocity_limits, acceleration_limits, max_velocity_scaling_factor,
1026  max_acceleration_scaling_factor);
1027 }
1028 
1030  robot_trajectory::RobotTrajectory& trajectory, const std::unordered_map<std::string, double>& velocity_limits,
1031  const std::unordered_map<std::string, double>& acceleration_limits, const double max_velocity_scaling_factor,
1032  const double max_acceleration_scaling_factor) const
1033 {
1034  if (trajectory.empty())
1035  return true;
1036 
1037  // Get the default joint limits from the robot model, then overwrite any that are provided as arguments
1038  const moveit::core::JointModelGroup* group = trajectory.getGroup();
1039  if (!group)
1040  {
1041  RCLCPP_ERROR(getLogger(), "It looks like the planner did not set the group the plan was computed for");
1042  return false;
1043  }
1044 
1045  // Validate scaling
1046  double velocity_scaling_factor = verifyScalingFactor(max_velocity_scaling_factor, VELOCITY);
1047  double acceleration_scaling_factor = verifyScalingFactor(max_acceleration_scaling_factor, ACCELERATION);
1048 
1049  // Get the velocity and acceleration limits for specified joints
1050  const moveit::core::RobotModel& rmodel = group->getParentModel();
1051  const std::vector<std::string>& vars = group->getVariableNames();
1052  std::vector<size_t> indices;
1053  if (!group->computeJointVariableIndices(group->getActiveJointModelNames(), indices))
1054  {
1055  RCLCPP_ERROR(getLogger(), "Failed to get active variable indices.");
1056  }
1057 
1058  const size_t num_joints = indices.size();
1059 
1060  Eigen::VectorXd max_velocity(num_joints);
1061  Eigen::VectorXd max_acceleration(num_joints);
1062  for (const auto idx : indices)
1063  {
1064  const moveit::core::VariableBounds& bounds = rmodel.getVariableBounds(vars[idx]);
1065 
1066  // VELOCITY LIMIT
1067  // Check if a custom limit was specified as an argument
1068  bool set_velocity_limit = false;
1069  auto it = velocity_limits.find(vars[idx]);
1070  if (it != velocity_limits.end())
1071  {
1072  max_velocity[idx] = it->second * velocity_scaling_factor;
1073  set_velocity_limit = true;
1074  }
1075 
1076  if (bounds.velocity_bounded_ && !set_velocity_limit)
1077  {
1078  // Set the default velocity limit, from robot model
1079  if (bounds.max_velocity_ < 0.0)
1080  {
1081  RCLCPP_ERROR(getLogger(), "Invalid max_velocity %f specified for '%s', must be greater than 0.0",
1082  bounds.max_velocity_, vars[idx].c_str());
1083  return false;
1084  }
1085  max_velocity[idx] =
1086  std::min(std::fabs(bounds.max_velocity_), std::fabs(bounds.min_velocity_)) * velocity_scaling_factor;
1087  set_velocity_limit = true;
1088  }
1089 
1090  if (!set_velocity_limit)
1091  {
1092  RCLCPP_ERROR_STREAM(getLogger(), "No velocity limit was defined for joint " << vars[idx].c_str()
1093  << "! You have to define velocity "
1094  "limits "
1095  "in the URDF or "
1096  "joint_limits.yaml");
1097  return false;
1098  }
1099 
1100  // ACCELERATION LIMIT
1101  // Check if a custom limit was specified as an argument
1102  bool set_acceleration_limit = false;
1103  it = acceleration_limits.find(vars[idx]);
1104  if (it != acceleration_limits.end())
1105  {
1106  max_acceleration[idx] = it->second * acceleration_scaling_factor;
1107  set_acceleration_limit = true;
1108  }
1109 
1110  if (bounds.acceleration_bounded_ && !set_acceleration_limit)
1111  {
1112  // Set the default acceleration limit, from robot model
1113  if (bounds.max_acceleration_ < 0.0)
1114  {
1115  RCLCPP_ERROR(getLogger(), "Invalid max_acceleration %f specified for '%s', must be greater than 0.0",
1116  bounds.max_acceleration_, vars[idx].c_str());
1117  return false;
1118  }
1119  max_acceleration[idx] = std::min(std::fabs(bounds.max_acceleration_), std::fabs(bounds.min_acceleration_)) *
1120  acceleration_scaling_factor;
1121  set_acceleration_limit = true;
1122  }
1123  if (!set_acceleration_limit)
1124  {
1125  RCLCPP_ERROR_STREAM(getLogger(), "No acceleration limit was defined for joint " << vars[idx].c_str()
1126  << "! You have to define "
1127  "acceleration "
1128  "limits in the URDF or "
1129  "joint_limits.yaml");
1130  return false;
1131  }
1132  }
1133 
1134  return doTimeParameterizationCalculations(trajectory, max_velocity, max_acceleration);
1135 }
1136 
1137 bool totgComputeTimeStamps(const size_t num_waypoints, robot_trajectory::RobotTrajectory& trajectory,
1138  const double max_velocity_scaling_factor, const double max_acceleration_scaling_factor)
1139 {
1140  // The algorithm is:
1141  // 1. Run TOTG with default settings once to find the optimal trajectory duration
1142  // 2. Calculate the timestep to get the desired num_waypoints:
1143  // new_delta_t = duration/(n-1) // subtract one for the initial waypoint
1144  // 3. Run TOTG again with the new timestep. This gives the exact num_waypoints you want (plus or minus one due to
1145  // numerical rounding)
1146 
1147  if (num_waypoints < 2)
1148  {
1149  RCLCPP_ERROR(getLogger(), "computeTimeStamps() requires num_waypoints > 1");
1150  return false;
1151  }
1152 
1153  TimeOptimalTrajectoryGeneration default_totg(0.1 /* default path tolerance */, 0.1 /* default resample_dt */);
1154  default_totg.computeTimeStamps(trajectory, max_velocity_scaling_factor, max_acceleration_scaling_factor);
1155  double optimal_duration = trajectory.getDuration();
1156  double new_resample_dt = optimal_duration / (num_waypoints - 1);
1157  TimeOptimalTrajectoryGeneration resample_totg(0.1 /* path tolerance */, new_resample_dt);
1158  resample_totg.computeTimeStamps(trajectory, max_velocity_scaling_factor, max_acceleration_scaling_factor);
1159  return true;
1160 }
1161 
1162 bool TimeOptimalTrajectoryGeneration::doTimeParameterizationCalculations(robot_trajectory::RobotTrajectory& trajectory,
1163  const Eigen::VectorXd& max_velocity,
1164  const Eigen::VectorXd& max_acceleration) const
1165 {
1166  // This lib does not actually work properly when angles wrap around, so we need to unwind the path first
1167  trajectory.unwind();
1168 
1169  const moveit::core::JointModelGroup* group = trajectory.getGroup();
1170  if (!group)
1171  {
1172  RCLCPP_ERROR(getLogger(), "It looks like the planner did not set the group the plan was computed for");
1173  return false;
1174  }
1175 
1176  if (hasMixedJointTypes(group))
1177  {
1178  RCLCPP_WARN(getLogger(), "There is a combination of revolute and prismatic joints in the robot model. TOTG's "
1179  "`path_tolerance` will not function correctly.");
1180  }
1181 
1182  const unsigned num_points = trajectory.getWayPointCount();
1183  const std::vector<int>& idx = group->getVariableIndexList();
1184  const unsigned num_joints = group->getVariableCount();
1185 
1186  // Have to convert into Eigen data structs and remove repeated points
1187  // (https://github.com/tobiaskunz/trajectories/issues/3)
1188  std::vector<Eigen::VectorXd> points;
1189  for (size_t p = 0; p < num_points; ++p)
1190  {
1191  moveit::core::RobotStatePtr waypoint = trajectory.getWayPointPtr(p);
1192  Eigen::VectorXd new_point(num_joints);
1193  // The first point should always be kept
1194  bool diverse_point = (p == 0);
1195 
1196  for (size_t j = 0; j < num_joints; ++j)
1197  {
1198  new_point[j] = waypoint->getVariablePosition(idx[j]);
1199  // If any joint angle is different, it's a unique waypoint
1200  if (p > 0 && std::fabs(new_point[j] - points.back()[j]) > min_angle_change_)
1201  {
1202  diverse_point = true;
1203  }
1204  }
1205 
1206  if (diverse_point)
1207  {
1208  points.push_back(new_point);
1209  // If the last point is not a diverse_point we replace the last added point with it to make sure to always have
1210  // the input end point as the last point
1211  }
1212  else if (p == num_points - 1)
1213  {
1214  points.back() = new_point;
1215  }
1216  }
1217 
1218  // Return trajectory with only the first waypoint if there are not multiple diverse points
1219  if (points.size() == 1)
1220  {
1222  waypoint.zeroVelocities();
1223  waypoint.zeroAccelerations();
1224  trajectory.clear();
1225  trajectory.addSuffixWayPoint(waypoint, 0.0);
1226  return true;
1227  }
1228 
1229  // Now actually call the algorithm
1230  std::optional<Path> path = Path::create(points, path_tolerance_);
1231  if (!path)
1232  {
1233  RCLCPP_ERROR(getLogger(), "Invalid path.");
1234  return false;
1235  }
1236 
1237  std::optional<Trajectory> parameterized = Trajectory::create(*path, max_velocity, max_acceleration, DEFAULT_TIMESTEP);
1238  if (!parameterized)
1239  {
1240  RCLCPP_ERROR(getLogger(), "Couldn't create trajectory");
1241  return false;
1242  }
1243 
1244  // Compute sample count
1245  const size_t sample_count = std::ceil(parameterized->getDuration() / resample_dt_);
1246 
1247  // Resample and fill in trajectory
1249  trajectory.clear();
1250  double last_t = 0;
1251  for (size_t sample = 0; sample <= sample_count; ++sample)
1252  {
1253  // always sample the end of the trajectory as well
1254  double t = std::min(parameterized->getDuration(), sample * resample_dt_);
1255  Eigen::VectorXd position = parameterized->getPosition(t);
1256  Eigen::VectorXd velocity = parameterized->getVelocity(t);
1257  Eigen::VectorXd acceleration = parameterized->getAcceleration(t);
1258 
1259  for (size_t j = 0; j < num_joints; ++j)
1260  {
1261  waypoint.setVariablePosition(idx[j], position[j]);
1262  waypoint.setVariableVelocity(idx[j], velocity[j]);
1263  waypoint.setVariableAcceleration(idx[j], acceleration[j]);
1264  }
1265 
1266  trajectory.addSuffixWayPoint(waypoint, t - last_t);
1267  last_t = t;
1268  }
1269 
1270  return true;
1271 }
1272 
1273 bool TimeOptimalTrajectoryGeneration::hasMixedJointTypes(const moveit::core::JointModelGroup* group) const
1274 {
1275  const std::vector<const moveit::core::JointModel*>& joint_models = group->getActiveJointModels();
1276 
1277  bool have_prismatic =
1278  std::any_of(joint_models.cbegin(), joint_models.cend(), [](const moveit::core::JointModel* joint_model) {
1279  return joint_model->getType() == moveit::core::JointModel::JointType::PRISMATIC;
1280  });
1281 
1282  bool have_revolute =
1283  std::any_of(joint_models.cbegin(), joint_models.cend(), [](const moveit::core::JointModel* joint_model) {
1284  return joint_model->getType() == moveit::core::JointModel::JointType::REVOLUTE;
1285  });
1286 
1287  return have_prismatic && have_revolute;
1288 }
1289 
1290 double TimeOptimalTrajectoryGeneration::verifyScalingFactor(const double requested_scaling_factor,
1291  const LimitType limit_type) const
1292 {
1293  std::string limit_type_str;
1294  double scaling_factor = DEFAULT_SCALING_FACTOR;
1295 
1296  const auto limit_type_it = LIMIT_TYPES.find(limit_type);
1297  if (limit_type_it != LIMIT_TYPES.end())
1298  {
1299  limit_type_str = limit_type_it->second + "_";
1300  }
1301 
1302  if (requested_scaling_factor > 0.0 && requested_scaling_factor <= 1.0)
1303  {
1304  scaling_factor = requested_scaling_factor;
1305  }
1306  else
1307  {
1308  RCLCPP_WARN(getLogger(), "Invalid max_%sscaling_factor %f specified, defaulting to %f instead.",
1309  limit_type_str.c_str(), requested_scaling_factor, scaling_factor);
1310  }
1311  return scaling_factor;
1312 }
1313 } // namespace trajectory_processing
const std::vector< const JointModel * > & getActiveJointModels() const
Get the active joints in this group (that have controllable DOF). This does not include mimic joints.
bool computeJointVariableIndices(const std::vector< std::string > &joint_names, std::vector< size_t > &joint_bijection) const
Computes the indices of joint variables given a vector of joint names to look up.
const RobotModel & getParentModel() const
Get the kinematic model this group is part of.
unsigned int getVariableCount() const
Get the number of variables that describe this joint group. This includes variables necessary for mim...
const std::vector< std::string > & getVariableNames() const
Get the names of the variables that make up the joints included in this group. The number of returned...
const std::vector< int > & getVariableIndexList() const
Get the index locations in the complete robot state for all the variables in this group.
const std::vector< std::string > & getActiveJointModelNames() const
Get the names of the active joints in this group. These are the names of the joints returned by getJo...
A joint from the robot. Models the transform that this joint applies in the kinematic chain....
Definition: joint_model.h:117
Definition of a kinematic model. This class is not thread safe, however multiple instances can be cre...
Definition: robot_model.h:76
const VariableBounds & getVariableBounds(const std::string &variable) const
Get the bounds for a specific variable. Throw an exception of variable is not found.
Definition: robot_model.h:433
Representation of a robot's state. This includes position, velocity, acceleration and effort.
Definition: robot_state.h:90
void setVariableAcceleration(const std::string &variable, double value)
Set the acceleration of a variable. If an unknown variable name is specified, an exception is thrown.
Definition: robot_state.h:381
void zeroVelocities()
Set all velocities to 0.0.
void setVariableVelocity(const std::string &variable, double value)
Set the velocity of a variable. If an unknown variable name is specified, an exception is thrown.
Definition: robot_state.h:282
void setVariablePosition(const std::string &variable, double value)
Set the position of a single variable. An exception is thrown if the variable name is not known.
Definition: robot_state.h:188
void zeroAccelerations()
Set all accelerations to 0.0.
Maintain a sequence of waypoints and the time durations between these waypoints.
const moveit::core::JointModelGroup * getGroup() const
RobotTrajectory & addSuffixWayPoint(const moveit::core::RobotState &state, double dt)
Add a point to the trajectory.
moveit::core::RobotStatePtr & getWayPointPtr(std::size_t index)
const moveit::core::RobotState & getWayPoint(std::size_t index) const
CircularPathSegment(const Eigen::VectorXd &start, const Eigen::VectorXd &intersection, const Eigen::VectorXd &end, double max_deviation)
Eigen::VectorXd getConfig(double s) const override
LinearPathSegment(const Eigen::VectorXd &start, const Eigen::VectorXd &end)
virtual Eigen::VectorXd getConfig(double s) const =0
virtual Eigen::VectorXd getCurvature(double s) const =0
virtual Eigen::VectorXd getTangent(double s) const =0
Eigen::VectorXd getTangent(double s) const
std::list< std::pair< double, bool > > getSwitchingPoints() const
Return a list of all switching points as a pair (arc length to switching point, discontinuity)
static std::optional< Path > create(const std::vector< Eigen::VectorXd > &waypoint, double max_deviation=DEFAULT_PATH_TOLERANCE)
Eigen::VectorXd getCurvature(double s) const
double getNextSwitchingPoint(double s, bool &discontinuity) const
Get the next switching point.
bool computeTimeStamps(robot_trajectory::RobotTrajectory &trajectory, const double max_velocity_scaling_factor=1.0, const double max_acceleration_scaling_factor=1.0) const override
Compute a trajectory with waypoints spaced equally in time (according to resample_dt_)....
TimeOptimalTrajectoryGeneration(const double path_tolerance=DEFAULT_PATH_TOLERANCE, const double resample_dt=0.1, const double min_angle_change=0.001)
Eigen::VectorXd getAcceleration(double time) const
Return the acceleration vector for a given point in time.
Eigen::VectorXd getPosition(double time) const
Return the position/configuration vector for a given point in time.
double getDuration() const
Returns the optimal duration of the trajectory.
Eigen::VectorXd getVelocity(double time) const
Return the velocity vector for a given point in time.
static std::optional< Trajectory > create(const Path &path, const Eigen::VectorXd &max_velocity, const Eigen::VectorXd &max_acceleration, double time_step=0.001)
Generates a time-optimal trajectory.
rclcpp::Logger getLogger(const std::string &name)
Creates a namespaced logger.
Definition: logger.cpp:79
double distance(const urdf::Pose &transform)
Definition: pr2_arm_ik.h:55
const std::unordered_map< LimitType, std::string > LIMIT_TYPES
bool totgComputeTimeStamps(const size_t num_waypoints, robot_trajectory::RobotTrajectory &trajectory, const double max_velocity_scaling_factor=1.0, const double max_acceleration_scaling_factor=1.0)
Compute a trajectory with the desired number of waypoints. Resampling the trajectory doesn't change t...