ilqgames
A new real-time solver for large-scale differential games.
augmented_lagrangian_solver.cpp
1 /*
2  * Copyright (c) 2020, 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 // Solver that implements an augmented Lagrangian method. For reference on these
40 // methods, please refer to Chapter 17 of Nocedal and Wright or the ALTRO paper:
41 // https://bjack205.github.io/assets/ALTRO.pdf.
42 //
43 ///////////////////////////////////////////////////////////////////////////////
44 
45 #include <ilqgames/dynamics/multi_player_dynamical_system.h>
46 #include <ilqgames/dynamics/multi_player_integrable_system.h>
47 #include <ilqgames/solver/augmented_lagrangian_solver.h>
48 #include <ilqgames/solver/game_solver.h>
49 #include <ilqgames/solver/ilq_solver.h>
50 #include <ilqgames/solver/lq_feedback_solver.h>
51 #include <ilqgames/solver/lq_open_loop_solver.h>
52 #include <ilqgames/solver/lq_solver.h>
53 #include <ilqgames/solver/problem.h>
54 #include <ilqgames/solver/solver_params.h>
55 #include <ilqgames/utils/linear_dynamics_approximation.h>
56 #include <ilqgames/utils/loop_timer.h>
57 #include <ilqgames/utils/operating_point.h>
58 #include <ilqgames/utils/quadratic_cost_approximation.h>
59 #include <ilqgames/utils/solver_log.h>
60 #include <ilqgames/utils/strategy.h>
61 #include <ilqgames/utils/types.h>
62 
63 #include <glog/logging.h>
64 #include <chrono>
65 #include <limits>
66 #include <memory>
67 #include <utility>
68 #include <vector>
69 
70 namespace ilqgames {
71 
72 std::shared_ptr<SolverLog> AugmentedLagrangianSolver::Solve(bool* success,
73  Time max_runtime) {
74  if (success) *success = true;
75 
76  // Cache initial problem solution so we can restore it at the end.
77  const auto& initial_op = problem_->CurrentOperatingPoint();
78  const auto& initial_strategies = problem_->CurrentStrategies();
79 
80  // Create new log.
81  std::shared_ptr<SolverLog> log = CreateNewLog();
82 
83  // Determine how much time should be allocated for any individual lower level
84  // solver call.
85  const Time max_runtime_unconstrained_problem =
86  (problem_->IsConstrained())
87  ? max_runtime / static_cast<Time>(params_.max_solver_iters)
88  : max_runtime;
89 
90  // Solve unconstrained problem.
91  bool unconstrained_success = false;
92  const auto unconstrained_log = unconstrained_solver_->Solve(
93  &unconstrained_success, max_runtime_unconstrained_problem);
94  log->AddLog(*unconstrained_log);
95 
96  VLOG_IF(2, !unconstrained_success)
97  << "Unconstrained solver failed on first call.";
98  VLOG_IF(2, unconstrained_success)
99  << "Unconstrained solver succeeded on first call.";
100  if (success) *success &= unconstrained_success;
101 
102  // Exit if problem is unconstrained.
103  if (!problem_->IsConstrained()) return log;
104 
105  // Run until convergence or until the time runs out.
106  Time elapsed = max_runtime_unconstrained_problem;
107  float max_constraint_error = constants::kInfinity;
108  while (log->NumIterates() < params_.max_solver_iters &&
109  max_constraint_error > params_.constraint_error_tolerance &&
110  elapsed < max_runtime - timer_.RuntimeUpperBound()) {
111  // Start loop timer.
112  timer_.Tic();
113 
114  // Increment multiplers in player costs, and in parallel compute the total
115  // squared constraint error.
116  max_constraint_error = -constants::kInfinity;
117  const OperatingPoint& op = log->FinalOperatingPoint();
118  for (auto& pc : problem_->PlayerCosts()) {
119  for (size_t kk = 0; kk < op.xs.size(); kk++) {
120  const Time t = op.t0 + time::kTimeStep * static_cast<float>(kk);
121  const auto& x = op.xs[kk];
122  const auto& us = op.us[kk];
123 
124  // Scale each lambda.
125  for (const auto& constraint : pc.StateConstraints()) {
126  const float constraint_error = constraint->Evaluate(t, x);
127  max_constraint_error =
128  std::max(max_constraint_error, constraint_error);
129  constraint->IncrementLambda(t, constraint_error);
130  }
131 
132  for (const auto& pair : pc.ControlConstraints()) {
133  const float constraint_error =
134  pair.second->Evaluate(t, us[pair.first]);
135  max_constraint_error =
136  std::max(max_constraint_error, constraint_error);
137  pair.second->IncrementLambda(t, constraint_error);
138  }
139  }
140  }
141 
142  // Scale mu.
143  Constraint::ScaleMu(params_.geometric_mu_scaling);
144 
145  // Log squared constraint violation.
146  VLOG(2) << "Max constraint violation at iteration " << log->NumIterates()
147  << " is " << max_constraint_error;
148 
149  // Update problem solution to make sure we pick up where we left off if the
150  // previous unconstrained solver succeeded.
151  if (unconstrained_success) {
152  problem_->OverwriteSolution(log->FinalOperatingPoint(),
153  log->FinalStrategies());
154  }
155 
156  // Run unconstrained solver to convergence. Since we will update problem
157  // solutions at each outer iteration, the unconstrained solver should
158  // automatically start where it left off.
159  const auto unconstrained_log = unconstrained_solver_->Solve(
160  &unconstrained_success, max_runtime_unconstrained_problem);
161 
162  VLOG_IF(2, unconstrained_success)
163  << "Unconstrained solver succeeded on iteration " << log->NumIterates();
164 
165  // If we failed then downscale all lambdas and mus for next iteration.
166  if (!unconstrained_success) {
167  VLOG(2) << "Unconstrained solver failed at iteration "
168  << log->NumIterates();
169  VLOG(2) << "Downscaling all multipliers.";
170  for (auto& pc : problem_->PlayerCosts()) {
171  for (const auto& constraint : pc.StateConstraints())
172  constraint->ScaleLambdas(params_.geometric_lambda_downscaling);
173  for (const auto& pair : pc.ControlConstraints())
174  pair.second->ScaleLambdas(params_.geometric_lambda_downscaling);
175  }
176 
177  Constraint::ScaleMu(params_.geometric_mu_downscaling);
178  }
179 
180  if (success) *success &= unconstrained_success;
181  log->AddLog(*unconstrained_log);
182 
183  // Record loop time.
184  elapsed += timer_.Toc();
185  }
186 
187  // If we're still failing constraint satisfaction check mark as failure.
188  if (max_constraint_error > params_.constraint_error_tolerance) {
189  LOG(WARNING) << "Solver could not satisfy all constraints.";
190  if (success) *success = false;
191  }
192 
193  // Maybe restore initial solution to this problem.
194  if (params_.reset_problem)
195  problem_->OverwriteSolution(initial_op, initial_strategies);
196 
197  // Reset all multipliers.
198  if (params_.reset_lambdas) {
199  for (auto& pc : problem_->PlayerCosts()) {
200  for (const auto& constraint : pc.StateConstraints())
201  constraint->ScaleLambdas(constants::kDefaultLambda);
202  for (const auto& pair : pc.ControlConstraints())
203  pair.second->ScaleLambdas(constants::kDefaultLambda);
204  }
205  }
206 
207  if (params_.reset_mu) Constraint::GlobalMu() = constants::kDefaultMu;
208 
209  return log;
210 }
211 
212 } // namespace ilqgames