ilqgames
A new real-time solver for large-scale differential games.
problem.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 // Base class specifying the problem interface for managing calls to the core
40 // ILQGame solver. Specific examples will be derived from this class.
41 //
42 ///////////////////////////////////////////////////////////////////////////////
43 
44 #include <ilqgames/solver/game_solver.h>
45 #include <ilqgames/solver/problem.h>
46 #include <ilqgames/utils/relative_time_tracker.h>
47 #include <ilqgames/utils/solver_log.h>
48 #include <ilqgames/utils/strategy.h>
49 #include <ilqgames/utils/types.h>
50 
51 #include <glog/logging.h>
52 #include <algorithm>
53 #include <memory>
54 #include <vector>
55 
56 // // Time horizon and step.
57 // DEFINE_double(time_horizon, 10.0, "Total time horizon (s).");
58 // DEFINE_double(time_step, 0.1, "Length of discrete time step (s).");
59 
60 namespace ilqgames {
61 
62 Problem::Problem() : initialized_(false) {}
63 
64 size_t Problem::SyncToExistingProblem(const VectorXf& x0, Time t0,
65  Time planner_runtime,
66  OperatingPoint& op) {
67  CHECK(initialized_);
68  CHECK_GE(planner_runtime, 0.0);
69  CHECK_LE(planner_runtime + t0, operating_point_->t0 + time::kTimeHorizon);
70  CHECK_GE(t0, operating_point_->t0);
71 
72  // Integrate x0 forward from t0 by approximately planner_runtime to get
73  // actual initial state. Integrate up to the next discrete timestep, then
74  // integrate for an integer number of discrete timesteps until by the *next*
75  // timestep at least 'planner_runtime' has elapsed (done by rounding).
76  constexpr float kRoundingError = 0.9;
77  const Time relative_t0 = t0 - op.t0;
78  size_t current_timestep = static_cast<size_t>(relative_t0 / time::kTimeStep);
79  Time remaining_time_this_step =
80  (current_timestep + 1) * time::kTimeStep - relative_t0;
81  if (remaining_time_this_step < kRoundingError * time::kTimeStep) {
82  current_timestep += 1;
83  remaining_time_this_step = time::kTimeStep - remaining_time_this_step;
84  }
85 
86  CHECK_LT(remaining_time_this_step, time::kTimeStep);
87 
88  // Initially, set x to the integrated version of x0 at the next timestep.
89  VectorXf x = dynamics_->IntegrateToNextTimeStep(t0, x0, *operating_point_,
90  *strategies_);
91  op.t0 = t0 + remaining_time_this_step;
92  if (remaining_time_this_step <= planner_runtime) {
93  const size_t num_steps_to_integrate = static_cast<size_t>(
94  constants::kSmallNumber + // Add to avoid truncation error.
95  (planner_runtime - remaining_time_this_step) / time::kTimeStep);
96  const size_t last_integration_timestep =
97  current_timestep + num_steps_to_integrate;
98 
99  x = dynamics_->Integrate(current_timestep + 1, last_integration_timestep, x,
100  *operating_point_, *strategies_);
101  op.t0 += time::kTimeStep * num_steps_to_integrate;
102  }
103 
104  // Find index of nearest state in the existing plan to this state.
105  const auto nearest_iter =
106  std::min_element(op.xs.begin(), op.xs.end(),
107  [this, &x](const VectorXf& x1, const VectorXf& x2) {
108  return dynamics_->DistanceBetween(x, x1) <
109  dynamics_->DistanceBetween(x, x2);
110  });
111 
112  // Set initial time to first timestamp in new problem.
113  const size_t first_timestep_in_new_problem =
114  std::distance(op.xs.begin(), nearest_iter);
115 
116  // Set initial state to this state.
117  x0_ = dynamics_->Stitch(*nearest_iter, x);
118 
119  // Update all costs to have the correct initial time.
120  RelativeTimeTracker::ResetInitialTime(op.t0);
121 
122  // Check an invariant.
123  CHECK_LE(std::abs(t0 + planner_runtime - op.t0), time::kTimeStep);
124  return first_timestep_in_new_problem;
125 }
126 
127 void Problem::SetUpNextRecedingHorizon(const VectorXf& x0, Time t0,
128  Time planner_runtime) {
129  CHECK(initialized_);
130 
131  // Sync to existing problem.
132  const size_t first_timestep_in_new_problem =
133  SyncToExistingProblem(x0, t0, planner_runtime, *operating_point_);
134 
135  // Set final timestep to consider in current operating point.
136  const size_t after_final_timestep =
137  first_timestep_in_new_problem + time::kNumTimeSteps;
138  const size_t timestep_iterator_end =
139  std::min(after_final_timestep, operating_point_->xs.size());
140 
141  // Populate strategies and opeating point for the remainder of the
142  // existing plan, reusing the old operating point when possible.
143  for (size_t kk = first_timestep_in_new_problem; kk < timestep_iterator_end;
144  kk++) {
145  const size_t kk_new_problem = kk - first_timestep_in_new_problem;
146 
147  // Set current state and controls in operating point.
148  operating_point_->xs[kk_new_problem].swap(operating_point_->xs[kk]);
149  operating_point_->us[kk_new_problem].swap(operating_point_->us[kk]);
150  CHECK_EQ(operating_point_->us[kk_new_problem].size(),
151  dynamics_->NumPlayers());
152 
153  // Set current stategy.
154  for (auto& strategy : *strategies_) {
155  strategy.Ps[kk_new_problem].swap(strategy.Ps[kk]);
156  strategy.alphas[kk_new_problem].swap(strategy.alphas[kk]);
157  }
158  }
159 
160  // Make sure operating point is the right size.
161  CHECK_GE(operating_point_->xs.size(), time::kNumTimeSteps);
162  if (operating_point_->xs.size() > time::kNumTimeSteps) {
163  operating_point_->xs.resize(time::kNumTimeSteps);
164  operating_point_->us.resize(time::kNumTimeSteps);
165  for (PlayerIndex ii = 0; ii < dynamics_->NumPlayers(); ii++) {
166  (*strategies_)[ii].Ps.resize(time::kNumTimeSteps);
167  (*strategies_)[ii].alphas.resize(time::kNumTimeSteps);
168  }
169  }
170 
171  // Set new operating point controls and strategies to zero and propagate
172  // state forward accordingly.
173  for (size_t kk = timestep_iterator_end - first_timestep_in_new_problem;
174  kk < time::kNumTimeSteps; kk++) {
175  operating_point_->us[kk].resize(dynamics_->NumPlayers());
176  for (size_t ii = 0; ii < dynamics_->NumPlayers(); ii++) {
177  (*strategies_)[ii].Ps[kk].setZero(dynamics_->UDim(ii), dynamics_->XDim());
178  (*strategies_)[ii].alphas[kk].setZero(dynamics_->UDim(ii));
179  operating_point_->us[kk][ii].setZero(dynamics_->UDim(ii));
180  }
181 
182  operating_point_->xs[kk] = dynamics_->Integrate(
183  RelativeTimeTracker::RelativeTime(kk - 1), time::kTimeStep,
184  operating_point_->xs[kk - 1], operating_point_->us[kk - 1]);
185  }
186 }
187 
188 void Problem::OverwriteSolution(const OperatingPoint& operating_point,
189  const std::vector<Strategy>& strategies) {
190  CHECK(initialized_);
191 
192  *operating_point_ = operating_point;
193  *strategies_ = strategies;
194 }
195 
196 bool Problem::IsConstrained() const {
197  for (const auto& pc : player_costs_) {
198  if (pc.IsConstrained()) return true;
199  }
200 
201  return false;
202 }
203 
204 } // namespace ilqgames