Skip to content

Commit 411cf91

Browse files
committed
refactor(autoware_mpc_lateral_controller): take the ROS clock out of MPC
MPC and SteeringPredictor receive the time of the control cycle as an argument and no longer hold a clock. The caller already passed the same time as the stamp of every message the cycle produces, so the computation now reads the instant the cycle started instead of an instant a few microseconds later. Signed-off-by: Kazuki Komiya <290287332+kazkomiya@users.noreply.github.com>
1 parent 15cb01c commit 411cf91

7 files changed

Lines changed: 93 additions & 23 deletions

File tree

‎control/autoware_mpc_lateral_controller/CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ if(BUILD_TESTING)
2828
test/test_mpc.cpp
2929
test/test_mpc_lateral_controller.cpp
3030
test/test_mpc_utils.cpp
31+
test/test_steering_predictor.cpp
3132
test/test_lowpass_filter.cpp
3233
)
3334
set(TEST_LATERAL_CONTROLLER_EXE test_lateral_controller)

‎control/autoware_mpc_lateral_controller/include/autoware/mpc_lateral_controller/mpc.hpp‎

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,6 @@ class MPC
244244
{
245245
private:
246246
const LogWriter * m_writer{&no_log}; // Where the control writes its log.
247-
rclcpp::Clock::SharedPtr m_clock = std::make_shared<rclcpp::Clock>(RCL_ROS_TIME); // ROS clock.
248247

249248
// Vehicle model used for MPC.
250249
std::shared_ptr<VehicleModelInterface> m_vehicle_model_ptr;
@@ -282,11 +281,12 @@ class MPC
282281
* @param trajectory The reference trajectory.
283282
* @param current_steer The current steering report.
284283
* @param current_kinematics The current vehicle kinematics.
284+
* @param stamp The time of this control cycle.
285285
* @return The MPC data on success, or the failure reason on error.
286286
*/
287287
tl::expected<MPCData, std::string> getData(
288288
const MPCTrajectory & trajectory, const SteeringReport & current_steer,
289-
const Odometry & current_kinematics);
289+
const Odometry & current_kinematics, const rclcpp::Time & stamp);
290290

291291
/**
292292
* @brief Get the initial state for MPC.
@@ -575,12 +575,6 @@ class MPC
575575
* @param writer The writer. It has to outlive this object.
576576
*/
577577
inline void setLogWriter(const LogWriter & writer) { m_writer = &writer; }
578-
579-
/**
580-
* @brief Set the RCLCPP clock to be used for time keeping.
581-
* @param clock The shared pointer to the RCLCPP clock.
582-
*/
583-
inline void setClock(rclcpp::Clock::SharedPtr clock) { m_clock = clock; }
584578
}; // class MPC
585579
} // namespace autoware::motion::control::mpc_lateral_controller
586580

‎control/autoware_mpc_lateral_controller/include/autoware/mpc_lateral_controller/steering_predictor.hpp‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,19 @@ class SteeringPredictor
3434

3535
/**
3636
* @brief Calculate the predicted steering based on the given vehicle model.
37+
* @param now The time of this computation.
3738
* @return The predicted steering angle.
3839
*/
39-
double calcSteerPrediction();
40+
double calcSteerPrediction(const rclcpp::Time & now);
4041

4142
/**
4243
* @brief Store the steering command in the buffer.
4344
* @param steer The steering command to be stored.
45+
* @param now The time the command is sent.
4446
*/
45-
void storeSteerCmd(const double steer);
47+
void storeSteerCmd(const double steer, const rclcpp::Time & now);
4648

4749
private:
48-
rclcpp::Logger m_logger = rclcpp::get_logger("mpc_steer_predictor");
49-
rclcpp::Clock::SharedPtr m_clock = std::make_shared<rclcpp::Clock>(RCL_ROS_TIME);
50-
5150
// The previously predicted steering value.
5251
double m_steer_prediction_prev = 0.0;
5352

‎control/autoware_mpc_lateral_controller/src/mpc.cpp‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,8 @@ MpcResult MPC::calculateMPC(
161161
applyVelocityDynamicsFilter(m_reference_trajectory, current_kinematics);
162162

163163
// get the necessary data
164-
const auto get_data_result = getData(reference_trajectory, current_steer, current_kinematics);
164+
const auto get_data_result =
165+
getData(reference_trajectory, current_steer, current_kinematics, rclcpp::Time(stamp));
165166
if (!get_data_result) {
166167
MpcResult failure_result{false, fmt::format("getting MPC Data ({}).", get_data_result.error())};
167168
setHeader(failure_result, stamp, m_reference_trajectory_frame_id);
@@ -258,7 +259,7 @@ MpcResult MPC::calculateMPC(
258259
mpc_matrix, x0_delayed, Uex, u_filtered, current_steer.steering_tire_angle, prediction_dt));
259260

260261
// save the control command for the steering prediction
261-
m_steering_predictor->storeSteerCmd(u_filtered);
262+
m_steering_predictor->storeSteerCmd(u_filtered, rclcpp::Time(stamp));
262263

263264
// save input to buffer for delay compensation
264265
m_input_buffer.push_back(ctrl_cmd.steering_tire_angle);
@@ -469,7 +470,7 @@ void MPC::resetPrevResult(const SteeringReport & current_steer)
469470

470471
tl::expected<MPCData, std::string> MPC::getData(
471472
const MPCTrajectory & traj, const SteeringReport & current_steer,
472-
const Odometry & current_kinematics)
473+
const Odometry & current_kinematics, const rclcpp::Time & stamp)
473474
{
474475
const auto current_pose = current_kinematics.pose.pose;
475476

@@ -479,7 +480,7 @@ tl::expected<MPCData, std::string> MPC::getData(
479480
const double traj_end_time = traj.relative_time.back();
480481

481482
const rclcpp::Time traj_stamp(traj.stamp);
482-
const double elapsed_time = (m_clock->now() - traj_stamp).seconds();
483+
const double elapsed_time = (stamp - traj_stamp).seconds();
483484
const double fused_time = std::clamp(elapsed_time, traj_start_time, traj_end_time);
484485
data.temporal_predicted_time = fused_time;
485486
data.temporal_fused_time = fused_time;
@@ -505,7 +506,7 @@ tl::expected<MPCData, std::string> MPC::getData(
505506
tf2::getYaw(current_pose.orientation) - tf2::getYaw(data.nearest_pose.orientation));
506507

507508
// get predicted steer
508-
data.predicted_steer = m_steering_predictor->calcSteerPrediction();
509+
data.predicted_steer = m_steering_predictor->calcSteerPrediction(stamp);
509510

510511
if (m_publish_debug_trajectories) {
511512
const auto autoware_traj = MPCUtils::convertToAutowareTrajectory(traj);

‎control/autoware_mpc_lateral_controller/src/mpc_lateral_controller.cpp‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,6 @@ MpcLateralController::MpcLateralController(
189189
m_mpc->initializeSteeringPredictor();
190190

191191
m_mpc->setLogWriter(writer_);
192-
m_mpc->setClock(clock_);
193192

194193
setupDiag();
195194
}

‎control/autoware_mpc_lateral_controller/src/steering_predictor.cpp‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ SteeringPredictor::SteeringPredictor(const double steer_tau, const double steer_
2222
{
2323
}
2424

25-
double SteeringPredictor::calcSteerPrediction()
25+
double SteeringPredictor::calcSteerPrediction(const rclcpp::Time & now)
2626
{
2727
auto t_start = m_time_prev;
28-
auto t_end = m_clock->now();
28+
auto t_end = now;
2929
m_time_prev = t_end;
3030

3131
const double duration = (t_end - t_start).seconds();
@@ -44,9 +44,9 @@ double SteeringPredictor::calcSteerPrediction()
4444
return predicted_steering;
4545
}
4646

47-
void SteeringPredictor::storeSteerCmd(const double steer)
47+
void SteeringPredictor::storeSteerCmd(const double steer, const rclcpp::Time & now)
4848
{
49-
const auto time_delayed = m_clock->now() + rclcpp::Duration::from_seconds(m_input_delay);
49+
const auto time_delayed = now + rclcpp::Duration::from_seconds(m_input_delay);
5050
Lateral cmd;
5151
cmd.stamp = time_delayed;
5252
cmd.steering_tire_angle = static_cast<float>(steer);
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2026 TIER IV, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "autoware/mpc_lateral_controller/steering_predictor.hpp"
16+
17+
#include <gtest/gtest.h>
18+
19+
#include <cmath>
20+
21+
namespace
22+
{
23+
using autoware::motion::control::mpc_lateral_controller::SteeringPredictor;
24+
25+
constexpr double steer_tau = 0.1;
26+
constexpr double steer_delay = 0.0;
27+
/// One cycle of the runs below. The answer of a cycle does not yet hold the command given
28+
/// in that same cycle, so the cycle is short against the time constant to keep that delay
29+
/// inside the tolerance the tests allow.
30+
constexpr double cycle = 0.001;
31+
32+
/// The angle the steering is asked to hold throughout each test below.
33+
constexpr double commanded_angle = 0.2;
34+
35+
rclcpp::Time at(const double seconds)
36+
{
37+
return rclcpp::Time(static_cast<int64_t>(seconds * 1e9), RCL_ROS_TIME);
38+
}
39+
40+
/// Issue the same command every cycle until the given time, and report the angle the
41+
/// predictor arrives at.
42+
///
43+
/// The predictor answers with the response of a first order lag to the commands it has
44+
/// been given, so a run that holds one command is a step response. A command is stored
45+
/// before the prediction of the same cycle is read, which is the order the controller
46+
/// uses.
47+
double hold_command_until(SteeringPredictor & predictor, const double seconds)
48+
{
49+
double predicted = 0.0;
50+
for (double time = 0.0; time <= seconds + 0.5 * cycle; time += cycle) {
51+
predictor.storeSteerCmd(commanded_angle, at(time));
52+
predicted = predictor.calcSteerPrediction(at(time));
53+
}
54+
return predicted;
55+
}
56+
} // namespace
57+
58+
/// A first order lag reaches 63.2 per cent of the command after one time constant.
59+
TEST(SteeringPredictorTest, ReachesSixtyThreePerCentAfterOneTimeConstant)
60+
{
61+
SteeringPredictor predictor(steer_tau, steer_delay);
62+
63+
const double predicted = hold_command_until(predictor, steer_tau);
64+
65+
EXPECT_NEAR(predicted, commanded_angle * (1.0 - std::exp(-1.0)), 0.01 * commanded_angle);
66+
}
67+
68+
/// The same lag reaches 95 per cent after three time constants.
69+
TEST(SteeringPredictorTest, ReachesNinetyFivePerCentAfterThreeTimeConstants)
70+
{
71+
SteeringPredictor predictor(steer_tau, steer_delay);
72+
73+
const double predicted = hold_command_until(predictor, 3.0 * steer_tau);
74+
75+
EXPECT_NEAR(predicted, commanded_angle * (1.0 - std::exp(-3.0)), 0.01 * commanded_angle);
76+
}

0 commit comments

Comments
 (0)