19using SteadyClock = std::chrono::steady_clock;
21double elapsedMilliseconds(
const SteadyClock::time_point& start) {
22 return std::chrono::duration<double, std::milli>(SteadyClock::now() - start).count();
25double elapsedMilliseconds(
const SteadyClock::time_point& start,
const SteadyClock::time_point& end) {
26 return std::chrono::duration<double, std::milli>(end - start).count();
32 : rclcpp::Node(node_name, options) {
35 "Frame ID of local vehicle frame (the ocp is defined in this frame)");
38 "Frame ID of frame that is fixed over time for finding temporal transforms");
40 "Time after which a received ego vehicle data is considered invalid [s]. Optimization will not "
41 "be run if ego data is invalid.");
43 "Name of the model to be used for trajectory optimization [karl, shuttle]");
49 "Write one CSV record for every completed solver run",
false,
false,
true);
52 "Run OCP once for each received reference trajectory (true) or on a timer (false)");
57 "Minimum distance to keep to obstacle in longitudinal direction [m]");
59 "Minimum distance to keep to obstacle in lateral direction [m]");
61 "Minimum distance to keep to boundary in lateral direction [m]");
63 "Threshold for standstill detection [m/s]. If the velocities of all states are below this "
64 "threshold, publish standstill trajectory");
66 "Use high-level stabilization strategy for init state (= init with current EgoData)");
69 "consider objects in optimization: 0 = none, 1 = static (no prediction), 2 = dynamic (with prediction)");
71 "Minimum probability for predicted object states to be considered",
true,
false,
false, 0.0, 1.0);
74 "consider route boundaries in optimization: 0 = no, 1 = suggested lane, 2 = including adjacent, 3 = drivable space");
76 "Threshold for bi-level stabilization: maximum velocity difference [m/s]");
79 "Threshold for bi-level stabilization: maximum yaw difference [degree]");
83 RCLCPP_INFO(get_logger(),
"Writing benchmark logs to '%s'.",
performance_logger_->path().c_str());
84 }
catch (
const std::exception& error) {
85 RCLCPP_ERROR(get_logger(),
"Could not initialize benchmark logging: %s", error.what());
96 const std::string& description,
97 const bool add_to_auto_reconfigurable_params,
98 const bool is_required,
100 const std::optional<double>& from_value,
101 const std::optional<double>& to_value,
102 const std::optional<double>& step_value,
103 const std::string& additional_constraints) {
104 rcl_interfaces::msg::ParameterDescriptor param_desc;
105 param_desc.description = description;
106 param_desc.additional_constraints = additional_constraints;
107 param_desc.read_only = read_only;
109 auto type = rclcpp::ParameterValue(param).get_type();
111 if (from_value.has_value() && to_value.has_value()) {
112 if constexpr (std::is_integral_v<T>) {
113 rcl_interfaces::msg::IntegerRange range;
114 range.set__from_value(
static_cast<T
>(from_value.value())).set__to_value(
static_cast<T
>(to_value.value()));
115 if (step_value.has_value()) range.set__step(
static_cast<T
>(step_value.value()));
116 param_desc.integer_range = {range};
117 }
else if constexpr (std::is_floating_point_v<T>) {
118 rcl_interfaces::msg::FloatingPointRange range;
119 range.set__from_value(
static_cast<T
>(from_value.value())).set__to_value(
static_cast<T
>(to_value.value()));
120 if (step_value.has_value()) range.set__step(
static_cast<T
>(step_value.value()));
121 param_desc.floating_point_range = {range};
123 RCLCPP_WARN(this->get_logger(),
"Parameter type of parameter '%s' does not support specifying a range", name.c_str());
127 this->declare_parameter(name, type, param_desc);
130 param = this->get_parameter(name).get_value<T>();
131 std::stringstream ss;
132 ss <<
"Loaded parameter '" << name <<
"': ";
135 for (
const auto& element : param) ss << element << (&element != ¶m.back() ?
", " :
"");
140 RCLCPP_INFO_STREAM(this->get_logger(), ss.str());
141 }
catch (rclcpp::exceptions::ParameterUninitializedException&) {
143 RCLCPP_FATAL_STREAM(this->get_logger(),
"Missing required parameter '" << name <<
"', exiting");
146 std::stringstream ss;
147 ss <<
"Missing parameter '" << name <<
"', using default value: ";
150 for (
const auto& element : param) ss << element << (&element != ¶m.back() ?
", " :
"");
155 RCLCPP_WARN_STREAM(this->get_logger(), ss.str());
156 this->set_parameters({rclcpp::Parameter(name, rclcpp::ParameterValue(param))});
160 if (add_to_auto_reconfigurable_params) {
161 std::function<void(
const rclcpp::Parameter&)> setter = [¶m](
const rclcpp::Parameter& p) { param = p.get_value<T>(); };
167 const std::vector<rclcpp::Parameter>& parameters) {
168 for (
const auto& param : parameters) {
170 if (param.get_name() == std::get<0>(auto_reconfigurable_param)) {
171 std::get<1>(auto_reconfigurable_param)(param);
172 RCLCPP_INFO(this->get_logger(),
"Reconfigured parameter '%s' to: %s", param.get_name().c_str(),
173 param.value_to_string().c_str());
178 if (param.get_name() ==
"run_as_callback") {
182 RCLCPP_WARN(this->get_logger(),
"OCP runs now periodically with frequency %f Hz",
optimization_freq_);
186 RCLCPP_WARN(this->get_logger(),
"OCP runs now on reference trajectory callback");
191 rcl_interfaces::msg::SetParametersResult result;
192 result.successful =
true;
198 tf2_buffer_ = std::make_unique<tf2_ros::Buffer>(this->get_clock());
206 ego_data_sub_ = this->create_subscription<perception_msgs::msg::EgoData>(
208 RCLCPP_INFO(this->get_logger(),
"Subscribed to '%s'",
ego_data_sub_->get_topic_name());
210 object_list_sub_ = this->create_subscription<perception_msgs::msg::ObjectList>(
212 RCLCPP_INFO(this->get_logger(),
"Subscribed to '%s'",
object_list_sub_->get_topic_name());
214 route_sub_ = this->create_subscription<route_planning_msgs::msg::Route>(
216 RCLCPP_INFO(this->get_logger(),
"Subscribed to '%s'",
route_sub_->get_topic_name());
219 "~/reference_trajectory", 1,
224 trajectory_pub_ = this->create_publisher<trajectory_planning_msgs::msg::Trajectory>(
"~/trajectory", 1);
225 RCLCPP_INFO(this->get_logger(),
"Publishing to '%s'",
trajectory_pub_->get_topic_name());
226 circles_pub_ = this->create_publisher<visualization_msgs::msg::MarkerArray>(
"~/visualization/object_circles", 1);
227 RCLCPP_INFO(this->get_logger(),
"Publishing to '%s'",
circles_pub_->get_topic_name());
228 ego_circles_pub_ = this->create_publisher<visualization_msgs::msg::MarkerArray>(
"~/visualization/ego_circles", 1);
229 RCLCPP_INFO(this->get_logger(),
"Publishing to '%s'",
ego_circles_pub_->get_topic_name());
230 boundary_pub_ = this->create_publisher<visualization_msgs::msg::MarkerArray>(
"~/visualization/boundaries", 1);
231 RCLCPP_INFO(this->get_logger(),
"Publishing to '%s'",
boundary_pub_->get_topic_name());
235 RCLCPP_INFO(this->get_logger(),
"OCP runs on reference trajectory callback");
237 RCLCPP_INFO(this->get_logger(),
"OCP runs continuously with frequency %f Hz",
optimization_freq_);
243 trajectory_planning_msgs::trajectory_access::initializeTrajectory(
248 trajectory_planning_msgs::trajectory_access::initializeTrajectory(
255 std::vector<const void*> link_subs;
256 link_subs.push_back(
static_cast<const void*
>(
ego_data_sub_->get_subscription_handle().get()));
257 link_subs.push_back(
static_cast<const void*
>(
object_list_sub_->get_subscription_handle().get()));
258 link_subs.push_back(
static_cast<const void*
>(
route_sub_->get_subscription_handle().get()));
260 std::vector<const void*> link_pubs;
261 link_pubs.push_back(
static_cast<const void*
>(
trajectory_pub_->get_publisher_handle().get()));
262 RCLCPP_INFO(get_logger(),
"Annotating message links for tracing with %zu subscriptions and %zu publications", link_subs.size(),
264 TRACETOOLS_TRACEPOINT(message_link_periodic_async, link_subs.data(), link_subs.size(), link_pubs.data(), link_pubs.size());
269 RCLCPP_FATAL(this->get_logger(),
"n_shots must be > 0, got %d",
n_shots_);
277 new_time_steps.front());
280 if (status != ACADOS_SUCCESS) {
281 RCLCPP_FATAL(this->get_logger(),
"%s_acados_create_with_discretization() returned status %d. Exiting.",
model_name_.c_str(),
293 RCLCPP_FATAL(this->get_logger(),
"Created solver has N=%d, expected n_shots=%d. Exiting.",
nlp_dims_->N,
n_shots_);
306 RCLCPP_ERROR(this->get_logger(),
"%s_acados_free() returned status %d.",
model_name_.c_str(), status);
311 RCLCPP_ERROR(this->get_logger(),
"%s_acados_free_capsule() returned status %d.",
model_name_.c_str(), status);
317 if (status != ACADOS_SUCCESS) {
318 RCLCPP_ERROR(this->get_logger(),
"%s_acados_reset() returned status %d. Recreating solver.",
model_name_.c_str(), status);
325 constexpr double MAX_CONTROL_GUESS_AGE_FACTOR = 0.5;
329 const size_t expected_control_size =
static_cast<size_t>(nu *
n_shots_);
330 if (x_init.size() !=
static_cast<size_t>(nx)) {
331 RCLCPP_ERROR(get_logger(),
"Initial state has size %zu, expected %d.", x_init.size(), nx);
336 std::vector<double> controls(expected_control_size, 0.0);
341 std::vector<double> lower_controls(nu);
342 std::vector<double> upper_controls(nu);
343 for (
int stage = 0; stage <
n_shots_; ++stage) {
344 const double previous_stage = (elapsed + stage * time_step) / time_step;
345 if (previous_stage >=
n_shots_)
break;
347 const int lower_stage =
static_cast<int>(std::floor(previous_stage));
348 const double interpolation_factor = previous_stage - lower_stage;
351 for (
int control = 0; control < nu; ++control) {
352 const double lower_value =
control_guess_[lower_stage * nu + control];
353 const double upper_value = lower_stage + 1 <
n_shots_ ?
control_guess_[(lower_stage + 1) * nu + control] : 0.0;
354 const double interpolated_control = lower_value + interpolation_factor * (upper_value - lower_value);
355 controls[stage * nu + control] = std::clamp(interpolated_control, lower_controls[control], upper_controls[control]);
362 std::vector<double> rollout_state = x_init;
363 std::vector<double> intermediate_state(nx);
364 std::vector<double> k1(nx), k2(nx), k3(nx), k4(nx);
365 const double integration_step = time_step / 2.0;
366 for (
int stage = 0; stage <
n_shots_; ++stage) {
367 double* control = &controls[stage * nu];
372 for (
int integration = 0; integration < 2; ++integration) {
374 for (
int i = 0; i < nx; ++i) intermediate_state[i] = rollout_state[i] + 0.5 * integration_step * k1[i];
376 for (
int i = 0; i < nx; ++i) intermediate_state[i] = rollout_state[i] + 0.5 * integration_step * k2[i];
378 for (
int i = 0; i < nx; ++i) intermediate_state[i] = rollout_state[i] + integration_step * k3[i];
380 for (
int i = 0; i < nx; ++i) {
381 rollout_state[i] += integration_step / 6.0 * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]);
390 const auto cycle_start = SteadyClock::now();
393 RCLCPP_WARN(this->get_logger(),
"EgoData outdated. Skipping planning cycle.");
397 trajectory_planning_msgs::msg::Trajectory::UniquePtr trajectory = std::make_unique<trajectory_planning_msgs::msg::Trajectory>();
401 trajectory->header.stamp =
ego_data_.header.stamp;
405 for (
int i = 0; i <=
n_shots_; ++i) trajectory_planning_msgs::trajectory_access::setT(*trajectory, i * dt, i);
409 RCLCPP_WARN(this->get_logger(),
"Standstill trajectory. Skipping planning cycle. Publish standstill trajectory.");
414 trajectory_planning_msgs::trajectory_access::setStandstill(*trajectory,
true);
431 auto logCompletedCycle = [&]() {
432 metrics.
cycle_ms = elapsedMilliseconds(cycle_start);
440 std::vector<double> x_init(*
nlp_dims_->nx, 0.0);
444 RCLCPP_WARN(this->get_logger(),
445 "Latest available trajectory is standstill. Using ego data for initial state (high-level initialization).");
450 std::stringstream ss;
451 ss <<
"Initial state: ";
452 for (
size_t i = 0; i < x_init.size(); ++i) ss <<
"x[" << i <<
"]: " << x_init[i] << (i != x_init.size() - 1 ?
", " :
"");
453 RCLCPP_DEBUG(this->get_logger(),
"%s", ss.str().c_str());
460 RCLCPP_WARN(this->get_logger(),
"Failed to update inputs. Skipping planning cycle.");
470 const auto solve_start = SteadyClock::now();
473 const auto solve_end = SteadyClock::now();
474 metrics.
solve_wall_ms = elapsedMilliseconds(solve_start, solve_end);
479 const bool usable_status =
480 metrics.
status == ACADOS_SUCCESS || metrics.
status == ACADOS_MAXITER || metrics.
status == ACADOS_TIMEOUT;
482 for (
int ii = 0; ii <=
nlp_dims_->N; ++ii) {
485 for (
int ii = 0; ii <
nlp_dims_->N; ++ii) {
491 const bool finite_solution = usable_status && std::isfinite(metrics.
cost_value) && std::isfinite(metrics.
res_eq) &&
493 std::all_of(
xtraj_.begin(),
xtraj_.end(), [](
double value) { return std::isfinite(value); }) &&
494 std::all_of(
utraj_.begin(),
utraj_.end(), [](
double value) { return std::isfinite(value); });
495 const bool primal_feasible =
496 finite_solution && metrics.
res_eq <= solver_opts->tol_eq && metrics.
res_ineq <= solver_opts->tol_ineq;
498 if (!primal_feasible) {
500 RCLCPP_WARN(this->get_logger(),
501 "Rejecting solver output: status=%d finite=%d primal residuals=[eq=%e, ineq=%e] tolerances=[eq=%e, ineq=%e].",
502 metrics.
status, finite_solution, metrics.
res_eq, metrics.
res_ineq, solver_opts->tol_eq, solver_opts->tol_ineq);
507 if (finite_solution && (metrics.
status == ACADOS_MAXITER || metrics.
status == ACADOS_TIMEOUT)) {
531 bool standstill =
true;
532 for (
int i = 0; i < trajectory_planning_msgs::trajectory_access::getSamplePointSize(*trajectory); ++i) {
533 if (trajectory_planning_msgs::trajectory_access::getV(*trajectory, i) >
standstill_threshold_) standstill =
false;
535 trajectory_planning_msgs::trajectory_access::setStandstill(*trajectory, standstill);
547 const char* cycle_time_color = metrics.
cycle_ms <= 100.0 ?
"\x1b[32m" :
"\x1b[31m";
548 RCLCPP_INFO(this->get_logger(),
"Published trajectory (cycle: %s%.2f ms\x1b[0m)", cycle_time_color, metrics.
cycle_ms);
552 const perception_msgs::msg::ObjectList& object_list,
553 const route_planning_msgs::msg::Route& route,
554 const trajectory_planning_msgs::msg::Trajectory& reference_trajectory) {
556 trajectory_planning_msgs::msg::Trajectory tf_reference_trajectory;
557 perception_msgs::msg::ObjectList tf_object_list;
558 route_planning_msgs::msg::Route tf_route;
561 tf_reference_trajectory =
565 if (!object_list.objects.empty() && object_list.header.frame_id !=
vehicle_frame_id_) {
569 tf_object_list = object_list;
573 if (!route.route_elements.empty() && route.header.frame_id !=
vehicle_frame_id_) {
579 }
catch (tf2::TransformException& ex) {
580 RCLCPP_WARN(this->get_logger(),
"Transformation is not available. Ex: %s", ex.what());
584 if (trajectory_planning_msgs::trajectory_access::getSamplePointSize(tf_reference_trajectory) <= 0) {
585 RCLCPP_ERROR(this->get_logger(),
"Reference trajectory contains no sample points.");
593 }
catch (
const std::exception& e) {
594 RCLCPP_ERROR(this->get_logger(),
"Exception while setting OCP parameters: %s", e.what());
602 const trajectory_planning_msgs::msg::Trajectory& reference_trajectory,
603 const route_planning_msgs::msg::Route& route) {
604 const auto start_time = std::chrono::steady_clock::now();
605 std::vector<double> global_params;
609 if (cost_weights.size() != expected_cost_weights_size) {
610 RCLCPP_ERROR(this->get_logger(),
"Size of cost_weights (%zu) does not match expected size (%zu).", cost_weights.size(),
611 expected_cost_weights_size);
612 throw std::runtime_error(
"Size of cost_weights does not match expected size.");
614 global_params.insert(global_params.end(), cost_weights.begin(), cost_weights.end());
617 global_params.push_back(
thw_);
624 std::vector<std::pair<double, double>> boundary_distances =
normalBoundaryDistance(reference_trajectory, route);
626 std::vector<double> ref;
627 for (
int i = 0; i < trajectory_planning_msgs::trajectory_access::getSamplePointSize(reference_trajectory); ++i) {
628 ref.push_back(trajectory_planning_msgs::trajectory_access::getTheta(reference_trajectory, i));
629 ref.push_back(trajectory_planning_msgs::trajectory_access::getX(reference_trajectory, i));
630 ref.push_back(trajectory_planning_msgs::trajectory_access::getY(reference_trajectory, i));
631 ref.push_back(trajectory_planning_msgs::trajectory_access::getV(reference_trajectory, i));
632 ref.push_back(boundary_distances[i].first);
633 ref.push_back(boundary_distances[i].second);
635 if (ref.size() >= n_ref_states) {
636 global_params.insert(global_params.end(), ref.begin(),
637 ref.begin() +
static_cast<std::vector<double>::difference_type
>(n_ref_states));
640 global_params.insert(global_params.end(), ref.begin(), ref.end());
642 while (global_params.size() < expected_cost_weights_size + 4 + n_ref_states) {
643 global_params.insert(global_params.end(), ref.end() -
static_cast<std::vector<double>::difference_type
>(state_width),
648 if (global_params.size() !=
static_cast<size_t>(
nlp_dims_->np_global)) {
649 RCLCPP_ERROR(this->get_logger(),
"Size of global parameters (%zu) does not match expected size (%d).", global_params.size(),
651 throw std::runtime_error(
"Size of global parameters does not match expected size.");
654 ocp_capsule_, global_params.data(),
static_cast<int>(global_params.size()));
655 if (status != ACADOS_SUCCESS) {
656 throw std::runtime_error(
"acados global parameter update failed with status " + std::to_string(status));
658 const auto elapsed_ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start_time).count();
659 RCLCPP_DEBUG(this->get_logger(),
"setOcpGlobalParameters duration: %.3f ms", elapsed_ms);
663 const perception_msgs::msg::ObjectList& object_list) {
664 const auto start_time = std::chrono::steady_clock::now();
665 struct PredictionData {
666 std::vector<double> time;
667 std::vector<double> x;
668 std::vector<double> y;
669 std::vector<double> yaw;
673 std::vector<std::vector<PredictionData>> object_predictions(object_list.objects.size());
674 const double object_stamp =
static_cast<double>(rclcpp::Time(object_list.header.stamp).nanoseconds()) / 1e9;
675 for (
size_t j = 0; j < object_list.objects.size(); ++j) {
676 const auto&
object = object_list.objects[j];
679 auto append_prediction = [&](
const auto& state_prediction,
size_t prediction_idx) {
680 PredictionData prediction{{object_stamp},
681 {perception_msgs::object_access::getX(
object)},
682 {perception_msgs::object_access::getY(
object)},
683 {perception_msgs::object_access::getYaw(
object)},
685 state_prediction.probability};
686 for (
const auto& predicted_state : state_prediction.states) {
687 prediction.time.push_back(
static_cast<double>(rclcpp::Time(predicted_state.header.stamp).nanoseconds()) / 1e9);
688 prediction.x.push_back(perception_msgs::object_access::getX(predicted_state));
689 prediction.y.push_back(perception_msgs::object_access::getY(predicted_state));
690 prediction.yaw.push_back(perception_msgs::object_access::getYaw(predicted_state));
692 object_predictions[j].push_back(std::move(prediction));
695 for (
size_t prediction_idx = 0; prediction_idx <
object.state_predictions.size(); ++prediction_idx) {
696 const auto& state_prediction =
object.state_predictions[prediction_idx];
698 append_prediction(state_prediction, prediction_idx);
701 if (object_predictions[j].empty()) append_prediction(
object.state_predictions[0], 0);
705 double floating_dynamic_weight = 1.0;
707 for (
int i = 0; i <=
n_shots_; ++i) {
711 std::vector<int> idx_dynamic_weight(n);
713 std::iota(idx_dynamic_weight.begin(), idx_dynamic_weight.end(), idx);
715 &floating_dynamic_weight, n);
716 if (status != ACADOS_SUCCESS) {
717 throw std::runtime_error(
"acados dynamic-weight update failed with status " + std::to_string(status));
724 std::vector<double> circles;
726 for (
size_t j = 0; j < object_list.objects.size(); ++j) {
727 std::vector<std::tuple<double, double, double>> target_states;
728 if (!object_predictions[j].empty()) {
729 for (
const auto& prediction : object_predictions[j]) {
730 double x_tgt = 0.0, y_tgt = 0.0, yaw_tgt = 0.0;
731 double des_time =
static_cast<double>(rclcpp::Time(ego_data.header.stamp).nanoseconds()) / 1e9 + dt * i;
732 if (des_time > prediction.time.back()) {
733 const double relative_des_time = des_time - prediction.time.front();
734 const double relative_max_time = prediction.time.back() - prediction.time.front();
735 RCLCPP_WARN(this->get_logger(),
736 "Prediction horizon shorter than requested interpolation time. "
737 "object=%zu prediction=%zu probability=%.3f desired_rel=%.3f s max_rel=%.3f s n_states=%zu. "
738 "Using last prediction state.",
739 j, prediction.index, prediction.probability, relative_des_time, relative_max_time,
740 prediction.time.size() - 1);
741 x_tgt = prediction.x.back();
742 y_tgt = prediction.y.back();
743 yaw_tgt = prediction.yaw.back();
749 target_states.emplace_back(x_tgt, y_tgt, yaw_tgt);
752 target_states.emplace_back(perception_msgs::object_access::getX(object_list.objects[j]),
753 perception_msgs::object_access::getY(object_list.objects[j]),
754 perception_msgs::object_access::getYaw(object_list.objects[j]));
757 double alpha = std::atan2(object_list.objects[j].state.reference_point.translation_to_geometric_center.y,
758 object_list.objects[j].state.reference_point.translation_to_geometric_center.x);
759 double a = std::sqrt(std::pow(object_list.objects[j].state.reference_point.translation_to_geometric_center.x, 2) +
760 std::pow(object_list.objects[j].state.reference_point.translation_to_geometric_center.y, 2));
761 for (
auto& [x_tgt, y_tgt, yaw_tgt] : target_states) {
764 x_tgt += a * std::cos(beta);
765 y_tgt += a * std::sin(beta);
767 std::vector<double> obj_circles =
768 discretizeBB2Circles(x_tgt, y_tgt, yaw_tgt, perception_msgs::object_access::getLength(object_list.objects[j]),
769 perception_msgs::object_access::getWidth(object_list.objects[j]));
771 circles.insert(circles.end(), obj_circles.begin(), obj_circles.end());
772 if (circles.size() >=
static_cast<size_t>(n)) {
777 if (circles.size() >=
static_cast<size_t>(n)) {
783 while (circles.size() <
static_cast<size_t>(n)) {
784 std::vector<double> dummy_circle = {10000.0, 10000.0, 1.0};
785 circles.insert(circles.end(), dummy_circle.begin(), dummy_circle.end());
788 RCLCPP_WARN(this->get_logger(),
"Circles vector size is not a multiple of the circle shape. Resizing.");
793 std::vector<int> idx_obstacles(n);
795 std::iota(idx_obstacles.begin(), idx_obstacles.end(), idx);
797 if (status != ACADOS_SUCCESS) {
798 throw std::runtime_error(
"acados obstacle update failed with status " + std::to_string(status));
801 const auto elapsed_ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start_time).count();
802 RCLCPP_DEBUG(this->get_logger(),
"setOcpParameters duration: %.3f ms", elapsed_ms);
static void keepNClosestObjects(perception_msgs::msg::ObjectList &object_list, const int n_objects)
Keeps the nearest forward objects and discards the remaining entries.
rclcpp::Publisher< visualization_msgs::msg::MarkerArray >::SharedPtr circles_pub_
void printSolution(const PerformanceMetrics &metrics)
Logs solver status and optional debug statistics for the last optimization run.
void setupSolver()
Creates the acados solver instance and initializes its state buffers.
trajectory_planning_msgs::msg::Trajectory reference_trajectory_
void setup()
Creates ROS interfaces, initializes cached messages and prepares the solver.
double optimization_horizon_
rclcpp::TimerBase::SharedPtr planning_timer_
std::vector< double > xtraj_
void routeCallback(const route_planning_msgs::msg::Route::ConstSharedPtr msg)
Stores the current route used for boundary constraints when enabled.
std::vector< double > cost_weights_
std::vector< int64_t > p_ref_path_shape_
void freeSolver()
Frees the acados solver and clears cached optimization results.
trajectory_planning_msgs::msg::Trajectory latest_valid_trajectory_
std::vector< double > control_guess_
uint8_t consider_boundaries_
double standstill_threshold_
perception_msgs::msg::EgoData ego_data_
void vizCircles(const std::vector< double > &obstacles)
Publishes visualization markers for the obstacle circles currently used by the optimizer.
virtual void initializeTrajectory(trajectory_planning_msgs::msg::Trajectory &trajectory)=0
Initializes an output trajectory message with the model-specific message type and size.
double min_prediction_probability_
std::string vehicle_frame_id_
static double wrap_angle_rad(double angle_rad, double min_val=-M_PI, double max_val=M_PI)
Wraps an angle into a configured interval.
uint8_t consider_objects_
bool high_level_stabilization_
std::vector< double > utraj_
ocp_nlp_solver * nlp_solver_
void setOcpParameters(const perception_msgs::msg::EgoData &ego_data, const perception_msgs::msg::ObjectList &object_list)
Writes stage-wise obstacle and dynamic weighting parameters into the OCP.
virtual void convertToTrajectoryMsg(trajectory_planning_msgs::msg::Trajectory &trajectory)=0
Maps the optimized state trajectory into the model-specific output message fields.
void objectListCallback(const perception_msgs::msg::ObjectList::ConstSharedPtr msg)
Stores the current object list when object handling is enabled.
ocp_nlp_config * nlp_config_
bool linearInterpolation(const std::vector< double > &X, const std::vector< double > &Y, const double &desired_x, double &output_y, const bool wrap_angle=false)
Interpolates a value from sampled data and optionally handles angle wrap-around.
void egoDataCallback(const perception_msgs::msg::EgoData::ConstSharedPtr msg)
Stores the latest ego state used by the optimizer.
double optimization_freq_
void vizEgoCircles(const std::vector< double > &x_trajectory, const std::string &model_name)
Publishes the ego-vehicle circle approximation used by the selected OCP model.
void planningCycle()
Runs one full planning cycle from input preparation, over solver execution, to trajectory publication...
std::vector< std::tuple< std::string, std::function< void(const rclcpp::Parameter &)> > > auto_reconfigurable_params_
bool trajectory2outputFrame(trajectory_planning_msgs::msg::Trajectory &trajectory)
Transforms the planned trajectory into the configured output frame (trajectory_frame_id_) if required...
bool updateOcpInputs(const perception_msgs::msg::EgoData &ego_data, const perception_msgs::msg::ObjectList &object_list, const route_planning_msgs::msg::Route &route, const trajectory_planning_msgs::msg::Trajectory &reference_trajectory)
Transforms external inputs into the optimizer frame and writes them into the OCP.
route_planning_msgs::msg::Route route_
std::unique_ptr< tf2_ros::Buffer > tf2_buffer_
rclcpp::Subscription< route_planning_msgs::msg::Route >::SharedPtr route_sub_
rcl_interfaces::msg::SetParametersResult parametersCallback(const std::vector< rclcpp::Parameter > ¶meters)
Applies parameter updates that can be reconfigured while the node is running.
OnSetParametersCallbackHandle::SharedPtr parameters_callback_
bool performance_logging_
std::vector< double > viz_circles_
rclcpp::Publisher< visualization_msgs::msg::MarkerArray >::SharedPtr boundary_pub_
std::vector< std::pair< double, double > > normalBoundaryDistance(const trajectory_planning_msgs::msg::Trajectory &reference_trajectory, const route_planning_msgs::msg::Route &route)
Computes minimum normal distances from the reference path to the active route boundaries.
ocp_model_capsule_t ocp_capsule_
perception_msgs::msg::ObjectList object_list_
std::string trajectory_frame_id_
virtual std::vector< double > getBiLevelX0(const perception_msgs::msg::EgoData &ego_data)=0
Computes the initial optimizer state using bi-level stabilizaion based on the model-specific EgoData ...
rclcpp::Subscription< perception_msgs::msg::EgoData >::SharedPtr ego_data_sub_
TrajectoryOptimizationNode(const std::string node_name, const rclcpp::NodeOptions &options)
Initializes the base node and loads the common optimizer configuration.
rclcpp::Publisher< visualization_msgs::msg::MarkerArray >::SharedPtr ego_circles_pub_
void declareAndLoadParameter(const std::string &name, T ¶m, const std::string &description, const bool add_to_auto_reconfigurable_params=true, const bool is_required=false, const bool read_only=false, const std::optional< double > &from_value=std::nullopt, const std::optional< double > &to_value=std::nullopt, const std::optional< double > &step_value=std::nullopt, const std::string &additional_constraints="")
Declares a ROS parameter, loads its value and optionally registers it for runtime updates.
rclcpp::Subscription< perception_msgs::msg::ObjectList >::SharedPtr object_list_sub_
std::string fixed_over_time_frame_id_
rclcpp::Time control_guess_stamp_
std::unique_ptr< PerformanceLogger > performance_logger_
void referenceTrajectoryCallback(const trajectory_planning_msgs::msg::Trajectory::ConstSharedPtr msg)
Updates the reference trajectory and optionally triggers optimization immediately.
~TrajectoryOptimizationNode() override
Releases solver resources owned by the node.
rclcpp::Publisher< trajectory_planning_msgs::msg::Trajectory >::SharedPtr trajectory_pub_
std::vector< int64_t > p_cost_weights_shape_
std::shared_ptr< tf2_ros::TransformListener > tf2_listener_
std::vector< int64_t > p_obstacle_circles_shape_
double d_min_obstacle_long_
std::vector< double > discretizeBB2Circles(const double x, const double y, const double yaw, const double length, const double width)
Approximates an oriented bounding box with a set of obstacle circles.
rclcpp::Subscription< trajectory_planning_msgs::msg::Trajectory >::SharedPtr reference_trajectory_sub_
double d_min_boundary_lat_
double d_min_obstacle_lat_
void setOcpGlobalParameters(const std::vector< double > &cost_weights, const trajectory_planning_msgs::msg::Trajectory &reference_trajectory, const route_planning_msgs::msg::Route &route)
Writes stage-independent data into the OCP.
bool setInitialGuess(const std::vector< double > &x_init, const rclcpp::Time &stamp)
Builds and sets a dynamically consistent NLP initial guess from the current state and cached controls...
void resetSolver()
Resets the optimizer memory while retaining the generated solver instance.
virtual std::vector< double > getHighLevelX0(const perception_msgs::msg::EgoData &ego_data)=0
Computes the initial optimizer state using high-level stabilization based on the model-specific EgoDa...
Namespace for trajectory_optimization package.
int acados_solve(ocp_model_capsule_t capsule)
Wrapper around the generated acados solve function.
int acados_free(ocp_model_capsule_t capsule)
Wrapper around the generated acados solver cleanup function.
ocp_nlp_solver * acados_get_nlp_solver(ocp_model_capsule_t capsule)
Wrapper around the generated accessor for the OCP solver instance.
ocp_nlp_out * acados_get_nlp_out(ocp_model_capsule_t capsule)
Wrapper around the generated accessor for the OCP output structure.
ocp_nlp_config * acados_get_nlp_config(ocp_model_capsule_t capsule)
Wrapper around the generated accessor for the OCP configuration.
int acados_create_with_discretization(ocp_model_capsule_t capsule, int n_time_steps, double *new_time_steps)
Wrapper around the generated acados create function with custom discretization.
void * acados_get_nlp_opts(ocp_model_capsule_t capsule)
Wrapper around the generated accessor for the OCP solver options.
int acados_free_capsule(ocp_model_capsule_t capsule)
Wrapper around the generated acados capsule cleanup function.
int acados_update_params_sparse(ocp_model_capsule_t capsule, int stage, int *idx, double *p, int n_update)
Wrapper around the generated sparse parameter update function.
constexpr bool is_vector_v
ocp_model_capsule_t acados_create_capsule(const std::string &model_name)
Creates the model-specific acados capsule selected by the configured model name.
ocp_nlp_dims * acados_get_nlp_dims(ocp_model_capsule_t capsule)
Wrapper around the generated accessor for the OCP dimensions.
int acados_reset(ocp_model_capsule_t capsule, int reset_qp_solver_mem, int reset_numerical_values, int reset_solver_options, int reset_x_to_x0_bar)
Resets selected solver memory without freeing and recreating the capsule.
const ocp_nlp_opts * acados_get_common_nlp_opts(ocp_nlp_config *config, void *solver_opts)
Returns the common NLP options stored inside the generated solver-specific options.
ocp_nlp_in * acados_get_nlp_in(ocp_model_capsule_t capsule)
Wrapper around the generated accessor for the OCP input structure.
void acados_evaluate_dynamics(ocp_model_capsule_t capsule, int stage, double *x, double *u, double *x_dot)
Evaluates the explicit model dynamics for a shooting stage.
int acados_set_p_global_and_precompute_dependencies(ocp_model_capsule_t capsule, double *data, int data_len)
Wrapper around the generated global-parameter update function.