Stabilizer.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 <chrono>
32 
33 namespace
34 {
35 const Eigen::Vector3d e_z = {0., 0., 1.};
36 }
37 
38 namespace lipm_walking
39 {
40 
41 // Repeat static constexpr declarations
42 // Fixes https://github.com/stephane-caron/lipm_walking_controller/issues/21
43 // See also https://stackoverflow.com/q/8016780
44 constexpr double Stabilizer::MAX_AVERAGE_DCM_ERROR;
45 constexpr double Stabilizer::MAX_COM_ADMITTANCE;
46 constexpr double Stabilizer::MAX_COP_ADMITTANCE;
47 constexpr double Stabilizer::MAX_DCM_D_GAIN;
48 constexpr double Stabilizer::MAX_DCM_I_GAIN;
49 constexpr double Stabilizer::MAX_DCM_P_GAIN;
50 constexpr double Stabilizer::MAX_DFZ_ADMITTANCE;
51 constexpr double Stabilizer::MAX_DFZ_DAMPING;
52 constexpr double Stabilizer::MAX_FDC_RX_VEL;
53 constexpr double Stabilizer::MAX_FDC_RY_VEL;
54 constexpr double Stabilizer::MAX_FDC_RZ_VEL;
55 constexpr double Stabilizer::MAX_ZMPCC_COM_OFFSET;
56 constexpr double Stabilizer::MIN_DSP_FZ;
57 
58 namespace
59 {
60 
61 inline Eigen::Vector2d vecFromError(const Eigen::Vector3d & error)
62 {
63  double x = -std::round(error.x() * 1000.);
64  double y = -std::round(error.y() * 1000.);
65  return Eigen::Vector2d{x, y};
66 }
67 
68 } // namespace
69 
70 Stabilizer::Stabilizer(const mc_rbdyn::Robot & controlRobot, const Pendulum & pendulum, double dt)
71 : dcmIntegrator_(dt, /* timeConstant = */ 5.), dcmDerivator_(dt, /* timeConstant = */ 1.), pendulum_(pendulum),
72  controlRobot_(controlRobot), dt_(dt), mass_(controlRobot.mass())
73 {
74 }
75 
76 void Stabilizer::addLogEntries(mc_rtc::Logger & logger)
77 {
78  logger.addLogEntry("stabilizer_contactState", [this]() -> double {
79  switch(contactState_)
80  {
82  return 0;
84  return 1;
86  return -1;
87  default:
88  return -3;
89  }
90  });
91  logger.addLogEntry("error_dcm_average", [this]() { return dcmAverageError_; });
92  logger.addLogEntry("error_dcm_pos", [this]() { return dcmError_; });
93  logger.addLogEntry("error_dcm_vel", [this]() { return dcmVelError_; });
94  logger.addLogEntry("error_dfz_force", [this]() { return dfzForceError_; });
95  logger.addLogEntry("error_dfz_height", [this]() { return dfzHeightError_; });
96  logger.addLogEntry("error_vdc", [this]() { return vdcHeightError_; });
97  logger.addLogEntry("error_zmp", [this]() { return zmpError_; });
98  logger.addLogEntry("perf_Stabilizer", [this]() { return runTime_; });
99  logger.addLogEntry("stabilizer_admittance_com", [this]() { return comAdmittance_; });
100  logger.addLogEntry("stabilizer_admittance_cop", [this]() { return copAdmittance_; });
101  logger.addLogEntry("stabilizer_admittance_dfz", [this]() { return dfzAdmittance_; });
102  logger.addLogEntry("stabilizer_dcmDerivator", [this]() { return dcmDerivator_.eval(); });
103  logger.addLogEntry("stabilizer_dcmDerivator_raw", [this]() { return dcmDerivator_.raw(); });
104  logger.addLogEntry("stabilizer_dcmDerivator_timeConstant", [this]() { return dcmDerivator_.timeConstant(); });
105  logger.addLogEntry("stabilizer_dcmGains_derivative", [this]() { return dcmDerivGain_; });
106  logger.addLogEntry("stabilizer_dcmGains_integral", [this]() { return dcmIntegralGain_; });
107  logger.addLogEntry("stabilizer_dcmGains_proportional", [this]() { return dcmPropGain_; });
108  logger.addLogEntry("stabilizer_dcmIntegrator", [this]() { return dcmIntegrator_.eval(); });
109  logger.addLogEntry("stabilizer_dcmIntegrator_timeConstant", [this]() { return dcmIntegrator_.timeConstant(); });
110  logger.addLogEntry("stabilizer_dfz_damping", [this]() { return dfzDamping_; });
111  logger.addLogEntry("stabilizer_fdqp_weights_ankleTorque",
112  [this]() { return std::pow(fdqpWeights_.ankleTorqueSqrt, 2); });
113  logger.addLogEntry("stabilizer_fdqp_weights_forceRatio",
114  [this]() { return std::pow(fdqpWeights_.forceRatioSqrt, 2); });
115  logger.addLogEntry("stabilizer_fdqp_weights_netWrench", [this]() { return std::pow(fdqpWeights_.netWrenchSqrt, 2); });
116  logger.addLogEntry("stabilizer_vdc_frequency", [this]() { return vdcFrequency_; });
117  logger.addLogEntry("stabilizer_vdc_stiffness", [this]() { return vdcStiffness_; });
118  logger.addLogEntry("stabilizer_wrench", [this]() { return distribWrench_; });
119  logger.addLogEntry("stabilizer_zmp", [this]() { return zmp(); });
120  logger.addLogEntry("stabilizer_zmpcc_comAccel", [this]() { return zmpccCoMAccel_; });
121  logger.addLogEntry("stabilizer_zmpcc_comOffset", [this]() { return zmpccCoMOffset_; });
122  logger.addLogEntry("stabilizer_zmpcc_comVel", [this]() { return zmpccCoMVel_; });
123  logger.addLogEntry("stabilizer_zmpcc_error", [this]() { return zmpccError_; });
124  logger.addLogEntry("stabilizer_zmpcc_leakRate", [this]() { return zmpccIntegrator_.rate(); });
125 }
126 
127 void Stabilizer::addGUIElements(std::shared_ptr<mc_rtc::gui::StateBuilder> gui)
128 {
129  using namespace mc_rtc::gui;
130  gui->addElement({"Stabilizer", "Main"}, Button("Disable stabilizer", [this]() { disable(); }),
131  Button("Reconfigure", [this]() { reconfigure(); }),
132  Button("Reset DCM integrator", [this]() { dcmIntegrator_.setZero(); }),
133  ArrayInput("Foot admittance", {"CoPx", "CoPy"},
134  [this]() -> Eigen::Vector2d {
135  return {copAdmittance_.x(), copAdmittance_.y()};
136  },
137  [this](const Eigen::Vector2d & a) {
138  copAdmittance_.x() = clamp(a(0), 0., MAX_COP_ADMITTANCE);
139  copAdmittance_.y() = clamp(a(1), 0., MAX_COP_ADMITTANCE);
140  }),
141  ArrayInput("Foot force difference", {"Admittance", "Damping"},
142  [this]() -> Eigen::Vector2d {
143  return {dfzAdmittance_, dfzDamping_};
144  },
145  [this](const Eigen::Vector2d & a) {
146  dfzAdmittance_ = clamp(a(0), 0., MAX_DFZ_ADMITTANCE);
147  dfzDamping_ = clamp(a(1), 0., MAX_DFZ_DAMPING);
148  }),
149  ArrayInput("DCM gains", {"Prop.", "Integral", "Deriv."},
150  [this]() -> Eigen::Vector3d {
151  return {dcmPropGain_, dcmIntegralGain_, dcmDerivGain_};
152  },
153  [this](const Eigen::Vector3d & gains) {
154  dcmPropGain_ = clamp(gains(0), 0., MAX_DCM_P_GAIN);
155  dcmIntegralGain_ = clamp(gains(1), 0., MAX_DCM_I_GAIN);
156  dcmDerivGain_ = clamp(gains(2), 0., MAX_DCM_D_GAIN);
157  }),
158  ArrayInput("DCM filters", {"Integrator T [s]", "Derivator T [s]"},
159  [this]() -> Eigen::Vector2d {
160  return {dcmIntegrator_.timeConstant(), dcmDerivator_.timeConstant()};
161  },
162  [this](const Eigen::Vector2d & T) {
163  dcmIntegrator_.timeConstant(T(0));
164  dcmDerivator_.timeConstant(T(1));
165  }));
166  gui->addElement({"Stabilizer", "Advanced"}, Button("Disable stabilizer", [this]() { disable(); }),
167  Button("Reconfigure", [this]() { reconfigure(); }),
168  Button("Reset CoM integrator", [this]() { zmpccIntegrator_.setZero(); }),
169  Checkbox("Apply CoM admittance only in double support?", [this]() { return zmpccOnlyDS_; },
170  [this]() { zmpccOnlyDS_ = !zmpccOnlyDS_; }),
171  ArrayInput("CoM admittance", {"Ax", "Ay"}, [this]() { return comAdmittance_; },
172  [this](const Eigen::Vector2d & a) {
173  comAdmittance_.x() = clamp(a.x(), 0., MAX_COM_ADMITTANCE);
174  comAdmittance_.y() = clamp(a.y(), 0., MAX_COM_ADMITTANCE);
175  }),
176  NumberInput("CoM integrator leak rate [Hz]", [this]() { return zmpccIntegrator_.rate(); },
177  [this](double T) { zmpccIntegrator_.rate(T); }),
178  ArrayInput("DCM pole placement", {"Pole1", "Pole2", "Pole3", "Lag [Hz]"},
179  [this]() -> Eigen::VectorXd { return polePlacement_; },
180  [this](const Eigen::VectorXd & polePlacement) {
181  double alpha = clamp(polePlacement(0), -20., -0.1);
182  double beta = clamp(polePlacement(1), -20., -0.1);
183  double gamma = clamp(polePlacement(2), -20., -0.1);
184  double lagFreq = clamp(polePlacement(3), 1., 200.);
185  polePlacement_ = {alpha, beta, gamma, lagFreq};
186 
187  double omega = pendulum_.omega();
188  double denom = pendulum_.omega() * lagFreq;
189  double T_integ = dcmIntegrator_.timeConstant();
190 
191  // Gains K_z for the ZMP feedback (Delta ZMP = K_z * Delta DCM)
192  double zmpPropGain =
193  (alpha * beta + beta * gamma + gamma * alpha + omega * lagFreq) / denom;
194  double zmpIntegralGain = -(alpha * beta * gamma) / denom;
195  double zmpDerivGain = -(alpha + beta + gamma + lagFreq - omega) / denom;
196 
197  // Our gains K are for closed-loop DCM (Delta dot(DCM) = -K * Delta DCM)
198  dcmPropGain_ = omega * (zmpPropGain - 1.);
199  dcmIntegralGain_ = omega * T_integ * zmpIntegralGain; // our integrator is an EMA
200  dcmDerivGain_ = omega * zmpDerivGain;
201  }),
202  ArrayInput("Vertical drift compensation", {"frequency", "stiffness"},
203  [this]() -> Eigen::Vector2d {
204  return {vdcFrequency_, vdcStiffness_};
205  },
206  [this](const Eigen::Vector2d & v) {
207  vdcFrequency_ = clamp(v(0), 0., 10.);
208  vdcStiffness_ = clamp(v(1), 0., 1e4);
209  }),
210  ArrayInput("Wrench distribution weights", {"Net wrench", "Ankle torques", "Force ratio"},
211  [this]() -> Eigen::Vector3d {
212  double netWrench = std::pow(fdqpWeights_.netWrenchSqrt, 2);
213  double ankleTorque = std::pow(fdqpWeights_.ankleTorqueSqrt, 2);
214  double forceRatio = std::pow(fdqpWeights_.forceRatioSqrt, 2);
215  return {netWrench, ankleTorque, forceRatio};
216  },
217  [this](const Eigen::Vector3d & weights) {
218  fdqpWeights_.netWrenchSqrt = std::sqrt(weights(0));
219  fdqpWeights_.ankleTorqueSqrt = std::sqrt(weights(1));
220  fdqpWeights_.forceRatioSqrt = std::sqrt(weights(2));
221  }));
222  gui->addElement({"Stabilizer", "Errors"}, Button("Disable stabilizer", [this]() { disable(); }),
223  Button("Reconfigure", [this]() { reconfigure(); }),
224  ArrayLabel("CoM offset [mm]", {"x", "y"}, [this]() { return vecFromError(zmpccCoMOffset_); }),
225  ArrayLabel("DCM average error [mm]", {"x", "y"}, [this]() { return vecFromError(dcmAverageError_); }),
226  ArrayLabel("DCM error [mm]", {"x", "y"}, [this]() { return vecFromError(dcmError_); }),
227  ArrayLabel("Foot force difference error", {"force [N]", "height [mm]"}, [this]() {
228  Eigen::Vector3d dfzError = {dfzForceError_ / 1000., dfzHeightError_, 0.};
229  return vecFromError(dfzError);
230  }));
231 }
232 
234 {
235  comAdmittance_.setZero();
236  copAdmittance_.setZero();
237  dcmDerivGain_ = 0.;
238  dcmIntegralGain_ = 0.;
239  dcmPropGain_ = 0.;
240  dfzAdmittance_ = 0.;
241  vdcFrequency_ = 0.;
242  vdcStiffness_ = 0.;
243 }
244 
245 void Stabilizer::configure(const mc_rtc::Configuration & config)
246 {
247  config_ = config;
248  reconfigure();
249 }
250 
252 {
253  fdqpWeights_.configure(config_("fdqp_weights"));
254  if(config_.has("admittance"))
255  {
256  auto admittance = config_("admittance");
257  comAdmittance_ = admittance("com");
258  copAdmittance_ = admittance("cop");
259  dfzAdmittance_ = admittance("dfz");
260  dfzDamping_ = admittance("dfz_damping");
261  }
262  if(config_.has("dcm_tracking"))
263  {
264  auto dcmTracking = config_("dcm_tracking");
265  dcmPropGain_ = dcmTracking("gains")("prop");
266  dcmIntegralGain_ = dcmTracking("gains")("integral");
267  dcmDerivGain_ = dcmTracking("gains")("deriv");
268  dcmDerivator_.timeConstant(dcmTracking("derivator_time_constant"));
269  dcmIntegrator_.timeConstant(dcmTracking("integrator_time_constant"));
270  }
271  if(config_.has("tasks"))
272  {
273  auto tasks = config_("tasks");
274  if(tasks.has("com"))
275  {
276  tasks("com")("active_joints", comActiveJoints_);
277  tasks("com")("stiffness", comStiffness_);
278  tasks("com")("weight", comWeight_);
279  }
280  if(tasks.has("contact"))
281  {
282  double d = tasks("contact")("damping");
283  double k = tasks("contact")("stiffness");
284  contactDamping_ = sva::MotionVecd({d, d, d}, {d, d, d});
285  contactStiffness_ = sva::MotionVecd({k, k, k}, {k, k, k});
286  tasks("contact")("stiffness", contactStiffness_);
287  tasks("contact")("weight", contactWeight_);
288  }
289  if(tasks.has("swing_foot"))
290  {
291  tasks("swing_foot")("stiffness", swingFootStiffness_);
292  tasks("swing_foot")("weight", swingFootWeight_);
293  }
294  }
295  if(config_.has("vdc"))
296  {
297  auto vdc = config_("vdc");
298  vdcFrequency_ = vdc("frequency");
299  vdcStiffness_ = vdc("stiffness");
300  }
301  if(config_.has("zmpcc"))
302  {
303  auto zmpcc = config_("zmpcc");
304  zmpccIntegrator_.rate(zmpcc("integrator_leak_rate"));
305  }
306 }
307 
308 void Stabilizer::reset(const mc_rbdyn::Robots & robots)
309 {
310  unsigned robotIndex = robots.robotIndex();
311 
312  comTask.reset(new mc_tasks::CoMTask(robots, robotIndex));
313  comTask->selectActiveJoints(comActiveJoints_);
314  comTask->setGains(comStiffness_, 2 * comStiffness_.cwiseSqrt());
315  comTask->weight(comWeight_);
316 
317  leftFootTask.reset(new mc_tasks::force::CoPTask("LeftFootCenter", robots, robotIndex));
318  rightFootTask.reset(new mc_tasks::force::CoPTask("RightFootCenter", robots, robotIndex));
321  setContact(leftFootTask, leftFootTask->surfacePose());
322  setContact(rightFootTask, rightFootTask->surfacePose());
323 
324  dcmDerivator_.setZero();
325  dcmIntegrator_.saturation(MAX_AVERAGE_DCM_ERROR);
326  dcmIntegrator_.setZero();
327  zmpccIntegrator_.saturation(MAX_ZMPCC_COM_OFFSET);
328  zmpccIntegrator_.setZero();
329 
330  Eigen::Vector3d staticForce = -mass_ * world::gravity;
331 
332  dcmAverageError_ = Eigen::Vector3d::Zero();
333  dcmError_ = Eigen::Vector3d::Zero();
334  dcmVelError_ = Eigen::Vector3d::Zero();
335  dfzForceError_ = 0.;
336  dfzHeightError_ = 0.;
337  distribWrench_ = {pendulum_.com().cross(staticForce), staticForce};
338  vdcHeightError_ = 0.;
339  zmpError_ = Eigen::Vector3d::Zero();
340  zmpccCoMAccel_ = Eigen::Vector3d::Zero();
341  zmpccCoMOffset_ = Eigen::Vector3d::Zero();
342  zmpccCoMVel_ = Eigen::Vector3d::Zero();
343  zmpccError_ = Eigen::Vector3d::Zero();
344 }
345 
346 void Stabilizer::checkGains()
347 {
348  clampInPlace(comAdmittance_.x(), 0., MAX_COM_ADMITTANCE, "CoM x-admittance");
349  clampInPlace(comAdmittance_.y(), 0., MAX_COM_ADMITTANCE, "CoM y-admittance");
350  clampInPlace(copAdmittance_.x(), 0., MAX_COP_ADMITTANCE, "CoP x-admittance");
351  clampInPlace(copAdmittance_.y(), 0., MAX_COP_ADMITTANCE, "CoP y-admittance");
352  clampInPlace(dcmDerivGain_, 0., MAX_DCM_D_GAIN, "DCM deriv x-gain");
353  clampInPlace(dcmIntegralGain_, 0., MAX_DCM_I_GAIN, "DCM integral x-gain");
354  clampInPlace(dcmPropGain_, 0., MAX_DCM_P_GAIN, "DCM prop x-gain");
355  clampInPlace(dfzAdmittance_, 0., MAX_DFZ_ADMITTANCE, "DFz admittance");
356 }
357 
358 void Stabilizer::addTasks(mc_solver::QPSolver & solver)
359 {
360  solver.addTask(comTask);
361  solver.addTask(leftFootTask);
362  solver.addTask(rightFootTask);
363 }
364 
365 void Stabilizer::removeTasks(mc_solver::QPSolver & solver)
366 {
367  solver.removeTask(comTask);
368  solver.removeTask(leftFootTask);
369  solver.removeTask(rightFootTask);
370 }
371 
372 void Stabilizer::setContact(std::shared_ptr<mc_tasks::force::CoPTask> footTask, const Contact & contact)
373 {
374  footTask->reset();
375  footTask->admittance(contactAdmittance());
376  footTask->setGains(contactStiffness_, contactDamping_);
377  footTask->targetPose(contact.pose);
378  footTask->weight(contactWeight_);
379  if(footTask->surface() == "LeftFootCenter")
380  {
381  leftFootContact = contact;
382  }
383  else if(footTask->surface() == "RightFootCenter")
384  {
385  rightFootContact = contact;
386  }
387  else
388  {
389  LOG_ERROR("Unknown foot surface: " << footTask->surface());
390  }
391 }
392 
393 void Stabilizer::setSwingFoot(std::shared_ptr<mc_tasks::force::CoPTask> footTask)
394 {
395  footTask->reset();
396  footTask->stiffness(swingFootStiffness_); // sets damping as well
397  footTask->weight(swingFootWeight_);
398 }
399 
400 bool Stabilizer::detectTouchdown(const std::shared_ptr<mc_tasks::force::CoPTask> footTask, const Contact & contact)
401 {
402  const sva::PTransformd X_0_s = footTask->surfacePose();
403  const sva::PTransformd & X_0_c = contact.pose;
404  sva::PTransformd X_c_s = X_0_s * X_0_c.inv();
405  double xDist = std::abs(X_c_s.translation().x());
406  double yDist = std::abs(X_c_s.translation().y());
407  double zDist = std::abs(X_c_s.translation().z());
408  double Fz = footTask->measuredWrench().force().z();
409  return (xDist < 0.03 && yDist < 0.03 && zDist < 0.03 && Fz > 50.);
410 }
411 
412 void Stabilizer::seekTouchdown(std::shared_ptr<mc_tasks::force::CoPTask> footTask)
413 {
414  constexpr double MAX_VEL = 0.01; // [m] / [s]
415  constexpr double TOUCHDOWN_FORCE = 50.; // [N]
416  constexpr double DESIRED_AFZ = MAX_VEL / TOUCHDOWN_FORCE;
417  if(footTask->measuredWrench().force().z() < TOUCHDOWN_FORCE)
418  {
419  auto a = footTask->admittance();
420  double AFz = clamp(DESIRED_AFZ, 0., 1e-2, "Contact seeking admittance");
421  footTask->admittance({a.couple(), {a.force().x(), a.force().y(), AFz}});
422  footTask->targetForce({0., 0., TOUCHDOWN_FORCE});
423  }
424 }
425 
426 void Stabilizer::setSupportFootGains()
427 {
428  sva::MotionVecd vdcContactStiffness = {contactStiffness_.angular(), {vdcStiffness_, vdcStiffness_, vdcStiffness_}};
429  switch(contactState_)
430  {
432  leftFootTask->admittance(contactAdmittance());
433  leftFootTask->setGains(contactStiffness_, contactDamping_);
434  rightFootTask->admittance(contactAdmittance());
435  rightFootTask->setGains(contactStiffness_, contactDamping_);
436  break;
438  leftFootTask->admittance(contactAdmittance());
439  leftFootTask->setGains(vdcContactStiffness, contactDamping_);
440  break;
442  rightFootTask->admittance(contactAdmittance());
443  rightFootTask->setGains(vdcContactStiffness, contactDamping_);
444  break;
445  }
446 }
447 
448 void Stabilizer::checkInTheAir()
449 {
450  double LFz = leftFootTask->measuredWrench().force().z();
451  double RFz = rightFootTask->measuredWrench().force().z();
452  inTheAir_ = (LFz < MIN_DSP_FZ && RFz < MIN_DSP_FZ);
453 }
454 
455 void Stabilizer::updateZMPFrame()
456 {
457  const sva::PTransformd & X_0_lc = leftFootContact.pose;
458  const sva::PTransformd & X_0_rc = rightFootContact.pose;
459  switch(contactState_)
460  {
462  zmpFrame_ = sva::interpolate(X_0_lc, X_0_rc, 0.5);
463  break;
465  zmpFrame_ = X_0_lc;
466  break;
468  zmpFrame_ = X_0_rc;
469  break;
470  }
471  measuredZMP_ = computeZMP(measuredWrench_);
472 }
473 
474 Eigen::Vector3d Stabilizer::computeZMP(const sva::ForceVecd & wrench) const
475 {
476  Eigen::Vector3d n = zmpFrame_.rotation().row(2);
477  Eigen::Vector3d p = zmpFrame_.translation();
478  const Eigen::Vector3d & force = wrench.force();
479  double normalForce = n.dot(force);
480  if(normalForce < 1.)
481  {
482  double lambda = std::pow(pendulum_.omega(), 2);
483  return measuredCoM_ + world::gravity / lambda; // default for logging
484  }
485  const Eigen::Vector3d & moment_0 = wrench.couple();
486  Eigen::Vector3d moment_p = moment_0 - p.cross(force);
487  return p + n.cross(moment_p) / normalForce;
488 }
489 
491 {
492  auto startTime = std::chrono::high_resolution_clock::now();
493  checkGains();
494  checkInTheAir();
495  setSupportFootGains();
496  updateZMPFrame();
497  auto desiredWrench = computeDesiredWrench();
498  switch(contactState_)
499  {
501  distributeWrench(desiredWrench);
502  break;
504  saturateWrench(desiredWrench, leftFootTask);
505  rightFootTask->setZeroTargetWrench();
506  break;
508  saturateWrench(desiredWrench, rightFootTask);
509  leftFootTask->setZeroTargetWrench();
510  break;
511  }
512  updateCoMTaskZMPCC();
513  updateFootForceDifferenceControl();
514  auto endTime = std::chrono::high_resolution_clock::now();
515  runTime_ = std::chrono::duration<double, std::milli>(endTime - startTime).count();
516 }
517 
518 void Stabilizer::updateState(const Eigen::Vector3d & com,
519  const Eigen::Vector3d & comd,
520  const sva::ForceVecd & wrench,
521  double leftFootRatio)
522 {
523  leftFootRatio_ = leftFootRatio;
524  measuredCoM_ = com;
525  measuredCoMd_ = comd;
526  measuredWrench_ = wrench;
527 }
528 
529 sva::ForceVecd Stabilizer::computeDesiredWrench()
530 {
531  double omega = pendulum_.omega();
532  Eigen::Vector3d comError = pendulum_.com() - measuredCoM_;
533  Eigen::Vector3d comdError = pendulum_.comd() - measuredCoMd_;
534  dcmError_ = comError + comdError / omega;
535  dcmError_.z() = 0.;
536 
537  if(inTheAir_)
538  {
539  dcmDerivator_.setZero();
540  dcmIntegrator_.append(Eigen::Vector3d::Zero());
541  }
542  else
543  {
544  zmpError_ = pendulum_.zmp() - measuredZMP_; // XXX: both in same plane?
545  zmpError_.z() = 0.;
546  dcmDerivator_.update(omega * (dcmError_ - zmpError_));
547  dcmIntegrator_.append(dcmError_);
548  }
549  dcmAverageError_ = dcmIntegrator_.eval();
550  dcmVelError_ = dcmDerivator_.eval();
551 
552  Eigen::Vector3d desiredCoMAccel = pendulum_.comdd();
553  desiredCoMAccel += omega * (dcmPropGain_ * dcmError_ + comdError);
554  desiredCoMAccel += omega * dcmIntegralGain_ * dcmAverageError_;
555  desiredCoMAccel += omega * dcmDerivGain_ * dcmVelError_;
556  auto desiredForce = mass_ * (desiredCoMAccel - world::gravity);
557 
558  // Previous implementation (up to v1.3):
559  // return {pendulum_.com().cross(desiredForce), desiredForce};
560  // See https://github.com/stephane-caron/lipm_walking_controller/issues/28
561 
562  return {measuredCoM_.cross(desiredForce), desiredForce};
563 }
564 
565 void Stabilizer::distributeWrench(const sva::ForceVecd & desiredWrench)
566 {
567  // Variables
568  // ---------
569  // x = [w_l_0 w_r_0] where
570  // w_l_0: spatial force vector of left foot contact in inertial frame
571  // w_r_0: spatial force vector of right foot contact in inertial frame
572  //
573  // Objective
574  // ---------
575  // Weighted minimization of the following tasks:
576  // w_l_0 + w_r_0 == desiredWrench -- realize desired contact wrench
577  // w_l_lankle == 0 -- minimize left foot ankle torque (anisotropic weight)
578  // w_r_rankle == 0 -- minimize right foot ankle torque (anisotropic weight)
579  // (1 - lfr) * w_l_lc.z() == lfr * w_r_rc.z()
580  //
581  // Constraints
582  // -----------
583  // CWC X_0_lc* w_l_0 <= 0 -- left foot wrench within contact wrench cone
584  // CWC X_0_rc* w_r_0 <= 0 -- right foot wrench within contact wrench cone
585  // (X_0_lc* w_l_0).z() > MIN_DSP_FZ -- minimum left foot contact force
586  // (X_0_rc* w_r_0).z() > MIN_DSP_FZ -- minimum right foot contact force
587 
588  const sva::PTransformd & X_0_lc = leftFootContact.pose;
589  const sva::PTransformd & X_0_rc = rightFootContact.pose;
590  sva::PTransformd X_0_lankle = leftFootContact.anklePose(sole_);
591  sva::PTransformd X_0_rankle = rightFootContact.anklePose(sole_);
592 
593  constexpr unsigned NB_VAR = 6 + 6;
594  constexpr unsigned COST_DIM = 6 + NB_VAR + 1;
595  Eigen::MatrixXd A;
596  Eigen::VectorXd b;
597  A.setZero(COST_DIM, NB_VAR);
598  b.setZero(COST_DIM);
599 
600  // |w_l_0 + w_r_0 - desiredWrench|^2
601  auto A_net = A.block<6, 12>(0, 0);
602  auto b_net = b.segment<6>(0);
603  A_net.block<6, 6>(0, 0) = Eigen::Matrix6d::Identity();
604  A_net.block<6, 6>(0, 6) = Eigen::Matrix6d::Identity();
605  b_net = desiredWrench.vector();
606 
607  // |ankle torques|^2
608  auto A_lankle = A.block<6, 6>(6, 0);
609  auto A_rankle = A.block<6, 6>(12, 6);
610  // anisotropic weights: taux, tauy, tauz, fx, fy, fz;
611  A_lankle.diagonal() << 1., 1., 1e-4, 1e-3, 1e-3, 1e-4;
612  A_rankle.diagonal() << 1., 1., 1e-4, 1e-3, 1e-3, 1e-4;
613  A_lankle *= X_0_lankle.dualMatrix();
614  A_rankle *= X_0_rankle.dualMatrix();
615 
616  // |(1 - lfr) * w_l_lc.force().z() - lfr * w_r_rc.force().z()|^2
617  double lfr = leftFootRatio_;
618  auto A_fratio = A.block<1, 12>(18, 0);
619  A_fratio.block<1, 6>(0, 0) = (1 - lfr) * X_0_lc.dualMatrix().bottomRows<1>();
620  A_fratio.block<1, 6>(0, 6) = -lfr * X_0_rc.dualMatrix().bottomRows<1>();
621 
622  // Apply weights
623  A_net *= fdqpWeights_.netWrenchSqrt;
624  b_net *= fdqpWeights_.netWrenchSqrt;
625  A_lankle *= fdqpWeights_.ankleTorqueSqrt;
626  A_rankle *= fdqpWeights_.ankleTorqueSqrt;
627  // b_lankle = 0
628  // b_rankle = 0
629  A_fratio *= fdqpWeights_.forceRatioSqrt;
630  // b_fratio = 0
631 
632  Eigen::MatrixXd Q = A.transpose() * A;
633  Eigen::VectorXd c = -A.transpose() * b;
634 
635  constexpr unsigned NB_CONS = 16 + 16 + 2;
636  Eigen::Matrix<double, NB_CONS, NB_VAR> A_ineq;
637  Eigen::VectorXd b_ineq;
638  A_ineq.setZero(NB_CONS, NB_VAR);
639  b_ineq.setZero(NB_CONS);
640  // CWC * w_l_lc <= 0
641  A_ineq.block<16, 6>(0, 0) = wrenchFaceMatrix_ * X_0_lc.dualMatrix();
642  // b_ineq.segment<16>(0) is already zero
643  // CWC * w_r_rc <= 0
644  A_ineq.block<16, 6>(16, 6) = wrenchFaceMatrix_ * X_0_rc.dualMatrix();
645  // b_ineq.segment<16>(16) is already zero
646  // w_l_lc.force().z() >= MIN_DSP_FZ
647  A_ineq.block<1, 6>(32, 0) = -X_0_lc.dualMatrix().bottomRows<1>();
648  b_ineq(32) = -MIN_DSP_FZ;
649  // w_r_rc.force().z() >= MIN_DSP_FZ
650  A_ineq.block<1, 6>(33, 6) = -X_0_rc.dualMatrix().bottomRows<1>();
651  b_ineq(33) = -MIN_DSP_FZ;
652 
653  qpSolver_.problem(NB_VAR, 0, NB_CONS);
654  Eigen::MatrixXd A_eq(0, 0);
655  Eigen::VectorXd b_eq;
656  b_eq.resize(0);
657 
658  bool solutionFound = qpSolver_.solve(Q, c, A_eq, b_eq, A_ineq, b_ineq, /* isDecomp = */ false);
659  if(!solutionFound)
660  {
661  LOG_ERROR("DS force distribution QP: solver found no solution");
662  return;
663  }
664 
665  Eigen::VectorXd x = qpSolver_.result();
666  sva::ForceVecd w_l_0(x.segment<3>(0), x.segment<3>(3));
667  sva::ForceVecd w_r_0(x.segment<3>(6), x.segment<3>(9));
668  distribWrench_ = w_l_0 + w_r_0;
669 
670  sva::ForceVecd w_l_lc = X_0_lc.dualMul(w_l_0);
671  sva::ForceVecd w_r_rc = X_0_rc.dualMul(w_r_0);
672  Eigen::Vector2d leftCoP = (e_z.cross(w_l_lc.couple()) / w_l_lc.force()(2)).head<2>();
673  Eigen::Vector2d rightCoP = (e_z.cross(w_r_rc.couple()) / w_r_rc.force()(2)).head<2>();
674  leftFootTask->targetCoP(leftCoP);
675  leftFootTask->targetForce(w_l_lc.force());
676  rightFootTask->targetCoP(rightCoP);
677  rightFootTask->targetForce(w_r_rc.force());
678 }
679 
680 void Stabilizer::saturateWrench(const sva::ForceVecd & desiredWrench,
681  std::shared_ptr<mc_tasks::force::CoPTask> & footTask)
682 {
683  constexpr unsigned NB_CONS = 16;
684  constexpr unsigned NB_VAR = 6;
685 
686  // Variables
687  // ---------
688  // x = [w_0] where
689  // w_0: spatial force vector of foot contact in inertial frame
690  //
691  // Objective
692  // ---------
693  // weighted minimization of |w_c - X_0_c* desiredWrench|^2
694  //
695  // Constraints
696  // -----------
697  // F X_0_c* w_0 <= 0 -- contact stability
698 
699  const sva::PTransformd & X_0_c = footTask->targetPose();
700 
701  Eigen::Matrix6d Q = Eigen::Matrix6d::Identity();
702  Eigen::Vector6d c = -desiredWrench.vector();
703 
704  Eigen::MatrixXd A_ineq = wrenchFaceMatrix_ * X_0_c.dualMatrix();
705  Eigen::VectorXd b_ineq;
706  b_ineq.setZero(NB_CONS);
707 
708  qpSolver_.problem(NB_VAR, 0, NB_CONS);
709  Eigen::MatrixXd A_eq(0, 0);
710  Eigen::VectorXd b_eq;
711  b_eq.resize(0);
712 
713  bool solutionFound = qpSolver_.solve(Q, c, A_eq, b_eq, A_ineq, b_ineq, /* isDecomp = */ true);
714  if(!solutionFound)
715  {
716  LOG_ERROR("SS force distribution QP: solver found no solution");
717  return;
718  }
719 
720  Eigen::VectorXd x = qpSolver_.result();
721  sva::ForceVecd w_0(x.head<3>(), x.tail<3>());
722  sva::ForceVecd w_c = X_0_c.dualMul(w_0);
723  Eigen::Vector2d cop = (e_z.cross(w_c.couple()) / w_c.force()(2)).head<2>();
724  footTask->targetCoP(cop);
725  footTask->targetForce(w_c.force());
726  distribWrench_ = w_0;
727 }
728 
729 void Stabilizer::updateCoMTaskZMPCC()
730 {
731  if(zmpccOnlyDS_ && contactState_ != ContactState::DoubleSupport)
732  {
733  zmpccCoMAccel_.setZero();
734  zmpccCoMVel_.setZero();
735  zmpccIntegrator_.add(Eigen::Vector3d::Zero(), dt_); // leak to zero
736  }
737  else
738  {
739  auto distribZMP = computeZMP(distribWrench_);
740  zmpccError_ = distribZMP - measuredZMP_;
741  const Eigen::Matrix3d & R_0_c = zmpFrame_.rotation();
742  const Eigen::Transpose<const Eigen::Matrix3d> R_c_0 = R_0_c.transpose();
743  Eigen::Vector3d comAdmittance = {comAdmittance_.x(), comAdmittance_.y(), 0.};
744  Eigen::Vector3d newVel = -R_c_0 * comAdmittance.cwiseProduct(R_0_c * zmpccError_);
745  Eigen::Vector3d newAccel = (newVel - zmpccCoMVel_) / dt_;
746  zmpccIntegrator_.add(newVel, dt_);
747  zmpccCoMAccel_ = newAccel;
748  zmpccCoMVel_ = newVel;
749  }
750  zmpccCoMOffset_ = zmpccIntegrator_.eval();
751  comTask->com(pendulum_.com() + zmpccCoMOffset_);
752  comTask->refVel(pendulum_.comd() + zmpccCoMVel_);
753  comTask->refAccel(pendulum_.comdd() + zmpccCoMAccel_);
754 }
755 
756 void Stabilizer::updateFootForceDifferenceControl()
757 {
758  if(contactState_ != ContactState::DoubleSupport || inTheAir_)
759  {
760  dfzForceError_ = 0.;
761  dfzHeightError_ = 0.;
762  vdcHeightError_ = 0.;
763  leftFootTask->refVelB({{0., 0., 0.}, {0., 0., 0.}});
764  rightFootTask->refVelB({{0., 0., 0.}, {0., 0., 0.}});
765  return;
766  }
767 
768  double LFz_d = leftFootTask->targetWrench().force().z();
769  double RFz_d = rightFootTask->targetWrench().force().z();
770  double LFz = leftFootTask->measuredWrench().force().z();
771  double RFz = rightFootTask->measuredWrench().force().z();
772  dfzForceError_ = (LFz_d - RFz_d) - (LFz - RFz);
773 
774  double LTz_d = leftFootTask->targetPose().translation().z();
775  double RTz_d = rightFootTask->targetPose().translation().z();
776  double LTz = leftFootTask->surfacePose().translation().z();
777  double RTz = rightFootTask->surfacePose().translation().z();
778  dfzHeightError_ = (LTz_d - RTz_d) - (LTz - RTz);
779  vdcHeightError_ = (LTz_d + RTz_d) - (LTz + RTz);
780 
781  // The ``dfzDamping`` gain is used for vertical foot force damping. This is
782  // an optional feature that we didn't use in practice with HRP-4 or HRP-2Kai.
783  // See https://github.com/stephane-caron/lipm_walking_controller/issues/50
784  double dz_ctrl = dfzAdmittance_ * dfzForceError_ - dfzDamping_ * dfzHeightError_;
785  double dz_vdc = vdcFrequency_ * vdcHeightError_;
786  sva::MotionVecd velF = {{0., 0., 0.}, {0., 0., dz_ctrl}};
787  sva::MotionVecd velT = {{0., 0., 0.}, {0., 0., dz_vdc}};
788  leftFootTask->refVelB(0.5 * (velT - velF));
789  rightFootTask->refVelB(0.5 * (velT + velF));
790 }
791 
792 } // namespace lipm_walking
static constexpr double MAX_DFZ_DAMPING
Maximum normalized damping in [Hz] for foot force difference control.
Definition: Stabilizer.h:70
Contact rightFootContact
Current right foot contact.
Definition: Stabilizer.h:388
double clamp(double v, double vMin, double vMax)
Clamp a value in a given interval.
Definition: clamp.h:47
static constexpr double MAX_DCM_I_GAIN
Maximum DCM average integral gain in [Hz].
Definition: Stabilizer.h:66
Stabilizer(const mc_rbdyn::Robot &robot, const Pendulum &pendulum, double dt)
Initialize stabilizer.
Definition: Stabilizer.cpp:70
double timeConstant() const
Get time constant of the filter.
void disable()
Disable all feedback components.
Definition: Stabilizer.cpp:233
static constexpr double MIN_DSP_FZ
Minimum normal contact force in [N] for DSP, used to avoid low-force targets when close to contact sw...
Definition: Stabilizer.h:79
void seekTouchdown(std::shared_ptr< mc_tasks::force::CoPTask > footTask)
Configure foot task for contact seeking.
Definition: Stabilizer.cpp:412
void addLogEntries(mc_rtc::Logger &logger)
Log stabilizer entries.
Definition: Stabilizer.cpp:76
static constexpr double MAX_FDC_RZ_VEL
Maximum z-axis angular velocity in [rad] / [s] for foot damping control.
Definition: Stabilizer.h:76
const Eigen::Vector3d & raw() const
Get raw value of input signal.
const Eigen::Vector3d & eval() const
Evaluate the smoothed statistic.
void addTasks(mc_solver::QPSolver &solver)
Add tasks to QP solver.
Definition: Stabilizer.cpp:358
void reconfigure()
Apply stored configuration.
Definition: Stabilizer.cpp:251
State of the inverted pendulum model.
Definition: Pendulum.h:40
sva::PTransformd anklePose(const Sole &sole) const
Get frame rooted at the ankle.
Definition: Contact.h:143
Contact leftFootContact
Current left foot contact.
Definition: Stabilizer.h:387
static constexpr double MAX_FDC_RX_VEL
Maximum x-axis angular velocity in [rad] / [s] for foot damping control.
Definition: Stabilizer.h:72
void run()
Update QP task targets.
Definition: Stabilizer.cpp:490
double rate() const
Get leak rate.
static EIGEN_MAKE_ALIGNED_OPERATOR_NEW constexpr double MAX_AVERAGE_DCM_ERROR
Maximum average (integral) DCM error in [m].
Definition: Stabilizer.h:62
void reset(const mc_rbdyn::Robots &robots)
Reset CoM and foot CoP tasks.
Definition: Stabilizer.cpp:308
bool detectTouchdown(const std::shared_ptr< mc_tasks::force::CoPTask > footTask, const Contact &contact)
Detect foot touchdown based on both force and distance.
Definition: Stabilizer.cpp:400
Eigen::Vector3d computeZMP(const sva::ForceVecd &wrench) const
Compute ZMP of a wrench in the output frame.
Definition: Stabilizer.cpp:474
static constexpr double MAX_DFZ_ADMITTANCE
Maximum admittance in [s] / [kg] for foot force difference control.
Definition: Stabilizer.h:68
static constexpr double MAX_ZMPCC_COM_OFFSET
Maximum CoM offset due to admittance control in [m].
Definition: Stabilizer.h:78
static constexpr double MAX_DCM_D_GAIN
Maximum DCM derivative gain (no unit)
Definition: Stabilizer.h:65
static constexpr double MAX_FDC_RY_VEL
Maximum y-axis angular velocity in [rad] / [s] for foot damping control.
Definition: Stabilizer.h:74
std::shared_ptr< mc_tasks::CoMTask > comTask
CoM position task.
Definition: Stabilizer.h:389
void addGUIElements(std::shared_ptr< mc_rtc::gui::StateBuilder > gui)
Add GUI panel.
Definition: Stabilizer.cpp:127
std::shared_ptr< mc_tasks::force::CoPTask > leftFootTask
Left foot hybrid position/force control task.
Definition: Stabilizer.h:390
static constexpr double MAX_COM_ADMITTANCE
Maximum admittance for CoM admittance control.
Definition: Stabilizer.h:63
std::shared_ptr< mc_tasks::force::CoPTask > rightFootTask
Right foot hybrid position/force control task.
Definition: Stabilizer.h:391
static constexpr double MAX_COP_ADMITTANCE
Maximum CoP admittance for foot damping control.
Definition: Stabilizer.h:64
void clampInPlace(double &v, double vMin, double vMax)
Clamp a value in a given interval.
Definition: clamp.h:72
const Eigen::Vector3d gravity
Definition: world.h:40
Eigen::Vector3d zmp() const
ZMP target after wrench distribution.
Definition: Stabilizer.h:279
void setContact(std::shared_ptr< mc_tasks::force::CoPTask > footTask, const Contact &contact)
Configure foot task for contact at a given location.
Definition: Stabilizer.cpp:372
const Eigen::Vector3d & eval() const
Get output value where the stationary offset has been filtered.
void updateState(const Eigen::Vector3d &com, const Eigen::Vector3d &comd, const sva::ForceVecd &wrench, double leftFootRatio)
Update real-robot state.
Definition: Stabilizer.cpp:518
Main controller namespace.
Definition: build.dox:1
void removeTasks(mc_solver::QPSolver &solver)
Remove tasks from QP solver.
Definition: Stabilizer.cpp:365
void setSwingFoot(std::shared_ptr< mc_tasks::force::CoPTask > footTask)
Configure foot task for swinging.
Definition: Stabilizer.cpp:393
Contacts wrap foot frames with extra info from the footstep plan.
Definition: Contact.h:58
double timeConstant() const
Get time constant of the filter.
static constexpr double MAX_DCM_P_GAIN
Maximum DCM proportional gain in [Hz].
Definition: Stabilizer.h:67
void configure(const mc_rtc::Configuration &)
Read configuration from dictionary.
Definition: Stabilizer.cpp:245
sva::PTransformd pose
Plücker transform of the contact in the inertial frame.
Definition: Contact.h:321