ilqgames
A new real-time solver for large-scale differential games.
check_local_nash_equilibrium.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 // Check whether or not a particular set of strategies is a local Nash
40 // equilibrium.
41 //
42 ///////////////////////////////////////////////////////////////////////////////
43 
44 #include <ilqgames/cost/player_cost.h>
45 #include <ilqgames/dynamics/multi_player_flat_system.h>
46 #include <ilqgames/dynamics/multi_player_integrable_system.h>
47 #include <ilqgames/utils/compute_strategy_costs.h>
48 #include <ilqgames/utils/operating_point.h>
49 #include <ilqgames/utils/quadratic_cost_approximation.h>
50 #include <ilqgames/utils/strategy.h>
51 #include <ilqgames/utils/types.h>
52 
53 #include <glog/logging.h>
54 #include <Eigen/Dense>
55 #include <random>
56 #include <vector>
57 
58 namespace ilqgames {
59 
60 bool NumericalCheckLocalNashEquilibrium(
61  const std::vector<PlayerCost>& player_costs,
62  const std::vector<Strategy>& strategies,
63  const OperatingPoint& operating_point,
64  const MultiPlayerIntegrableSystem& dynamics, const VectorXf& x0,
65  float max_perturbation, bool open_loop) {
66  CHECK_EQ(strategies.size(), player_costs.size());
67  CHECK_EQ(strategies.size(), dynamics.NumPlayers());
68  CHECK_EQ(x0.size(), dynamics.XDim());
69 
70  const size_t num_time_steps = strategies[0].Ps.size();
71  CHECK_EQ(num_time_steps, strategies[0].alphas.size());
72 
73  // Compute nominal equilibrium cost and be sure to use only 1-step Euler
74  // integration.
75  const bool was_integrating_using_euler =
76  MultiPlayerIntegrableSystem::IntegrationUsesEuler();
77  if (!was_integrating_using_euler)
78  MultiPlayerIntegrableSystem::IntegrateUsingEuler();
79  const std::vector<float> nominal_costs = ComputeStrategyCosts(
80  player_costs, strategies, operating_point, dynamics, x0, open_loop);
81 
82  // For each player, perturb strategies with Gaussian noise a bunch of times
83  // and if cost decreases then return false.
84  std::vector<Strategy> perturbed_strategies_lower(strategies);
85  std::vector<Strategy> perturbed_strategies_upper(strategies);
86  for (PlayerIndex ii = 0; ii < dynamics.NumPlayers(); ii++) {
87  for (size_t kk = 0; kk < num_time_steps - 1; kk++) {
88  VectorXf& alphak_lower = perturbed_strategies_lower[ii].alphas[kk];
89  VectorXf& alphak_upper = perturbed_strategies_upper[ii].alphas[kk];
90 
91  for (size_t jj = 0; jj < alphak_lower.size(); jj++) {
92  alphak_lower(jj) -= max_perturbation;
93  alphak_upper(jj) += max_perturbation;
94 
95  // Compute new costs.
96  const std::vector<float> perturbed_costs_lower =
97  ComputeStrategyCosts(player_costs, perturbed_strategies_lower,
98  operating_point, dynamics, x0, open_loop);
99  const std::vector<float> perturbed_costs_upper =
100  ComputeStrategyCosts(player_costs, perturbed_strategies_upper,
101  operating_point, dynamics, x0, open_loop);
102 
103  // Check Nash condition.
104  if (std::min(perturbed_costs_lower[ii], perturbed_costs_upper[ii]) <
105  nominal_costs[ii]) {
106  // std::printf(
107  // "player %hu, timestep %zu: nominal %f > perturbed %f\n ", ii,
108  // kk, nominal_costs[ii], std::min(perturbed_costs_lower[ii],
109  // perturbed_costs_lower[ii]));
110  // std::cout << "nominal u: " <<
111  // operating_point.us[kk][ii].transpose()
112  // << ", alpha original: "
113  // << strategies[ii].alphas[kk].transpose()
114  // << ", vs. perturbed " << alphak_lower.transpose()
115  // << std::endl;
116 
117  // Other users will likely want RK4 integration.
118  if (!was_integrating_using_euler)
119  MultiPlayerIntegrableSystem::IntegrateUsingRK4();
120  return false;
121  }
122 
123  // Reset this alpha.
124  alphak_lower = strategies[ii].alphas[kk];
125  alphak_upper = strategies[ii].alphas[kk];
126  }
127  }
128  }
129 
130  // Other users will likely want RK4 integration.
131  MultiPlayerIntegrableSystem::IntegrateUsingRK4();
132  return true;
133 }
134 
135 bool NumericalCheckLocalNashEquilibrium(const Problem& problem,
136  float max_perturbation,
137  bool open_loop) {
138  return NumericalCheckLocalNashEquilibrium(
139  problem.PlayerCosts(), problem.CurrentStrategies(),
140  problem.CurrentOperatingPoint(), *problem.Dynamics(),
141  problem.InitialState(), max_perturbation, open_loop);
142 }
143 
144 bool CheckSufficientLocalNashEquilibrium(
145  const std::vector<PlayerCost>& player_costs,
146  const OperatingPoint& operating_point,
147  const std::shared_ptr<const MultiPlayerIntegrableSystem> dynamics) {
148  // Unpack number of players and number of time steps.
149  const PlayerIndex num_players = player_costs.size();
150  const size_t num_time_steps = operating_point.xs.size();
151  const Dimension xdim = operating_point.xs[0].size();
152 
153  // Set up quadratic cost approximations.
154  std::vector<QuadraticCostApproximation> quadraticization(
155  num_players, QuadraticCostApproximation(xdim));
156 
157  // Quadraticize costs and check PSD conditions.
158  for (size_t kk = 0; kk < num_time_steps; kk++) {
159  const Time t = operating_point.t0 + static_cast<Time>(kk) * time::kTimeStep;
160  VectorXf x = operating_point.xs[kk];
161  std::vector<VectorXf> us = operating_point.us[kk];
162 
163  // Maybe convert out of linear system coordinates.
164  if (dynamics.get() && dynamics->TreatAsLinear()) {
165  const auto& dyn =
166  *static_cast<const MultiPlayerFlatSystem*>(dynamics.get());
167 
168  // Previous x, us are actually xi, vs.
169  x = dyn.FromLinearSystemState(x.eval());
170  us = dyn.LinearizingControls(x, std::vector<VectorXf>(us));
171  }
172 
173  std::transform(player_costs.begin(), player_costs.end(),
174  quadraticization.begin(),
175  [&t, &x, &us](const PlayerCost& cost) {
176  return cost.Quadraticize(t, x, us);
177  });
178 
179  // Check if Q, Rs PSD.
180  constexpr float kErrorMargin = 1e-4;
181  for (const auto& q : quadraticization) {
182  const auto eig_Q = Eigen::SelfAdjointEigenSolver<MatrixXf>(q.state.hess);
183  if (eig_Q.eigenvalues().minCoeff() < -kErrorMargin) {
184  // std::cout << "Failed at timestep " << kk << std::endl;
185  // std::cout << "Q is: \n" << q.Q << std::endl;
186  // std::cout << "Q evals are: " << eig_Q.eigenvalues().transpose()
187  // << std::endl;
188  return false;
189  }
190 
191  for (const auto& entry : q.control) {
192  const auto eig_R =
193  Eigen::SelfAdjointEigenSolver<MatrixXf>(entry.second.hess);
194  if (eig_R.eigenvalues().minCoeff() < -kErrorMargin) return false;
195  }
196  }
197  }
198 
199  return true;
200 }
201 
202 bool CheckSufficientLocalNashEquilibrium(const Problem& problem) {
203  return CheckSufficientLocalNashEquilibrium(problem.PlayerCosts(),
204  problem.CurrentOperatingPoint(),
205  problem.Dynamics());
206 }
207 
208 } // namespace ilqgames