blob: 32461d3540ded0853af57dfa732d1ea56b53d743 [file] [log] [blame]
Austin Schuh60e77942022-05-16 17:48:24 -07001#include "aos/util/trapezoid_profile.h"
briansf0165ca2013-03-02 06:17:47 +00002
Stephan Pleinesb1177672024-05-27 17:48:32 -07003#include <compare>
4#include <cstdlib>
5#include <memory>
6#include <ratio>
7
8#include "Eigen/Dense" // IWYU pragma: keep
Austin Schuh60e77942022-05-16 17:48:24 -07009#include "gtest/gtest.h"
briansf0165ca2013-03-02 06:17:47 +000010
Stephan Pleinesf63bde82024-01-13 15:59:33 -080011namespace aos::util::testing {
briansf0165ca2013-03-02 06:17:47 +000012
13class TrapezoidProfileTest : public ::testing::Test {
14 public:
15 EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
16
17 protected:
Austin Schuhb3b799e2024-02-20 14:20:12 -080018 TrapezoidProfileTest() : profile_(kDeltaTime) {
briansf0165ca2013-03-02 06:17:47 +000019 position_.setZero();
20 profile_.set_maximum_acceleration(0.75);
Austin Schuhe197a962024-02-20 18:10:12 -080021 profile_.set_maximum_deceleration(0.75);
briansf0165ca2013-03-02 06:17:47 +000022 profile_.set_maximum_velocity(1.75);
23 }
24
25 // Runs an iteration.
Austin Schuh60e77942022-05-16 17:48:24 -070026 void RunIteration(double goal_position, double goal_velocity) {
27 position_ = profile_.Update(goal_position, goal_velocity);
briansf0165ca2013-03-02 06:17:47 +000028 }
29
Austin Schuhb3b799e2024-02-20 14:20:12 -080030 void RunFor(double goal_position, double goal_velocity,
31 std::chrono::nanoseconds duration) {
32 while (duration > std::chrono::nanoseconds(0)) {
33 position_ = profile_.Update(goal_position, goal_velocity);
34 duration -= kDeltaTime;
35 }
36
37 ASSERT_EQ(duration.count(), 0);
38 }
39
briansf0165ca2013-03-02 06:17:47 +000040 const Eigen::Matrix<double, 2, 1> &position() { return position_; }
41
Austin Schuhe197a962024-02-20 18:10:12 -080042 AsymmetricTrapezoidProfile profile_;
briansf0165ca2013-03-02 06:17:47 +000043
44 ::testing::AssertionResult At(double position, double velocity) {
briansd412b3f2013-03-03 21:13:44 +000045 static const double kDoubleNear = 0.00001;
46 if (::std::abs(velocity - position_(1)) > kDoubleNear) {
Austin Schuh60e77942022-05-16 17:48:24 -070047 return ::testing::AssertionFailure()
48 << "velocity is " << position_(1) << " not " << velocity;
briansf0165ca2013-03-02 06:17:47 +000049 }
briansd412b3f2013-03-03 21:13:44 +000050 if (::std::abs(position - position_(0)) > kDoubleNear) {
Austin Schuh60e77942022-05-16 17:48:24 -070051 return ::testing::AssertionFailure()
52 << "position is " << position_(0) << " not " << position;
briansf0165ca2013-03-02 06:17:47 +000053 }
Austin Schuh60e77942022-05-16 17:48:24 -070054 return ::testing::AssertionSuccess()
55 << "at " << position << " moving at " << velocity;
briansf0165ca2013-03-02 06:17:47 +000056 }
57
58 private:
Austin Schuhb3b799e2024-02-20 14:20:12 -080059 static constexpr ::std::chrono::nanoseconds kDeltaTime =
Austin Schuh214e9c12016-11-25 17:26:20 -080060 ::std::chrono::milliseconds(10);
briansf0165ca2013-03-02 06:17:47 +000061
62 Eigen::Matrix<double, 2, 1> position_;
63};
Austin Schuh214e9c12016-11-25 17:26:20 -080064
Austin Schuhb3b799e2024-02-20 14:20:12 -080065constexpr ::std::chrono::nanoseconds TrapezoidProfileTest::kDeltaTime;
briansf0165ca2013-03-02 06:17:47 +000066
67TEST_F(TrapezoidProfileTest, ReachesGoal) {
Austin Schuhe197a962024-02-20 18:10:12 -080068 RunFor(3, 0, std::chrono::milliseconds(4500));
briansf0165ca2013-03-02 06:17:47 +000069 EXPECT_TRUE(At(3, 0));
70}
71
Austin Schuhb3b799e2024-02-20 14:20:12 -080072// Tests that decreasing the maximum velocity in the middle when it is already
Ben Fredricksonf33d6532015-03-15 00:29:29 -070073// moving faster than the new max is handled correctly.
74TEST_F(TrapezoidProfileTest, ContinousUnderVelChange) {
75 profile_.set_maximum_velocity(1.75);
Austin Schuhe197a962024-02-20 18:10:12 -080076 RunFor(12.0, 0, std::chrono::milliseconds(10));
Ben Fredricksonf33d6532015-03-15 00:29:29 -070077 double last_pos = position()(0);
78 double last_vel = 1.75;
79 for (int i = 0; i < 1600; ++i) {
80 if (i == 400) {
81 profile_.set_maximum_velocity(0.75);
82 }
Austin Schuhe197a962024-02-20 18:10:12 -080083 RunFor(12.0, 0, std::chrono::milliseconds(10));
Ben Fredricksonf33d6532015-03-15 00:29:29 -070084 if (i >= 400) {
85 EXPECT_TRUE(::std::abs(last_pos - position()(0)) <= 1.75 * 0.01);
86 EXPECT_NEAR(last_vel, ::std::abs(last_pos - position()(0)), 0.0001);
87 }
88 last_vel = ::std::abs(last_pos - position()(0));
89 last_pos = position()(0);
90 }
91 EXPECT_TRUE(At(12.0, 0));
92}
93
briansf0165ca2013-03-02 06:17:47 +000094// There is some somewhat tricky code for dealing with going backwards.
95TEST_F(TrapezoidProfileTest, Backwards) {
Austin Schuhe197a962024-02-20 18:10:12 -080096 RunFor(-2, 0, std::chrono::milliseconds(4000));
briansf0165ca2013-03-02 06:17:47 +000097 EXPECT_TRUE(At(-2, 0));
98}
99
100TEST_F(TrapezoidProfileTest, SwitchGoalInMiddle) {
Austin Schuhe197a962024-02-20 18:10:12 -0800101 RunFor(-2, 0, std::chrono::milliseconds(2000));
briansf0165ca2013-03-02 06:17:47 +0000102 EXPECT_FALSE(At(-2, 0));
Austin Schuhe197a962024-02-20 18:10:12 -0800103 RunFor(0, 0, std::chrono::milliseconds(5500));
briansf0165ca2013-03-02 06:17:47 +0000104 EXPECT_TRUE(At(0, 0));
105}
106
107// Checks to make sure that it hits top speed.
108TEST_F(TrapezoidProfileTest, TopSpeed) {
Austin Schuhe197a962024-02-20 18:10:12 -0800109 RunFor(4, 0, std::chrono::milliseconds(2000));
briansf0165ca2013-03-02 06:17:47 +0000110 EXPECT_NEAR(1.5, position()(1), 10e-5);
Austin Schuhe197a962024-02-20 18:10:12 -0800111 RunFor(4, 0, std::chrono::milliseconds(20000));
briansf0165ca2013-03-02 06:17:47 +0000112 EXPECT_TRUE(At(4, 0));
113}
114
Austin Schuh5d571d02017-02-11 12:28:17 -0800115// Tests that the position and velocity exactly match at the end. Some code we
116// have assumes this to be true as a simplification.
117TEST_F(TrapezoidProfileTest, ExactlyReachesGoal) {
Austin Schuhe197a962024-02-20 18:10:12 -0800118 RunFor(1, 0, std::chrono::milliseconds(4500));
Austin Schuh5d571d02017-02-11 12:28:17 -0800119 EXPECT_EQ(position()(1), 0.0);
120 EXPECT_EQ(position()(0), 1.0);
121}
122
Austin Schuhe197a962024-02-20 18:10:12 -0800123// Tests that we can move a goal without the trajectory teleporting. The goal
124// needs to move to something we haven't already passed, but will blow by.
Austin Schuhb3b799e2024-02-20 14:20:12 -0800125TEST_F(TrapezoidProfileTest, MoveGoal) {
126 profile_.set_maximum_acceleration(2.0);
Austin Schuhe197a962024-02-20 18:10:12 -0800127 profile_.set_maximum_deceleration(2.0);
Austin Schuhb3b799e2024-02-20 14:20:12 -0800128 profile_.set_maximum_velocity(2.0);
129
130 RunFor(5.0, 0, std::chrono::seconds(1));
131 EXPECT_TRUE(At(1.0, 2.0));
132 RunFor(5.0, 0, std::chrono::seconds(1));
133 EXPECT_TRUE(At(3.0, 2.0));
134 RunFor(3.5, 0, std::chrono::seconds(1));
135 EXPECT_TRUE(At(4.0, 0.0));
136 RunFor(3.5, 0, std::chrono::seconds(1));
137 EXPECT_TRUE(At(3.5, 0.0));
138}
139
Austin Schuhe197a962024-02-20 18:10:12 -0800140// Tests that we can move a goal back before where we currently are without
141// teleporting.
142TEST_F(TrapezoidProfileTest, MoveGoalFar) {
143 profile_.set_maximum_acceleration(2.0);
144 profile_.set_maximum_deceleration(2.0);
145 profile_.set_maximum_velocity(2.0);
146
147 RunFor(5.0, 0, std::chrono::seconds(1));
148 EXPECT_TRUE(At(1.0, 2.0));
149 RunFor(5.0, 0, std::chrono::seconds(1));
150 EXPECT_TRUE(At(3.0, 2.0));
151 RunFor(2.5, 0, std::chrono::seconds(1));
152 EXPECT_TRUE(At(4.0, 0.0));
153 RunFor(2.5, 0, std::chrono::seconds(2));
154 EXPECT_TRUE(At(2.5, 0.0));
155}
156
157// Tests that we can move a goal without the trajectory teleporting. The goal
158// needs to move to something we haven't already passed, but will blow by. Do
159// this one in the negative direction.
160TEST_F(TrapezoidProfileTest, MoveGoalNegative) {
161 profile_.set_maximum_acceleration(2.0);
162 profile_.set_maximum_deceleration(2.0);
163 profile_.set_maximum_velocity(2.0);
164
165 RunFor(-5.0, 0, std::chrono::seconds(1));
166 EXPECT_TRUE(At(-1.0, -2.0));
167 RunFor(-5.0, 0, std::chrono::seconds(1));
168 EXPECT_TRUE(At(-3.0, -2.0));
169 RunFor(-3.5, 0, std::chrono::seconds(1));
170 EXPECT_TRUE(At(-4.0, 0.0));
171 RunFor(-3.5, 0, std::chrono::seconds(1));
172 EXPECT_TRUE(At(-3.5, 0.0));
173}
174
175// Tests that we can move a goal back before where we currently are without
176// teleporting. Do this one in the negative direction.
177TEST_F(TrapezoidProfileTest, MoveGoalNegativeFar) {
178 profile_.set_maximum_acceleration(2.0);
179 profile_.set_maximum_deceleration(2.0);
180 profile_.set_maximum_velocity(2.0);
181
182 RunFor(-5.0, 0, std::chrono::seconds(1));
183 EXPECT_TRUE(At(-1.0, -2.0));
184 RunFor(-5.0, 0, std::chrono::seconds(1));
185 EXPECT_TRUE(At(-3.0, -2.0));
186 RunFor(-2.5, 0, std::chrono::seconds(1));
187 EXPECT_TRUE(At(-4.0, 0.0));
188 RunFor(-2.5, 0, std::chrono::seconds(2));
189 EXPECT_TRUE(At(-2.5, 0.0));
190}
191
192// Tests that we can execute a profile with acceleration and deceleration not
193// matching in magnitude.
194TEST_F(TrapezoidProfileTest, AsymmetricAccelDecel) {
195 // Accelerates up until t=1. Will be at x=0.5
196 profile_.set_maximum_acceleration(1.0);
197 // Decelerates in t=0.5 Will take x=0.25
198 profile_.set_maximum_deceleration(2.0);
199 profile_.set_maximum_velocity(1.0);
200
201 RunFor(1.75, 0, std::chrono::seconds(1));
202
203 EXPECT_TRUE(At(0.5, 1.0));
204
205 RunFor(1.75, 0, std::chrono::seconds(1));
206 EXPECT_TRUE(At(1.5, 1.0));
207 RunFor(1.75, 0, std::chrono::milliseconds(500));
208 EXPECT_TRUE(At(1.75, 0.0));
209}
210
211// Tests that we can execute a profile with acceleration and deceleration not
212// matching in magnitude, and hitting saturation.
213TEST_F(TrapezoidProfileTest, AsymmetricAccelDecelUnconstrained) {
214 // Accelerates up until t=1. Will be at x=0.5
215 profile_.set_maximum_acceleration(1.0);
216 // Decelerates in t=0.5 Will take x=0.25
217 profile_.set_maximum_deceleration(2.0);
218 profile_.set_maximum_velocity(2.0);
219
220 RunFor(0.75, 0, std::chrono::seconds(1));
221 EXPECT_TRUE(At(0.5, 1.0));
222
223 RunFor(0.75, 0, std::chrono::milliseconds(500));
224 EXPECT_TRUE(At(0.75, 0.0));
225}
226
227// Tests that we can execute a profile with acceleration and deceleration not
228// matching in magnitude, and hitting saturation.
229TEST_F(TrapezoidProfileTest, AsymmetricAccelDecelUnconstrainedNegative) {
230 // Accelerates up until t=1. Will be at x=0.5
231 profile_.set_maximum_acceleration(1.0);
232 // Decelerates in t=0.5 Will take x=0.25
233 profile_.set_maximum_deceleration(2.0);
234 profile_.set_maximum_velocity(2.0);
235
236 RunFor(-0.75, 0, std::chrono::seconds(1));
237 EXPECT_TRUE(At(-0.5, -1.0));
238
239 RunFor(-0.75, 0, std::chrono::milliseconds(500));
240 EXPECT_TRUE(At(-0.75, 0.0));
241}
242
243// Tests that we can execute a profile with acceleration and deceleration not
244// matching in magnitude when going in the negative direction.
245TEST_F(TrapezoidProfileTest, AsymmetricAccelDecelNegative) {
246 // Accelerates up until t=1. Will be at x=0.5
247 profile_.set_maximum_acceleration(1.0);
248 // Decelerates in t=0.5 Will take x=0.25
249 profile_.set_maximum_deceleration(2.0);
250 profile_.set_maximum_velocity(1.0);
251
252 RunFor(-1.75, 0, std::chrono::seconds(1));
253
254 EXPECT_TRUE(At(-0.5, -1.0));
255
256 RunFor(-1.75, 0, std::chrono::seconds(1));
257 EXPECT_TRUE(At(-1.5, -1.0));
258 RunFor(-1.75, 0, std::chrono::milliseconds(500));
259 EXPECT_TRUE(At(-1.75, 0.0));
260}
261
262// Tests that we can move the goal when an asymmetric profile is executing.
263TEST_F(TrapezoidProfileTest, AsymmetricAccelDecelMoveGoal) {
264 // Accelerates up until t=1. Will be at x=0.5
265 profile_.set_maximum_acceleration(1.0);
266 // Decelerates in t=0.5 Will take x=0.25
267 profile_.set_maximum_deceleration(2.0);
268 profile_.set_maximum_velocity(1.0);
269
270 RunFor(1.75, 0, std::chrono::seconds(1));
271
272 EXPECT_TRUE(At(0.5, 1.0));
273
274 RunFor(1.75, 0, std::chrono::seconds(1));
275 EXPECT_TRUE(At(1.5, 1.0));
276 RunFor(1.6, 0, std::chrono::milliseconds(500));
277 EXPECT_TRUE(At(1.75, 0.0));
278 RunFor(1.6, 0, std::chrono::milliseconds(520));
279 RunFor(1.6, 0, std::chrono::milliseconds(2500));
280 EXPECT_TRUE(At(1.6, 0.0));
281}
282
283// Tests that we can move the goal when an asymmetric profile is executing in
284// the negative direction.
285TEST_F(TrapezoidProfileTest, AsymmetricAccelDecelMoveGoalFar) {
286 // Accelerates up until t=1. Will be at x=0.5
287 profile_.set_maximum_acceleration(1.0);
288 // Decelerates in t=0.5 Will take x=0.25
289 profile_.set_maximum_deceleration(2.0);
290 profile_.set_maximum_velocity(1.0);
291
292 RunFor(1.75, 0, std::chrono::seconds(1));
293
294 EXPECT_TRUE(At(0.5, 1.0));
295
296 RunFor(1.75, 0, std::chrono::seconds(1));
297 EXPECT_TRUE(At(1.5, 1.0));
298 RunFor(1.0, 0, std::chrono::milliseconds(500));
299 EXPECT_TRUE(At(1.75, 0.0));
300 RunFor(1.0, 0, std::chrono::milliseconds(2500));
301 EXPECT_TRUE(At(1.0, 0.0));
302}
303
Stephan Pleinesf63bde82024-01-13 15:59:33 -0800304} // namespace aos::util::testing