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 <mc_rtc/gui.h>
29 
30 #include <iomanip>
34 
35 namespace lipm_walking
36 {
37 
38 // Repeat static constexpr declarations
39 // Fixes https://github.com/stephane-caron/lipm_walking_controller/issues/21
40 // See also https://stackoverflow.com/q/8016780
42 constexpr unsigned ModelPredictiveControl::INPUT_SIZE;
43 constexpr unsigned ModelPredictiveControl::NB_STEPS;
44 constexpr unsigned ModelPredictiveControl::STATE_SIZE;
45 
47 {
48  velCostMat_.setZero();
49  constexpr double T = SAMPLING_PERIOD;
50  double S = T * T / 2; // "square"
51  double C = T * T * T / 6; // "cube"
52  Eigen::Matrix<double, STATE_SIZE, STATE_SIZE> stateMatrix;
53  // clang-format off
54  stateMatrix <<
55  1, 0, T, 0, S, 0,
56  0, 1, 0, T, 0, S,
57  0, 0, 1, 0, T, 0,
58  0, 0, 0, 1, 0, T,
59  0, 0, 0, 0, 1, 0,
60  0, 0, 0, 0, 0, 1;
61  // clang-format on
62  Eigen::Matrix<double, STATE_SIZE, INPUT_SIZE> inputMatrix;
63  // clang-format off
64  inputMatrix <<
65  C, 0,
66  0, C,
67  S, 0,
68  0, S,
69  T, 0,
70  0, T;
71  // clang-format on
72  Eigen::VectorXd biasVector = Eigen::VectorXd::Zero(STATE_SIZE);
73  initState_ = Eigen::VectorXd::Zero(STATE_SIZE);
74  previewSystem_ = std::make_shared<copra::PreviewSystem>(stateMatrix, inputMatrix, biasVector, initState_, NB_STEPS);
75  mc_rtc::log::success("Initialized new ModelPredictiveControl solver");
76 }
77 
78 void ModelPredictiveControl::configure(const mc_rtc::Configuration & config)
79 {
80  if(config.has("weights"))
81  {
82  auto weights = config("weights");
83  weights("jerk", jerkWeight);
84  weights("vel", velWeights);
85  weights("zmp", zmpWeight);
86  }
87 }
88 
89 void ModelPredictiveControl::addGUIElements(std::shared_ptr<mc_rtc::gui::StateBuilder> gui)
90 {
91  using namespace mc_rtc::gui;
92  gui->addElement({"Walking", "CoM"},
93  ComboInput(
94  "MPC QP solver", {"QuadProgDense", "QLD"},
95  [this]() -> std::string
96  {
97  switch(solver_)
98  {
99  case copra::SolverFlag::QLD:
100  return "QLD";
101  case copra::SolverFlag::QuadProgDense:
102  default:
103  return "QuadProgDense";
104  }
105  },
106  [this](const std::string & solver)
107  {
108  if(solver == "QLD")
109  {
110  solver_ = copra::SolverFlag::QLD;
111  }
112  else // (solver == "QuadProgDense")
113  {
114  solver_ = copra::SolverFlag::QuadProgDense;
115  }
116  }),
117  ArrayInput(
118  "MPC QP cost weights", {"jerk", "vel_x", "vel_y", "zmp"},
119  [this]()
120  {
121  Eigen::VectorXd weights(4);
122  weights[0] = jerkWeight;
123  weights[1] = velWeights.x();
124  weights[2] = velWeights.y();
125  weights[3] = zmpWeight;
126  return weights;
127  },
128  [this](const Eigen::VectorXd & weights)
129  {
130  jerkWeight = weights[0];
131  velWeights.x() = weights[1];
132  velWeights.y() = weights[2];
133  zmpWeight = weights[3];
134  }));
135 }
136 
137 void ModelPredictiveControl::addLogEntries(mc_rtc::Logger & logger)
138 {
139  logger.addLogEntry("mpc_velRef", [this]() -> Eigen::Vector2d { return velRef_.head<2>(); });
140  logger.addLogEntry("mpc_weights_jerk", [this]() { return jerkWeight; });
141  logger.addLogEntry("mpc_weights_vel", [this]() { return velWeights; });
142  logger.addLogEntry("mpc_weights_zmp", [this]() { return zmpWeight; });
143  logger.addLogEntry("mpc_zmpRef", [this]() -> Eigen::Vector2d { return zmpRef_.head<2>(); });
144  logger.addLogEntry("perf_MPCBuildAndSolve", [this]() { return buildAndSolveTime_; });
145  logger.addLogEntry("perf_MPCSolve", [this]() { return solveTime_; });
146 }
147 
148 void ModelPredictiveControl::phaseDurations(double initSupportDuration,
149  double doubleSupportDuration,
150  double targetSupportDuration)
151 {
152  constexpr double T = SAMPLING_PERIOD;
153 
154  unsigned nbStepsSoFar = 0;
155  nbInitSupportSteps_ = std::min(static_cast<unsigned>(std::round(initSupportDuration / T)), NB_STEPS - nbStepsSoFar);
156  nbStepsSoFar += nbInitSupportSteps_;
157  nbDoubleSupportSteps_ =
158  std::min(static_cast<unsigned>(std::round(doubleSupportDuration / T)), NB_STEPS - nbStepsSoFar);
159  nbStepsSoFar += nbDoubleSupportSteps_;
160  nbTargetSupportSteps_ =
161  std::min(static_cast<unsigned>(std::round(targetSupportDuration / T)), NB_STEPS - nbStepsSoFar);
162  nbStepsSoFar += nbTargetSupportSteps_;
163  if(nbTargetSupportSteps_ > 0) // full preview
164  {
165  nbNextDoubleSupportSteps_ = NB_STEPS - nbStepsSoFar; // always positive
166  }
167  for(long i = 0; i <= NB_STEPS; i++)
168  {
169  // SSP constraint is enforced at the very first step of DSP
170  if(i < nbInitSupportSteps_ || (0 < i && i == nbInitSupportSteps_))
171  {
172  indexToHrep_[i] = 0;
173  }
174  else if(i - nbInitSupportSteps_ < nbDoubleSupportSteps_)
175  {
176  indexToHrep_[i] = 1;
177  }
178  else if(nbTargetSupportSteps_ > 0)
179  {
180  if(i - nbInitSupportSteps_ - nbDoubleSupportSteps_ <= nbTargetSupportSteps_)
181  {
182  indexToHrep_[i] = 2;
183  }
184  else if(nbNextDoubleSupportSteps_ > 0)
185  {
186  indexToHrep_[i] = 3;
187  }
188  else // (nbNextDoubleSupportSteps_ == 0)
189  {
190  indexToHrep_[i] = 2;
191  }
192  }
193  else // (nbTargetSupportSteps_ == 0)
194  {
195  indexToHrep_[i] = 1;
196  }
197  }
198 }
199 
200 void ModelPredictiveControl::computeZMPRef()
201 {
202  zmpRef_.setZero();
203  Eigen::Vector2d p_0 = initContact_.anklePos(sole_).head<2>();
204  Eigen::Vector2d p_1 = targetContact_.anklePos(sole_).head<2>();
205  Eigen::Vector2d p_2 = nextContact_.anklePos(sole_).head<2>();
206  if(nbTargetSupportSteps_ < 1) // stop during first DSP
207  {
208  p_1 = 0.5 * (initContact_.anklePos(sole_) + targetContact_.anklePos(sole_)).head<2>();
209  }
210  for(long i = 0; i <= NB_STEPS; i++)
211  {
212  if(indexToHrep_[i] <= 1)
213  {
214  long j = i - nbInitSupportSteps_;
215  double x = (nbDoubleSupportSteps_ > 0) ? static_cast<double>(j) / nbDoubleSupportSteps_ : 0.;
216  x = clamp(x, 0., 1.);
217  zmpRef_.segment<2>(2 * i) = (1. - x) * p_0 + x * p_1;
218  }
219  else // (indexToHrep_[i] <= 3), which implies nbTargetSupportSteps_ > 0
220  {
221  long j = i - nbInitSupportSteps_ - nbDoubleSupportSteps_ - nbTargetSupportSteps_;
222  double x = (nbNextDoubleSupportSteps_ > 0) ? static_cast<double>(j) / nbNextDoubleSupportSteps_ : 0;
223  x = clamp(x, 0., 1.);
224  zmpRef_.segment<2>(2 * i) = (1. - x) * p_1 + x * p_2;
225  }
226  }
227 }
228 
229 void ModelPredictiveControl::updateTerminalConstraint()
230 {
231  Eigen::MatrixXd E_dcm = Eigen::MatrixXd::Zero(2, STATE_SIZE * (NB_STEPS + 1));
232  Eigen::MatrixXd E_zmp = Eigen::MatrixXd::Zero(2, STATE_SIZE * (NB_STEPS + 1));
233  if(nbTargetSupportSteps_ < 1) // half preview
234  {
235  unsigned i = nbInitSupportSteps_ + nbDoubleSupportSteps_;
236  E_dcm.block<2, 6>(0, 6 * i) = dcmFromState_;
237  E_zmp.block<2, 6>(0, 6 * i) = zmpFromState_;
238  }
239  else // full preview
240  {
241  E_dcm.rightCols<6>() = dcmFromState_;
242  E_zmp.rightCols<6>() = zmpFromState_;
243  }
244  Eigen::Vector2d dcmTarget = zmpRef_.tail<2>();
245  Eigen::Vector2d zmpTarget = zmpRef_.tail<2>();
246  termDCMCons_ = std::make_shared<copra::TrajectoryConstraint>(E_dcm, dcmTarget, /* isInequalityConstraint = */ false);
247  termZMPCons_ = std::make_shared<copra::TrajectoryConstraint>(E_zmp, zmpTarget, /* isInequalityConstraint = */ false);
248 }
249 
250 void ModelPredictiveControl::updateZMPConstraint()
251 {
252  hreps_[0] = initContact_.hrep();
253  hreps_[2] = targetContact_.hrep();
254  unsigned totalRows = 0;
255  for(long i = 0; i <= NB_STEPS; i++)
256  {
257  unsigned hrepIndex = indexToHrep_[i];
258  if(hrepIndex % 2 == 0)
259  {
260  const auto & hrep = hreps_[hrepIndex];
261  totalRows += static_cast<unsigned>(hrep.first.rows());
262  }
263  }
264  Eigen::MatrixXd A{totalRows, STATE_SIZE * (NB_STEPS + 1)};
265  Eigen::VectorXd b{totalRows};
266  A.setZero();
267  long nextRow = 0;
268  for(long i = 0; i <= NB_STEPS; i++)
269  {
270  unsigned hrepIndex = indexToHrep_[i];
271  if(hrepIndex % 2 == 0)
272  {
273  const auto & hrep = hreps_[indexToHrep_[i]];
274  unsigned consRows = static_cast<unsigned>(hrep.first.rows());
275  A.block(nextRow, STATE_SIZE * i, consRows, STATE_SIZE) = hrep.first * zmpFromState_;
276  b.segment(nextRow, consRows) = hrep.second;
277  nextRow += consRows;
278  }
279  }
280  zmpCons_ = std::make_shared<copra::TrajectoryConstraint>(A, b);
281 }
282 
283 void ModelPredictiveControl::updateJerkCost()
284 {
285  Eigen::Matrix2d jerkMat = Eigen::Matrix2d::Identity();
286  Eigen::Vector2d jerkVec = Eigen::Vector2d::Zero();
287  jerkCost_ = std::make_shared<copra::ControlCost>(jerkMat, jerkVec);
288  jerkCost_->weight(jerkWeight);
289 }
290 
291 void ModelPredictiveControl::updateVelCost()
292 {
293  velRef_.setZero();
294  const Eigen::Matrix3d & R_0 = initContact_.pose.rotation();
295  const Eigen::Matrix3d & R_1 = targetContact_.pose.rotation();
296  const Eigen::Matrix3d & R_2 = nextContact_.pose.rotation();
297  Eigen::Vector2d v_0 = initContact_.refVel.head<2>();
298  Eigen::Vector2d v_1 = targetContact_.refVel.head<2>();
299  Eigen::Vector2d v_2 = nextContact_.refVel.head<2>();
300  if(nbTargetSupportSteps_ < 1) // stop during first DSP
301  {
302  v_1 = {0., 0.};
303  }
304  Eigen::Matrix2d R;
305  Eigen::Vector2d v;
306  for(long i = 0; i <= NB_STEPS; i++)
307  {
308  if(indexToHrep_[i] <= 1)
309  {
310  double w = static_cast<double>(i) / (nbInitSupportSteps_ + nbDoubleSupportSteps_);
311  w = clamp(w, 0., 1.);
312  R = slerp(R_0, R_1, w).topLeftCorner<2, 2>();
313  v = (1. - w) * v_0 + w * v_1;
314  }
315  else // (indexToHrep_[i] <= 3), which implies nbTargetSupportSteps_ > 0
316  {
317  long i2 = i - nbInitSupportSteps_ - nbDoubleSupportSteps_; // >= 0
318  double w = static_cast<double>(i2) / (nbTargetSupportSteps_ + nbNextDoubleSupportSteps_);
319  w = clamp(w, 0., 1.);
320  R = slerp(R_1, R_2, w).topLeftCorner<2, 2>();
321  ;
322  v = (1. - w) * v_1 + w * v_2;
323  }
324  velCostMat_.block<2, STATE_SIZE>(2 * i, STATE_SIZE * i).block<2, 2>(0, 2) = R;
325  velRef_.segment<2>(2 * i) = R * v;
326  }
327  velCost_ = std::make_shared<copra::TrajectoryCost>(velCostMat_, velRef_);
328  velCost_->weights(velWeights);
329 }
330 
331 void ModelPredictiveControl::updateZMPCost()
332 {
333  zmpCost_ = std::make_shared<copra::TrajectoryCost>(zmpFromState_, zmpRef_);
334  zmpCost_->weight(zmpWeight);
335  zmpCost_->autoSpan(); // repeat zmpFromState
336 }
337 
339 {
340  using namespace std::chrono;
341  auto startTime = high_resolution_clock::now();
342 
343  computeZMPRef();
344 
345  previewSystem_->xInit(initState_);
346  updateTerminalConstraint();
347  updateZMPConstraint();
348  updateJerkCost();
349  updateVelCost();
350  updateZMPCost();
351 
352  copra::LMPC lmpc(previewSystem_, solver_);
353  lmpc.addConstraint(termDCMCons_);
354  lmpc.addConstraint(termZMPCons_);
355  lmpc.addConstraint(zmpCons_);
356  lmpc.addCost(jerkCost_);
357  lmpc.addCost(velCost_);
358  lmpc.addCost(zmpCost_);
359 
360  bool solutionFound = lmpc.solve();
361  if(solutionFound)
362  {
363  solution_.reset(new Preview(lmpc.trajectory(), lmpc.control()));
364  }
365  else
366  {
367  mc_rtc::log::error("Model predictive control problem has no solution");
368  solution_.reset(new Preview());
369  }
370 
371  auto endTime = high_resolution_clock::now();
372  buildAndSolveTime_ = 1000. * duration_cast<duration<double>>(endTime - startTime).count();
373  solveTime_ = 1000. * lmpc.solveTime();
374  return solutionFound;
375 }
376 
377 } // namespace lipm_walking
double clamp(double v, double vMin, double vMax)
Definition: clamp.h:47
Eigen::Matrix3d slerp(const Eigen::Matrix3d &from, const Eigen::Matrix3d &to, double t)
Definition: slerp.h:48
Eigen::Vector3d refVel
Definition: Contact.h:307
sva::PTransformd pose
Definition: Contact.h:314
Eigen::Vector3d anklePos(const Sole &sole) const
Definition: Contact.h:114
HrepXd hrep() const
Definition: Contact.h:266
void addLogEntries(mc_rtc::Logger &logger)
void addGUIElements(std::shared_ptr< mc_rtc::gui::StateBuilder > gui)
static constexpr EIGEN_MAKE_ALIGNED_OPERATOR_NEW double SAMPLING_PERIOD
void configure(const mc_rtc::Configuration &)
void phaseDurations(double initSupportDuration, double doubleSupportDuration, double targetSupportDuration)