ilqgames
A new real-time solver for large-scale differential games.
flat_roundabout_merging_example.cpp
1 /*
2  * Copyright (c) 2019, The Regents of the University of California (Regents).
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above
13  * copyright notice, this list of conditions and the following
14  * disclaimer in the documentation and/or other materials provided
15  * with the distribution.
16  *
17  * 3. Neither the name of the copyright holder nor the names of its
18  * contributors may be used to endorse or promote products derived
19  * from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS
22  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31  * POSSIBILITY OF SUCH DAMAGE.
32  *
33  * Please contact the author(s) of this library if you have any questions.
34  * Authors: David Fridovich-Keil ( dfk@eecs.berkeley.edu )
35  */
36 
37 ///////////////////////////////////////////////////////////////////////////////
38 //
39 // Roundabout merging example for feedback linearizable systems.
40 //
41 ///////////////////////////////////////////////////////////////////////////////
42 
43 #include <ilqgames/cost/curvature_cost.h>
44 #include <ilqgames/cost/final_time_cost.h>
45 #include <ilqgames/cost/locally_convex_proximity_cost.h>
46 #include <ilqgames/cost/nominal_path_length_cost.h>
47 #include <ilqgames/cost/proximity_cost.h>
48 #include <ilqgames/cost/quadratic_cost.h>
49 #include <ilqgames/cost/quadratic_norm_cost.h>
50 #include <ilqgames/cost/quadratic_polyline2_cost.h>
51 #include <ilqgames/cost/route_progress_cost.h>
52 #include <ilqgames/cost/semiquadratic_cost.h>
53 #include <ilqgames/cost/semiquadratic_norm_cost.h>
54 #include <ilqgames/cost/semiquadratic_polyline2_cost.h>
55 #include <ilqgames/cost/weighted_convex_proximity_cost.h>
56 #include <ilqgames/dynamics/concatenated_flat_system.h>
57 #include <ilqgames/dynamics/single_player_flat_car_6d.h>
58 #include <ilqgames/dynamics/single_player_flat_unicycle_4d.h>
59 #include <ilqgames/examples/flat_roundabout_merging_example.h>
60 #include <ilqgames/examples/roundabout_lane_center.h>
61 #include <ilqgames/geometry/polyline2.h>
62 #include <ilqgames/solver/problem.h>
63 #include <ilqgames/utils/initialize_along_route.h>
64 #include <ilqgames/utils/solver_log.h>
65 #include <ilqgames/utils/strategy.h>
66 #include <ilqgames/utils/types.h>
67 
68 #include <math.h>
69 #include <memory>
70 #include <vector>
71 
72 namespace ilqgames {
73 
74 namespace {
75 // Time.
76 static constexpr Time kTimeStep = 0.1; // s
77 static constexpr Time kTimeHorizon = 10.0; // s
78 static constexpr size_t kNumTimeSteps =
79  static_cast<size_t>(kTimeHorizon / kTimeStep);
80 
81 // Cost weights.
82 static constexpr float kAuxCostWeight = 4.0;
83 static constexpr float kGoalCostWeight = 10.0;
84 
85 static constexpr float kMaxVCostWeight = 1000.0;
86 static constexpr float kNominalVCostWeight = 10.0;
87 
88 static constexpr float kLaneCostWeight = 25.0;
89 static constexpr float kLaneBoundaryCostWeight = 100.0;
90 
91 static constexpr float kMinProximity = 6.0;
92 static constexpr float kP1ProximityCostWeight = 100.0;
93 static constexpr float kP2ProximityCostWeight = 100.0;
94 static constexpr float kP3ProximityCostWeight = 100.0;
95 static constexpr float kP4ProximityCostWeight = 100.0;
96 using ProxCost = ProximityCost;
97 
98 static constexpr bool kOrientedRight = true;
99 
100 // Lane width.
101 static constexpr float kLaneHalfWidth = 2.5; // m
102 
103 // Nominal and max speed.
104 static constexpr float kP1MaxV = 12.0; // m/s
105 static constexpr float kP2MaxV = 12.0; // m/s
106 static constexpr float kP3MaxV = 12.0; // m/s
107 static constexpr float kP4MaxV = 12.0; // m/s
108 static constexpr float kMinV = 1.0; // m/s
109 
110 static constexpr float kP1NominalV = 10.0; // m/s
111 static constexpr float kP2NominalV = 10.0; // m/s
112 static constexpr float kP3NominalV = 10.0; // m/s
113 static constexpr float kP4NominalV = 10.0; // m/s
114 
115 // Initial distance from roundabout.
116 static constexpr float kP1InitialDistanceToRoundabout = 25.0; // m
117 static constexpr float kP2InitialDistanceToRoundabout = 10.0; // m
118 static constexpr float kP3InitialDistanceToRoundabout = 25.0; // m
119 static constexpr float kP4InitialDistanceToRoundabout = 10.0; // m
120 
121 static constexpr float kP1InitialSpeed = 3.0; // m/s
122 static constexpr float kP2InitialSpeed = 2.0; // m/s
123 static constexpr float kP3InitialSpeed = 3.0; // m/s
124 static constexpr float kP4InitialSpeed = 2.0; // m/s
125 
126 // State dimensions.
127 static constexpr float kInterAxleDistance = 4.0; // m
128 using P1 = SinglePlayerFlatCar6D;
129 using P2 = SinglePlayerFlatCar6D;
130 using P3 = SinglePlayerFlatCar6D;
131 using P4 = SinglePlayerFlatCar6D;
132 
133 static const Dimension kP1XIdx = P1::kPxIdx;
134 static const Dimension kP1YIdx = P1::kPyIdx;
135 static const Dimension kP1HeadingIdx = P1::kThetaIdx;
136 static const Dimension kP1VIdx = P1::kVIdx;
137 static const Dimension kP1VxIdx = P1::kVxIdx;
138 static const Dimension kP1VyIdx = P1::kVyIdx;
139 
140 static const Dimension kP2XIdx = P1::kNumXDims + P2::kPxIdx;
141 static const Dimension kP2YIdx = P1::kNumXDims + P2::kPyIdx;
142 static const Dimension kP2HeadingIdx = P1::kNumXDims + P2::kThetaIdx;
143 static const Dimension kP2VIdx = P1::kNumXDims + P2::kVIdx;
144 static const Dimension kP2VxIdx = P1::kNumXDims + P2::kVxIdx;
145 static const Dimension kP2VyIdx = P1::kNumXDims + P2::kVyIdx;
146 
147 static const Dimension kP3XIdx = P1::kNumXDims + P2::kNumXDims + P3::kPxIdx;
148 static const Dimension kP3YIdx = P1::kNumXDims + P2::kNumXDims + P3::kPyIdx;
149 static const Dimension kP3HeadingIdx =
150  P1::kNumXDims + P2::kNumXDims + P3::kThetaIdx;
151 static const Dimension kP3VIdx = P1::kNumXDims + P2::kNumXDims + P3::kVIdx;
152 static const Dimension kP3VxIdx = P1::kNumXDims + P2::kNumXDims + P3::kVxIdx;
153 static const Dimension kP3VyIdx = P1::kNumXDims + P2::kNumXDims + P3::kVyIdx;
154 
155 static const Dimension kP4XIdx =
156  P1::kNumXDims + P2::kNumXDims + P3::kNumXDims + P4::kPxIdx;
157 static const Dimension kP4YIdx =
158  P1::kNumXDims + P2::kNumXDims + P3::kNumXDims + P4::kPyIdx;
159 static const Dimension kP4HeadingIdx =
160  P1::kNumXDims + P2::kNumXDims + P3::kNumXDims + P4::kThetaIdx;
161 static const Dimension kP4VIdx =
162  P1::kNumXDims + P2::kNumXDims + P3::kNumXDims + P4::kVIdx;
163 static const Dimension kP4VxIdx =
164  P1::kNumXDims + P2::kNumXDims + P3::kNumXDims + P4::kVxIdx;
165 static const Dimension kP4VyIdx =
166  P1::kNumXDims + P2::kNumXDims + P3::kNumXDims + P4::kVyIdx;
167 
168 // Set up lanes for each player.
169 static constexpr float kAngleOffset = M_PI_2 * 0.5;
170 static constexpr float kWedgeSize = M_PI;
171 const std::vector<float> angles = {kAngleOffset,
172  kAngleOffset + 2.0 * M_PI / 4.0,
173  kAngleOffset + 2.0 * 2.0 * M_PI / 4.0,
174  kAngleOffset + 3.0 * 2.0 * M_PI / 4.0};
175 const Polyline2 lane1(RoundaboutLaneCenter(angles[0], angles[0] + kWedgeSize,
176  kP1InitialDistanceToRoundabout));
177 const Polyline2 lane2(RoundaboutLaneCenter(angles[1], angles[1] + kWedgeSize,
178  kP2InitialDistanceToRoundabout));
179 const Polyline2 lane3(RoundaboutLaneCenter(angles[2], angles[2] + kWedgeSize,
180  kP3InitialDistanceToRoundabout));
181 const Polyline2 lane4(RoundaboutLaneCenter(angles[3], angles[3] + kWedgeSize,
182  kP4InitialDistanceToRoundabout));
183 
184 } // anonymous namespace
185 
186 void FlatRoundaboutMergingExample::ConstructDynamics() {
187  dynamics_.reset(
188  new ConcatenatedFlatSystem({std::make_shared<P1>(kInterAxleDistance),
189  std::make_shared<P2>(kInterAxleDistance),
190  std::make_shared<P3>(kInterAxleDistance),
191  std::make_shared<P4>(kInterAxleDistance)}));
192 }
193 
194 void FlatRoundaboutMergingExample::ConstructInitialState() {
195  VectorXf x0 = VectorXf::Zero(dynamics_->XDim());
196  x0(kP1XIdx) = lane1.Segments()[0].FirstPoint().x();
197  x0(kP1YIdx) = lane1.Segments()[0].FirstPoint().y();
198  x0(kP1HeadingIdx) = lane1.Segments()[0].Heading();
199  x0(kP1VIdx) = kP1InitialSpeed;
200  x0(kP2XIdx) = lane2.Segments()[0].FirstPoint().x();
201  x0(kP2YIdx) = lane2.Segments()[0].FirstPoint().y();
202  x0(kP2HeadingIdx) = lane2.Segments()[0].Heading();
203  x0(kP2VIdx) = kP2InitialSpeed;
204  x0(kP3XIdx) = lane3.Segments()[0].FirstPoint().x();
205  x0(kP3YIdx) = lane3.Segments()[0].FirstPoint().y();
206  x0(kP3HeadingIdx) = lane3.Segments()[0].Heading();
207  x0(kP3VIdx) = kP3InitialSpeed;
208  x0(kP4XIdx) = lane4.Segments()[0].FirstPoint().x();
209  x0(kP4YIdx) = lane4.Segments()[0].FirstPoint().y();
210  x0(kP4HeadingIdx) = lane4.Segments()[0].Heading();
211  x0(kP4VIdx) = kP4InitialSpeed;
212 
213  x0_ = dynamics_->ToLinearSystemState(x0);
214 }
215 
216 void FlatRoundaboutMergingExample::ConstructInitialOperatingPoint() {
217  // Initialize operating points to follow these lanes at the nominal speed.
218  InitializeAlongRoute(lane1, 0.0, kP1InitialSpeed, {kP1XIdx, kP1YIdx},
219  operating_point_.get());
220  InitializeAlongRoute(lane2, 0.0, kP2InitialSpeed, {kP2XIdx, kP2YIdx},
221  operating_point_.get());
222  InitializeAlongRoute(lane3, 0.0, kP3InitialSpeed, {kP3XIdx, kP3YIdx},
223  operating_point_.get());
224  InitializeAlongRoute(lane4, 0.0, kP4InitialSpeed, {kP4XIdx, kP4YIdx},
225  operating_point_.get());
226 }
227 
228 void FlatRoundaboutMergingExample::ConstructPlayerCosts() {
229  // Set up costs for all players.
230  player_costs_.emplace_back("P1");
231  player_costs_.emplace_back("P2");
232  player_costs_.emplace_back("P3");
233  player_costs_.emplace_back("P4");
234  auto& p1_cost = player_costs_[0];
235  auto& p2_cost = player_costs_[1];
236  auto& p3_cost = player_costs_[2];
237  auto& p4_cost = player_costs_[3];
238 
239  // Stay in lanes.
240  const std::shared_ptr<QuadraticPolyline2Cost> p1_lane_cost(
241  new QuadraticPolyline2Cost(kLaneCostWeight, lane1, {kP1XIdx, kP1YIdx},
242  "LaneCenter"));
243  const std::shared_ptr<SemiquadraticPolyline2Cost> p1_lane_r_cost(
244  new SemiquadraticPolyline2Cost(kLaneBoundaryCostWeight, lane1,
245  {kP1XIdx, kP1YIdx}, kLaneHalfWidth,
246  kOrientedRight, "LaneRightBoundary"));
247  const std::shared_ptr<SemiquadraticPolyline2Cost> p1_lane_l_cost(
248  new SemiquadraticPolyline2Cost(kLaneBoundaryCostWeight, lane1,
249  {kP1XIdx, kP1YIdx}, -kLaneHalfWidth,
250  !kOrientedRight, "LaneLeftBoundary"));
251  p1_cost.AddStateCost(p1_lane_cost);
252  p1_cost.AddStateCost(p1_lane_r_cost);
253  p1_cost.AddStateCost(p1_lane_l_cost);
254 
255  const std::shared_ptr<QuadraticPolyline2Cost> p2_lane_cost(
256  new QuadraticPolyline2Cost(kLaneCostWeight, lane2, {kP2XIdx, kP2YIdx},
257  "LaneCenter"));
258  const std::shared_ptr<SemiquadraticPolyline2Cost> p2_lane_r_cost(
259  new SemiquadraticPolyline2Cost(kLaneBoundaryCostWeight, lane2,
260  {kP2XIdx, kP2YIdx}, kLaneHalfWidth,
261  kOrientedRight, "LaneRightBoundary"));
262  const std::shared_ptr<SemiquadraticPolyline2Cost> p2_lane_l_cost(
263  new SemiquadraticPolyline2Cost(kLaneBoundaryCostWeight, lane2,
264  {kP2XIdx, kP2YIdx}, -kLaneHalfWidth,
265  !kOrientedRight, "LaneLeftBoundary"));
266  p2_cost.AddStateCost(p2_lane_cost);
267  p2_cost.AddStateCost(p2_lane_r_cost);
268  p2_cost.AddStateCost(p2_lane_l_cost);
269 
270  const std::shared_ptr<QuadraticPolyline2Cost> p3_lane_cost(
271  new QuadraticPolyline2Cost(kLaneCostWeight, lane3, {kP3XIdx, kP3YIdx},
272  "LaneCenter"));
273  const std::shared_ptr<SemiquadraticPolyline2Cost> p3_lane_r_cost(
274  new SemiquadraticPolyline2Cost(kLaneBoundaryCostWeight, lane3,
275  {kP3XIdx, kP3YIdx}, kLaneHalfWidth,
276  kOrientedRight, "LaneRightBoundary"));
277  const std::shared_ptr<SemiquadraticPolyline2Cost> p3_lane_l_cost(
278  new SemiquadraticPolyline2Cost(kLaneBoundaryCostWeight, lane3,
279  {kP3XIdx, kP3YIdx}, -kLaneHalfWidth,
280  !kOrientedRight, "LaneLeftBoundary"));
281  p3_cost.AddStateCost(p3_lane_cost);
282  p3_cost.AddStateCost(p3_lane_r_cost);
283  p3_cost.AddStateCost(p3_lane_l_cost);
284 
285  const std::shared_ptr<QuadraticPolyline2Cost> p4_lane_cost(
286  new QuadraticPolyline2Cost(kLaneCostWeight, lane4, {kP4XIdx, kP4YIdx},
287  "LaneCenter"));
288  const std::shared_ptr<SemiquadraticPolyline2Cost> p4_lane_r_cost(
289  new SemiquadraticPolyline2Cost(kLaneBoundaryCostWeight, lane4,
290  {kP4XIdx, kP4YIdx}, kLaneHalfWidth,
291  kOrientedRight, "LaneRightBoundary"));
292  const std::shared_ptr<SemiquadraticPolyline2Cost> p4_lane_l_cost(
293  new SemiquadraticPolyline2Cost(kLaneBoundaryCostWeight, lane4,
294  {kP4XIdx, kP4YIdx}, -kLaneHalfWidth,
295  !kOrientedRight, "LaneLeftBoundary"));
296  p4_cost.AddStateCost(p4_lane_cost);
297  p4_cost.AddStateCost(p4_lane_r_cost);
298  p4_cost.AddStateCost(p4_lane_l_cost);
299 
300  // Max/min/nominal speed costs.
301  const std::shared_ptr<RouteProgressCost> p1_progress_cost(
302  new RouteProgressCost(kNominalVCostWeight, kP1NominalV, lane1,
303  {kP1XIdx, kP1YIdx}, "RouteProgress"));
304  p1_cost.AddStateCost(p1_progress_cost);
305 
306  const std::shared_ptr<RouteProgressCost> p2_progress_cost(
307  new RouteProgressCost(kNominalVCostWeight, kP2NominalV, lane2,
308  {kP2XIdx, kP2YIdx}, "RouteProgress"));
309  p2_cost.AddStateCost(p2_progress_cost);
310 
311  const std::shared_ptr<RouteProgressCost> p3_progress_cost(
312  new RouteProgressCost(kNominalVCostWeight, kP3NominalV, lane3,
313  {kP3XIdx, kP3YIdx}, "RouteProgress"));
314  p3_cost.AddStateCost(p3_progress_cost);
315 
316  const std::shared_ptr<RouteProgressCost> p4_progress_cost(
317  new RouteProgressCost(kNominalVCostWeight, kP4NominalV, lane4,
318  {kP4XIdx, kP4YIdx}, "RouteProgress"));
319  p4_cost.AddStateCost(p4_progress_cost);
320 
321  // Penalize control effort.
322  constexpr Dimension kApplyInAllDimensions = -1;
323  const auto aux_cost = std::make_shared<QuadraticCost>(
324  kAuxCostWeight, kApplyInAllDimensions, 0.0, "Auxiliary Input");
325  p1_cost.AddControlCost(0, aux_cost);
326  p2_cost.AddControlCost(1, aux_cost);
327  p3_cost.AddControlCost(2, aux_cost);
328  p4_cost.AddControlCost(3, aux_cost);
329 
330  // Pairwise proximity costs.
331  const std::shared_ptr<ProxCost> p1p2_proximity_cost(
332  new ProxCost(kP1ProximityCostWeight, {kP1XIdx, kP1YIdx},
333  {kP2XIdx, kP2YIdx}, kMinProximity, "ProximityP2"));
334  const std::shared_ptr<ProxCost> p1p3_proximity_cost(
335  new ProxCost(kP1ProximityCostWeight, {kP1XIdx, kP1YIdx},
336  {kP3XIdx, kP3YIdx}, kMinProximity, "ProximityP3"));
337  const std::shared_ptr<ProxCost> p1p4_proximity_cost(
338  new ProxCost(kP1ProximityCostWeight, {kP1XIdx, kP1YIdx},
339  {kP4XIdx, kP4YIdx}, kMinProximity, "ProximityP4"));
340  p1_cost.AddStateCost(p1p2_proximity_cost);
341  // p1_cost.AddStateCost(p1p3_proximity_cost);
342  p1_cost.AddStateCost(p1p4_proximity_cost);
343 
344  const std::shared_ptr<ProxCost> p2p1_proximity_cost(
345  new ProxCost(kP2ProximityCostWeight, {kP2XIdx, kP2YIdx},
346  {kP1XIdx, kP1YIdx}, kMinProximity, "ProximityP1"));
347  const std::shared_ptr<ProxCost> p2p3_proximity_cost(
348  new ProxCost(kP2ProximityCostWeight, {kP2XIdx, kP2YIdx},
349  {kP3XIdx, kP3YIdx}, kMinProximity, "ProximityP3"));
350  const std::shared_ptr<ProxCost> p2p4_proximity_cost(
351  new ProxCost(kP2ProximityCostWeight, {kP2XIdx, kP2YIdx},
352  {kP4XIdx, kP4YIdx}, kMinProximity, "ProximityP4"));
353  p2_cost.AddStateCost(p2p1_proximity_cost);
354  p2_cost.AddStateCost(p2p3_proximity_cost);
355  // p2_cost.AddStateCost(p2p4_proximity_cost);
356 
357  const std::shared_ptr<ProxCost> p3p1_proximity_cost(
358  new ProxCost(kP3ProximityCostWeight, {kP3XIdx, kP3YIdx},
359  {kP1XIdx, kP1YIdx}, kMinProximity, "ProximityP1"));
360  const std::shared_ptr<ProxCost> p3p2_proximity_cost(
361  new ProxCost(kP3ProximityCostWeight, {kP3XIdx, kP3YIdx},
362  {kP2XIdx, kP2YIdx}, kMinProximity, "ProximityP2"));
363  const std::shared_ptr<ProxCost> p3p4_proximity_cost(
364  new ProxCost(kP3ProximityCostWeight, {kP3XIdx, kP3YIdx},
365  {kP4XIdx, kP4YIdx}, kMinProximity, "ProximityP4"));
366  // p3_cost.AddStateCost(p3p1_proximity_cost);
367  p3_cost.AddStateCost(p3p2_proximity_cost);
368  p3_cost.AddStateCost(p3p4_proximity_cost);
369 
370  const std::shared_ptr<ProxCost> p4p1_proximity_cost(
371  new ProxCost(kP4ProximityCostWeight, {kP4XIdx, kP4YIdx},
372  {kP1XIdx, kP1YIdx}, kMinProximity, "ProximityP1"));
373  const std::shared_ptr<ProxCost> p4p2_proximity_cost(
374  new ProxCost(kP4ProximityCostWeight, {kP4XIdx, kP4YIdx},
375  {kP2XIdx, kP2YIdx}, kMinProximity, "ProximityP2"));
376  const std::shared_ptr<ProxCost> p4p3_proximity_cost(
377  new ProxCost(kP4ProximityCostWeight, {kP4XIdx, kP4YIdx},
378  {kP3XIdx, kP3YIdx}, kMinProximity, "ProximityP3"));
379  p4_cost.AddStateCost(p4p1_proximity_cost);
380  // p4_cost.AddStateCost(p4p2_proximity_cost);
381  p4_cost.AddStateCost(p4p3_proximity_cost);
382 }
383 
384 inline std::vector<float> FlatRoundaboutMergingExample::Xs(
385  const VectorXf& xi) const {
386  return {xi(kP1XIdx), xi(kP2XIdx), xi(kP3XIdx), xi(kP4XIdx)};
387 }
388 
389 inline std::vector<float> FlatRoundaboutMergingExample::Ys(
390  const VectorXf& xi) const {
391  return {xi(kP1YIdx), xi(kP2YIdx), xi(kP3YIdx), xi(kP4YIdx)};
392 }
393 
394 inline std::vector<float> FlatRoundaboutMergingExample::Thetas(
395  const VectorXf& xi) const {
396  const VectorXf x = dynamics_->FromLinearSystemState(xi);
397  return {x(kP1HeadingIdx), x(kP2HeadingIdx), x(kP3HeadingIdx),
398  x(kP4HeadingIdx)};
399 }
400 
401 } // namespace ilqgames