Introduction
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{equation} \begin{array}{l} 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} \\ \quad \tau_{min} - n \leq BPX \leq \tau_{max} - n \\ P = [P_1, \ldots, P_{n_t}], \quad \ddot{q} = PX = \sum_i P_i \ddot{q}_i \\ \tau = BPX^{opt} + n \end{array} \end{equation}
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.
Simulation
To run this example run the following command from the build/examples directory:
./torqueQontrol_GHC robot_name
where robot_name can be only panda
Full code
15 #include "mujoco/mujoco_sim.h"
16 #include "Qontrol/Qontrol.hpp"
17 #include "trajectory_generation/trajectory_generation.h"
19 using namespace Qontrol;
21 class MujocoQontrol :
public MujocoSim
25 std::shared_ptr<Model::RobotModel<ModelImpl::PINOCCHIO>> model;
26 std::shared_ptr<JointTorqueProblem> torque_problem;
27 std::shared_ptr<Task::CartesianAcceleration<ControlOutput::JointTorque>> main_task;
28 std::shared_ptr<Task::CartesianAcceleration<ControlOutput::JointTorque>> elbow_task;
31 std::shared_ptr<Qontrol::Task::GenericTask> regularization_task;
34 TrajectoryGeneration* traj;
35 std::string resource_path;
36 bool initialized =
false;
39 void initController()
override
44 torque_problem = std::make_shared<Qontrol::JointTorqueProblem>(model,ResolutionStrategy::Generalized);
48 Eigen::MatrixXd selection_matrix = Eigen::MatrixXd::Zero(6,6);
49 selection_matrix(1,1) = 1.0;
50 elbow_task->setControledFrameName(
"panda_link4");
51 elbow_task->setReferenceFrame(ReferenceFrame::LOCAL_WORLD_ALIGNED);
52 elbow_task->setSelectionMatrix(selection_matrix);
53 regularization_task = torque_problem->task_set->add(
"regularizationTask",14,14,1.0);
56 auto joint_velocity_constraint = torque_problem->constraint_set->add<
Constraint::JointVelocity>(
"JointVelocityConstraint");
57 auto joint_torque_constraint = torque_problem->constraint_set->add<
Constraint::JointTorque>(
"JointTorqueConstraint");
59 joint_configuration_constraint->setHorizon(30);
60 joint_velocity_constraint->setHorizon(30);
63 torque_problem->init();
64 std::cout <<
"Model loaded with " << model->getNrOfDegreesOfFreedom() <<
" DoF" << std::endl;
66 mju_copy(d->qpos, m->key_qpos, m->nu);
67 robot_state.joint_position.resize(model->getNrOfDegreesOfFreedom());
68 robot_state.joint_velocity.resize(model->getNrOfDegreesOfFreedom());
70 traj =
new TrajectoryGeneration(resource_path+
"trajectory.csv", m->opt.timestep);
75 void updateController()
override
81 std::cout <<
"Waiting for initialization" << std::endl;
82 std::this_thread::sleep_for(std::chrono::milliseconds(100));
85 for (
int i=0; i<model->getNrOfDegreesOfFreedom() ; ++i)
87 robot_state.joint_position[i] = d->qpos[i];
88 robot_state.joint_velocity[i] = d->qvel[i];
90 model->setRobotState(robot_state);
93 pinocchio::SE3 traj_pose(traj->pose.matrix());
96 pinocchio::SE3 current_pose(model->getFramePose(model->getTipFrameName()).matrix());
97 const pinocchio::SE3 tipMdes = current_pose.actInv(traj_pose);
98 auto err = pinocchio::log6(tipMdes).toVector();
100 Eigen::Matrix<double, 6, 1> p_gains;
101 p_gains << 1000, 1000, 1000, 1000, 1000, 1000;
103 Eigen::Matrix<double, 6, 1> d_gains = 2.0 * p_gains.cwiseSqrt();
104 Eigen::Matrix<double, 6, 1> xdd_star =
105 p_gains.cwiseProduct(err) -
106 d_gains.cwiseProduct(model->getFrameVelocity(model->getTipFrameName())) ;
108 double y_des_elbow =0.15;
113 double current_y_elbow_pose = model->getFramePose(
"panda_link4").matrix()(1,3);
114 double error_elbow_y = y_des_elbow-current_y_elbow_pose;
115 Eigen::VectorXd xdd_star_elbow = Eigen::VectorXd::Zero(6);
116 xdd_star_elbow[1]= 500.0 * error_elbow_y -100.0*model->getFrameVelocity(
"panda_link4").matrix()(1,3);
118 Eigen::MatrixXd alpha_1 = Eigen::MatrixXd::Zero(7,7);
119 Eigen::MatrixXd alpha_2 = Eigen::MatrixXd::Identity(7,7);
121 std::vector<Eigen::MatrixXd> alphas;
122 alphas.push_back(alpha_1);
123 alphas.push_back(alpha_2);
126 torque_problem->updateProjector(alphas);
129 main_task->setTargetAcceleration(xdd_star);
130 elbow_task->setTargetAcceleration(xdd_star_elbow);
132 regularization_task->setE(0.00001*Eigen::MatrixXd::Identity(14,14));
133 regularization_task->setf(Eigen::VectorXd::Zero(14));
136 torque_problem->update(m->opt.timestep);
138 if (torque_problem->solutionFound())
140 sendJointTorque(torque_problem->getJointTorqueCommand());
146 int main(
int argc,
const char** argv) {
147 MujocoQontrol mujoco_qontrol;
148 Qontrol::Log::Logger::parseArgv(argc, argv);
151 mjv_defaultCamera(&cam);
154 mjv_defaultOption(&opt);
157 mjv_defaultPerturb(&pert);
160 auto sim = std::make_unique<mj::Simulate>(
161 std::make_unique<mj::GlfwAdapter>(),
162 &cam, &opt, &pert,
false
165 std::string robot = argv[1];
166 std::string mujoco_scene =
"./resources/"+robot+
"/scene.xml";
167 mujoco_qontrol.resource_path =
"./resources/"+robot+
"/";
170 std::thread physicsthreadhandle( &MujocoQontrol::PhysicsThread, mujoco_qontrol, sim.get(), mujoco_scene.c_str());
174 physicsthreadhandle.join();
Explanation of the code
Declaration
First we declare all the objects that will be used to define our problem.
0 std::shared_ptr<Model::RobotModel<ModelImpl::PINOCCHIO>> model;
We use pinocchio for our model library.
25 std::shared_ptr<JointTorqueProblem> torque_problem;
The output of our qp controller is at the torque level.
26 std::shared_ptr<Task::CartesianAcceleration<ControlOutput::JointTorque>> main_task;
We then declare tree tasks that will be updated every milliseconds.
The main task is expressed as a Cartesian Acceleration task.
27 std::shared_ptr<Task::CartesianAcceleration<ControlOutput::JointTorque>> elbow_task;
We add the secondary task to control the elbow along y axis.
28 std::shared_ptr<Qontrol::Task::GenericTask> regularization_task;
And we add a regularisation task (also at the torque level).
Initialization
39 void initController()
override
42 Model::RobotModel<ModelImpl::PINOCCHIO>::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.
44 torque_problem = std::make_shared<Qontrol::JointTorqueProblem>(model,ResolutionStrategy::Generalized);
46 main_task = torque_problem->task_set->add<Task::CartesianAcceleration>(
"MainTask");
47 elbow_task = torque_problem->task_set->add<Task::CartesianAcceleration>(
"CommunicationTask");
48 Eigen::MatrixXd selection_matrix = Eigen::MatrixXd::Zero(6,6);
49 selection_matrix(1,1) = 1.0;
50 elbow_task->setControledFrameName(
"panda_link4");
51 elbow_task->setReferenceFrame(ReferenceFrame::LOCAL_WORLD_ALIGNED);
52 elbow_task->setSelectionMatrix(selection_matrix);
53 regularization_task = torque_problem->task_set->add(
"regularizationTask",14,14,1.0);
We then fill the task set of torque_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.
55 auto joint_configuration_constraint = torque_problem->constraint_set->add<Constraint::JointConfiguration>(
"JointConfigurationConstraint");
56 auto joint_velocity_constraint = torque_problem->constraint_set->add<Constraint::JointVelocity>(
"JointVelocityConstraint");
57 auto joint_torque_constraint = torque_problem->constraint_set->add<Constraint::JointTorque>(
"JointTorqueConstraint");
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.
59 joint_configuration_constraint->setHorizon(30);
60 joint_velocity_constraint->setHorizon(30);
63 torque_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.
Update
76 void updateController()
override
82 std::cout <<
"Waiting for initialization" << std::endl;
83 std::this_thread::sleep_for(std::chrono::milliseconds(100));
86 for (
int i=0; i<model->getNrOfDegreesOfFreedom() ; ++i)
88 robot_state.joint_position[i] = d->qpos[i];
89 robot_state.joint_velocity[i] = d->qvel[i];
91 model->setRobotState(robot_state);
The update function is called every milliseconds. At the beginning of each update we fill the new robot state according to the simulated robot.
We also define the Cartesian goal for the end effector.
94 pinocchio::SE3 traj_pose(traj->pose.matrix());
97 pinocchio::SE3 current_pose(model->getFramePose(model->getTipFrameName()).matrix());
We then compute the desired Cartesian acceleration using a simple PD controller. Pinocchio is used to compute the error between the desired Cartesian pose and the current Cartesian pose. This is done by the log6 function. The p_gains and d_gains variables are the gains of the PD controller.
Similarly to the previous desired Cartesian acceleration computation, we compute the desired acceleration of the elbow along the y axis. Here the reference is either 0.15m, either -0.15m depending of the simulation time.
Generalized Hierarchical Control is based on the projection P of the tasks on the other tasks' nullspace. In order to compute the overall projection P, we need to create a vector of \(\alpha_i\) matrices defined such that:
\(\alpha_i = diag(\alpha_{i1} I_{m_1}, \ldots, \alpha_{ij} I_{m_j}, \ldots, \alpha_{i_{n_t}} I_{m_{n_t}})\)
where \(\alpha_i\) is a diagonal matrix, the main diagonal blocks of which are square matrices: \(\alpha_{ij} I_{m_j}\) . \(I_{m_j}\) is the \(m_j\) x \(m_j\) identity matrix, and \(\alpha_{ij} \in [0, 1]\). By convention, the coefficient \(\alpha_{ij}\) indicates the priority of task j with respect to task i.
- \(\alpha_{ij} = 0 \) corresponds to the case where task j has strict lower priority with respect to task i.
- \(0 < \alpha_{ij} < 1\) corresponds to a soft (non-strict) priority between the two tasks: the greater the value of \(\alpha_{ij}\) , the higher the importance level of task j with respect to task i.
- \(\alpha_{ij} = 1 \) corresponds to the case where task j has a strict higher priority with respect to task i.
There is a particular case induced by the proposed formulation and corresponding to the influence of task i on itself. Even though not intuitive, this self-influence has to be interpreted in terms of task existence, modulated by \(\alpha_{ii}\) . If \(\alpha_{ii} = 1\) then task i is projected into its own null-space, i.e. it is basically canceled out. Decreasing \(\alpha_{ii}\) continuously to 0 is a simple and elegant way to introduce the task in the set of tasks. Conversely, increasing \(\alpha_{ii}\) continuously from 0 to 1 provides with a proper task deletion procedure.
The updateProjector method update the projector with the alphas. Note that alphas values can be modified over time to smoothly change the hierarchy between the tasks.
Finally, we set the E and f for each task, including the regularization task. Unlike the other tasks,the regularization task is weighted. In this example, the weight is added in the E matrix.
Once we updated the necassary tasks and constraints we can update the whole problem. If a solution to the problem exist we can then get it and send it to the simulated robot.
Main function
The main function function fetches the robot name given in argv and starts the Mujoco simulation.