LCOV - code coverage report
Current view: top level - trajectory - TrajectoryUtils.h Hit Total Coverage
Test: doc-coverage.info Lines: 5 5 100.0 %
Date: 2026-04-20 18:26:22

          Line data    Source code
       1             : #ifndef TRAJECTORY_UTILS_H
       2             : #define TRAJECTORY_UTILS_H
       3             : 
       4             : #include <mav_trajectory_generation/polynomial_optimization_nonlinear.h>
       5             : #include <mav_trajectory_generation/segment.h>
       6             : #include <mav_trajectory_generation/trajectory.h>
       7             : #include <mav_trajectory_generation/vertex.h>
       8             : 
       9             : #include <Eigen/Core>
      10             : #include <optional>
      11             : #include <vector>
      12             : 
      13             : #include "utils/NodeUtils.h"
      14             : 
      15             : namespace mav_trajectory_generation {
      16             : 
      17             : /**
      18             :  * @brief Plans a minimum-derivative polynomial path through a sequence of distinct waypoints
      19             :  *
      20             :  * Constructs a nonlinear polynomial optimisation problem with velocity and
      21             :  * acceleration magnitude constraints, solves it, and scales segment times to
      22             :  * guarantee the constraints are met.  The starting velocity is constrained to
      23             :  * start_vel; the endpoint has all derivatives up to DerivativeToOptimize
      24             :  * constrained to zero by makeStartOrEnd().
      25             :  *
      26             :  * @tparam DerivativeToOptimize Derivative order to minimise (e.g. ACCELERATION)
      27             :  *
      28             :  * @param waypoints  [in] Ordered waypoints; must contain at least two distinct points
      29             :  * @param start_vel  [in] Initial velocity vector of the same dimension as the waypoints
      30             :  * @param v_max      [in] Maximum velocity magnitude constraint
      31             :  * @param a_max      [in] Maximum acceleration magnitude constraint
      32             :  *
      33             :  * @return Planned trajectory segments, or nullopt if optimisation fails
      34             :  */
      35             : template <int DerivativeToOptimize>
      36           1 : std::optional<Segment::Vector> optimize_path(const std::vector<Eigen::VectorXd>& waypoints,
      37             :                                              const Eigen::VectorXd& start_vel, double v_max, double a_max) {
      38             :     const int dimension = start_vel.size();
      39             :     constexpr int N     = 2 * (DerivativeToOptimize + 1);
      40             : 
      41             :     Vertex::Vector vertices;
      42             :     for (size_t i = 0; i < waypoints.size(); ++i) {
      43             :         Vertex vertex(dimension);
      44             :         if (i == 0) {
      45             :             vertex.makeStartOrEnd(waypoints[i], DerivativeToOptimize);
      46             :             vertex.addConstraint(derivative_order::VELOCITY, start_vel);
      47             :         } else if (i == waypoints.size() - 1) {
      48             :             vertex.makeStartOrEnd(waypoints[i], DerivativeToOptimize);
      49             :         } else {
      50             :             vertex.addConstraint(derivative_order::POSITION, waypoints[i]);
      51             :         }
      52             :         vertices.push_back(vertex);
      53             :     }
      54             : 
      55             :     std::vector<double> segment_times = estimateSegmentTimes(vertices, v_max, a_max);
      56             : 
      57             :     NonlinearOptimizationParameters parameters;
      58             :     PolynomialOptimizationNonLinear<N> opt(dimension, parameters);
      59             :     if (!opt.setupFromVertices(vertices, segment_times, DerivativeToOptimize)) {
      60             :         RCLCPP_WARN(Controller::NodeUtils::get_logger(), "[TRAJ PLANNER] setupFromVertices failed");
      61             :         return std::nullopt;
      62             :     }
      63             : 
      64             :     opt.addMaximumMagnitudeConstraint(derivative_order::VELOCITY, v_max);
      65             :     opt.addMaximumMagnitudeConstraint(derivative_order::ACCELERATION, a_max);
      66             : 
      67             :     int result = opt.optimize();
      68             :     if (result < 0 && result != nlopt::ROUNDOFF_LIMITED) {
      69             :         RCLCPP_WARN(Controller::NodeUtils::get_logger(), "[TRAJ PLANNER] nlopt optimization failed: %s",
      70             :                     nlopt::returnValueToString(result).c_str());
      71             :         // TODO: need to fix error handling in mav_trajectory_generation itself
      72             :         // currently it just try catches and reports -1 (nlopt::FAILURE) even if the error is something recoverable like
      73             :         // nlopt::ROUNDOFF_LIMITED (which is the error usually)
      74             : 
      75             :         // do not block return for now
      76             :         // return std::nullopt;
      77             :     }
      78             : 
      79             :     Trajectory trajectory;
      80             :     opt.getTrajectory(&trajectory);
      81             : 
      82             :     if (!trajectory.scaleSegmentTimesToMeetConstraints(v_max, a_max)) {
      83             :         RCLCPP_WARN(Controller::NodeUtils::get_logger(), "[TRAJ PLANNER] scaleSegmentTimesToMeetConstraints failed");
      84             :         return std::nullopt;
      85             :     }
      86             : 
      87             :     Segment::Vector segments;
      88             :     trajectory.getSegments(&segments);
      89             :     return segments;
      90             : }
      91             : 
      92             : /**
      93             :  * @brief Creates a zero-velocity constant-position polynomial segment
      94             :  *
      95             :  * @tparam DerivativeToOptimize Derivative order used to size the polynomial (must match the rest of the trajectory)
      96             :  *
      97             :  * @param value    [in] Position value to hold for the duration of the segment
      98             :  * @param duration [in] Segment duration in seconds
      99             :  *
     100             :  * @return Constant polynomial segment of the requested duration
     101             :  */
     102             : template <int DerivativeToOptimize>
     103           1 : Segment get_constant_segment(const Eigen::VectorXd& value, double duration) {
     104             :     constexpr int N     = 2 * (DerivativeToOptimize + 1);
     105             :     const int dimension = value.size();
     106             : 
     107             :     Segment seg(N, dimension);
     108             :     seg.setTime(duration);
     109             : 
     110             :     for (int d = 0; d < dimension; ++d) {
     111             :         Eigen::VectorXd coeffs = Eigen::VectorXd::Zero(N);
     112             :         coeffs(0)              = value(d);
     113             :         seg[d].setCoefficients(coeffs);
     114             :     }
     115             : 
     116             :     return seg;
     117             : }
     118             : 
     119             : /**
     120             :  * @brief Plans a trajectory through waypoints, inserting constant segments at duplicate points
     121             :  *
     122             :  * Consecutive identical waypoints are replaced by a constant-position segment
     123             :  * of duration min_segment_time.  Runs of distinct waypoints are planned with
     124             :  * optimize_path().  Resulting segments are concatenated into a single Trajectory.
     125             :  *
     126             :  * @tparam DerivativeToOptimize Derivative order to minimise
     127             :  *
     128             :  * @param waypoints         [in] Ordered waypoints; must contain at least two entries
     129             :  * @param start_vel         [in] Initial velocity vector
     130             :  * @param v_max             [in] Maximum velocity magnitude constraint
     131             :  * @param a_max             [in] Maximum acceleration magnitude constraint
     132             :  * @param min_segment_time  [in] Duration (s) of constant segments inserted at duplicate waypoints
     133             :  *
     134             :  * @return Combined trajectory, or nullopt if planning fails for any sub-path
     135             :  */
     136             : template <int DerivativeToOptimize>
     137           1 : std::optional<Trajectory> calculate_trajectory(const std::vector<Eigen::VectorXd>& waypoints,
     138             :                                                const Eigen::VectorXd& start_vel, double v_max, double a_max,
     139             :                                                double min_segment_time) {
     140             :     if (waypoints.size() < 2) {
     141             :         return std::nullopt;
     142             :     }
     143             : 
     144             :     const int dimension = start_vel.size();
     145             :     Segment::Vector all_segments;
     146             : 
     147             :     Eigen::VectorXd current_vel = start_vel;
     148             : 
     149             :     size_t i = 0;
     150             :     while (i < waypoints.size() - 1) {
     151             :         if (waypoints[i] == waypoints[i + 1]) {
     152             :             all_segments.push_back(get_constant_segment<DerivativeToOptimize>(waypoints[i], min_segment_time));
     153             :             current_vel = Eigen::VectorXd::Zero(dimension);
     154             :             ++i;
     155             :         } else {
     156             :             std::vector<Eigen::VectorXd> path;
     157             :             path.push_back(waypoints[i]);
     158             :             while (i < waypoints.size() - 1 && waypoints[i] != waypoints[i + 1]) {
     159             :                 path.push_back(waypoints[i + 1]);
     160             :                 ++i;
     161             :             }
     162             : 
     163             :             auto segments = optimize_path<DerivativeToOptimize>(path, current_vel, v_max, a_max);
     164             :             if (!segments) {
     165             :                 return std::nullopt;
     166             :             }
     167             : 
     168             :             all_segments.insert(all_segments.end(), segments->begin(), segments->end());
     169             :             current_vel = Eigen::VectorXd::Zero(dimension);
     170             :         }
     171             :     }
     172             : 
     173             :     if (all_segments.empty()) {
     174             :         return std::nullopt;
     175             :     }
     176             : 
     177             :     Trajectory trajectory;
     178             :     trajectory.setSegments(all_segments);
     179             :     return trajectory;
     180             : }
     181             : 
     182             : /**
     183             :  * @brief Describes a contiguous slice of trajectory dimensions sharing a common velocity/acceleration budget
     184             :  */
     185           1 : struct DimensionGroup {
     186             :     /** @brief Index of the first dimension in this group */
     187             :     int start;
     188             : 
     189             :     /** @brief Number of dimensions in this group */
     190             :     int size;
     191             : 
     192             :     /** @brief Maximum velocity magnitude for this group */
     193             :     double v_max;
     194             : 
     195             :     /** @brief Maximum acceleration magnitude for this group */
     196             :     double a_max;
     197             : };
     198             : 
     199             : /**
     200             :  * @brief Plans a multi-DOF trajectory by solving each dimension group independently and concatenating the results
     201             :  *
     202             :  * Splits the M-dimensional waypoints into sub-problems defined by groups,
     203             :  * calls calculate_trajectory on each sub-problem, then appends all resulting
     204             :  * trajectories along the dimension axis.  This allows different axes (e.g. XY
     205             :  * vs Z vs Yaw) to have independent kinematic budgets.
     206             :  *
     207             :  * @tparam DerivativeToOptimize Derivative order to minimise
     208             :  *
     209             :  * @param waypoints         [in] M-dimensional waypoints; must contain at least two entries
     210             :  * @param start_vel         [in] M-dimensional initial velocity
     211             :  * @param groups            [in] Dimension groups that together cover all M dimensions
     212             :  * @param min_segment_time  [in] Duration (s) inserted at duplicate waypoints
     213             :  *
     214             :  * @return Combined multi-DOF trajectory, or nullopt if any group fails
     215             :  */
     216             : template <int DerivativeToOptimize>
     217           1 : std::optional<Trajectory> calculate_trajectory_multidim(const std::vector<Eigen::VectorXd>& waypoints,
     218             :                                                         const Eigen::VectorXd& start_vel,
     219             :                                                         const std::vector<DimensionGroup>& groups,
     220             :                                                         double min_segment_time) {
     221             :     if (waypoints.size() < 2 || groups.empty()) {
     222             :         return std::nullopt;
     223             :     }
     224             : 
     225             :     std::optional<Trajectory> combined;
     226             : 
     227             :     for (const auto& group : groups) {
     228             :         std::vector<Eigen::VectorXd> sub_waypoints;
     229             :         sub_waypoints.reserve(waypoints.size());
     230             :         for (const auto& wp : waypoints) {
     231             :             sub_waypoints.push_back(wp.segment(group.start, group.size));
     232             :         }
     233             : 
     234             :         Eigen::VectorXd sub_vel = start_vel.segment(group.start, group.size);
     235             : 
     236             :         auto traj = calculate_trajectory<DerivativeToOptimize>(sub_waypoints, sub_vel, group.v_max, group.a_max,
     237             :                                                                min_segment_time);
     238             :         if (!traj) {
     239             :             return std::nullopt;
     240             :         }
     241             : 
     242             :         if (!combined) {
     243             :             combined = *traj;
     244             :         } else {
     245             :             Trajectory appended;
     246             :             if (!combined->getTrajectoryWithAppendedDimension(*traj, &appended)) {
     247             :                 return std::nullopt;
     248             :             }
     249             :             combined = appended;
     250             :         }
     251             :     }
     252             : 
     253             :     return combined;
     254             : }
     255             : 
     256             : }  // namespace mav_trajectory_generation
     257             : 
     258             : #endif  // TRAJECTORY_UTILS_H

Generated by: LCOV version 1.14