18using SteadyClock = std::chrono::steady_clock;
20double elapsedMilliseconds(
const SteadyClock::time_point& start) {
21 return std::chrono::duration<double, std::milli>(SteadyClock::now() - start).count();
24double elapsedMilliseconds(
const SteadyClock::time_point& start,
const SteadyClock::time_point& end) {
25 return std::chrono::duration<double, std::milli>(end - start).count();
31 : rclcpp::Node(node_name, options) {
34 "Frame ID of local vehicle frame (the ocp is defined in this frame)");
37 "Frame ID of frame that is fixed over time for finding temporal transforms");
39 "Time after which a received ego vehicle data is considered invalid [s]. Optimization will not "
40 "be run if ego data is invalid.");
42 "Name of the model to be used for trajectory optimization [karl, shuttle]");
48 "Write one CSV record for every completed solver run",
false,
false,
true);
51 "Run OCP once for each received reference trajectory (true) or on a timer (false)");
56 "Minimum distance to keep to obstacle in longitudinal direction [m]");
58 "Minimum distance to keep to obstacle in lateral direction [m]");
60 "Minimum distance to keep to boundary in lateral direction [m]");
62 "Threshold for standstill detection [m/s]. If the velocities of all states are below this "
63 "threshold, publish standstill trajectory");
65 "Use high-level stabilization strategy for init state (= init with current EgoData)");
68 "add initial state of OCP to beginning of reference trajectory if this starts in front of ego vehicle");
71 "consider objects in optimization: 0 = none, 1 = static (no prediction), 2 = dynamic (with prediction)");
73 "Minimum probability for predicted object states to be considered",
true,
false,
false, 0.0, 1.0);
76 "consider route boundaries in optimization: 0 = no, 1 = suggested lane, 2 = including adjacent, 3 = drivable space");
78 "Threshold for bi-level stabilization: maximum velocity difference [m/s]");
80 "Threshold for bi-level stabilization: maximum acceleration difference [m/s^2]");
83 "Threshold for bi-level stabilization: maximum yaw difference [degree]");
87 RCLCPP_INFO(get_logger(),
"Writing benchmark logs to '%s'.",
performance_logger_->path().c_str());
88 }
catch (
const std::exception& error) {
89 RCLCPP_ERROR(get_logger(),
"Could not initialize benchmark logging: %s", error.what());
100 const std::string& description,
101 const bool add_to_auto_reconfigurable_params,
102 const bool is_required,
103 const bool read_only,
104 const std::optional<double>& from_value,
105 const std::optional<double>& to_value,
106 const std::optional<double>& step_value,
107 const std::string& additional_constraints) {
108 rcl_interfaces::msg::ParameterDescriptor param_desc;
109 param_desc.description = description;
110 param_desc.additional_constraints = additional_constraints;
111 param_desc.read_only = read_only;
113 auto type = rclcpp::ParameterValue(param).get_type();
115 if (from_value.has_value() && to_value.has_value()) {
116 if constexpr (std::is_integral_v<T>) {
117 rcl_interfaces::msg::IntegerRange range;
118 range.set__from_value(
static_cast<T
>(from_value.value())).set__to_value(
static_cast<T
>(to_value.value()));
119 if (step_value.has_value()) range.set__step(
static_cast<T
>(step_value.value()));
120 param_desc.integer_range = {range};
121 }
else if constexpr (std::is_floating_point_v<T>) {
122 rcl_interfaces::msg::FloatingPointRange range;
123 range.set__from_value(
static_cast<T
>(from_value.value())).set__to_value(
static_cast<T
>(to_value.value()));
124 if (step_value.has_value()) range.set__step(
static_cast<T
>(step_value.value()));
125 param_desc.floating_point_range = {range};
127 RCLCPP_WARN(this->get_logger(),
"Parameter type of parameter '%s' does not support specifying a range", name.c_str());
131 this->declare_parameter(name, type, param_desc);
134 param = this->get_parameter(name).get_value<T>();
135 std::stringstream ss;
136 ss <<
"Loaded parameter '" << name <<
"': ";
139 for (
const auto& element : param) ss << element << (&element != ¶m.back() ?
", " :
"");
144 RCLCPP_INFO_STREAM(this->get_logger(), ss.str());
145 }
catch (rclcpp::exceptions::ParameterUninitializedException&) {
147 RCLCPP_FATAL_STREAM(this->get_logger(),
"Missing required parameter '" << name <<
"', exiting");
150 std::stringstream ss;
151 ss <<
"Missing parameter '" << name <<
"', using default value: ";
154 for (
const auto& element : param) ss << element << (&element != ¶m.back() ?
", " :
"");
159 RCLCPP_WARN_STREAM(this->get_logger(), ss.str());
160 this->set_parameters({rclcpp::Parameter(name, rclcpp::ParameterValue(param))});
164 if (add_to_auto_reconfigurable_params) {
165 std::function<void(
const rclcpp::Parameter&)> setter = [¶m](
const rclcpp::Parameter& p) { param = p.get_value<T>(); };
171 const std::vector<rclcpp::Parameter>& parameters) {
172 for (
const auto& param : parameters) {
174 if (param.get_name() == std::get<0>(auto_reconfigurable_param)) {
175 std::get<1>(auto_reconfigurable_param)(param);
176 RCLCPP_INFO(this->get_logger(),
"Reconfigured parameter '%s' to: %s", param.get_name().c_str(),
177 param.value_to_string().c_str());
182 if (param.get_name() ==
"run_as_callback") {
186 RCLCPP_WARN(this->get_logger(),
"OCP runs now periodically with frequency %f Hz",
optimization_freq_);
190 RCLCPP_WARN(this->get_logger(),
"OCP runs now on reference trajectory callback");
195 rcl_interfaces::msg::SetParametersResult result;
196 result.successful =
true;
202 tf2_buffer_ = std::make_unique<tf2_ros::Buffer>(this->get_clock());
210 ego_data_sub_ = this->create_subscription<perception_msgs::msg::EgoData>(
212 RCLCPP_INFO(this->get_logger(),
"Subscribed to '%s'",
ego_data_sub_->get_topic_name());
214 object_list_sub_ = this->create_subscription<perception_msgs::msg::ObjectList>(
216 RCLCPP_INFO(this->get_logger(),
"Subscribed to '%s'",
object_list_sub_->get_topic_name());
218 route_sub_ = this->create_subscription<route_planning_msgs::msg::Route>(
220 RCLCPP_INFO(this->get_logger(),
"Subscribed to '%s'",
route_sub_->get_topic_name());
223 "~/reference_trajectory", 1,
228 trajectory_pub_ = this->create_publisher<trajectory_planning_msgs::msg::Trajectory>(
"~/trajectory", 1);
229 RCLCPP_INFO(this->get_logger(),
"Publishing to '%s'",
trajectory_pub_->get_topic_name());
230 circles_pub_ = this->create_publisher<visualization_msgs::msg::MarkerArray>(
"~/visualization/object_circles", 1);
231 RCLCPP_INFO(this->get_logger(),
"Publishing to '%s'",
circles_pub_->get_topic_name());
232 ego_circles_pub_ = this->create_publisher<visualization_msgs::msg::MarkerArray>(
"~/visualization/ego_circles", 1);
233 RCLCPP_INFO(this->get_logger(),
"Publishing to '%s'",
ego_circles_pub_->get_topic_name());
234 boundary_pub_ = this->create_publisher<visualization_msgs::msg::MarkerArray>(
"~/visualization/boundaries", 1);
235 RCLCPP_INFO(this->get_logger(),
"Publishing to '%s'",
boundary_pub_->get_topic_name());
239 RCLCPP_INFO(this->get_logger(),
"OCP runs on reference trajectory callback");
241 RCLCPP_INFO(this->get_logger(),
"OCP runs continuously with frequency %f Hz",
optimization_freq_);
247 trajectory_planning_msgs::trajectory_access::initializeTrajectory(
252 trajectory_planning_msgs::trajectory_access::initializeTrajectory(
259 std::vector<const void*> link_subs;
260 link_subs.push_back(
static_cast<const void*
>(
ego_data_sub_->get_subscription_handle().get()));
261 link_subs.push_back(
static_cast<const void*
>(
object_list_sub_->get_subscription_handle().get()));
262 link_subs.push_back(
static_cast<const void*
>(
route_sub_->get_subscription_handle().get()));
264 std::vector<const void*> link_pubs;
265 link_pubs.push_back(
static_cast<const void*
>(
trajectory_pub_->get_publisher_handle().get()));
266 RCLCPP_INFO(get_logger(),
"Annotating message links for tracing with %zu subscriptions and %zu publications", link_subs.size(),
268 TRACETOOLS_TRACEPOINT(message_link_periodic_async, link_subs.data(), link_subs.size(), link_pubs.data(), link_pubs.size());
273 RCLCPP_FATAL(this->get_logger(),
"n_shots must be > 0, got %d",
n_shots_);
281 new_time_steps.front());
284 if (status != ACADOS_SUCCESS) {
285 RCLCPP_FATAL(this->get_logger(),
"%s_acados_create_with_discretization() returned status %d. Exiting.",
model_name_.c_str(),
297 RCLCPP_FATAL(this->get_logger(),
"Created solver has N=%d, expected n_shots=%d. Exiting.",
nlp_dims_->N,
n_shots_);
310 RCLCPP_ERROR(this->get_logger(),
"%s_acados_free() returned status %d.",
model_name_.c_str(), status);
315 RCLCPP_ERROR(this->get_logger(),
"%s_acados_free_capsule() returned status %d.",
model_name_.c_str(), status);
321 if (status != ACADOS_SUCCESS) {
322 RCLCPP_ERROR(this->get_logger(),
"%s_acados_reset() returned status %d. Recreating solver.",
model_name_.c_str(), status);
329 constexpr double MAX_CONTROL_GUESS_AGE_FACTOR = 0.5;
333 const size_t expected_control_size =
static_cast<size_t>(nu *
n_shots_);
334 if (x_init.size() !=
static_cast<size_t>(nx)) {
335 RCLCPP_ERROR(get_logger(),
"Initial state has size %zu, expected %d.", x_init.size(), nx);
340 std::vector<double> controls(expected_control_size, 0.0);
345 for (
int stage = 0; stage <
n_shots_; ++stage) {
346 const double previous_stage = (elapsed + stage * time_step) / time_step;
347 if (previous_stage >=
n_shots_)
break;
349 const int lower_stage =
static_cast<int>(std::floor(previous_stage));
350 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 controls[stage * nu + control] = lower_value + interpolation_factor * (upper_value - lower_value);
361 std::vector<double> rollout_state = x_init;
362 std::vector<double> intermediate_state(nx);
363 std::vector<double> k1(nx), k2(nx), k3(nx), k4(nx);
364 const double integration_step = time_step / 2.0;
365 for (
int stage = 0; stage <
n_shots_; ++stage) {
366 double* control = &controls[stage * nu];
371 for (
int integration = 0; integration < 2; ++integration) {
373 for (
int i = 0; i < nx; ++i) intermediate_state[i] = rollout_state[i] + 0.5 * integration_step * k1[i];
375 for (
int i = 0; i < nx; ++i) intermediate_state[i] = rollout_state[i] + 0.5 * integration_step * k2[i];
377 for (
int i = 0; i < nx; ++i) intermediate_state[i] = rollout_state[i] + integration_step * k3[i];
379 for (
int i = 0; i < nx; ++i) {
380 rollout_state[i] += integration_step / 6.0 * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]);
389 const auto cycle_start = SteadyClock::now();
392 RCLCPP_WARN(this->get_logger(),
"EgoData outdated. Skipping planning cycle.");
396 trajectory_planning_msgs::msg::Trajectory::UniquePtr trajectory = std::make_unique<trajectory_planning_msgs::msg::Trajectory>();
400 trajectory->header.stamp =
ego_data_.header.stamp;
404 for (
int i = 0; i <=
n_shots_; ++i) trajectory_planning_msgs::trajectory_access::setT(*trajectory, i * dt, i);
408 RCLCPP_WARN(this->get_logger(),
"Standstill trajectory. Skipping planning cycle. Publish standstill trajectory.");
413 trajectory_planning_msgs::trajectory_access::setStandstill(*trajectory,
true);
430 auto logCompletedCycle = [&]() {
431 metrics.
cycle_ms = elapsedMilliseconds(cycle_start);
437 std::vector<double> x_init(*
nlp_dims_->nx, 0.0);
441 RCLCPP_WARN(this->get_logger(),
442 "Latest available trajectory is standstill. Using ego data for initial state (high-level initialization).");
447 std::stringstream ss;
448 ss <<
"Initial state: ";
449 for (
size_t i = 0; i < x_init.size(); ++i) ss <<
"x[" << i <<
"]: " << x_init[i] << (i != x_init.size() - 1 ?
", " :
"");
450 RCLCPP_DEBUG(this->get_logger(),
"%s", ss.str().c_str());
457 RCLCPP_WARN(this->get_logger(),
"Failed to update inputs. Skipping planning cycle.");
467 const auto solve_start = SteadyClock::now();
470 const auto solve_end = SteadyClock::now();
471 metrics.
solve_wall_ms = elapsedMilliseconds(solve_start, solve_end);
475 if (metrics.
status == ACADOS_NAN_DETECTED || metrics.
status == ACADOS_MINSTEP || metrics.
status == ACADOS_QP_FAILURE) {
484 for (
int ii = 0; ii <=
nlp_dims_->N; ++ii) {
487 for (
int ii = 0; ii <
nlp_dims_->N; ++ii) {
502 bool standstill =
true;
503 for (
int i = 0; i < trajectory_planning_msgs::trajectory_access::getSamplePointSize(*trajectory); ++i) {
504 if (trajectory_planning_msgs::trajectory_access::getV(*trajectory, i) >
standstill_threshold_) standstill =
false;
506 trajectory_planning_msgs::trajectory_access::setStandstill(*trajectory, standstill);
518 const char* cycle_time_color = metrics.
cycle_ms <= 100.0 ?
"\x1b[32m" :
"\x1b[31m";
519 RCLCPP_INFO(this->get_logger(),
"Published trajectory (cycle: %s%.2f ms\x1b[0m)", cycle_time_color, metrics.
cycle_ms);
523 const perception_msgs::msg::ObjectList& object_list,
524 const route_planning_msgs::msg::Route& route,
525 const trajectory_planning_msgs::msg::Trajectory& reference_trajectory,
526 const std::vector<double>& x_init) {
528 trajectory_planning_msgs::msg::Trajectory tf_reference_trajectory;
529 perception_msgs::msg::ObjectList tf_object_list;
530 route_planning_msgs::msg::Route tf_route;
533 tf_reference_trajectory =
536 if (
add_x_init_to_ref_ && trajectory_planning_msgs::trajectory_access::getX(tf_reference_trajectory, 0) > 0.0) {
537 RCLCPP_INFO(this->get_logger(),
"Adding x_init to beginning of reference trajectory");
538 std::vector<double> x_0_ref = {0.0, x_init[0], x_init[1], x_init[3]};
539 tf_reference_trajectory.states.insert(tf_reference_trajectory.states.begin(), x_0_ref.begin(), x_0_ref.end());
542 if (!object_list.objects.empty() && object_list.header.frame_id !=
vehicle_frame_id_) {
546 tf_object_list = object_list;
550 if (!route.route_elements.empty() && route.header.frame_id !=
vehicle_frame_id_) {
556 }
catch (tf2::TransformException& ex) {
557 RCLCPP_WARN(this->get_logger(),
"Transformation is not available. Ex: %s", ex.what());
561 if (trajectory_planning_msgs::trajectory_access::getSamplePointSize(tf_reference_trajectory) <= 0) {
562 RCLCPP_ERROR(this->get_logger(),
"Reference trajectory contains no sample points.");
570 }
catch (
const std::exception& e) {
571 RCLCPP_ERROR(this->get_logger(),
"Exception while setting OCP parameters: %s", e.what());
579 const trajectory_planning_msgs::msg::Trajectory& reference_trajectory,
580 const route_planning_msgs::msg::Route& route) {
581 const auto start_time = std::chrono::steady_clock::now();
582 std::vector<double> global_params;
586 if (cost_weights.size() != expected_cost_weights_size) {
587 RCLCPP_ERROR(this->get_logger(),
"Size of cost_weights (%zu) does not match expected size (%zu).", cost_weights.size(),
588 expected_cost_weights_size);
589 throw std::runtime_error(
"Size of cost_weights does not match expected size.");
591 global_params.insert(global_params.end(), cost_weights.begin(), cost_weights.end());
594 global_params.push_back(
thw_);
601 std::vector<std::pair<double, double>> boundary_distances =
normalBoundaryDistance(reference_trajectory, route);
603 std::vector<double> ref;
604 for (
int i = 0; i < trajectory_planning_msgs::trajectory_access::getSamplePointSize(reference_trajectory); ++i) {
605 ref.push_back(trajectory_planning_msgs::trajectory_access::getTheta(reference_trajectory, i));
606 ref.push_back(trajectory_planning_msgs::trajectory_access::getX(reference_trajectory, i));
607 ref.push_back(trajectory_planning_msgs::trajectory_access::getY(reference_trajectory, i));
608 ref.push_back(trajectory_planning_msgs::trajectory_access::getV(reference_trajectory, i));
609 ref.push_back(boundary_distances[i].first);
610 ref.push_back(boundary_distances[i].second);
612 if (ref.size() >= n_ref_states) {
613 global_params.insert(global_params.end(), ref.begin(),
614 ref.begin() +
static_cast<std::vector<double>::difference_type
>(n_ref_states));
617 global_params.insert(global_params.end(), ref.begin(), ref.end());
619 while (global_params.size() < expected_cost_weights_size + 4 + n_ref_states) {
620 global_params.insert(global_params.end(), ref.end() -
static_cast<std::vector<double>::difference_type
>(state_width),
625 if (global_params.size() !=
static_cast<size_t>(
nlp_dims_->np_global)) {
626 RCLCPP_ERROR(this->get_logger(),
"Size of global parameters (%zu) does not match expected size (%d).", global_params.size(),
628 throw std::runtime_error(
"Size of global parameters does not match expected size.");
631 ocp_capsule_, global_params.data(),
static_cast<int>(global_params.size()));
632 if (status != ACADOS_SUCCESS) {
633 throw std::runtime_error(
"acados global parameter update failed with status " + std::to_string(status));
635 const auto elapsed_ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start_time).count();
636 RCLCPP_DEBUG(this->get_logger(),
"setOcpGlobalParameters duration: %.3f ms", elapsed_ms);
640 const perception_msgs::msg::ObjectList& object_list) {
641 const auto start_time = std::chrono::steady_clock::now();
643 double floating_dynamic_weight = 1.0;
645 for (
int i = 0; i <=
n_shots_; ++i) {
649 std::vector<int> idx_dynamic_weight(n);
651 std::iota(idx_dynamic_weight.begin(), idx_dynamic_weight.end(), idx);
653 &floating_dynamic_weight, n);
654 if (status != ACADOS_SUCCESS) {
655 throw std::runtime_error(
"acados dynamic-weight update failed with status " + std::to_string(status));
662 std::vector<double> circles;
664 for (
size_t j = 0; j < object_list.objects.size(); ++j) {
665 std::vector<double> TIME, X, Y, YAW;
666 std::vector<std::tuple<double, double, double>> target_states;
668 TIME.push_back(
static_cast<double>(rclcpp::Time(object_list.header.stamp).nanoseconds()) / 1e9);
669 X.push_back(perception_msgs::object_access::getX(object_list.objects[j]));
670 Y.push_back(perception_msgs::object_access::getY(object_list.objects[j]));
671 YAW.push_back(perception_msgs::object_access::getYaw(object_list.objects[j]));
674 auto appendPredictionTargetState = [&](
const auto& state_prediction,
size_t prediction_idx) {
675 std::vector<double> prediction_time = TIME;
676 std::vector<double> prediction_x = X;
677 std::vector<double> prediction_y = Y;
678 std::vector<double> prediction_yaw = YAW;
679 for (
const auto& predicted_state : state_prediction.states) {
680 prediction_time.push_back(
static_cast<double>(rclcpp::Time(predicted_state.header.stamp).nanoseconds()) / 1e9);
681 prediction_x.push_back(perception_msgs::object_access::getX(predicted_state));
682 prediction_y.push_back(perception_msgs::object_access::getY(predicted_state));
683 prediction_yaw.push_back(perception_msgs::object_access::getYaw(predicted_state));
685 double x_tgt = 0.0, y_tgt = 0.0, yaw_tgt = 0.0;
686 double des_time =
static_cast<double>(rclcpp::Time(ego_data.header.stamp).nanoseconds()) / 1e9 + dt * i;
687 if (des_time > prediction_time.back()) {
688 const double relative_des_time = des_time - prediction_time.front();
689 const double relative_max_time = prediction_time.back() - prediction_time.front();
690 RCLCPP_WARN(this->get_logger(),
691 "Prediction horizon shorter than requested interpolation time. "
692 "object=%zu prediction=%zu probability=%.3f desired_rel=%.3f s max_rel=%.3f s n_states=%zu. "
693 "Using last prediction state.",
694 j, prediction_idx, state_prediction.probability, relative_des_time, relative_max_time,
695 state_prediction.states.size());
696 x_tgt = prediction_x.back();
697 y_tgt = prediction_y.back();
698 yaw_tgt = prediction_yaw.back();
704 target_states.emplace_back(x_tgt, y_tgt, yaw_tgt);
708 bool has_prediction_above_threshold =
false;
709 for (
size_t prediction_idx = 0; prediction_idx < object_list.objects[j].state_predictions.size(); ++prediction_idx) {
710 const auto& state_prediction = object_list.objects[j].state_predictions[prediction_idx];
714 has_prediction_above_threshold =
true;
715 appendPredictionTargetState(state_prediction, prediction_idx);
717 if (!has_prediction_above_threshold) {
719 appendPredictionTargetState(object_list.objects[j].state_predictions[0], 0);
723 target_states.emplace_back(X.front(), Y.front(), YAW.front());
726 double alpha = std::atan2(object_list.objects[j].state.reference_point.translation_to_geometric_center.y,
727 object_list.objects[j].state.reference_point.translation_to_geometric_center.x);
728 double a = std::sqrt(std::pow(object_list.objects[j].state.reference_point.translation_to_geometric_center.x, 2) +
729 std::pow(object_list.objects[j].state.reference_point.translation_to_geometric_center.y, 2));
730 for (
auto& [x_tgt, y_tgt, yaw_tgt] : target_states) {
733 x_tgt += a * std::cos(beta);
734 y_tgt += a * std::sin(beta);
736 std::vector<double> obj_circles =
737 discretizeBB2Circles(x_tgt, y_tgt, yaw_tgt, perception_msgs::object_access::getLength(object_list.objects[j]),
738 perception_msgs::object_access::getWidth(object_list.objects[j]));
740 circles.insert(circles.end(), obj_circles.begin(), obj_circles.end());
741 if (circles.size() >=
static_cast<size_t>(n)) {
746 if (circles.size() >=
static_cast<size_t>(n)) {
752 while (circles.size() <
static_cast<size_t>(n)) {
753 std::vector<double> dummy_circle = {10000.0, 10000.0, 1.0};
754 circles.insert(circles.end(), dummy_circle.begin(), dummy_circle.end());
757 RCLCPP_WARN(this->get_logger(),
"Circles vector size is not a multiple of the circle shape. Resizing.");
762 std::vector<int> idx_obstacles(n);
764 std::iota(idx_obstacles.begin(), idx_obstacles.end(), idx);
766 if (status != ACADOS_SUCCESS) {
767 throw std::runtime_error(
"acados obstacle update failed with status " + std::to_string(status));
770 const auto elapsed_ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start_time).count();
771 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.
void logPerformance(const PerformanceMetrics &metrics)
Emits one machine-readable performance record when performance logging is enabled.
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.
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, const std::vector< double > &x_init)
Transforms external inputs into the optimizer frame and writes them into the OCP.
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...
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.
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.