CARLA
Loading...
Searching...
No Matches
test_traffic_manager.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma
2// de Barcelona (UAB).
3//
4// This work is licensed under the terms of the MIT license.
5// For a copy, see <https://opensource.org/licenses/MIT>.
6
7#include "test.h"
8
9#include <algorithm>
10#include <cmath>
11#include <limits>
12#include <utility>
13#include <vector>
14
15#include <carla/geom/Location.h>
20
21namespace cg = carla::geom;
30
31namespace {
32
33constexpr float kRadiusTolerance{1e-2f};
34constexpr float kLocationTolerance{1e-3f};
35
36void ExpectLocationNear(
37 const cg::Location &actual,
38 const cg::Location &expected,
39 float tolerance) {
40 EXPECT_NEAR(actual.x, expected.x, tolerance);
41 EXPECT_NEAR(actual.y, expected.y, tolerance);
42 EXPECT_NEAR(actual.z, expected.z, tolerance);
43}
44
45} // namespace
46
47// -----------------------------------------------------------------------------
48// GetThreePointCircleRadius
49// -----------------------------------------------------------------------------
50
51TEST(TrafficManagerGeometry, ThreePointCircleRadius_EquilateralTriangle) {
52 // Equilateral triangle with side s has circumradius s / sqrt(3).
53 constexpr float side{6.0f};
54 const float expected_radius{side / std::sqrt(3.0f)};
55
56 const cg::Location p1{0.0f, 0.0f, 0.0f};
57 const cg::Location p2{side, 0.0f, 0.0f};
58 const cg::Location p3{side / 2.0f, side * std::sqrt(3.0f) / 2.0f, 0.0f};
59
60 const float radius{GetThreePointCircleRadius(p1, p2, p3)};
61 EXPECT_NEAR(radius, expected_radius, kRadiusTolerance);
62}
63
64TEST(TrafficManagerGeometry, ThreePointCircleRadius_RightTriangle) {
65 // A right triangle's circumradius equals half the hypotenuse.
66 // Triangle with legs 3 and 4, hypotenuse 5, expected radius 2.5.
67 const cg::Location p1{0.0f, 0.0f, 0.0f};
68 const cg::Location p2{3.0f, 0.0f, 0.0f};
69 const cg::Location p3{0.0f, 4.0f, 0.0f};
70
71 const float radius{GetThreePointCircleRadius(p1, p2, p3)};
72 EXPECT_NEAR(radius, 2.5f, kRadiusTolerance);
73}
74
75TEST(TrafficManagerGeometry, ThreePointCircleRadius_NearlyCollinearReturnsLarge) {
76 // Three nearly-collinear points (tiny y offset on the middle point) should
77 // produce a radius above the junction gating threshold so callers fall
78 // through the "no offset" branch, while staying finite (distinct from the
79 // perfectly-collinear sentinel asserted in the next test).
80 const cg::Location p1{0.0f, 0.0f, 0.0f};
81 const cg::Location p2{1.0f, 1e-3f, 0.0f};
82 const cg::Location p3{2.0f, 0.0f, 0.0f};
83
84 const float radius{GetThreePointCircleRadius(p1, p2, p3)};
86 EXPECT_LT(radius, std::numeric_limits<float>::max());
87}
88
89TEST(TrafficManagerGeometry, ThreePointCircleRadius_PerfectlyCollinearReturnsMax) {
90 // Perfectly collinear points have no circumscribing circle. The
91 // implementation returns std::numeric_limits<float>::max() as a sentinel
92 // to flag the degenerate case.
93 const cg::Location p1{0.0f, 0.0f, 0.0f};
94 const cg::Location p2{5.0f, 0.0f, 0.0f};
95 const cg::Location p3{10.0f, 0.0f, 0.0f};
96
97 const float radius{GetThreePointCircleRadius(p1, p2, p3)};
98 EXPECT_EQ(radius, std::numeric_limits<float>::max());
99}
100
101// -----------------------------------------------------------------------------
102// InterpolateBufferAt (the SimpleWaypoint-free seam used by GetTargetData)
103// -----------------------------------------------------------------------------
104
105TEST(TrafficManagerInterpolation, InterpolateBufferAt_EmptyBufferReturnsVehicleLocation) {
106 const std::vector<cg::Location> empty;
107 const cg::Location vehicle_location{1.0f, 2.0f, 3.0f};
108
109 const auto [location, index] = InterpolateBufferAt(empty, 5.0f, vehicle_location);
110 ExpectLocationNear(location, vehicle_location, kLocationTolerance);
111 EXPECT_EQ(index, 0u);
112}
113
114TEST(TrafficManagerInterpolation, InterpolateBufferAt_SingleElementReturnsThatElement) {
115 const std::vector<cg::Location> single{{7.0f, 0.0f, 0.0f}};
116 const cg::Location vehicle_location{0.0f, 0.0f, 0.0f};
117
118 const auto [location, index] = InterpolateBufferAt(single, 5.0f, vehicle_location);
119 ExpectLocationNear(location, single.front(), kLocationTolerance);
120 EXPECT_EQ(index, 0u);
121}
122
123TEST(TrafficManagerInterpolation, InterpolateBufferAt_LinearInterpBetweenTwoPoints) {
124 // Two waypoints at x=0 and x=10. Vehicle at origin. Asking for distance
125 // 5 should land at the midpoint (5, 0, 0) with closest_index 0.
126 const std::vector<cg::Location> buffer{
127 {0.0f, 0.0f, 0.0f},
128 {10.0f, 0.0f, 0.0f}};
129 const cg::Location vehicle_location{0.0f, 0.0f, 0.0f};
130
131 const auto [location, index] = InterpolateBufferAt(buffer, 5.0f, vehicle_location);
132 ExpectLocationNear(location, cg::Location{5.0f, 0.0f, 0.0f}, kLocationTolerance);
133 EXPECT_EQ(index, 0u);
134}
135
136TEST(TrafficManagerInterpolation, InterpolateBufferAt_DistanceShorterThanFirstWaypoint) {
137 // Vehicle at origin; first waypoint at x=10. Asking for distance 3 falls
138 // before the first waypoint, so the helper brackets with the first segment
139 // (indices 0 and 1) and interpolates a point ahead of the vehicle along
140 // the planned path.
141 const std::vector<cg::Location> buffer{
142 {10.0f, 0.0f, 0.0f},
143 {20.0f, 0.0f, 0.0f}};
144 const cg::Location vehicle_location{0.0f, 0.0f, 0.0f};
145
146 const auto [location, index] = InterpolateBufferAt(buffer, 3.0f, vehicle_location);
147 // closest=0, farthest=0 → fix-up to (0, 1):
148 // t = (3 - 10) / (20 - 10) = -0.7
149 // x = 10 + (20 - 10) * -0.7 = 3
150 ExpectLocationNear(location, cg::Location{3.0f, 0.0f, 0.0f}, kLocationTolerance);
151 EXPECT_EQ(index, 0u);
152}
153
154TEST(TrafficManagerInterpolation, InterpolateBufferAt_DistanceBeyondLastWaypoint) {
155 // Every waypoint is closer than target_distance, so the helper clamps to
156 // the last waypoint instead of extrapolating off the front segment.
157 const std::vector<cg::Location> buffer{
158 {1.0f, 0.0f, 0.0f},
159 {2.0f, 0.0f, 0.0f},
160 {3.0f, 0.0f, 0.0f}};
161 const cg::Location vehicle_location{0.0f, 0.0f, 0.0f};
162
163 const auto [location, index] = InterpolateBufferAt(buffer, 100.0f, vehicle_location);
164 ExpectLocationNear(location, buffer.back(), kLocationTolerance);
165 EXPECT_EQ(index, buffer.size() - 1u);
166}
167
168TEST(TrafficManagerInterpolation, InterpolateBufferAt_BracketBetweenLastTwoWaypoints) {
169 // Target falls between the second-to-last and last waypoints. The
170 // corrected loop must walk the full buffer to find the bracketing pair
171 // (the previous implementation stopped at size - 1 and missed it).
172 const std::vector<cg::Location> buffer{
173 {1.0f, 0.0f, 0.0f},
174 {5.0f, 0.0f, 0.0f},
175 {100.0f, 0.0f, 0.0f}};
176 const cg::Location vehicle_location{0.0f, 0.0f, 0.0f};
177
178 const auto [location, index] = InterpolateBufferAt(buffer, 10.0f, vehicle_location);
179 // closest=1 (dist 5), farthest=2 (dist 100):
180 // t = (10 - 5) / (100 - 5) ≈ 0.0526
181 // x ≈ 5 + (100 - 5) * 0.0526 = 10
182 ExpectLocationNear(location, cg::Location{10.0f, 0.0f, 0.0f}, kLocationTolerance);
183 EXPECT_EQ(index, 1u);
184}
185
186TEST(TrafficManagerInterpolation, InterpolateBufferAt_CoincidentBracketReturnsClosest) {
187 // Two consecutive waypoints at the same location. The span is zero and
188 // the implementation must return the close location to avoid divide-by-zero.
189 const std::vector<cg::Location> buffer{
190 {5.0f, 0.0f, 0.0f},
191 {5.0f, 0.0f, 0.0f},
192 {15.0f, 0.0f, 0.0f}};
193 const cg::Location vehicle_location{0.0f, 0.0f, 0.0f};
194
195 const auto [location, index] = InterpolateBufferAt(buffer, 5.0f, vehicle_location);
196 ExpectLocationNear(location, cg::Location{5.0f, 0.0f, 0.0f}, kLocationTolerance);
197 EXPECT_EQ(index, 0u);
198}
199
200// -----------------------------------------------------------------------------
201// GetPathSpeedLimit
202// -----------------------------------------------------------------------------
203
204namespace {
205
206constexpr float kSampleSpacing{5.0f};
207constexpr float kLateralAcceleration{3.0f};
208constexpr float kBrakingDeceleration{2.0f};
209constexpr float kFreeSpeed{30.0f};
210
211/// A straight run along +x followed by a left arc, sampled every `spacing` m.
212std::vector<cg::Location> StraightThenArc(
213 float straight_length,
214 float radius,
215 float arc_degrees,
216 float spacing) {
217
218 std::vector<cg::Location> path;
219 for (float s = 0.0f; s < straight_length; s += spacing) {
220 path.push_back(cg::Location{s, 0.0f, 0.0f});
221 }
222 const cg::Location centre{straight_length, radius, 0.0f};
223 const float arc_radians = arc_degrees * 3.14159265f / 180.0f;
224 for (float travelled = 0.0f; travelled <= radius * arc_radians; travelled += spacing) {
225 const float angle = travelled / radius;
226 path.push_back(cg::Location{centre.x + radius * std::sin(angle),
227 centre.y - radius * std::cos(angle),
228 0.0f});
229 }
230 return path;
231}
232
233} // namespace
234
235TEST(TrafficManagerPathSpeed, PathSpeedLimit_StraightPathIsNotLimited) {
236 const std::vector<cg::Location> path{
237 {0.0f, 0.0f, 0.0f}, {5.0f, 0.0f, 0.0f}, {10.0f, 0.0f, 0.0f},
238 {15.0f, 0.0f, 0.0f}, {20.0f, 0.0f, 0.0f}};
239
240 EXPECT_FLOAT_EQ(
241 GetPathSpeedLimit(path, 0.0f, kSampleSpacing, kLateralAcceleration,
242 kBrakingDeceleration, kFreeSpeed),
243 kFreeSpeed);
244}
245
246TEST(TrafficManagerPathSpeed, PathSpeedLimit_ShortPathIsNotLimited) {
247 const std::vector<cg::Location> path{{0.0f, 0.0f, 0.0f}, {5.0f, 0.0f, 0.0f}};
248
249 EXPECT_FLOAT_EQ(
250 GetPathSpeedLimit(path, 0.0f, kSampleSpacing, kLateralAcceleration,
251 kBrakingDeceleration, kFreeSpeed),
252 kFreeSpeed);
253}
254
255TEST(TrafficManagerPathSpeed, PathSpeedLimit_ArcUnderfootAllowsItsOwnSpeed) {
256 const float radius = 11.0f;
257 const std::vector<cg::Location> path = StraightThenArc(0.0f, radius, 120.0f, kSampleSpacing);
258
259 EXPECT_NEAR(
260 GetPathSpeedLimit(path, 0.0f, kSampleSpacing, kLateralAcceleration,
261 kBrakingDeceleration, kFreeSpeed),
262 std::sqrt(radius * kLateralAcceleration),
263 0.1f);
264}
265
266TEST(TrafficManagerPathSpeed, PathSpeedLimit_HoldsSteadyAsAnArcIsConsumed) {
267 const float radius = 11.0f;
268 const std::vector<cg::Location> path = StraightThenArc(0.0f, radius, 180.0f, kSampleSpacing);
269
270 std::vector<float> limits;
271 for (size_t consumed = 0u; consumed < 4u; ++consumed) {
272 for (float offset = 0.0f; offset < kSampleSpacing; offset += 1.0f) {
273 const std::vector<cg::Location> remaining(path.begin() + static_cast<long>(consumed),
274 path.end());
275 limits.push_back(
276 GetPathSpeedLimit(remaining, -offset, kSampleSpacing, kLateralAcceleration,
277 kBrakingDeceleration, kFreeSpeed));
278 }
279 }
280
281 const auto [lowest, highest] = std::minmax_element(limits.begin(), limits.end());
282 EXPECT_LT(*highest - *lowest, 0.35f) << "limit swings by " << (*highest - *lowest) << " m/s";
283}
284
285TEST(TrafficManagerPathSpeed, PathSpeedLimit_AllowsMoreSpeedFurtherFromTheTurn) {
286 const float radius = 11.0f;
287 const std::vector<cg::Location> near_path = StraightThenArc(10.0f, radius, 90.0f, kSampleSpacing);
288 const std::vector<cg::Location> far_path = StraightThenArc(40.0f, radius, 90.0f, kSampleSpacing);
289
290 const float near_limit = GetPathSpeedLimit(
291 near_path, 0.0f, kSampleSpacing, kLateralAcceleration, kBrakingDeceleration, kFreeSpeed);
292 const float far_limit = GetPathSpeedLimit(
293 far_path, 0.0f, kSampleSpacing, kLateralAcceleration, kBrakingDeceleration, kFreeSpeed);
294
295 EXPECT_GT(far_limit, near_limit);
296 EXPECT_GT(near_limit, std::sqrt(radius * kLateralAcceleration));
297}
298
299TEST(TrafficManagerPathSpeed, PathSpeedLimit_TighterTurnAllowsLessSpeed) {
300 // Junction connectors are stored more densely than the map resolution.
301 constexpr float kJunctionSpacing{2.0f};
302 const std::vector<cg::Location> tight = StraightThenArc(0.0f, 6.0f, 120.0f, kJunctionSpacing);
303 const std::vector<cg::Location> open = StraightThenArc(0.0f, 25.0f, 120.0f, kJunctionSpacing);
304
305 EXPECT_LT(
306 GetPathSpeedLimit(tight, 0.0f, kSampleSpacing, kLateralAcceleration,
307 kBrakingDeceleration, kFreeSpeed),
308 GetPathSpeedLimit(open, 0.0f, kSampleSpacing, kLateralAcceleration,
309 kBrakingDeceleration, kFreeSpeed));
310}
311
312TEST(TrafficManagerPathSpeed, PathSpeedLimit_PathShorterThanTwoSamplesIsNotLimited) {
313 const std::vector<cg::Location> stub = StraightThenArc(0.0f, 6.0f, 60.0f, 2.0f);
314 ASSERT_LT(stub.size() * 2.0f, 2.0f * kSampleSpacing);
315
316 EXPECT_FLOAT_EQ(
317 GetPathSpeedLimit(stub, 0.0f, kSampleSpacing, kLateralAcceleration,
318 kBrakingDeceleration, kFreeSpeed),
319 kFreeSpeed);
320}
321
322TEST(TrafficManagerPathSpeed, PathSpeedLimit_NonPositiveSpacingIsNotLimited) {
323 const std::vector<cg::Location> path = StraightThenArc(0.0f, 6.0f, 120.0f, 2.0f);
324
325 EXPECT_FLOAT_EQ(
326 GetPathSpeedLimit(path, 0.0f, 0.0f, kLateralAcceleration, kBrakingDeceleration, kFreeSpeed),
327 kFreeSpeed);
328}
329
330TEST(TrafficManagerPathSpeed, PathSpeedLimit_ArcBehindTheVehicleIsNotCreditedWithBraking) {
331 const float radius = 11.0f;
332 const std::vector<cg::Location> path = StraightThenArc(0.0f, radius, 180.0f, kSampleSpacing);
333
334 const float behind = GetPathSpeedLimit(
335 path, -kSampleSpacing, kSampleSpacing, kLateralAcceleration, kBrakingDeceleration,
336 kFreeSpeed);
337 const float ahead = GetPathSpeedLimit(
338 path, kSampleSpacing, kSampleSpacing, kLateralAcceleration, kBrakingDeceleration,
339 kFreeSpeed);
340
341 EXPECT_NEAR(behind, std::sqrt(radius * kLateralAcceleration), 0.1f);
342 EXPECT_GT(ahead, behind);
343}
344
345// -----------------------------------------------------------------------------
346// LargeVehicleJunctionOffsetProfile (Option C: bounded inboard excursion)
347// -----------------------------------------------------------------------------
348
349namespace {
350
351constexpr float kProfileMaxOffset{2.0f};
352constexpr float kProfileMaxOffsetPoint{0.3f};
353constexpr float kProfileInboardScale{0.25f};
354constexpr float kProfileTolerance{1e-3f};
355
356float SampleProfile(float t, float inboard_scale) {
358 t, kProfileMaxOffset, kProfileMaxOffsetPoint, inboard_scale);
359}
360
361} // namespace
362
363TEST(TrafficManagerWideTurn, OffsetProfile_ZeroAtEntryAndExit) {
364 // t = 0 is the junction exit, t = 1 is the entry; both must produce no
365 // offset so the manoeuvre starts and ends on the centreline.
366 EXPECT_NEAR(SampleProfile(0.0f, kProfileInboardScale), 0.0f, kProfileTolerance);
367 EXPECT_NEAR(SampleProfile(1.0f, kProfileInboardScale), 0.0f, kProfileTolerance);
368}
369
370TEST(TrafficManagerWideTurn, OffsetProfile_OutboardSwingReachesFullMagnitude) {
371 // The outboard (negative) swing near the entry is preserved at full
372 // magnitude so the vehicle still opens the turn up. Its peak sits at
373 // t = 1 - max_offset_point.
374 const float outboard{SampleProfile(1.0f - kProfileMaxOffsetPoint, kProfileInboardScale)};
375 EXPECT_NEAR(outboard, -kProfileMaxOffset, kProfileTolerance);
376}
377
378TEST(TrafficManagerWideTurn, OffsetProfile_InboardExcursionIsCapped) {
379 // The inboard (positive) excursion is the exit cut-in that drives the rear
380 // into the inside shoulder. Across the whole junction it must never exceed
381 // inboard_scale * max_offset, and never fall below -max_offset.
382 const float inboard_cap{kProfileInboardScale * kProfileMaxOffset};
383 for (int i = 0; i <= 100; ++i) {
384 const float t{static_cast<float>(i) / 100.0f};
385 const float offset{SampleProfile(t, kProfileInboardScale)};
386 EXPECT_LE(offset, inboard_cap + kProfileTolerance);
387 EXPECT_GE(offset, -kProfileMaxOffset - kProfileTolerance);
388 }
389}
390
391TEST(TrafficManagerWideTurn, OffsetProfile_ZeroInboardScaleRemovesCutIn) {
392 // With inboard_scale = 0 the exit cut-in is removed entirely: the profile is
393 // never positive (it only swings outboard, then returns to the centreline).
394 for (int i = 0; i <= 100; ++i) {
395 const float t{static_cast<float>(i) / 100.0f};
396 EXPECT_LE(SampleProfile(t, 0.0f), kProfileTolerance);
397 }
398}
399
400// -----------------------------------------------------------------------------
401// LargeVehicleOffsetMagnitude (Option B: dimension-aware magnitude)
402// -----------------------------------------------------------------------------
403
404namespace {
405
406constexpr float kRefLength{6.0f};
407constexpr float kOffsetGain{0.25f};
408constexpr float kOffsetCap{1.5f};
409
410float SampleMagnitude(float length) {
411 return LargeVehicleOffsetMagnitude(length, kRefLength, kOffsetGain, kOffsetCap);
412}
413
414} // namespace
415
416TEST(TrafficManagerWideTurn, OffsetMagnitude_ZeroAtOrBelowReferenceLength) {
417 EXPECT_NEAR(SampleMagnitude(kRefLength), 0.0f, kProfileTolerance);
418 EXPECT_NEAR(SampleMagnitude(kRefLength - 2.0f), 0.0f, kProfileTolerance);
419}
420
421TEST(TrafficManagerWideTurn, OffsetMagnitude_RampsThenClampsToCap) {
422 // length 8 -> 0.25 * 2 = 0.5; length 12 -> 0.25 * 6 = 1.5 (cap); beyond is clamped.
423 EXPECT_NEAR(SampleMagnitude(8.0f), 0.5f, kProfileTolerance);
424 EXPECT_NEAR(SampleMagnitude(12.0f), kOffsetCap, kProfileTolerance);
425 EXPECT_NEAR(SampleMagnitude(20.0f), kOffsetCap, kProfileTolerance);
426}
427
428TEST(TrafficManagerWideTurn, OffsetMagnitude_MonotonicNonDecreasingInLength) {
429 float previous{SampleMagnitude(0.0f)};
430 for (float length = 0.0f; length <= 20.0f; length += 0.5f) {
431 const float current{SampleMagnitude(length)};
432 EXPECT_GE(current, previous - kProfileTolerance);
433 previous = current;
434 }
435}
436
437// -----------------------------------------------------------------------------
438// IsOffsetSideOccupied (Option A: collision-aware gating decision)
439// -----------------------------------------------------------------------------
440
441namespace {
442
443// Ego at the origin, heading +x, swinging toward +y.
444const cg::Location kEgoLocation{0.0f, 0.0f, 0.0f};
445const cg::Vector3D kEgoForward{1.0f, 0.0f, 0.0f};
446const cg::Vector3D kOffsetDirection{0.0f, 1.0f, 0.0f};
447constexpr float kOffsetMagnitude{1.5f};
448constexpr float kLateralClearance{1.0f};
449constexpr float kLongitudinalWindow{5.0f};
450
451bool SampleOccupied(const std::vector<std::pair<cg::Location, float>> &neighbours) {
453 kEgoLocation,
454 kEgoForward,
455 kOffsetDirection,
456 kOffsetMagnitude,
457 kLateralClearance,
458 kLongitudinalWindow,
459 neighbours);
460}
461
462} // namespace
463
464TEST(TrafficManagerWideTurn, OffsetSideOccupied_EmptyNeighboursIsClear) {
465 EXPECT_FALSE(SampleOccupied({}));
466}
467
468TEST(TrafficManagerWideTurn, OffsetSideOccupied_NeighbourInBandBlocks) {
469 // 2 m to the offset side, alongside the ego: within the swing band.
470 EXPECT_TRUE(SampleOccupied({{{0.0f, 2.0f, 0.0f}, 1.0f}}));
471}
472
473TEST(TrafficManagerWideTurn, OffsetSideOccupied_FarLateralNeighbourIsClear) {
474 // 10 m to the side is well beyond offset_magnitude + clearance + radius.
475 EXPECT_FALSE(SampleOccupied({{{0.0f, 10.0f, 0.0f}, 1.0f}}));
476}
477
478TEST(TrafficManagerWideTurn, OffsetSideOccupied_OppositeSideNeighbourIsClear) {
479 // A neighbour on the far side of the offset direction does not block.
480 EXPECT_FALSE(SampleOccupied({{{0.0f, -3.0f, 0.0f}, 1.0f}}));
481}
482
483TEST(TrafficManagerWideTurn, OffsetSideOccupied_NeighbourAheadOutsideWindowIsClear) {
484 // On the offset side but 20 m ahead: outside the longitudinal window.
485 EXPECT_FALSE(SampleOccupied({{{20.0f, 1.0f, 0.0f}, 1.0f}}));
486}
487
488// -----------------------------------------------------------------------------
489// IsRefreshDue
490// -----------------------------------------------------------------------------
491
492namespace {
493
494namespace tmgr = carla::traffic_manager;
497
498constexpr double kNeverRead{-std::numeric_limits<double>::infinity()};
499
500} // namespace
501
502TEST(TrafficManagerRefreshCadence, IsRefreshDue_NothingReadYetIsDue) {
505}
506
507TEST(TrafficManagerRefreshCadence, IsRefreshDue_SameFrameIsNotDue) {
509}
510
511TEST(TrafficManagerRefreshCadence, IsRefreshDue_WithinThePeriodIsNotDue) {
512 EXPECT_FALSE(refresh::IsRefreshDue(10.24, 10.0, 0.25));
513}
514
515TEST(TrafficManagerRefreshCadence, IsRefreshDue_AtOrPastThePeriodIsDue) {
516 EXPECT_TRUE(refresh::IsRefreshDue(10.25, 10.0, 0.25));
517 EXPECT_TRUE(refresh::IsRefreshDue(11.0, 10.0, 0.25));
518}
519
520TEST(TrafficManagerRefreshCadence, IsRefreshDue_ClockGoingBackwardsIsDue) {
521 // A new episode restarts elapsed_seconds at zero.
522 EXPECT_TRUE(refresh::IsRefreshDue(0.05, 300.0, 1.0));
523}
524
525// -----------------------------------------------------------------------------
526// IsEarlyRefreshDue
527// -----------------------------------------------------------------------------
528
529namespace {
530
531struct LightRefreshTrace {
532 int refreshes{0};
533 int steps_from_registration_to_a_refresh{-1};
534};
535
536/// Mirrors VehicleLightStage's refresh cadence for a vehicle registered on
537/// @a registration_step that the server never reports; pass @a steps for none.
538LightRefreshTrace TraceLightRefreshes(
539 const int steps,
540 const double step_period,
541 const int registration_step) {
542
543 LightRefreshTrace trace{};
544 double last_update{kNeverRead};
545 bool missing_from_the_last_refresh{false};
546
547 for (int step{0}; step < steps; ++step) {
548 const double now{step_period * static_cast<double>(step + 1)};
549 const bool refreshed{
551 if (refreshed) {
552 last_update = now;
553 missing_from_the_last_refresh = false;
554 ++trace.refreshes;
555 }
556
557 if (step < registration_step) {
558 continue;
559 }
560 if (refreshed && trace.steps_from_registration_to_a_refresh < 0) {
561 trace.steps_from_registration_to_a_refresh = step - registration_step;
562 }
563 if (refresh::IsEarlyRefreshDue(refreshed, missing_from_the_last_refresh)) {
564 last_update = kNeverRead;
565 }
566 if (refreshed) {
567 missing_from_the_last_refresh = true;
568 }
569 }
570 return trace;
571}
572
573constexpr int kThirtyFpsSteps{300};
574constexpr double kThirtyFpsPeriod{1.0 / 30.0};
575
576} // namespace
577
578TEST(TrafficManagerRefreshCadence, IsEarlyRefreshDue_VehicleSeenForTheFirstTimeIsWorthARead) {
579 EXPECT_TRUE(refresh::IsEarlyRefreshDue(false, false));
580}
581
582TEST(TrafficManagerRefreshCadence, IsEarlyRefreshDue_VehicleTheServerOmitsIsNotWorthAnother) {
583 EXPECT_FALSE(refresh::IsEarlyRefreshDue(false, true));
584}
585
586TEST(TrafficManagerRefreshCadence, IsEarlyRefreshDue_StepThatAlreadyReadNeedsNoRead) {
587 EXPECT_FALSE(refresh::IsEarlyRefreshDue(true, false));
588 EXPECT_FALSE(refresh::IsEarlyRefreshDue(true, true));
589}
590
591TEST(TrafficManagerRefreshCadence, EarlyRefresh_VehicleTheServerNeverReportsKeepsTheRegularCadence) {
592 const LightRefreshTrace reported{
593 TraceLightRefreshes(kThirtyFpsSteps, kThirtyFpsPeriod, kThirtyFpsSteps)};
594 const LightRefreshTrace omitted{TraceLightRefreshes(kThirtyFpsSteps, kThirtyFpsPeriod, 3)};
595
596 EXPECT_LE(omitted.refreshes, reported.refreshes + 1);
597 EXPECT_LE(
598 static_cast<double>(reported.refreshes),
599 1.0 + static_cast<double>(kThirtyFpsSteps) * kThirtyFpsPeriod /
601}
602
603TEST(TrafficManagerRefreshCadence, EarlyRefresh_NewlyRegisteredVehicleIsReadPromptly) {
604 const LightRefreshTrace omitted{TraceLightRefreshes(kThirtyFpsSteps, kThirtyFpsPeriod, 3)};
605 EXPECT_EQ(omitted.steps_from_registration_to_a_refresh, 1);
606}
607
608// -----------------------------------------------------------------------------
609// IsBatchSyncDue
610// -----------------------------------------------------------------------------
611
612namespace {
613
614struct BatchSyncTrace {
615 int syncs{0};
616 uint64_t deepest_queue{0u};
617};
618
619BatchSyncTrace TraceBatchSyncs(const int steps, const uint64_t limit) {
620 BatchSyncTrace trace{};
621 uint64_t unwaited{limit};
622 for (int step{0}; step < steps; ++step) {
623 if (refresh::IsBatchSyncDue(unwaited, limit)) {
624 unwaited = 0u;
625 ++trace.syncs;
626 } else {
627 ++unwaited;
628 }
629 trace.deepest_queue = std::max(trace.deepest_queue, unwaited);
630 }
631 return trace;
632}
633
634/// The same queue bounded by simulation time instead.
635BatchSyncTrace TraceTimeGatedBatchSyncs(
636 const int steps,
637 const double fixed_delta_seconds,
638 const double period) {
639
640 BatchSyncTrace trace{};
641 uint64_t unwaited{0u};
642 double last_sync{kNeverRead};
643 for (int step{0}; step < steps; ++step) {
644 const double now{fixed_delta_seconds * static_cast<double>(step + 1)};
645 if (refresh::IsRefreshDue(now, last_sync, period)) {
646 unwaited = 0u;
647 last_sync = now;
648 ++trace.syncs;
649 } else {
650 ++unwaited;
651 }
652 trace.deepest_queue = std::max(trace.deepest_queue, unwaited);
653 }
654 return trace;
655}
656
657constexpr int kBatchSteps{1000};
658
659} // namespace
660
661TEST(TrafficManagerBatchSync, IsBatchSyncDue_WithinTheBoundNothingIsWaitedFor) {
665}
666
667TEST(TrafficManagerBatchSync, IsBatchSyncDue_AtOrPastTheBoundOneIsWaitedFor) {
672}
673
674TEST(TrafficManagerBatchSync, QueueNeverHoldsMoreThanThePermittedBatches) {
675 const BatchSyncTrace trace{TraceBatchSyncs(kBatchSteps, refresh::MAX_UNWAITED_CONTROL_BATCHES)};
676 EXPECT_LE(trace.deepest_queue, refresh::MAX_UNWAITED_CONTROL_BATCHES);
677 EXPECT_EQ(trace.syncs, kBatchSteps / static_cast<int>(refresh::MAX_UNWAITED_CONTROL_BATCHES + 1u));
678}
679
680TEST(TrafficManagerBatchSync, SlowServerWithAFixedDeltaIsBoundedTheSameWayAsAFastOne) {
681 // With a fixed delta a simulation-time bound scales with the delta, not the
682 // queue: at 0.05 s a one-second period is twenty batches.
683 const BatchSyncTrace counted{TraceBatchSyncs(kBatchSteps, refresh::MAX_UNWAITED_CONTROL_BATCHES)};
684 const BatchSyncTrace timed{TraceTimeGatedBatchSyncs(kBatchSteps, 0.05, 1.0)};
685
686 EXPECT_LE(counted.deepest_queue, refresh::MAX_UNWAITED_CONTROL_BATCHES);
687 EXPECT_GT(timed.deepest_queue, refresh::MAX_UNWAITED_CONTROL_BATCHES);
688}
689
690// -----------------------------------------------------------------------------
691// ShapeReferenceVelocity
692// -----------------------------------------------------------------------------
693
694namespace {
695
696constexpr float kControlDt{0.05f};
697constexpr float kCruiseTarget{60.0f / 3.6f};
698
699float Shape(
700 float previous_reference,
701 float vehicle_speed,
702 float target_velocity,
703 float control_dt,
704 bool emergency_stop = false) {
705
706 return tmgr::PID::ShapeReferenceVelocity(
707 previous_reference, vehicle_speed, target_velocity, control_dt, emergency_stop);
708}
709
710} // namespace
711
712TEST(TrafficManagerComfortConstants, ReferenceLeadOutrunsTheThrottleSaturationError) {
713 for (const auto &longitudinal : {pid_constants::LONGITUDIAL_PARAM,
716 pid_constants::MAX_THROTTLE / longitudinal[0]);
717 }
718}
719
720TEST(TrafficManagerReferenceVelocity, ShapeReference_RampsAwayFromRestAtComfortAcceleration) {
721 EXPECT_FLOAT_EQ(
722 Shape(0.0f, 0.0f, kCruiseTarget, kControlDt),
724}
725
726TEST(TrafficManagerReferenceVelocity, ShapeReference_NeverExceedsTheTarget) {
727 EXPECT_FLOAT_EQ(
728 Shape(kCruiseTarget - 0.05f, kCruiseTarget - 0.05f, kCruiseTarget,
730 kCruiseTarget);
731}
732
733TEST(TrafficManagerReferenceVelocity, ShapeReference_DecelerationIsPassedThroughUnshaped) {
734 EXPECT_FLOAT_EQ(Shape(20.0f, 20.0f, 5.0f, kControlDt), 5.0f);
735}
736
737TEST(TrafficManagerReferenceVelocity, ShapeReference_StaleLowReferenceIsRecoveredInOneStep) {
738 // e.g. a flushed state entry or a K-turn recovery.
739 EXPECT_FLOAT_EQ(Shape(0.0f, 20.0f, 25.0f, kControlDt), 20.0f);
740}
741
742TEST(TrafficManagerReferenceVelocity, ShapeReference_StaleHighReferenceIsCappedToTheLead) {
743 EXPECT_FLOAT_EQ(
744 Shape(100.0f, 5.0f, 25.0f, kControlDt),
746}
747
748TEST(TrafficManagerReferenceVelocity, ShapeReference_LeadStaysBoundedWhileTheVehicleCannotFollow) {
749 // Uphill, or wedged against a kerb: the vehicle never gains speed.
750 float reference{0.0f};
751 for (int step = 0; step < 400; ++step) {
752 reference = Shape(reference, 0.0f, kCruiseTarget, kControlDt);
753 }
754 EXPECT_FLOAT_EQ(reference, pid_constants::REFERENCE_LEAD_FRACTION * kCruiseTarget);
755}
756
757TEST(TrafficManagerReferenceVelocity, ShapeReference_EmergencyStopDiscardsTheStoredReference) {
758 const float held{Shape(25.0f, 5.0f, 20.0f, kControlDt, true)};
759 const float not_held{Shape(25.0f, 5.0f, 20.0f, kControlDt, false)};
760
761 EXPECT_FLOAT_EQ(held, 5.0f + pid_constants::COMFORT_ACCELERATION * kControlDt);
762 EXPECT_LT(held, not_held);
763}
764
765TEST(TrafficManagerReferenceVelocity, ShapeReference_SlowServerStillRampsAtTheCompensatedRate) {
766 // The step is bounded by MAX_CONTROL_DT, as the controller is.
767 EXPECT_FLOAT_EQ(
768 Shape(0.0f, 0.0f, kCruiseTarget, 1.0f),
770}
771
772TEST(TrafficManagerReferenceVelocity, ShapeReference_ShortFrameStillAdvancesTheReference) {
773 EXPECT_FLOAT_EQ(
774 Shape(0.0f, 0.0f, kCruiseTarget, 0.0001f),
776}
777
778// -----------------------------------------------------------------------------
779// RelativeVelocityDeviation
780// -----------------------------------------------------------------------------
781
782TEST(TrafficManagerReferenceVelocity, VelocityDeviation_MatchesThePlainRatioOnceCaughtUp) {
783 EXPECT_FLOAT_EQ(tmgr::PID::RelativeVelocityDeviation(20.0f, 10.0f, 20.0f), 0.5f);
784}
785
786TEST(TrafficManagerReferenceVelocity, VelocityDeviation_ZeroTargetBehindAStoppedVehicleIsFinite) {
787 const float reference{Shape(0.0f, 0.0f, 0.0f, kControlDt)};
788 const float deviation{tmgr::PID::RelativeVelocityDeviation(reference, 0.0f, 0.0f)};
789
790 EXPECT_TRUE(std::isfinite(deviation));
791 EXPECT_FLOAT_EQ(deviation, 0.0f);
792}
793
794TEST(TrafficManagerReferenceVelocity, VelocityDeviation_ZeroTargetDoesNotPoisonTheNextStep) {
795 // A NaN deviation would reach the steering through the derivative term.
796 StateEntry previous{};
797 StateEntry current{};
798 const float reference{Shape(0.0f, 0.0f, 0.0f, kControlDt)};
799 current.velocity_deviation = tmgr::PID::RelativeVelocityDeviation(reference, 0.0f, 0.0f);
800 current.reference_velocity = reference;
801
802 const ActuationSignal first{tmgr::PID::RunStep(
804 0.0f, kControlDt)};
805 EXPECT_TRUE(std::isfinite(first.throttle));
806 EXPECT_TRUE(std::isfinite(first.brake));
807 EXPECT_TRUE(std::isfinite(first.steer));
808
809 const ActuationSignal second{tmgr::PID::RunStep(
811 0.0f, kControlDt)};
812 EXPECT_TRUE(std::isfinite(second.steer));
813 EXPECT_TRUE(std::isfinite(second.throttle));
814}
815
816// -----------------------------------------------------------------------------
817// SmoothActuation
818// -----------------------------------------------------------------------------
819
820namespace {
821
822StateEntry PreviousCommand(float throttle, float brake, float steer) {
823 StateEntry state{};
824 state.throttle = throttle;
825 state.brake = brake;
826 state.steer = steer;
827 return state;
828}
829
830ActuationSignal Smooth(
831 const StateEntry &previous,
832 float control_dt,
833 float vehicle_speed,
834 ActuationSignal demand) {
835
836 tmgr::PID::SmoothActuation(previous, control_dt, vehicle_speed, demand);
837 return demand;
838}
839
840} // namespace
841
842TEST(TrafficManagerActuationSmoothing, LaunchRamp_LimitsTheThrottleRiseFromRest) {
843 const ActuationSignal smoothed{Smooth(
844 PreviousCommand(0.0f, 0.0f, 0.0f), kControlDt, 0.0f,
846
847 EXPECT_FLOAT_EQ(smoothed.throttle, pid_constants::MAX_LAUNCH_THROTTLE_RISE_RATE * kControlDt);
848}
849
850TEST(TrafficManagerActuationSmoothing, LaunchRamp_AlwaysClearsTheThrottleDeadband) {
851 const ActuationSignal smoothed{Smooth(
852 PreviousCommand(0.0f, 0.0f, 0.0f), 0.0001f, 0.0f,
854
855 EXPECT_GT(smoothed.throttle, pid_constants::THROTTLE_DEADBAND);
856 EXPECT_FLOAT_EQ(
857 smoothed.throttle,
859}
860
861TEST(TrafficManagerActuationSmoothing, LaunchRamp_ReachesFullThrottleInABoundedNumberOfSteps) {
862 StateEntry previous{PreviousCommand(0.0f, 0.0f, 0.0f)};
863 for (int step = 0; step < 200; ++step) {
864 const ActuationSignal smoothed{Smooth(
865 previous, kControlDt, 0.0f,
867 previous.throttle = smoothed.throttle;
868 }
869 EXPECT_FLOAT_EQ(previous.throttle, pid_constants::MAX_THROTTLE);
870}
871
872TEST(TrafficManagerActuationSmoothing, LaunchRamp_IsReleasedAboveTheLaunchSpeed) {
873 const ActuationSignal smoothed{Smooth(
874 PreviousCommand(0.0f, 0.0f, 0.0f), kControlDt,
877
878 EXPECT_FLOAT_EQ(smoothed.throttle, pid_constants::MAX_THROTTLE);
879}
880
881TEST(TrafficManagerActuationSmoothing, LaunchRamp_NeverRaisesAFallingThrottle) {
882 const ActuationSignal smoothed{Smooth(
883 PreviousCommand(0.5f, 0.0f, 0.0f), kControlDt, 1.0f,
884 ActuationSignal{0.1f, 0.0f, 0.0f})};
885
886 EXPECT_FLOAT_EQ(smoothed.throttle, 0.1f);
887}
888
889TEST(TrafficManagerActuationSmoothing, Deadband_HoldsTheThrottleDitherAndPassesARealStep) {
890 const StateEntry previous{PreviousCommand(0.5f, 0.0f, 0.0f)};
891
892 EXPECT_FLOAT_EQ(
893 Smooth(previous, kControlDt, 10.0f, ActuationSignal{0.505f, 0.0f, 0.0f}).throttle, 0.5f);
894 EXPECT_FLOAT_EQ(
895 Smooth(previous, kControlDt, 10.0f, ActuationSignal{0.52f, 0.0f, 0.0f}).throttle, 0.52f);
896}
897
898TEST(TrafficManagerActuationSmoothing, Deadband_HoldsTheBrakeDitherAndPassesARealStep) {
899 const StateEntry previous{PreviousCommand(0.0f, 0.4f, 0.0f)};
900
901 EXPECT_FLOAT_EQ(
902 Smooth(previous, kControlDt, 10.0f, ActuationSignal{0.0f, 0.4005f, 0.0f}).brake, 0.4f);
903 EXPECT_FLOAT_EQ(
904 Smooth(previous, kControlDt, 10.0f, ActuationSignal{0.0f, 0.45f, 0.0f}).brake, 0.45f);
905}
906
907TEST(TrafficManagerActuationSmoothing, Deadband_ZeroPedalDemandIsAlwaysHonoured) {
908 const ActuationSignal smoothed{Smooth(
909 PreviousCommand(0.005f, 0.005f, 0.0f), kControlDt, 10.0f,
910 ActuationSignal{0.0f, 0.0f, 0.0f})};
911
912 EXPECT_FLOAT_EQ(smoothed.throttle, 0.0f);
913 EXPECT_FLOAT_EQ(smoothed.brake, 0.0f);
914}
915
916TEST(TrafficManagerActuationSmoothing, Deadband_HoldsTheSteeringDitherAtUrbanSpeed) {
917 const StateEntry previous{PreviousCommand(0.0f, 0.0f, 0.1f)};
918
919 EXPECT_FLOAT_EQ(
920 Smooth(previous, kControlDt, 5.0f, ActuationSignal{0.0f, 0.0f, 0.1015f}).steer, 0.1f);
921 EXPECT_FLOAT_EQ(
922 Smooth(previous, kControlDt, 5.0f, ActuationSignal{0.0f, 0.0f, 0.105f}).steer, 0.105f);
923}
924
925TEST(TrafficManagerActuationSmoothing, Deadband_SteeringBandShrinksWithSpeed) {
926 const StateEntry previous{PreviousCommand(0.0f, 0.0f, 0.1f)};
927 const ActuationSignal demand{0.0f, 0.0f, 0.1005f};
928
929 EXPECT_FLOAT_EQ(
930 Smooth(previous, kControlDt, pid_constants::STEER_DEADBAND_REF_SPEED, demand).steer, 0.1f);
931 EXPECT_FLOAT_EQ(
932 Smooth(previous, kControlDt, 4.0f * pid_constants::STEER_DEADBAND_REF_SPEED, demand).steer,
933 0.1005f);
934}
long long first
Definition LaneGraph.cpp:34
rpc::DebugShape Shape
static const float COMFORT_ACCELERATION
Definition Constants.h:323
static constexpr float MIN_CONTROL_DT
Definition Constants.h:291
static const float REFERENCE_LEAD_FRACTION
Definition Constants.h:327
static const std::vector< float > LONGITUDIAL_PARAM
Definition Constants.h:328
static const std::vector< float > LONGITUDIAL_HIGHWAY_PARAM
Definition Constants.h:329
static constexpr float MAX_LAUNCH_THROTTLE_RISE_RATE
Definition Constants.h:309
static constexpr float THROTTLE_DEADBAND
Definition Constants.h:310
static const float STEER_DEADBAND_REF_SPEED
Definition Constants.h:319
static const std::vector< float > LATERAL_PARAM
Definition Constants.h:339
bool IsRefreshDue(const double now, const double last, const double period)
Also true when the clock jumps backwards (new episode) or on NaN, so an unusable clock refreshes rath...
Definition Constants.h:57
bool IsBatchSyncDue(const uint64_t unwaited_batches, const uint64_t limit)
Definition Constants.h:69
bool IsEarlyRefreshDue(const bool refreshed_this_step, const bool missing_from_the_last_refresh)
A vehicle the server already omitted from a fresh list (dormant on a large map) must not re-arm the u...
Definition Constants.h:63
float LargeVehicleJunctionOffsetProfile(float t, float max_offset, float max_offset_point, float inboard_scale)
Lateral offset profile for the large-vehicle wide turn, as a function of t, the fraction of the junct...
float GetPathSpeedLimit(const std::vector< cg::Location > &path, const float path_start_offset, const float sample_spacing, const float lateral_acceleration, const float braking_deceleration, const float max_speed)
Lowest sqrt(R * lateral_acceleration + 2 * braking_deceleration * distance) over every three consecut...
std::pair< cg::Location, uint64_t > InterpolateBufferAt(const std::vector< cg::Location > &waypoint_locations, float target_distance, cg::Location vehicle_location)
Locations-only variant of GetTargetData.
float GetThreePointCircleRadius(cg::Location first_location, cg::Location middle_location, cg::Location last_location)
Returns the radius of the circle defined by three points in the XY plane.
bool IsOffsetSideOccupied(cg::Location ego_location, cg::Vector3D ego_forward, cg::Vector3D offset_direction, float offset_magnitude, float lateral_clearance, float longitudinal_window, const std::vector< std::pair< cg::Location, float > > &neighbours)
Returns true when a neighbouring vehicle sits in the lateral band that the wide-turn offset would swi...
float LargeVehicleOffsetMagnitude(float vehicle_length, float reference_length, float gain, float offset_cap)
Wide-turn offset magnitude scaled by vehicle length: clamp(gain * (vehicle_length - reference_length)...
Structure to hold the actuation signals.
Structure to hold the controller state.
TEST(TrafficManagerGeometry, ThreePointCircleRadius_EquilateralTriangle)