ModelPredictiveControl.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2018-2019, CNRS-UM LIRMM
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 met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice,
9  * this list of conditions and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright notice,
12  * this list of conditions and the following disclaimer in the documentation
13  * and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25  * POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include <iomanip>
32 
33 namespace lipm_walking
34 {
35 
36 // Repeat static constexpr declarations
37 // Fixes https://github.com/stephane-caron/lipm_walking_controller/issues/21
38 // See also https://stackoverflow.com/q/8016780
40 constexpr unsigned ModelPredictiveControl::INPUT_SIZE;
41 constexpr unsigned ModelPredictiveControl::NB_STEPS;
42 constexpr unsigned ModelPredictiveControl::STATE_SIZE;
43 
45 {
46  velCostMat_.setZero();
47  constexpr double T = SAMPLING_PERIOD;
48  double S = T * T / 2; // "square"
49  double C = T * T * T / 6; // "cube"
50  Eigen::Matrix<double, STATE_SIZE, STATE_SIZE> stateMatrix;
51  // clang-format off
52  stateMatrix <<
53  1, 0, T, 0, S, 0,
54  0, 1, 0, T, 0, S,
55  0, 0, 1, 0, T, 0,
56  0, 0, 0, 1, 0, T,
57  0, 0, 0, 0, 1, 0,
58  0, 0, 0, 0, 0, 1;
59  // clang-format on
60  Eigen::Matrix<double, STATE_SIZE, INPUT_SIZE> inputMatrix;
61  // clang-format off
62  inputMatrix <<
63  C, 0,
64  0, C,
65  S, 0,
66  0, S,
67  T, 0,
68  0, T;
69  // clang-format on
70  Eigen::VectorXd biasVector = Eigen::VectorXd::Zero(STATE_SIZE);
71  initState_ = Eigen::VectorXd::Zero(STATE_SIZE);
72  previewSystem_ = std::make_shared<copra::PreviewSystem>(stateMatrix, inputMatrix, biasVector, initState_, NB_STEPS);
73  LOG_SUCCESS("Initialized new ModelPredictiveControl solver");
74 }
75 
76 void ModelPredictiveControl::configure(const mc_rtc::Configuration & config)
77 {
78  if(config.has("weights"))
79  {
80  auto weights = config("weights");
81  weights("jerk", jerkWeight);
82  weights("vel", velWeights);
83  weights("zmp", zmpWeight);
84  }
85 }
86 
87 void ModelPredictiveControl::addGUIElements(std::shared_ptr<mc_rtc::gui::StateBuilder> gui)
88 {
89  using namespace mc_rtc::gui;
90  gui->addElement({"Walking", "CoM"},
91  ComboInput("MPC QP solver", {"QuadProgDense", "QLD"},
92  [this]() {
93  switch(solver_)
94  {
95  case copra::SolverFlag::QLD:
96  return "QLD";
97  case copra::SolverFlag::QuadProgDense:
98  default:
99  return "QuadProgDense";
100  }
101  },
102  [this](const std::string & solver) {
103  if(solver == "QLD")
104  {
105  solver_ = copra::SolverFlag::QLD;
106  }
107  else // (solver == "QuadProgDense")
108  {
109  solver_ = copra::SolverFlag::QuadProgDense;
110  }
111  }),
112  ArrayInput("MPC QP cost weights", {"jerk", "vel_x", "vel_y", "zmp"},
113  [this]() {
114  Eigen::VectorXd weights(4);
115  weights[0] = jerkWeight;
116  weights[1] = velWeights.x();
117  weights[2] = velWeights.y();
118  weights[3] = zmpWeight;
119  return weights;
120  },
121  [this](const Eigen::VectorXd & weights) {
122  jerkWeight = weights[0];
123  velWeights.x() = weights[1];
124  velWeights.y() = weights[2];
125  zmpWeight = weights[3];
126  }));
127 }
128 
129 void ModelPredictiveControl::addLogEntries(mc_rtc::Logger & logger)
130 {
131  logger.addLogEntry("mpc_velRef", [this]() -> Eigen::Vector2d { return velRef_.head<2>(); });
132  logger.addLogEntry("mpc_weights_jerk", [this]() { return jerkWeight; });
133  logger.addLogEntry("mpc_weights_vel", [this]() { return velWeights; });
134  logger.addLogEntry("mpc_weights_zmp", [this]() { return zmpWeight; });
135  logger.addLogEntry("mpc_zmpRef", [this]() -> Eigen::Vector2d { return zmpRef_.head<2>(); });
136  logger.addLogEntry("perf_MPCBuildAndSolve", [this]() { return buildAndSolveTime_; });
137  logger.addLogEntry("perf_MPCSolve", [this]() { return solveTime_; });
138 }
139 
140 void ModelPredictiveControl::phaseDurations(double initSupportDuration,
141  double doubleSupportDuration,
142  double targetSupportDuration)
143 {
144  constexpr double T = SAMPLING_PERIOD;
145 
146  unsigned nbStepsSoFar = 0;
147  nbInitSupportSteps_ = std::min(static_cast<unsigned>(std::round(initSupportDuration / T)), NB_STEPS - nbStepsSoFar);
148  nbStepsSoFar += nbInitSupportSteps_;
149  nbDoubleSupportSteps_ =
150  std::min(static_cast<unsigned>(std::round(doubleSupportDuration / T)), NB_STEPS - nbStepsSoFar);
151  nbStepsSoFar += nbDoubleSupportSteps_;
152  nbTargetSupportSteps_ =
153  std::min(static_cast<unsigned>(std::round(targetSupportDuration / T)), NB_STEPS - nbStepsSoFar);
154  nbStepsSoFar += nbTargetSupportSteps_;
155  if(nbTargetSupportSteps_ > 0) // full preview
156  {
157  nbNextDoubleSupportSteps_ = NB_STEPS - nbStepsSoFar; // always positive
158  }
159  for(long i = 0; i <= NB_STEPS; i++)
160  {
161  // SSP constraint is enforced at the very first step of DSP
162  if(i < nbInitSupportSteps_ || (0 < i && i == nbInitSupportSteps_))
163  {
164  indexToHrep_[i] = 0;
165  }
166  else if(i - nbInitSupportSteps_ < nbDoubleSupportSteps_)
167  {
168  indexToHrep_[i] = 1;
169  }
170  else if(nbTargetSupportSteps_ > 0)
171  {
172  if(i - nbInitSupportSteps_ - nbDoubleSupportSteps_ <= nbTargetSupportSteps_)
173  {
174  indexToHrep_[i] = 2;
175  }
176  else if(nbNextDoubleSupportSteps_ > 0)
177  {
178  indexToHrep_[i] = 3;
179  }
180  else // (nbNextDoubleSupportSteps_ == 0)
181  {
182  indexToHrep_[i] = 2;
183  }
184  }
185  else // (nbTargetSupportSteps_ == 0)
186  {
187  indexToHrep_[i] = 1;
188  }
189  }
190 }
191 
192 void ModelPredictiveControl::computeZMPRef()
193 {
194  zmpRef_.setZero();
195  Eigen::Vector2d p_0 = initContact_.anklePos(sole_).head<2>();
196  Eigen::Vector2d p_1 = targetContact_.anklePos(sole_).head<2>();
197  Eigen::Vector2d p_2 = nextContact_.anklePos(sole_).head<2>();
198  if(nbTargetSupportSteps_ < 1) // stop during first DSP
199  {
200  p_1 = 0.5 * (initContact_.anklePos(sole_) + targetContact_.anklePos(sole_)).head<2>();
201  }
202  for(long i = 0; i <= NB_STEPS; i++)
203  {
204  if(indexToHrep_[i] <= 1)
205  {
206  long j = i - nbInitSupportSteps_;
207  double x = (nbDoubleSupportSteps_ > 0) ? static_cast<double>(j) / nbDoubleSupportSteps_ : 0.;
208  x = clamp(x, 0., 1.);
209  zmpRef_.segment<2>(2 * i) = (1. - x) * p_0 + x * p_1;
210  }
211  else // (indexToHrep_[i] <= 3), which implies nbTargetSupportSteps_ > 0
212  {
213  long j = i - nbInitSupportSteps_ - nbDoubleSupportSteps_ - nbTargetSupportSteps_;
214  double x = (nbNextDoubleSupportSteps_ > 0) ? static_cast<double>(j) / nbNextDoubleSupportSteps_ : 0;
215  x = clamp(x, 0., 1.);
216  zmpRef_.segment<2>(2 * i) = (1. - x) * p_1 + x * p_2;
217  }
218  }
219 }
220 
221 void ModelPredictiveControl::updateTerminalConstraint()
222 {
223  Eigen::MatrixXd E_dcm = Eigen::MatrixXd::Zero(2, STATE_SIZE * (NB_STEPS + 1));
224  Eigen::MatrixXd E_zmp = Eigen::MatrixXd::Zero(2, STATE_SIZE * (NB_STEPS + 1));
225  if(nbTargetSupportSteps_ < 1) // half preview
226  {
227  unsigned i = nbInitSupportSteps_ + nbDoubleSupportSteps_;
228  E_dcm.block<2, 6>(0, 6 * i) = dcmFromState_;
229  E_zmp.block<2, 6>(0, 6 * i) = zmpFromState_;
230  }
231  else // full preview
232  {
233  E_dcm.rightCols<6>() = dcmFromState_;
234  E_zmp.rightCols<6>() = zmpFromState_;
235  }
236  Eigen::Vector2d dcmTarget = zmpRef_.tail<2>();
237  Eigen::Vector2d zmpTarget = zmpRef_.tail<2>();
238  termDCMCons_ = std::make_shared<copra::TrajectoryConstraint>(E_dcm, dcmTarget, /* isInequalityConstraint = */ false);
239  termZMPCons_ = std::make_shared<copra::TrajectoryConstraint>(E_zmp, zmpTarget, /* isInequalityConstraint = */ false);
240 }
241 
242 void ModelPredictiveControl::updateZMPConstraint()
243 {
244  hreps_[0] = initContact_.hrep();
245  hreps_[2] = targetContact_.hrep();
246  unsigned totalRows = 0;
247  for(long i = 0; i <= NB_STEPS; i++)
248  {
249  unsigned hrepIndex = indexToHrep_[i];
250  if(hrepIndex % 2 == 0)
251  {
252  const auto & hrep = hreps_[hrepIndex];
253  totalRows += static_cast<unsigned>(hrep.first.rows());
254  }
255  }
256  Eigen::MatrixXd A{totalRows, STATE_SIZE * (NB_STEPS + 1)};
257  Eigen::VectorXd b{totalRows};
258  A.setZero();
259  long nextRow = 0;
260  for(long i = 0; i <= NB_STEPS; i++)
261  {
262  unsigned hrepIndex = indexToHrep_[i];
263  if(hrepIndex % 2 == 0)
264  {
265  const auto & hrep = hreps_[indexToHrep_[i]];
266  unsigned consRows = static_cast<unsigned>(hrep.first.rows());
267  A.block(nextRow, STATE_SIZE * i, consRows, STATE_SIZE) = hrep.first * zmpFromState_;
268  b.segment(nextRow, consRows) = hrep.second;
269  nextRow += consRows;
270  }
271  }
272  zmpCons_ = std::make_shared<copra::TrajectoryConstraint>(A, b);
273 }
274 
275 void ModelPredictiveControl::updateJerkCost()
276 {
277  Eigen::Matrix2d jerkMat = Eigen::Matrix2d::Identity();
278  Eigen::Vector2d jerkVec = Eigen::Vector2d::Zero();
279  jerkCost_ = std::make_shared<copra::ControlCost>(jerkMat, jerkVec);
280  jerkCost_->weight(jerkWeight);
281 }
282 
283 void ModelPredictiveControl::updateVelCost()
284 {
285  velRef_.setZero();
286  const Eigen::Matrix3d & R_0 = initContact_.pose.rotation();
287  const Eigen::Matrix3d & R_1 = targetContact_.pose.rotation();
288  const Eigen::Matrix3d & R_2 = nextContact_.pose.rotation();
289  Eigen::Vector2d v_0 = initContact_.refVel.head<2>();
290  Eigen::Vector2d v_1 = targetContact_.refVel.head<2>();
291  Eigen::Vector2d v_2 = nextContact_.refVel.head<2>();
292  if(nbTargetSupportSteps_ < 1) // stop during first DSP
293  {
294  v_1 = {0., 0.};
295  }
296  Eigen::Matrix2d R;
297  Eigen::Vector2d v;
298  for(long i = 0; i <= NB_STEPS; i++)
299  {
300  if(indexToHrep_[i] <= 1)
301  {
302  double w = static_cast<double>(i) / (nbInitSupportSteps_ + nbDoubleSupportSteps_);
303  w = clamp(w, 0., 1.);
304  R = slerp(R_0, R_1, w).topLeftCorner<2, 2>();
305  v = (1. - w) * v_0 + w * v_1;
306  }
307  else // (indexToHrep_[i] <= 3), which implies nbTargetSupportSteps_ > 0
308  {
309  long i2 = i - nbInitSupportSteps_ - nbDoubleSupportSteps_; // >= 0
310  double w = static_cast<double>(i2) / (nbTargetSupportSteps_ + nbNextDoubleSupportSteps_);
311  w = clamp(w, 0., 1.);
312  R = slerp(R_1, R_2, w).topLeftCorner<2, 2>();
313  v = (1. - w) * v_1 + w * v_2;
314  }
315  velCostMat_.block<2, STATE_SIZE>(2 * i, STATE_SIZE * i).block<2, 2>(0, 2) = R;
316  velRef_.segment<2>(2 * i) = R * v;
317  }
318  velCost_ = std::make_shared<copra::TrajectoryCost>(velCostMat_, velRef_);
319  velCost_->weights(velWeights);
320 }
321 
322 void ModelPredictiveControl::updateZMPCost()
323 {
324  zmpCost_ = std::make_shared<copra::TrajectoryCost>(zmpFromState_, zmpRef_);
325  zmpCost_->weight(zmpWeight);
326  zmpCost_->autoSpan(); // repeat zmpFromState
327 }
328 
330 {
331  using namespace std::chrono;
332  auto startTime = high_resolution_clock::now();
333 
334  computeZMPRef();
335 
336  previewSystem_->xInit(initState_);
337  updateTerminalConstraint();
338  updateZMPConstraint();
339  updateJerkCost();
340  updateVelCost();
341  updateZMPCost();
342 
343  copra::LMPC lmpc(previewSystem_, solver_);
344  lmpc.addConstraint(termDCMCons_);
345  lmpc.addConstraint(termZMPCons_);
346  lmpc.addConstraint(zmpCons_);
347  lmpc.addCost(jerkCost_);
348  lmpc.addCost(velCost_);
349  lmpc.addCost(zmpCost_);
350 
351  bool solutionFound = lmpc.solve();
352  if(solutionFound)
353  {
354  solution_.reset(new Preview(lmpc.trajectory(), lmpc.control()));
355  }
356  else
357  {
358  LOG_ERROR("Model predictive control problem has no solution");
359  solution_.reset(new Preview());
360  }
361 
362  auto endTime = high_resolution_clock::now();
363  buildAndSolveTime_ = 1000. * duration_cast<duration<double>>(endTime - startTime).count();
364  solveTime_ = 1000. * lmpc.solveTime();
365  return solutionFound;
366 }
367 
368 } // namespace lipm_walking
static constexpr unsigned INPUT_SIZE
Input is the 2D horizontal CoM jerk.
double clamp(double v, double vMin, double vMax)
Clamp a value in a given interval.
Definition: clamp.h:47
Eigen::Vector2d velWeights
Weights of CoM velocity tracking cost.
static constexpr unsigned NB_STEPS
Number of sampling steps.
double zmpWeight
Weight of reference ZMP tracking cost.
static EIGEN_MAKE_ALIGNED_OPERATOR_NEW constexpr double SAMPLING_PERIOD
Duration of each sampling step in [s].
ModelPredictiveControl()
Initialize new problem.
static constexpr unsigned STATE_SIZE
State is the 6D stacked vector of CoM positions, velocities and accelerations.
double jerkWeight
Weight of CoM jerk regularization cost.
void addLogEntries(mc_rtc::Logger &logger)
Log stabilizer entries.
void configure(const mc_rtc::Configuration &)
Read configuration from dictionary.
bool buildAndSolve()
Build and solve the model predictive control quadratic program.
Main controller namespace.
Definition: build.dox:1
Solution of a model predictive control problem.
Definition: Preview.h:42
Eigen::Matrix3d slerp(const Eigen::Matrix3d &from, const Eigen::Matrix3d &to, double t)
Spherical linear interpolation between two rotation matrices.
Definition: slerp.h:48
void phaseDurations(double initSupportDuration, double doubleSupportDuration, double targetSupportDuration)
Set duration of the initial single-support phase.
void addGUIElements(std::shared_ptr< mc_rtc::gui::StateBuilder > gui)
Add GUI panel.