blob: 54ffeb51836e860b02cab52d0c804e1eef7ac3e6 [file] [log] [blame]
Austin Schuh60e77942022-05-16 17:48:24 -07001#include "aos/util/trapezoid_profile.h"
briansf0165ca2013-03-02 06:17:47 +00002
3#include "Eigen/Dense"
Austin Schuhe197a962024-02-20 18:10:12 -08004#include "glog/logging.h"
Austin Schuh60e77942022-05-16 17:48:24 -07005#include "gtest/gtest.h"
briansf0165ca2013-03-02 06:17:47 +00006
Stephan Pleinesf63bde82024-01-13 15:59:33 -08007namespace aos::util::testing {
briansf0165ca2013-03-02 06:17:47 +00008
9class TrapezoidProfileTest : public ::testing::Test {
10 public:
11 EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
12
13 protected:
Austin Schuhb3b799e2024-02-20 14:20:12 -080014 TrapezoidProfileTest() : profile_(kDeltaTime) {
briansf0165ca2013-03-02 06:17:47 +000015 position_.setZero();
16 profile_.set_maximum_acceleration(0.75);
Austin Schuhe197a962024-02-20 18:10:12 -080017 profile_.set_maximum_deceleration(0.75);
briansf0165ca2013-03-02 06:17:47 +000018 profile_.set_maximum_velocity(1.75);
19 }
20
21 // Runs an iteration.
Austin Schuh60e77942022-05-16 17:48:24 -070022 void RunIteration(double goal_position, double goal_velocity) {
23 position_ = profile_.Update(goal_position, goal_velocity);
briansf0165ca2013-03-02 06:17:47 +000024 }
25
Austin Schuhb3b799e2024-02-20 14:20:12 -080026 void RunFor(double goal_position, double goal_velocity,
27 std::chrono::nanoseconds duration) {
28 while (duration > std::chrono::nanoseconds(0)) {
29 position_ = profile_.Update(goal_position, goal_velocity);
30 duration -= kDeltaTime;
31 }
32
33 ASSERT_EQ(duration.count(), 0);
34 }
35
briansf0165ca2013-03-02 06:17:47 +000036 const Eigen::Matrix<double, 2, 1> &position() { return position_; }
37
Austin Schuhe197a962024-02-20 18:10:12 -080038 AsymmetricTrapezoidProfile profile_;
briansf0165ca2013-03-02 06:17:47 +000039
40 ::testing::AssertionResult At(double position, double velocity) {
briansd412b3f2013-03-03 21:13:44 +000041 static const double kDoubleNear = 0.00001;
42 if (::std::abs(velocity - position_(1)) > kDoubleNear) {
Austin Schuh60e77942022-05-16 17:48:24 -070043 return ::testing::AssertionFailure()
44 << "velocity is " << position_(1) << " not " << velocity;
briansf0165ca2013-03-02 06:17:47 +000045 }
briansd412b3f2013-03-03 21:13:44 +000046 if (::std::abs(position - position_(0)) > kDoubleNear) {
Austin Schuh60e77942022-05-16 17:48:24 -070047 return ::testing::AssertionFailure()
48 << "position is " << position_(0) << " not " << position;
briansf0165ca2013-03-02 06:17:47 +000049 }
Austin Schuh60e77942022-05-16 17:48:24 -070050 return ::testing::AssertionSuccess()
51 << "at " << position << " moving at " << velocity;
briansf0165ca2013-03-02 06:17:47 +000052 }
53
54 private:
Austin Schuhb3b799e2024-02-20 14:20:12 -080055 static constexpr ::std::chrono::nanoseconds kDeltaTime =
Austin Schuh214e9c12016-11-25 17:26:20 -080056 ::std::chrono::milliseconds(10);
briansf0165ca2013-03-02 06:17:47 +000057
58 Eigen::Matrix<double, 2, 1> position_;
59};
Austin Schuh214e9c12016-11-25 17:26:20 -080060
Austin Schuhb3b799e2024-02-20 14:20:12 -080061constexpr ::std::chrono::nanoseconds TrapezoidProfileTest::kDeltaTime;
briansf0165ca2013-03-02 06:17:47 +000062
63TEST_F(TrapezoidProfileTest, ReachesGoal) {
Austin Schuhe197a962024-02-20 18:10:12 -080064 RunFor(3, 0, std::chrono::milliseconds(4500));
briansf0165ca2013-03-02 06:17:47 +000065 EXPECT_TRUE(At(3, 0));
66}
67
Austin Schuhb3b799e2024-02-20 14:20:12 -080068// Tests that decreasing the maximum velocity in the middle when it is already
Ben Fredricksonf33d6532015-03-15 00:29:29 -070069// moving faster than the new max is handled correctly.
70TEST_F(TrapezoidProfileTest, ContinousUnderVelChange) {
71 profile_.set_maximum_velocity(1.75);
Austin Schuhe197a962024-02-20 18:10:12 -080072 RunFor(12.0, 0, std::chrono::milliseconds(10));
Ben Fredricksonf33d6532015-03-15 00:29:29 -070073 double last_pos = position()(0);
74 double last_vel = 1.75;
75 for (int i = 0; i < 1600; ++i) {
76 if (i == 400) {
77 profile_.set_maximum_velocity(0.75);
78 }
Austin Schuhe197a962024-02-20 18:10:12 -080079 RunFor(12.0, 0, std::chrono::milliseconds(10));
Ben Fredricksonf33d6532015-03-15 00:29:29 -070080 if (i >= 400) {
81 EXPECT_TRUE(::std::abs(last_pos - position()(0)) <= 1.75 * 0.01);
82 EXPECT_NEAR(last_vel, ::std::abs(last_pos - position()(0)), 0.0001);
83 }
84 last_vel = ::std::abs(last_pos - position()(0));
85 last_pos = position()(0);
86 }
87 EXPECT_TRUE(At(12.0, 0));
88}
89
briansf0165ca2013-03-02 06:17:47 +000090// There is some somewhat tricky code for dealing with going backwards.
91TEST_F(TrapezoidProfileTest, Backwards) {
Austin Schuhe197a962024-02-20 18:10:12 -080092 RunFor(-2, 0, std::chrono::milliseconds(4000));
briansf0165ca2013-03-02 06:17:47 +000093 EXPECT_TRUE(At(-2, 0));
94}
95
96TEST_F(TrapezoidProfileTest, SwitchGoalInMiddle) {
Austin Schuhe197a962024-02-20 18:10:12 -080097 RunFor(-2, 0, std::chrono::milliseconds(2000));
briansf0165ca2013-03-02 06:17:47 +000098 EXPECT_FALSE(At(-2, 0));
Austin Schuhe197a962024-02-20 18:10:12 -080099 RunFor(0, 0, std::chrono::milliseconds(5500));
briansf0165ca2013-03-02 06:17:47 +0000100 EXPECT_TRUE(At(0, 0));
101}
102
103// Checks to make sure that it hits top speed.
104TEST_F(TrapezoidProfileTest, TopSpeed) {
Austin Schuhe197a962024-02-20 18:10:12 -0800105 RunFor(4, 0, std::chrono::milliseconds(2000));
briansf0165ca2013-03-02 06:17:47 +0000106 EXPECT_NEAR(1.5, position()(1), 10e-5);
Austin Schuhe197a962024-02-20 18:10:12 -0800107 RunFor(4, 0, std::chrono::milliseconds(20000));
briansf0165ca2013-03-02 06:17:47 +0000108 EXPECT_TRUE(At(4, 0));
109}
110
Austin Schuh5d571d02017-02-11 12:28:17 -0800111// Tests that the position and velocity exactly match at the end. Some code we
112// have assumes this to be true as a simplification.
113TEST_F(TrapezoidProfileTest, ExactlyReachesGoal) {
Austin Schuhe197a962024-02-20 18:10:12 -0800114 RunFor(1, 0, std::chrono::milliseconds(4500));
Austin Schuh5d571d02017-02-11 12:28:17 -0800115 EXPECT_EQ(position()(1), 0.0);
116 EXPECT_EQ(position()(0), 1.0);
117}
118
Austin Schuhe197a962024-02-20 18:10:12 -0800119// Tests that we can move a goal without the trajectory teleporting. The goal
120// needs to move to something we haven't already passed, but will blow by.
Austin Schuhb3b799e2024-02-20 14:20:12 -0800121TEST_F(TrapezoidProfileTest, MoveGoal) {
122 profile_.set_maximum_acceleration(2.0);
Austin Schuhe197a962024-02-20 18:10:12 -0800123 profile_.set_maximum_deceleration(2.0);
Austin Schuhb3b799e2024-02-20 14:20:12 -0800124 profile_.set_maximum_velocity(2.0);
125
126 RunFor(5.0, 0, std::chrono::seconds(1));
127 EXPECT_TRUE(At(1.0, 2.0));
128 RunFor(5.0, 0, std::chrono::seconds(1));
129 EXPECT_TRUE(At(3.0, 2.0));
130 RunFor(3.5, 0, std::chrono::seconds(1));
131 EXPECT_TRUE(At(4.0, 0.0));
132 RunFor(3.5, 0, std::chrono::seconds(1));
133 EXPECT_TRUE(At(3.5, 0.0));
134}
135
Austin Schuhe197a962024-02-20 18:10:12 -0800136// Tests that we can move a goal back before where we currently are without
137// teleporting.
138TEST_F(TrapezoidProfileTest, MoveGoalFar) {
139 profile_.set_maximum_acceleration(2.0);
140 profile_.set_maximum_deceleration(2.0);
141 profile_.set_maximum_velocity(2.0);
142
143 RunFor(5.0, 0, std::chrono::seconds(1));
144 EXPECT_TRUE(At(1.0, 2.0));
145 RunFor(5.0, 0, std::chrono::seconds(1));
146 EXPECT_TRUE(At(3.0, 2.0));
147 RunFor(2.5, 0, std::chrono::seconds(1));
148 EXPECT_TRUE(At(4.0, 0.0));
149 RunFor(2.5, 0, std::chrono::seconds(2));
150 EXPECT_TRUE(At(2.5, 0.0));
151}
152
153// Tests that we can move a goal without the trajectory teleporting. The goal
154// needs to move to something we haven't already passed, but will blow by. Do
155// this one in the negative direction.
156TEST_F(TrapezoidProfileTest, MoveGoalNegative) {
157 profile_.set_maximum_acceleration(2.0);
158 profile_.set_maximum_deceleration(2.0);
159 profile_.set_maximum_velocity(2.0);
160
161 RunFor(-5.0, 0, std::chrono::seconds(1));
162 EXPECT_TRUE(At(-1.0, -2.0));
163 RunFor(-5.0, 0, std::chrono::seconds(1));
164 EXPECT_TRUE(At(-3.0, -2.0));
165 RunFor(-3.5, 0, std::chrono::seconds(1));
166 EXPECT_TRUE(At(-4.0, 0.0));
167 RunFor(-3.5, 0, std::chrono::seconds(1));
168 EXPECT_TRUE(At(-3.5, 0.0));
169}
170
171// Tests that we can move a goal back before where we currently are without
172// teleporting. Do this one in the negative direction.
173TEST_F(TrapezoidProfileTest, MoveGoalNegativeFar) {
174 profile_.set_maximum_acceleration(2.0);
175 profile_.set_maximum_deceleration(2.0);
176 profile_.set_maximum_velocity(2.0);
177
178 RunFor(-5.0, 0, std::chrono::seconds(1));
179 EXPECT_TRUE(At(-1.0, -2.0));
180 RunFor(-5.0, 0, std::chrono::seconds(1));
181 EXPECT_TRUE(At(-3.0, -2.0));
182 RunFor(-2.5, 0, std::chrono::seconds(1));
183 EXPECT_TRUE(At(-4.0, 0.0));
184 RunFor(-2.5, 0, std::chrono::seconds(2));
185 EXPECT_TRUE(At(-2.5, 0.0));
186}
187
188// Tests that we can execute a profile with acceleration and deceleration not
189// matching in magnitude.
190TEST_F(TrapezoidProfileTest, AsymmetricAccelDecel) {
191 // Accelerates up until t=1. Will be at x=0.5
192 profile_.set_maximum_acceleration(1.0);
193 // Decelerates in t=0.5 Will take x=0.25
194 profile_.set_maximum_deceleration(2.0);
195 profile_.set_maximum_velocity(1.0);
196
197 RunFor(1.75, 0, std::chrono::seconds(1));
198
199 EXPECT_TRUE(At(0.5, 1.0));
200
201 RunFor(1.75, 0, std::chrono::seconds(1));
202 EXPECT_TRUE(At(1.5, 1.0));
203 RunFor(1.75, 0, std::chrono::milliseconds(500));
204 EXPECT_TRUE(At(1.75, 0.0));
205}
206
207// Tests that we can execute a profile with acceleration and deceleration not
208// matching in magnitude, and hitting saturation.
209TEST_F(TrapezoidProfileTest, AsymmetricAccelDecelUnconstrained) {
210 // Accelerates up until t=1. Will be at x=0.5
211 profile_.set_maximum_acceleration(1.0);
212 // Decelerates in t=0.5 Will take x=0.25
213 profile_.set_maximum_deceleration(2.0);
214 profile_.set_maximum_velocity(2.0);
215
216 RunFor(0.75, 0, std::chrono::seconds(1));
217 EXPECT_TRUE(At(0.5, 1.0));
218
219 RunFor(0.75, 0, std::chrono::milliseconds(500));
220 EXPECT_TRUE(At(0.75, 0.0));
221}
222
223// Tests that we can execute a profile with acceleration and deceleration not
224// matching in magnitude, and hitting saturation.
225TEST_F(TrapezoidProfileTest, AsymmetricAccelDecelUnconstrainedNegative) {
226 // Accelerates up until t=1. Will be at x=0.5
227 profile_.set_maximum_acceleration(1.0);
228 // Decelerates in t=0.5 Will take x=0.25
229 profile_.set_maximum_deceleration(2.0);
230 profile_.set_maximum_velocity(2.0);
231
232 RunFor(-0.75, 0, std::chrono::seconds(1));
233 EXPECT_TRUE(At(-0.5, -1.0));
234
235 RunFor(-0.75, 0, std::chrono::milliseconds(500));
236 EXPECT_TRUE(At(-0.75, 0.0));
237}
238
239// Tests that we can execute a profile with acceleration and deceleration not
240// matching in magnitude when going in the negative direction.
241TEST_F(TrapezoidProfileTest, AsymmetricAccelDecelNegative) {
242 // Accelerates up until t=1. Will be at x=0.5
243 profile_.set_maximum_acceleration(1.0);
244 // Decelerates in t=0.5 Will take x=0.25
245 profile_.set_maximum_deceleration(2.0);
246 profile_.set_maximum_velocity(1.0);
247
248 RunFor(-1.75, 0, std::chrono::seconds(1));
249
250 EXPECT_TRUE(At(-0.5, -1.0));
251
252 RunFor(-1.75, 0, std::chrono::seconds(1));
253 EXPECT_TRUE(At(-1.5, -1.0));
254 RunFor(-1.75, 0, std::chrono::milliseconds(500));
255 EXPECT_TRUE(At(-1.75, 0.0));
256}
257
258// Tests that we can move the goal when an asymmetric profile is executing.
259TEST_F(TrapezoidProfileTest, AsymmetricAccelDecelMoveGoal) {
260 // Accelerates up until t=1. Will be at x=0.5
261 profile_.set_maximum_acceleration(1.0);
262 // Decelerates in t=0.5 Will take x=0.25
263 profile_.set_maximum_deceleration(2.0);
264 profile_.set_maximum_velocity(1.0);
265
266 RunFor(1.75, 0, std::chrono::seconds(1));
267
268 EXPECT_TRUE(At(0.5, 1.0));
269
270 RunFor(1.75, 0, std::chrono::seconds(1));
271 EXPECT_TRUE(At(1.5, 1.0));
272 RunFor(1.6, 0, std::chrono::milliseconds(500));
273 EXPECT_TRUE(At(1.75, 0.0));
274 RunFor(1.6, 0, std::chrono::milliseconds(520));
275 RunFor(1.6, 0, std::chrono::milliseconds(2500));
276 EXPECT_TRUE(At(1.6, 0.0));
277}
278
279// Tests that we can move the goal when an asymmetric profile is executing in
280// the negative direction.
281TEST_F(TrapezoidProfileTest, AsymmetricAccelDecelMoveGoalFar) {
282 // Accelerates up until t=1. Will be at x=0.5
283 profile_.set_maximum_acceleration(1.0);
284 // Decelerates in t=0.5 Will take x=0.25
285 profile_.set_maximum_deceleration(2.0);
286 profile_.set_maximum_velocity(1.0);
287
288 RunFor(1.75, 0, std::chrono::seconds(1));
289
290 EXPECT_TRUE(At(0.5, 1.0));
291
292 RunFor(1.75, 0, std::chrono::seconds(1));
293 EXPECT_TRUE(At(1.5, 1.0));
294 RunFor(1.0, 0, std::chrono::milliseconds(500));
295 EXPECT_TRUE(At(1.75, 0.0));
296 RunFor(1.0, 0, std::chrono::milliseconds(2500));
297 EXPECT_TRUE(At(1.0, 0.0));
298}
299
Stephan Pleinesf63bde82024-01-13 15:59:33 -0800300} // namespace aos::util::testing