Acceleration Control GHC Interactive#
Introduction#
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:
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.