trajectory_optimization v1.3.1
Loading...
Searching...
No Matches
trajectory_optimization_node.cpp
Go to the documentation of this file.
1// Copyright Institute for Automotive Engineering (ika), RWTH Aachen University
2// SPDX-License-Identifier: Apache-2.0
3
4#include <chrono>
5#include <cmath>
6#include <functional>
7#include <thread>
8
10
16
17namespace {
18using SteadyClock = std::chrono::steady_clock;
19
20double elapsedMilliseconds(const SteadyClock::time_point& start) {
21 return std::chrono::duration<double, std::milli>(SteadyClock::now() - start).count();
22}
23
24double elapsedMilliseconds(const SteadyClock::time_point& start, const SteadyClock::time_point& end) {
25 return std::chrono::duration<double, std::milli>(end - start).count();
26}
27
28} // namespace
29
30TrajectoryOptimizationNode::TrajectoryOptimizationNode(const std::string node_name, const rclcpp::NodeOptions& options)
31 : rclcpp::Node(node_name, options) {
32 // declare and load node parameters
33 this->declareAndLoadParameter("vehicle_frame_id", vehicle_frame_id_,
34 "Frame ID of local vehicle frame (the ocp is defined in this frame)");
35 this->declareAndLoadParameter("trajectory_frame_id", trajectory_frame_id_, "Frame ID of output trajectory");
36 this->declareAndLoadParameter("fixed_over_time_frame_id", fixed_over_time_frame_id_,
37 "Frame ID of frame that is fixed over time for finding temporal transforms");
38 this->declareAndLoadParameter("ego_data_timeout", ego_data_timeout_,
39 "Time after which a received ego vehicle data is considered invalid [s]. Optimization will not "
40 "be run if ego data is invalid.");
41 this->declareAndLoadParameter("model_name", model_name_,
42 "Name of the model to be used for trajectory optimization [karl, shuttle]");
43 this->declareAndLoadParameter("optimization_frequency", optimization_freq_, "Optimization frequency in Hz");
44 this->declareAndLoadParameter("n_shots", n_shots_, "Number of shooting intervals in optimization horizon");
45 this->declareAndLoadParameter("optimization_horizon", optimization_horizon_, "Optimization Horizon in seconds");
46 this->declareAndLoadParameter("verbose", verbose_, "Print solver statistics");
47 this->declareAndLoadParameter("performance_logging", performance_logging_,
48 "Write one CSV record for every completed solver run", false, false, true);
49 this->declareAndLoadParameter("debug_visualization", debug_viz_, "Publish debug visualization markers (e.g. obstacle circles)");
50 this->declareAndLoadParameter("run_as_callback", run_as_callback_,
51 "Run OCP once for each received reference trajectory (true) or on a timer (false)");
52 this->declareAndLoadParameter("cost_weights", cost_weights_, "Cost function weights");
53 this->declareAndLoadParameter("dynamic_weight", dynamic_weight_, "Dynamic weight alpha");
54 this->declareAndLoadParameter("thw", thw_, "Time headway to front vehicle");
55 this->declareAndLoadParameter("d_min_obstacle_long", d_min_obstacle_long_,
56 "Minimum distance to keep to obstacle in longitudinal direction [m]");
57 this->declareAndLoadParameter("d_min_obstacle_lat", d_min_obstacle_lat_,
58 "Minimum distance to keep to obstacle in lateral direction [m]");
59 this->declareAndLoadParameter("d_min_boundary_lat", d_min_boundary_lat_,
60 "Minimum distance to keep to boundary in lateral direction [m]");
61 this->declareAndLoadParameter("standstill_threshold", standstill_threshold_,
62 "Threshold for standstill detection [m/s]. If the velocities of all states are below this "
63 "threshold, publish standstill trajectory");
64 this->declareAndLoadParameter("high_level_stabilization", high_level_stabilization_,
65 "Use high-level stabilization strategy for init state (= init with current EgoData)");
67 "add_x_init_to_ref", add_x_init_to_ref_,
68 "add initial state of OCP to beginning of reference trajectory if this starts in front of ego vehicle");
70 "consider_objects", consider_objects_,
71 "consider objects in optimization: 0 = none, 1 = static (no prediction), 2 = dynamic (with prediction)");
72 this->declareAndLoadParameter("min_prediction_probability", min_prediction_probability_,
73 "Minimum probability for predicted object states to be considered", true, false, false, 0.0, 1.0);
75 "consider_boundaries", consider_boundaries_,
76 "consider route boundaries in optimization: 0 = no, 1 = suggested lane, 2 = including adjacent, 3 = drivable space");
77 this->declareAndLoadParameter("bi_level_dV", bi_level_dV_,
78 "Threshold for bi-level stabilization: maximum velocity difference [m/s]");
79 this->declareAndLoadParameter("bi_level_dA", bi_level_dA_,
80 "Threshold for bi-level stabilization: maximum acceleration difference [m/s^2]");
81 this->declareAndLoadParameter("bi_level_dY", bi_level_dY_, "Threshold for bi-level stabilization: maximum y-offset [m]");
82 this->declareAndLoadParameter("bi_level_dYaw", bi_level_dYaw_,
83 "Threshold for bi-level stabilization: maximum yaw difference [degree]");
85 try {
86 performance_logger_ = std::make_unique<PerformanceLogger>(get_name());
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());
90 }
91 }
92 this->setup();
93}
94
96
97template <typename T>
99 T& param,
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;
112
113 auto type = rclcpp::ParameterValue(param).get_type();
114
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};
126 } else {
127 RCLCPP_WARN(this->get_logger(), "Parameter type of parameter '%s' does not support specifying a range", name.c_str());
128 }
129 }
130
131 this->declare_parameter(name, type, param_desc);
132
133 try {
134 param = this->get_parameter(name).get_value<T>();
135 std::stringstream ss;
136 ss << "Loaded parameter '" << name << "': ";
137 if constexpr (is_vector_v<T>) {
138 ss << "[";
139 for (const auto& element : param) ss << element << (&element != &param.back() ? ", " : "");
140 ss << "]";
141 } else {
142 ss << param;
143 }
144 RCLCPP_INFO_STREAM(this->get_logger(), ss.str());
145 } catch (rclcpp::exceptions::ParameterUninitializedException&) {
146 if (is_required) {
147 RCLCPP_FATAL_STREAM(this->get_logger(), "Missing required parameter '" << name << "', exiting");
148 exit(EXIT_FAILURE);
149 } else {
150 std::stringstream ss;
151 ss << "Missing parameter '" << name << "', using default value: ";
152 if constexpr (is_vector_v<T>) {
153 ss << "[";
154 for (const auto& element : param) ss << element << (&element != &param.back() ? ", " : "");
155 ss << "]";
156 } else {
157 ss << param;
158 }
159 RCLCPP_WARN_STREAM(this->get_logger(), ss.str());
160 this->set_parameters({rclcpp::Parameter(name, rclcpp::ParameterValue(param))});
161 }
162 }
163
164 if (add_to_auto_reconfigurable_params) {
165 std::function<void(const rclcpp::Parameter&)> setter = [&param](const rclcpp::Parameter& p) { param = p.get_value<T>(); };
166 auto_reconfigurable_params_.push_back(std::make_tuple(name, setter));
167 }
168}
169
170rcl_interfaces::msg::SetParametersResult TrajectoryOptimizationNode::parametersCallback(
171 const std::vector<rclcpp::Parameter>& parameters) {
172 for (const auto& param : parameters) {
173 for (auto& auto_reconfigurable_param : auto_reconfigurable_params_) {
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());
178 break;
179 }
180 }
181 // handle special cases
182 if (param.get_name() == "run_as_callback") {
184 planning_timer_ = this->create_wall_timer(std::chrono::duration<double>(1 / optimization_freq_),
186 RCLCPP_WARN(this->get_logger(), "OCP runs now periodically with frequency %f Hz", optimization_freq_);
187 } else if (run_as_callback_ && planning_timer_) {
188 planning_timer_->cancel();
189 planning_timer_.reset();
190 RCLCPP_WARN(this->get_logger(), "OCP runs now on reference trajectory callback");
191 }
192 }
193 }
194 // mark parameter change successful
195 rcl_interfaces::msg::SetParametersResult result;
196 result.successful = true;
197
198 return result;
199}
200
202 tf2_buffer_ = std::make_unique<tf2_ros::Buffer>(this->get_clock());
203 tf2_listener_ = std::make_shared<tf2_ros::TransformListener>(*tf2_buffer_);
204
205 // create a callback for dynamic parameter configuration
206 parameters_callback_ = this->add_on_set_parameters_callback(
207 std::bind(&TrajectoryOptimizationNode::parametersCallback, this, std::placeholders::_1));
208
209 // set up subscriber for input topics
210 ego_data_sub_ = this->create_subscription<perception_msgs::msg::EgoData>(
211 "~/ego_data", 1, std::bind(&TrajectoryOptimizationNode::egoDataCallback, this, std::placeholders::_1));
212 RCLCPP_INFO(this->get_logger(), "Subscribed to '%s'", ego_data_sub_->get_topic_name());
213
214 object_list_sub_ = this->create_subscription<perception_msgs::msg::ObjectList>(
215 "~/object_list", 1, std::bind(&TrajectoryOptimizationNode::objectListCallback, this, std::placeholders::_1));
216 RCLCPP_INFO(this->get_logger(), "Subscribed to '%s'", object_list_sub_->get_topic_name());
217
218 route_sub_ = this->create_subscription<route_planning_msgs::msg::Route>(
219 "~/route", 1, std::bind(&TrajectoryOptimizationNode::routeCallback, this, std::placeholders::_1));
220 RCLCPP_INFO(this->get_logger(), "Subscribed to '%s'", route_sub_->get_topic_name());
221
222 reference_trajectory_sub_ = this->create_subscription<trajectory_planning_msgs::msg::Trajectory>(
223 "~/reference_trajectory", 1,
224 std::bind(&TrajectoryOptimizationNode::referenceTrajectoryCallback, this, std::placeholders::_1));
225 RCLCPP_INFO(this->get_logger(), "Subscribed to '%s'", reference_trajectory_sub_->get_topic_name());
226
227 // set up publisher for output topics
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());
236
237 // create timer for planning cycle
238 if (run_as_callback_) {
239 RCLCPP_INFO(this->get_logger(), "OCP runs on reference trajectory callback");
240 } else {
241 RCLCPP_INFO(this->get_logger(), "OCP runs continuously with frequency %f Hz", optimization_freq_);
242 planning_timer_ = this->create_wall_timer(std::chrono::duration<double>(1 / optimization_freq_),
244 }
245
246 // init reference trajectory
247 trajectory_planning_msgs::trajectory_access::initializeTrajectory(
248 reference_trajectory_, trajectory_planning_msgs::msg::REFERENCE::TYPE_ID, n_shots_ + 1);
250
251 // init latest trajectory (doesn't matter which type, only used for standstill detection)
252 trajectory_planning_msgs::trajectory_access::initializeTrajectory(
253 latest_valid_trajectory_, trajectory_planning_msgs::msg::DRIVABLE::TYPE_ID, n_shots_ + 1);
255
256 setupSolver();
257
258 // Annotate message links for tracing: Publish trajectory periodically, which depends an all subscribed topics.
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()));
263 link_subs.push_back(static_cast<const void*>(reference_trajectory_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(),
267 link_pubs.size());
268 TRACETOOLS_TRACEPOINT(message_link_periodic_async, link_subs.data(), link_subs.size(), link_pubs.data(), link_pubs.size());
269}
270
272 if (n_shots_ <= 0) {
273 RCLCPP_FATAL(this->get_logger(), "n_shots must be > 0, got %d", n_shots_);
274 exit(1);
275 }
276
277 // Create exactly once. This also supports a runtime horizon that differs from the generated default.
279 std::vector<double> new_time_steps(n_shots_, optimization_horizon_ / n_shots_);
280 RCLCPP_INFO(this->get_logger(), "Create OCP with: horizon = %f, n_shots = %d, dt = %f", optimization_horizon_, n_shots_,
281 new_time_steps.front());
283
284 if (status != ACADOS_SUCCESS) {
285 RCLCPP_FATAL(this->get_logger(), "%s_acados_create_with_discretization() returned status %d. Exiting.", model_name_.c_str(),
286 status);
287 exit(1);
288 }
289
296 if (nlp_dims_->N != n_shots_) {
297 RCLCPP_FATAL(this->get_logger(), "Created solver has N=%d, expected n_shots=%d. Exiting.", nlp_dims_->N, n_shots_);
298 exit(1);
299 }
300
301 xtraj_.resize(*nlp_dims_->nx * (n_shots_ + 1));
302 utraj_.resize(*nlp_dims_->nu * n_shots_);
303 control_guess_.clear();
304}
305
307 // free solver
309 if (status != 0) {
310 RCLCPP_ERROR(this->get_logger(), "%s_acados_free() returned status %d.", model_name_.c_str(), status);
311 }
312 // free solver capsule
314 if (status != 0) {
315 RCLCPP_ERROR(this->get_logger(), "%s_acados_free_capsule() returned status %d.", model_name_.c_str(), status);
316 }
317}
318
320 const int status = trajectory_optimization::acados_reset(ocp_capsule_, 1, 0, 0, 0);
321 if (status != ACADOS_SUCCESS) {
322 RCLCPP_ERROR(this->get_logger(), "%s_acados_reset() returned status %d. Recreating solver.", model_name_.c_str(), status);
323 freeSolver();
324 setupSolver();
325 }
326}
327
328bool TrajectoryOptimizationNode::setInitialGuess(const std::vector<double>& x_init, const rclcpp::Time& stamp) {
329 constexpr double MAX_CONTROL_GUESS_AGE_FACTOR = 0.5;
330 const int nx = *nlp_dims_->nx;
331 const int nu = *nlp_dims_->nu;
332 const double time_step = optimization_horizon_ / n_shots_;
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);
336 return false;
337 }
338
339 // Fall back to zero controls if no sufficiently recent accepted solution is available.
340 std::vector<double> controls(expected_control_size, 0.0);
341 if (control_guess_.size() == expected_control_size) {
342 const double elapsed = (stamp - control_guess_stamp_).seconds();
343 if (elapsed >= 0.0 && elapsed < MAX_CONTROL_GUESS_AGE_FACTOR * optimization_horizon_) {
344 // Shift the previous controls to the current planning time and interpolate between shooting nodes.
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;
348
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);
355 }
356 }
357 }
358 }
359
360 ocp_nlp_out_set_values_to_zero(nlp_config_, nlp_dims_, nlp_out_);
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];
367 ocp_nlp_out_set(nlp_config_, nlp_dims_, nlp_out_, nlp_in_, stage, "x", rollout_state.data());
368 ocp_nlp_out_set(nlp_config_, nlp_dims_, nlp_out_, nlp_in_, stage, "u", control);
369
370 // Match sim_method_num_stages=4 and sim_method_num_steps=2 from the generated OCP.
371 for (int integration = 0; integration < 2; ++integration) {
372 trajectory_optimization::acados_evaluate_dynamics(ocp_capsule_, stage, rollout_state.data(), control, k1.data());
373 for (int i = 0; i < nx; ++i) intermediate_state[i] = rollout_state[i] + 0.5 * integration_step * k1[i];
374 trajectory_optimization::acados_evaluate_dynamics(ocp_capsule_, stage, intermediate_state.data(), control, k2.data());
375 for (int i = 0; i < nx; ++i) intermediate_state[i] = rollout_state[i] + 0.5 * integration_step * k2[i];
376 trajectory_optimization::acados_evaluate_dynamics(ocp_capsule_, stage, intermediate_state.data(), control, k3.data());
377 for (int i = 0; i < nx; ++i) intermediate_state[i] = rollout_state[i] + integration_step * k3[i];
378 trajectory_optimization::acados_evaluate_dynamics(ocp_capsule_, stage, intermediate_state.data(), control, k4.data());
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]);
381 }
382 }
383 }
384 ocp_nlp_out_set(nlp_config_, nlp_dims_, nlp_out_, nlp_in_, n_shots_, "x", rollout_state.data());
385 return true;
386}
387
389 const auto cycle_start = SteadyClock::now();
390 if (debug_viz_) viz_circles_.clear();
391 if (rclcpp::Time(this->now()) - rclcpp::Time(ego_data_.header.stamp) > rclcpp::Duration::from_seconds(ego_data_timeout_)) {
392 RCLCPP_WARN(this->get_logger(), "EgoData outdated. Skipping planning cycle.");
393 return;
394 }
395 // init trajectory message and set header
396 trajectory_planning_msgs::msg::Trajectory::UniquePtr trajectory = std::make_unique<trajectory_planning_msgs::msg::Trajectory>();
397 initializeTrajectory(*trajectory);
398
399 trajectory->header.frame_id = vehicle_frame_id_;
400 trajectory->header.stamp = ego_data_.header.stamp; // use latest ego_data stamp as trajectory stamp
401
402 // init time-steps of trajectory to ensure increasing time-steps even for standstill trajectories
403 double dt = optimization_horizon_ / n_shots_;
404 for (int i = 0; i <= n_shots_; ++i) trajectory_planning_msgs::trajectory_access::setT(*trajectory, i * dt, i);
405
406 // check if the reference trajectory is standstill
407 if (trajectory_planning_msgs::trajectory_access::getStandstill(reference_trajectory_)) {
408 RCLCPP_WARN(this->get_logger(), "Standstill trajectory. Skipping planning cycle. Publish standstill trajectory.");
409 // transform trajectory to output frame
410 if (!trajectory2outputFrame(*trajectory)) {
411 return;
412 }
413 trajectory_planning_msgs::trajectory_access::setStandstill(*trajectory, true);
414 trajectory_pub_->publish(std::move(trajectory));
415 // Invalidate the warm start and reset the solver once when entering standstill.
416 if (!control_guess_.empty()) {
417 control_guess_.clear();
418 resetSolver();
419 }
420 return;
421 }
422
423 PerformanceMetrics metrics;
424 metrics.cycle = ++logging_cycle_;
425 metrics.ego_stamp_ns = rclcpp::Time(ego_data_.header.stamp).nanoseconds();
426 metrics.reference_stamp_ns = rclcpp::Time(reference_trajectory_.header.stamp).nanoseconds();
427 metrics.route_stamp_ns = rclcpp::Time(route_.header.stamp).nanoseconds();
428 metrics.reference_points = trajectory_planning_msgs::trajectory_access::getSamplePointSize(reference_trajectory_);
429 metrics.objects = static_cast<int>(object_list_.objects.size());
430 auto logCompletedCycle = [&]() {
431 metrics.cycle_ms = elapsedMilliseconds(cycle_start);
432 metrics.postprocessing_ms = metrics.cycle_ms - metrics.preprocessing_ms - metrics.solve_wall_ms;
433 logPerformance(metrics);
434 };
435
436 // set initial state
437 std::vector<double> x_init(*nlp_dims_->nx, 0.0);
438 if (!trajectory_planning_msgs::trajectory_access::getStandstill(latest_valid_trajectory_)) {
440 } else {
441 RCLCPP_WARN(this->get_logger(),
442 "Latest available trajectory is standstill. Using ego data for initial state (high-level initialization).");
443 x_init = getHighLevelX0(ego_data_);
444 }
445
446 // debug print of initial state
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());
451
452 ocp_nlp_constraints_model_set(nlp_config_, nlp_dims_, nlp_in_, nlp_out_, 0, "lbx", x_init.data());
453 ocp_nlp_constraints_model_set(nlp_config_, nlp_dims_, nlp_in_, nlp_out_, 0, "ubx", x_init.data());
454
455 // update inputs to the ocp; skip planning cycle if update fails
457 RCLCPP_WARN(this->get_logger(), "Failed to update inputs. Skipping planning cycle.");
458 return;
459 }
460
461 if (!setInitialGuess(x_init, rclcpp::Time(ego_data_.header.stamp))) {
462 control_guess_.clear();
463 return;
464 }
465
466 // solve the optimization problem
467 const auto solve_start = SteadyClock::now();
468 metrics.preprocessing_ms = elapsedMilliseconds(cycle_start, solve_start);
470 const auto solve_end = SteadyClock::now();
471 metrics.solve_wall_ms = elapsedMilliseconds(solve_start, solve_end);
472
474
475 if (metrics.status == ACADOS_NAN_DETECTED || metrics.status == ACADOS_MINSTEP || metrics.status == ACADOS_QP_FAILURE) {
476 printSolution(metrics);
477 // Keep the last accepted controls; the failed solver output is not added to the cache.
478 resetSolver();
479 logCompletedCycle();
480 return;
481 }
482
483 // get solution
484 for (int ii = 0; ii <= nlp_dims_->N; ++ii) {
485 ocp_nlp_out_get(nlp_config_, nlp_dims_, nlp_out_, ii, "x", &xtraj_[ii * *nlp_dims_->nx]);
486 }
487 for (int ii = 0; ii < nlp_dims_->N; ++ii) {
488 ocp_nlp_out_get(nlp_config_, nlp_dims_, nlp_out_, ii, "u", &utraj_[ii * *nlp_dims_->nu]);
489 }
491 control_guess_stamp_ = rclcpp::Time(ego_data_.header.stamp);
492
493 printSolution(metrics);
494 if (debug_viz_) {
497 }
498
499 // convert output into trajectory message
500 convertToTrajectoryMsg(*trajectory);
501
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;
505 }
506 trajectory_planning_msgs::trajectory_access::setStandstill(*trajectory, standstill);
507
508 // transform trajectory to output frame
509 if (!trajectory2outputFrame(*trajectory)) {
510 logCompletedCycle();
511 return;
512 }
513
514 latest_valid_trajectory_ = *trajectory;
515 trajectory_pub_->publish(std::move(trajectory));
516 metrics.published = true;
517 logCompletedCycle();
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);
520}
521
522bool TrajectoryOptimizationNode::updateOcpInputs(const perception_msgs::msg::EgoData& ego_data,
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) {
527 // transform inputs to target base_link frame
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;
531 try {
532 // reference trajectory
533 tf_reference_trajectory =
534 tf2_buffer_->transform(reference_trajectory, vehicle_frame_id_, tf2_ros::fromMsg(ego_data.header.stamp),
535 fixed_over_time_frame_id_, tf2::durationFromSec(0.01));
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());
540 }
541 // object list
542 if (!object_list.objects.empty() && object_list.header.frame_id != vehicle_frame_id_) {
543 tf_object_list = tf2_buffer_->transform(object_list, vehicle_frame_id_, tf2_ros::fromMsg(ego_data.header.stamp),
544 fixed_over_time_frame_id_, tf2::durationFromSec(0.01));
545 } else {
546 tf_object_list = object_list;
547 }
548 keepNClosestObjects(tf_object_list, static_cast<int>(p_obstacle_circles_shape_[0]));
549 // route
550 if (!route.route_elements.empty() && route.header.frame_id != vehicle_frame_id_) {
551 tf_route = tf2_buffer_->transform(route, vehicle_frame_id_, tf2_ros::fromMsg(ego_data.header.stamp),
552 fixed_over_time_frame_id_, tf2::durationFromSec(0.1));
553 } else {
554 tf_route = route;
555 }
556 } catch (tf2::TransformException& ex) {
557 RCLCPP_WARN(this->get_logger(), "Transformation is not available. Ex: %s", ex.what());
558 return false;
559 }
560
561 if (trajectory_planning_msgs::trajectory_access::getSamplePointSize(tf_reference_trajectory) <= 0) {
562 RCLCPP_ERROR(this->get_logger(), "Reference trajectory contains no sample points.");
563 return false;
564 }
565
566 // update ocp parameters
567 try {
568 this->setOcpGlobalParameters(cost_weights_, tf_reference_trajectory, tf_route);
569 this->setOcpParameters(ego_data, tf_object_list);
570 } catch (const std::exception& e) {
571 RCLCPP_ERROR(this->get_logger(), "Exception while setting OCP parameters: %s", e.what());
572 return false;
573 }
574
575 return true;
576}
577
578void TrajectoryOptimizationNode::setOcpGlobalParameters(const std::vector<double>& cost_weights,
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;
583
584 // cost weights
585 const auto expected_cost_weights_size = static_cast<size_t>(p_cost_weights_shape_[0] * p_cost_weights_shape_[1]);
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.");
590 }
591 global_params.insert(global_params.end(), cost_weights.begin(), cost_weights.end());
592
593 // other cost params
594 global_params.push_back(thw_);
595 global_params.push_back(d_min_obstacle_long_);
596 global_params.push_back(d_min_obstacle_lat_);
597 global_params.push_back(d_min_boundary_lat_);
598
599 // reference path (including boundaries)
600 const size_t n_ref_states = static_cast<size_t>(p_ref_path_shape_[0] * p_ref_path_shape_[1]);
601 std::vector<std::pair<double, double>> boundary_distances = normalBoundaryDistance(reference_trajectory, route);
602 // fill ref vector for ocp -> psi, x, y, v, d_bound_left, d_bound_right
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); // left boundary distance
610 ref.push_back(boundary_distances[i].second); // right boundary distance
611 }
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));
615 } else {
616 // Repeat the final valid state. Infinity padding can propagate NaNs through closest-point calculations.
617 global_params.insert(global_params.end(), ref.begin(), ref.end());
618 const size_t state_width = static_cast<size_t>(p_ref_path_shape_[1]);
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),
621 ref.end());
622 }
623 }
624
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(),
627 nlp_dims_->np_global);
628 throw std::runtime_error("Size of global parameters does not match expected size.");
629 }
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));
634 }
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);
637}
638
639void TrajectoryOptimizationNode::setOcpParameters(const perception_msgs::msg::EgoData& ego_data,
640 const perception_msgs::msg::ObjectList& object_list) {
641 const auto start_time = std::chrono::steady_clock::now();
642 // loop over shooting intervals
643 double floating_dynamic_weight = 1.0;
644 double dt = optimization_horizon_ / n_shots_;
645 for (int i = 0; i <= n_shots_; ++i) {
646 // dynamic weight
647 int idx = 0;
648 int n = 1;
649 std::vector<int> idx_dynamic_weight(n);
650 // fill vector with values from idx to idx + n
651 std::iota(idx_dynamic_weight.begin(), idx_dynamic_weight.end(), idx);
652 int status = trajectory_optimization::acados_update_params_sparse(ocp_capsule_, i, idx_dynamic_weight.data(),
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));
656 }
657 floating_dynamic_weight *= dynamic_weight_;
658
659 // obstacles
660 idx += n;
661 n = static_cast<int>(p_obstacle_circles_shape_[0] * p_obstacle_circles_shape_[1]);
662 std::vector<double> circles; // [x1, y1, r1, x2, y2, r2, ...]
663
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;
667 // TODO(ika): Build prediction arrays once per object outside the shooting-interval loop.
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]));
672 if (consider_objects_ == CONSIDER_OBJECTS::PREDICTED_OBJECTS && !object_list.objects[j].state_predictions.empty()) {
673 // build one target state for a single prediction hypothesis at the current shooting interval
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));
684 }
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();
699 } else {
700 linearInterpolation(prediction_time, prediction_x, des_time, x_tgt);
701 linearInterpolation(prediction_time, prediction_y, des_time, y_tgt);
702 linearInterpolation(prediction_time, prediction_yaw, des_time, yaw_tgt, true);
703 }
704 target_states.emplace_back(x_tgt, y_tgt, yaw_tgt);
705 };
706
707 // consider all prediction hypotheses whose probability exceeds the configured threshold
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];
711 if (state_prediction.probability <= min_prediction_probability_) {
712 continue;
713 }
714 has_prediction_above_threshold = true;
715 appendPredictionTargetState(state_prediction, prediction_idx);
716 }
717 if (!has_prediction_above_threshold) {
718 // always keep at least one prediction hypothesis for predicted objects
719 appendPredictionTargetState(object_list.objects[j].state_predictions[0], 0);
720 }
721 } else {
722 // static object handling or missing predictions: use the current object state
723 target_states.emplace_back(X.front(), Y.front(), YAW.front());
724 }
725
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) {
731 // ensure that x_tgt and y_tgt represent the geometric center of the object
732 double beta = wrap_angle_rad(yaw_tgt - alpha);
733 x_tgt += a * std::cos(beta);
734 y_tgt += a * std::sin(beta);
735
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]));
739
740 circles.insert(circles.end(), obj_circles.begin(), obj_circles.end());
741 if (circles.size() >= static_cast<size_t>(n)) {
742 circles.resize(n);
743 break;
744 }
745 }
746 if (circles.size() >= static_cast<size_t>(n)) {
747 break;
748 }
749 }
750 // fill up with dummy "ghost" obstacle circles at (10000, 10000) to avoid NaNs in the optimization problem
751 // TODO: improve this // NOLINT(google-readability-todo)
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());
755 }
756 if ((circles.size() % p_obstacle_circles_shape_[1]) != 0) {
757 RCLCPP_WARN(this->get_logger(), "Circles vector size is not a multiple of the circle shape. Resizing.");
758 circles.resize(n);
759 }
760 if (debug_viz_) viz_circles_.insert(viz_circles_.end(), circles.begin(), circles.end());
761
762 std::vector<int> idx_obstacles(n);
763 // fill vector with values from idx to idx + n
764 std::iota(idx_obstacles.begin(), idx_obstacles.end(), idx);
765 status = trajectory_optimization::acados_update_params_sparse(ocp_capsule_, i, idx_obstacles.data(), circles.data(), n);
766 if (status != ACADOS_SUCCESS) {
767 throw std::runtime_error("acados obstacle update failed with status " + std::to_string(status));
768 }
769 }
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);
772}
773
774} // namespace trajectory_optimization
static void collectSolverStatistics(PerformanceMetrics &metrics, ocp_nlp_solver *solver, ocp_nlp_config *config, ocp_nlp_dims *dims, ocp_nlp_in *input, ocp_nlp_out *output)
Reads timing, iteration, cost, and residual statistics from acados.
static void keepNClosestObjects(perception_msgs::msg::ObjectList &object_list, const int n_objects)
Keeps the nearest forward objects and discards the remaining entries.
Definition utils.cpp:86
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.
Definition utils.cpp:441
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.
void routeCallback(const route_planning_msgs::msg::Route::ConstSharedPtr msg)
Stores the current route used for boundary constraints when enabled.
Definition callbacks.cpp:31
void freeSolver()
Frees the acados solver and clears cached optimization results.
trajectory_planning_msgs::msg::Trajectory latest_valid_trajectory_
void vizCircles(const std::vector< double > &obstacles)
Publishes visualization markers for the obstacle circles currently used by the optimizer.
Definition utils.cpp:338
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.
Definition utils.cpp:475
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.
Definition utils.cpp:14
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.
Definition callbacks.cpp:12
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.
Definition utils.cpp:21
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.
Definition callbacks.cpp:7
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.
Definition utils.cpp:361
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...
Definition utils.cpp:71
rclcpp::Subscription< route_planning_msgs::msg::Route >::SharedPtr route_sub_
rcl_interfaces::msg::SetParametersResult parametersCallback(const std::vector< rclcpp::Parameter > &parameters)
Applies parameter updates that can be reconfigured while the node is running.
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.
Definition utils.cpp:154
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 &param, 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_
void referenceTrajectoryCallback(const trajectory_planning_msgs::msg::Trajectory::ConstSharedPtr msg)
Updates the reference trajectory and optionally triggers optimization immediately.
Definition callbacks.cpp:22
~TrajectoryOptimizationNode() override
Releases solver resources owned by the node.
rclcpp::Publisher< trajectory_planning_msgs::msg::Trajectory >::SharedPtr trajectory_pub_
std::shared_ptr< tf2_ros::TransformListener > tf2_listener_
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.
Definition utils.cpp:116
rclcpp::Subscription< trajectory_planning_msgs::msg::Trajectory >::SharedPtr reference_trajectory_sub_
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.
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.