Qontrol
Torque control

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

\begin{equation}\begin{array}{ccc}\boldsymbol{\tau}^{opt} = & \underset{\boldsymbol{\tau}}{\mathrm{argmin}} & ||\boldsymbol{\dot{v}}(\boldsymbol{\tau}) - \boldsymbol{\dot{v}}^{target} || + \omega || \boldsymbol{\tau} - (\boldsymbol{g} - \boldsymbol{\dot{q}})||^2_{M^{-1}}\\& \textrm{s.t.} & \boldsymbol{\tau^{min}} \leq \boldsymbol{\tau} \leq \boldsymbol{\tau^{max}}. \\ & & \boldsymbol{q}^{min} \leq \boldsymbol{q}(\boldsymbol\tau) \leq \boldsymbol{q}^{max} \\ & & \boldsymbol{\dot{q}}^{min} \leq \boldsymbol{\dot{q}}(\boldsymbol\tau) \leq \boldsymbol{\dot{q}}^{max} \end{array} \end{equation}

.

The robot main tasks consists in following a simple trajectory defined in Cartesian space. 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 robot_name

where robot_name can be either panda or universal_robots_ur5e

Full code

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 #include "trajectory_generation/trajectory_generation.h"
18 
19 #define MUJOCO_PLUGIN_DIR "mujoco_plugin"
20 
21 
22 using namespace Qontrol;
23 
24 class MujocoQontrol : public MujocoSim
25 {
26 public:
27 //------------------------------------------- simulation -------------------------------------------
28 std::shared_ptr<Model::RobotModel<Model::RobotModelImplType::PINOCCHIO>> model;
29 std::shared_ptr<JointTorqueProblem> torque_problem;
30 std::shared_ptr<Task::CartesianAcceleration<ControlOutput::JointTorque>> main_task;
31 std::shared_ptr<Task::JointTorque<ControlOutput::JointTorque>> regularisation_task;
32 pinocchio::SE3 init_pose;
33 
34 bool init = true;
35 Qontrol::RobotState robot_state;
36 TrajectoryGeneration* traj;
37 std::string resource_path;
38 
39 void initController() override
40 {
41  model =
43 
44  torque_problem = std::make_shared<Qontrol::JointTorqueProblem>(model);
45  main_task = torque_problem->task_set->add<Task::CartesianAcceleration>("MainTask");
46  regularisation_task = torque_problem->task_set->add<Task::JointTorque>("RegularisationTask",1e-5);
47 
48  auto joint_configuration_constraint = torque_problem->constraint_set->add<Constraint::JointConfiguration>("JointConfigurationConstraint");
49  auto joint_velocity_constraint = torque_problem->constraint_set->add<Constraint::JointVelocity>("JointVelocityConstraint");
50  auto joint_torque_constraint = torque_problem->constraint_set->add<Constraint::JointTorque>("JointTorqueConstraint");
51 
52  mju_copy(d->qpos, m->key_qpos, m->nu );
53  robot_state.joint_position.resize(model->getNrOfDegreesOfFreedom());
54  robot_state.joint_velocity.resize(model->getNrOfDegreesOfFreedom());
55 
56  for (int i=0; i<model->getNrOfDegreesOfFreedom() ; ++i)
57  {
58  robot_state.joint_position[i] = d->qpos[i];
59  robot_state.joint_velocity[i] = d->qvel[i];
60  }
61  model->setRobotState(robot_state);
62 
63  traj = new TrajectoryGeneration(resource_path+"trajectory.csv", m->opt.timestep);
64 }
65 
66 void updateController() override
67 {
68  for (int i=0; i<model->getNrOfDegreesOfFreedom() ; ++i)
69  {
70  robot_state.joint_position[i] = d->qpos[i];
71  robot_state.joint_velocity[i] = d->qvel[i];
72  }
73  model->setRobotState(robot_state);
74 
75  traj->update();
76  pinocchio::SE3 traj_pose(traj->pose.matrix());
77 
78  pinocchio::SE3 current_pose(model->getFramePose(model->getTipFrameName()).matrix());
79  const pinocchio::SE3 tipMdes = current_pose.actInv(traj_pose);
80  auto err = pinocchio::log6(tipMdes).toVector();
81 
82  Eigen::Matrix<double, 6, 1> p_gains;
83  p_gains << 1000, 1000, 1000, 1000, 1000, 1000;
84 
85  Eigen::Matrix<double, 6, 1> d_gains = 2.0 * p_gains.cwiseSqrt();
86  Eigen::Matrix<double, 6, 1> xdd_star =
87  p_gains.cwiseProduct(err) +
88  d_gains.cwiseProduct(traj->velocity - model->getFrameVelocity(model->getTipFrameName())) + traj->acceleration;
89 
90  main_task->setTargetAcceleration(xdd_star);
91  regularisation_task->setTargetTorque(model->getJointGravityTorques() -
92  robot_state.joint_velocity);
93  regularisation_task->setWeightingMatrix(
94  model->getInverseJointInertiaMatrix());
95  torque_problem->update(m->opt.timestep);
96 
97  if (torque_problem->solutionFound())
98  {
99  sendJointTorque(torque_problem->getJointTorqueCommand());
100  }
101 }
102 };
103 
104 int main(int argc, const char** argv) {
105  MujocoQontrol mujoco_qontrol;
106  Qontrol::Log::Logger::parseArgv(argc, argv);
107 
108  mjvScene scn;
109  mjv_defaultScene(&scn);
110 
111  mjvCamera cam;
112  mjv_defaultCamera(&cam);
113 
114  mjvOption opt;
115  mjv_defaultOption(&opt);
116 
117  mjvPerturb pert;
118  mjv_defaultPerturb(&pert);
119 
120  // simulate object encapsulates the UI
121  auto sim = std::make_unique<mj::Simulate>(
122  std::make_unique<mj::GlfwAdapter>(),
123  &scn, &cam, &opt, &pert, /* fully_managed = */ true
124  );
125 
126 
127  std::string robot = argv[1];
128  std::string mujoco_model = "./resources/"+robot+"/scene.xml";
129  mujoco_qontrol.resource_path = "./resources/"+robot+"/";
130 
131  // start physics thread
132  std::thread physicsthreadhandle( &MujocoQontrol::PhysicsThread, mujoco_qontrol, sim.get(), mujoco_model);
133 
134  // start simulation UI loop (blocking call)
135  sim->RenderLoop();
136  physicsthreadhandle.join();
137 
138  return 0;
139 }

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<Model::RobotModelImplType::PINOCCHIO>> model;

We use pinocchio for our model library.

28 std::shared_ptr<JointTorqueProblem> torque_problem;

The output of our qp controller is at the torque level.

29 std::shared_ptr<Task::CartesianAcceleration<ControlOutput::JointTorque>> main_task;

We then declare two tasks that will be updated every milliseconds.

The main task is expressed as a Cartesian Acceleration task.

30 std::shared_ptr<Task::JointTorque<ControlOutput::JointTorque>> regularisation_task;

And we add a regularisation task (also at the torque level).

Initialization

39 void initController() override
40 {
41  model =
42  Model::RobotModel<Model::RobotModelImplType::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.

43 
44  torque_problem = std::make_shared<Qontrol::JointTorqueProblem>(model);
45  main_task = torque_problem->task_set->add<Task::CartesianAcceleration>("MainTask");
46  regularisation_task = torque_problem->task_set->add<Task::JointTorque>("RegularisationTask",1e-5);

We then fill the task set of torque_problem with the main task and the regularisation task. Each tasks is given a name and a relative weight \( \omega \). This weight can be modified at any time.

47 
48  auto joint_configuration_constraint = torque_problem->constraint_set->add<Constraint::JointConfiguration>("JointConfigurationConstraint");
49  auto joint_velocity_constraint = torque_problem->constraint_set->add<Constraint::JointVelocity>("JointVelocityConstraint");
50  auto joint_torque_constraint = torque_problem->constraint_set->add<Constraint::JointTorque>("JointTorqueConstraint");

We then fill the constraint set of torque_problem with the three pre-implemented constraints. Each constraint is given a name. These constraints will automatically be updated during the update of Qontrol.

51 
52  mju_copy(d->qpos, m->key_qpos, m->nu );
53  robot_state.joint_position.resize(model->getNrOfDegreesOfFreedom());
54  robot_state.joint_velocity.resize(model->getNrOfDegreesOfFreedom());
55 
56  for (int i=0; i<model->getNrOfDegreesOfFreedom() ; ++i)
57  {
58  robot_state.joint_position[i] = d->qpos[i];
59  robot_state.joint_velocity[i] = d->qvel[i];
60  }
61  model->setRobotState(robot_state);

We also create the robot state and fill it with the simulated robot current state.

62 
63  traj = new TrajectoryGeneration(resource_path+"trajectory.csv", m->opt.timestep);

We create a simple trajectory that has been precalculated and store in a csv file. This trajectory start at the robot current Cartesian pose and does a translation of (-0.1, -0,1, -0.1) m.

Update

67 void updateController() override
68 {
69  for (int i=0; i<model->getNrOfDegreesOfFreedom() ; ++i)
70  {
71  robot_state.joint_position[i] = d->qpos[i];
72  robot_state.joint_velocity[i] = d->qvel[i];
73  }
74  model->setRobotState(robot_state);
75 
76  traj->update();

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 update the trajectory so that it gives the next Cartesian pose to reach in 1 ms.

77  pinocchio::SE3 traj_pose(traj->pose.matrix());
78 
79  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.

80  const pinocchio::SE3 tipMdes = current_pose.actInv(traj_pose);
81  auto err = pinocchio::log6(tipMdes).toVector();
82 
83  Eigen::Matrix<double, 6, 1> p_gains;
84  p_gains << 1000, 1000, 1000, 1000, 1000, 1000;
85 
86  Eigen::Matrix<double, 6, 1> d_gains = 2.0 * p_gains.cwiseSqrt();
87  Eigen::Matrix<double, 6, 1> xdd_star =
88  p_gains.cwiseProduct(err) +
89  d_gains.cwiseProduct(traj->velocity - model->getFrameVelocity(model->getTipFrameName())) + traj->acceleration;
90 
91  main_task->setTargetAcceleration(xdd_star);
92  regularisation_task->setTargetTorque(model->getJointGravityTorques() -
93  robot_state.joint_velocity);
94  regularisation_task->setWeightingMatrix(
95  model->getInverseJointInertiaMatrix());

The desired Cartesian acceleration is then fed to the main task. The regularisation task is also updated so that it compensate for gravity plus a damping term. The resulting regularisation task would be written : \( || \boldsymbol{\tau} - (\boldsymbol{g} - \boldsymbol{\dot{q}})||^2_{M^{-1}}\)

96  torque_problem->update(m->opt.timestep);
97 
98  if (torque_problem->solutionFound())
99  {
100  sendJointTorque(torque_problem->getJointTorqueCommand());
101  }
102 }
103 };

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

104 
105 int main(int argc, const char** argv) {
106  MujocoQontrol mujoco_qontrol;
107  Qontrol::Log::Logger::parseArgv(argc, argv);
108 
109  mjvScene scn;
110  mjv_defaultScene(&scn);
111 
112  mjvCamera cam;
113  mjv_defaultCamera(&cam);
114 
115  mjvOption opt;
116  mjv_defaultOption(&opt);
117 
118  mjvPerturb pert;
119  mjv_defaultPerturb(&pert);
120 
121  // simulate object encapsulates the UI
122  auto sim = std::make_unique<mj::Simulate>(
123  std::make_unique<mj::GlfwAdapter>(),
124  &scn, &cam, &opt, &pert, /* fully_managed = */ true
125  );
126 
127 
128  std::string robot = argv[1];
129  std::string mujoco_model = "./resources/"+robot+"/scene.xml";
130  mujoco_qontrol.resource_path = "./resources/"+robot+"/";
131 
132  // start physics thread
133  std::thread physicsthreadhandle( &MujocoQontrol::PhysicsThread, mujoco_qontrol, sim.get(), mujoco_model);
134 
135  // start simulation UI loop (blocking call)
136  sim->RenderLoop();
137  physicsthreadhandle.join();
138 
139  return 0;

The main function function fetches the robot name given in argv and starts the Mujoco simulation.

Qontrol::Task::CartesianAcceleration
Implementation of a Cartesian Acceleration task.
Definition: CartesianAcceleration.hpp:34
Qontrol::RobotState
Robot state.
Definition: GenericModel.hpp:32
Qontrol::Constraint::JointTorque
Implemtentation of a joint torque constraint.
Definition: JointTorque.hpp:34
Qontrol::Task::JointTorque
Implemtentation of a joint torque task.
Definition: JointTorque.hpp:34
Qontrol::Constraint::JointVelocity
Implemtentation of a joint velocity constraint.
Definition: JointVelocity.hpp:35
Qontrol::Model::RobotModel
Decalaration of a template specialization for the model library.
Definition: GenericModel.hpp:437
Qontrol::Constraint::JointConfiguration
Implemtentation of a joint configuration constraint.
Definition: JointConfiguration.hpp:35