Acceleration Control GHC Interactive#

Introduction#

Acceleration GHC Interactive Example

In this interactive example, the robot end-effector tracks a mocap target while the elbow oscillates along the Y-axis. The Generalized Hierarchical Control (GHC) strategy ensures that the secondary elbow task is projected into the null-space of the main end-effector task, allowing the elbow to move without disturbing the tracking accuracy of the robot tip.

The controller is built from:

  • a main Cartesian acceleration task for end-effector tracking,

  • a secondary Cartesian acceleration task on panda_link4,

  • a regularization task,

  • joint configuration and joint velocity constraints.

The example uses the interactive MuJoCo scene, so the target can be moved directly in the viewer.

This resolution strategy is based on the Generalized Hierarchical Control method published in this paper. Most existing techniques to handle strict task priorities in hierarchical control are based on null-space projectors or a sequence of quadratic programs; whereas non strict task priorities are usually handled by optimization based on a weighting strategy. This approach handles both strict and non-strict priorities of an arbitrary number of tasks, and achieves multiple priority rearrangements simultaneously. It relies on a generalized projector, which makes it possible to completely project a task into the null-space of a set of tasks, while partially projecting it into the null-space of some other tasks. Priority transitions are achieved by smooth variations of the generalized projector.

Formulation#

The following example solves a qp problem expressed at the torque level such that:

\[\begin{split}\begin{aligned} X^T &= [\ddot{q}_1^T, \ldots, \ddot{q}_{n_t}^T] \\ X^{opt} &= \underset{X}{\mathrm{argmin}} \sum_{i=1}^{n_t} \left\| J_i S_{q_i} X + \dot{J}_i \dot{q} - a_i^* \right\|^2 + \left\| X \right\|^2 \\ &\textrm{s.t.} \\ &\quad 2 \left( \frac{q_{min} - q_k}{\Delta T^2} - \frac{\dot{q}_k}{\Delta T} \right) \leq PX \leq 2 \left( \frac{q_{max} - q_k}{\Delta T^2} - \frac{\dot{q}_k}{\Delta T} \right)\\ &\quad \frac{\dot{q}_{min} - \dot{q}_k}{\Delta T} \leq PX \leq \frac{\dot{q}_{max} - \dot{q}_k}{\Delta T} \\ P &= [P_1, \ldots, P_{n_t}], \quad \ddot{q} = PX = \sum_i P_i \ddot{q}_i \\ \end{aligned}\end{split}\]

Where P is the concatenation of the generalized projectors of each task.

For the i-th task we compute its projector \(P_i\) using its corresponding \(\alpha_i\) input: \(P_i(\alpha_i) = I_n − B_i(J_{s_i})^T \alpha_{i,r}^s(\alpha_i ,origin) B_i (J_{s_i} )\)

Where \(J_{s_i}\) is a matrix made of the rows of J sorted in descending order with respect to the values of the diagonal elements in \(\alpha_i\) .

The values in \(\alpha_i\) are sorted accordingly, leading to \(\alpha_{i}^s\).

\(B_i(J_{s_i})\) is a \(r\) x \(n\) matrix , (where \(r = rank(J_{s_i} )\)), such that the rows of \(B_i(J_{s_i})\) form an orthonormal basis of the joint space.

Finally, \(\alpha_{i,r}^s\) is a diagonal matrix of degree r. The vector \(origin\) is a vector of the row indexes of \(J_{s_i}\) selected during the construction of the orthonormal basis \(B_i\)

The robot main tasks consists in keeping the end effector at a defined pose in Cartesian space. A second task is added to control the position on the y axis of the elbow of the robot. The mujoco library is used to simulate the robot behaviour.

Full Code

You can find the source code of this example here.

  1// Copyright 2021 DeepMind Technologies Limited
  2//
  3// Licensed under the Apache License, Version 2.0 (the "License");
  4// you may not use this file except in compliance with the License.
  5// You may obtain a copy of the License at
  6//
  7//     http://www.apache.org/licenses/LICENSE-2.0
  8//
  9// Unless required by applicable law or agreed to in writing, software
 10// distributed under the License is distributed on an "AS IS" BASIS,
 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12// See the License for the specific language governing permissions and
 13// limitations under the License.
 14
 15#include "mujoco/mujoco_sim.h"
 16#include "Qontrol/Qontrol.hpp"
 17
 18using namespace Qontrol;
 19
 20class MujocoQontrol : public MujocoSim 
 21{
 22public:
 23//------------------------------------------- simulation -------------------------------------------
 24std::shared_ptr<Model::RobotModel> model;
 25std::shared_ptr<JointAccelerationProblem> acceleration_problem;
 26std::shared_ptr<Task::CartesianAcceleration<ControlOutput::JointAcceleration>> main_task;
 27std::shared_ptr<Task::CartesianAcceleration<ControlOutput::JointAcceleration>> elbow_task;
 28std::shared_ptr<Qontrol::Task::GenericTask> regularization_task;
 29
 30Qontrol::RobotState robot_state;
 31std::string resource_path;
 32bool initialized = false;
 33double t = 0;
 34
 35void initController() override
 36{
 37  model =
 38      Model::RobotModel::loadModelFromFile(resource_path+"robot.urdf");
 39  
 40  acceleration_problem = std::make_shared<Qontrol::JointAccelerationProblem>(model,ResolutionStrategy::Generalized);
 41  //tasks  (weights are useless)
 42  main_task = acceleration_problem->task_set->add<Task::CartesianAcceleration>("MainTask"); 
 43  elbow_task = acceleration_problem->task_set->add<Task::CartesianAcceleration>("CommunicationTask");
 44  Eigen::MatrixXd selection_matrix = Eigen::MatrixXd::Zero(6,6);
 45  selection_matrix(1,1) = 1.0; //y axis control only
 46  elbow_task->setControledFrameName("panda_link4");
 47  elbow_task->setReferenceFrame(ReferenceFrame::LOCAL_WORLD_ALIGNED);
 48  elbow_task->setSelectionMatrix(selection_matrix);
 49  regularization_task = acceleration_problem->task_set->add("regularizationTask",14,14,1.0); //regularisation dimension = n_dof*n_tasks (other than regularisation)
 50  //constraints
 51  auto joint_configuration_constraint = acceleration_problem->constraint_set->add<Constraint::JointConfiguration>("JointConfigurationConstraint");
 52  auto joint_velocity_constraint = acceleration_problem->constraint_set->add<Constraint::JointVelocity>("JointVelocityConstraint");
 53
 54  joint_configuration_constraint->setHorizon(30);
 55  joint_velocity_constraint->setHorizon(30);
 56
 57  //init() -> resizes properly some variables / to be called once the tasks and constraints are defined
 58  acceleration_problem->init();
 59  std::cout << "Model loaded with " << model->getNrOfDegreesOfFreedom() << " DoF" << std::endl;
 60
 61  mju_copy(d->qpos, m->key_qpos, m->nu);
 62  robot_state.joint_position.resize(model->getNrOfDegreesOfFreedom());
 63  robot_state.joint_velocity.resize(model->getNrOfDegreesOfFreedom());
 64
 65  for (int i=0; i<model->getNrOfDegreesOfFreedom() ; ++i)
 66  {
 67    robot_state.joint_position[i] = d->qpos[i];
 68    robot_state.joint_velocity[i] = d->qvel[i];
 69  }
 70
 71  model->setRobotState(robot_state);
 72  initialiseMocapPose(model->getFramePose(model->getTipFrameName()).matrix());
 73  
 74  initialized = true;
 75}
 76
 77void updateController() override
 78{
 79  
 80  while (!initialized)
 81  {
 82    // init_pose = model->getFramePose(model->getTipFrameName());
 83    std::cout << "Waiting for initialization" << std::endl;
 84    std::this_thread::sleep_for(std::chrono::milliseconds(100));
 85  }
 86
 87  for (int i=0; i<model->getNrOfDegreesOfFreedom() ; ++i)
 88  {
 89    robot_state.joint_position[i] = d->qpos[i];
 90    robot_state.joint_velocity[i] = d->qvel[i];
 91  }
 92  model->setRobotState(robot_state);
 93
 94  Eigen::Affine3d target_pose;
 95  target_pose.translation() = Eigen::Vector3d(d->mocap_pos[0],d->mocap_pos[1],d->mocap_pos[2]);
 96  target_pose.linear() = Eigen::Quaterniond(d->mocap_quat[0],d->mocap_quat[1],d->mocap_quat[2],d->mocap_quat[3]).toRotationMatrix();
 97  pinocchio::SE3 traj_pose(target_pose.matrix());
 98
 99  //get desired cartesian acceleration for main task
100  pinocchio::SE3 current_pose(model->getFramePose(model->getTipFrameName()).matrix());
101  const pinocchio::SE3 tipMdes = current_pose.actInv(traj_pose);
102  auto err = pinocchio::log6(tipMdes).toVector();
103
104  Eigen::Matrix<double, 6, 1> p_gains;
105  p_gains << 1000, 1000, 1000, 1000, 1000, 1000;
106
107  Eigen::Matrix<double, 6, 1> d_gains = 2.0 * p_gains.cwiseSqrt();
108  Eigen::Matrix<double, 6, 1> xdd_star =
109      p_gains.cwiseProduct(err) -
110      d_gains.cwiseProduct(model->getFrameVelocity(model->getTipFrameName())) ;
111  
112  double y_des_elbow = 0.15;
113  t+=m->opt.timestep;
114  if(sin(0.5*t)<0){
115    y_des_elbow = -0.15;
116  }
117  double current_y_elbow_pose = model->getFramePose("panda_link4").matrix()(1,3);
118  double error_elbow_y =  y_des_elbow-current_y_elbow_pose;
119  Eigen::VectorXd xdd_star_elbow = Eigen::VectorXd::Zero(6);
120  double k_elbow = 125.0;
121  xdd_star_elbow[1]= k_elbow * error_elbow_y -2.0*std::sqrt(k_elbow)*model->getFrameVelocity("panda_link4").matrix()(1,3);  
122
123  Eigen::VectorXd alpha_1 = Eigen::VectorXd::Zero(7); //here absolute priority to main task
124  Eigen::VectorXd alpha_2 = Eigen::VectorXd::Ones(7); //activation of the elbow task but should not impac tthe main task
125  alpha_2[6] = 0.0;
126  std::vector<Eigen::VectorXd> alphas;
127  alphas.push_back(alpha_1);
128  alphas.push_back(alpha_2);
129
130  //update the projectors of GHC
131  acceleration_problem->updateProjector(alphas);
132  
133  //TASKS UPDATE
134  main_task->setTargetAcceleration(xdd_star);
135  elbow_task->setTargetAcceleration(xdd_star_elbow);
136
137  regularization_task->setE(0.00001*Eigen::MatrixXd::Identity(14,14)); //here 0.00001 is the weight of regularization task
138  regularization_task->setf(Eigen::VectorXd::Zero(14));
139
140  //update joint acceleration problem
141  acceleration_problem->update(m->opt.timestep);
142
143  if (acceleration_problem->solutionFound())
144  {
145    sendJointVelocity(acceleration_problem->getJointVelocityCommand());
146  }
147} 
148};
149
150
151int main(int argc, const char** argv) {
152  MujocoQontrol mujoco_qontrol;
153  Qontrol::Log::Logger::parseArgv(argc, argv);
154
155  mjvCamera cam;
156  mjv_defaultCamera(&cam);
157
158  mjvOption opt;
159  mjv_defaultOption(&opt);
160
161  mjvPerturb pert;
162  mjv_defaultPerturb(&pert);
163
164  // simulate object encapsulates the UI
165  auto sim = std::make_unique<mj::Simulate>(
166      std::make_unique<mj::GlfwAdapter>(),
167      &cam, &opt, &pert, /* is_passive = */ false
168  );
169
170  std::string robot = argv[1];
171  std::string mujoco_scene = "./resources/"+robot+"/scene_interactive.xml";
172  mujoco_qontrol.resource_path = "./resources/"+robot+"/";
173
174  // start physics thread
175  std::thread physicsthreadhandle( &MujocoQontrol::PhysicsThread, mujoco_qontrol, sim.get(), mujoco_scene.c_str());
176
177  // start simulation UI loop (blocking call)
178  sim->RenderLoop();
179  physicsthreadhandle.join();
180
181  return 0;
182}

Explanation of the code

Initialization

  model =
      Model::RobotModel::loadModelFromFile(resource_path+"robot.urdf");

During initialization we instantiate the model with the robot urdf.

We initialize the problem by giving it the model. By default, the qpmad library is used.

  acceleration_problem = std::make_shared<Qontrol::JointAccelerationProblem>(model,ResolutionStrategy::Generalized);
  //tasks  (weights are useless)
  main_task = acceleration_problem->task_set->add<Task::CartesianAcceleration>("MainTask"); 
  elbow_task = acceleration_problem->task_set->add<Task::CartesianAcceleration>("CommunicationTask");
  Eigen::MatrixXd selection_matrix = Eigen::MatrixXd::Zero(6,6);
  selection_matrix(1,1) = 1.0; //y axis control only
  elbow_task->setControledFrameName("panda_link4");
  elbow_task->setReferenceFrame(ReferenceFrame::LOCAL_WORLD_ALIGNED);
  elbow_task->setSelectionMatrix(selection_matrix);
  regularization_task = acceleration_problem->task_set->add("regularizationTask",14,14,1.0); //regularisation dimension = n_dof*n_tasks (other than regularisation)

We then fill the task set of acceleration_problem with the main task, the elbow task and the regularisation task. We define a controlled frame for each task. By default it is the end effector of the robot. Additionnaly, it is possible to define the reference frame considered (local,world, local_world_aligned). The default value is LOCAL. Finally, if the cartesian task dimension is lower than 6, a selection matrix can be defined. Each tasks is given a name and a relative weight \(\omega\). This weight is not considered when we use the Generalized approach.

  auto joint_configuration_constraint = acceleration_problem->constraint_set->add<Constraint::JointConfiguration>("JointConfigurationConstraint");
  auto joint_velocity_constraint = acceleration_problem->constraint_set->add<Constraint::JointVelocity>("JointVelocityConstraint");

We then define the constraint set of torque_problem. Constraints of the robot are already pre-implemented and can be added as shown in this example.

  acceleration_problem->init();

Here we define that we are not using the default Weighting resolution strategy. It MUST be called after the tasks and constraints definition.

The problem is created in generalized mode:

  acceleration_problem = std::make_shared<Qontrol::JointAccelerationProblem>(model,ResolutionStrategy::Generalized);

The secondary elbow task is configured to control only the Y axis:

  Eigen::MatrixXd selection_matrix = Eigen::MatrixXd::Zero(6,6);
  selection_matrix(1,1) = 1.0; //y axis control only
  elbow_task->setControledFrameName("panda_link4");
  elbow_task->setReferenceFrame(ReferenceFrame::LOCAL_WORLD_ALIGNED);
  elbow_task->setSelectionMatrix(selection_matrix);

The mocap target is read from the interactive MuJoCo scene and converted to a Cartesian target pose:

  Eigen::Affine3d target_pose;
  target_pose.translation() = Eigen::Vector3d(d->mocap_pos[0],d->mocap_pos[1],d->mocap_pos[2]);
  target_pose.linear() = Eigen::Quaterniond(d->mocap_quat[0],d->mocap_quat[1],d->mocap_quat[2],d->mocap_quat[3]).toRotationMatrix();
  pinocchio::SE3 traj_pose(target_pose.matrix());

The GHC projector is updated before solving the acceleration problem:

  Eigen::VectorXd alpha_1 = Eigen::VectorXd::Zero(7); //here absolute priority to main task
  Eigen::VectorXd alpha_2 = Eigen::VectorXd::Ones(7); //activation of the elbow task but should not impac tthe main task
  alpha_2[6] = 0.0;
  std::vector<Eigen::VectorXd> alphas;
  alphas.push_back(alpha_1);
  alphas.push_back(alpha_2);

  //update the projectors of GHC
  acceleration_problem->updateProjector(alphas);

The executable loads the interactive scene:

  std::string mujoco_scene = "./resources/"+robot+"/scene_interactive.xml";

A dedicated Python version of this GHC interactive acceleration example is not available yet in bindings/python/examples. For now, the closest interactive Python example is Acceleration Qontrol Interactive, which documents the non-GHC acceleration controller workflow.