Velocity Qontrol Wall Constraint Interactive#
Introduction#
This example demonstrates how to use Qontrol to enforce cartesian plane constraints on a robot’s end-effector velocity. The QP problem is formulated to minimize the tracking error of a Cartesian trajectory while respecting joint velocity limits and a cartesian plane constraint (e.g., a virtual wall).
We define the robot base as the robot baselink frame \(\mathcal{R}_O\) (it can be changed with the root_frame_name, but this frame has to be in the robot URDF). In this example, we set the plane with the normal vector (pointing towards the constraint region) as \(u = -x_O\), and the point E (belonging to the plane) to 10 cm from the baselink \(OE = -0.1 x_O\).
The MuJoCo library is used to simulate the robot’s behavior.
Formulation#
The QP problem is expressed at the joint velocity level and is defined as:
The main task is to track a Cartesian velocity trajectory.
The regularization task minimizes joint velocities.
The cartesian plane constraint ensures that the end-effector does not cross a virtual wall.
—
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
18
19using namespace Qontrol;
20
21class MujocoQontrol : public MujocoSim
22{
23public:
24//------------------------------------------- simulation -------------------------------------------
25std::shared_ptr<Qontrol::Model::RobotModel> model;
26std::shared_ptr<Qontrol::JointVelocityProblem> velocity_problem;
27std::shared_ptr<Qontrol::Task::CartesianVelocity<Qontrol::ControlOutput::JointVelocity>> main_task;
28std::shared_ptr<Qontrol::Constraint::CartesianPlane<Qontrol::ControlOutput::JointVelocity>> wallConstraint;
29
30Qontrol::RobotState robot_state;
31Eigen::Vector3d u_;
32Eigen::Vector3d point_;
33
34std::string resource_path;
35
36void initController() override
37{
38 model =
39 Model::RobotModel::loadModelFromFile(resource_path+"robot.urdf");
40
41 velocity_problem = std::make_shared<Qontrol::JointVelocityProblem>(model);
42 main_task = velocity_problem->task_set->add<Task::CartesianVelocity>("MainTask");
43 auto regularisation_task = velocity_problem->task_set->add<Task::JointVelocity>("RegularisationTask",1e-5);
44
45 auto joint_configuration_constraint = velocity_problem->constraint_set->add<Constraint::JointConfiguration>("JointConfigurationConstraint");
46 auto joint_velocity_constraint = velocity_problem->constraint_set->add<Constraint::JointVelocity>("JointVelocityConstraint");
47
48
49 wallConstraint = velocity_problem->constraint_set->add<Constraint::CartesianPlane>("wallConstraint");
50
51 robot_state.joint_position.resize(model->getNrOfDegreesOfFreedom());
52 robot_state.joint_velocity.resize(model->getNrOfDegreesOfFreedom());
53 mju_copy(d->qpos, m->key_qpos, m->nu);
54
55 for (int i=0; i<model->getNrOfDegreesOfFreedom() ; ++i)
56 {
57 robot_state.joint_position[i] = d->qpos[i];
58 robot_state.joint_velocity[i] = d->qvel[i];
59 }
60
61 model->setRobotState(robot_state);
62 initialiseMocapPose(model->getFramePose(model->getTipFrameName()).matrix());
63
64 // Set wall plane parameters from geom in scene
65 int wall_geom_id = mj_name2id(m, mjOBJ_GEOM, "wall_plane");
66 Eigen::Vector3d wall_pos(
67 d->geom_xpos[3*wall_geom_id+0],
68 d->geom_xpos[3*wall_geom_id+1],
69 d->geom_xpos[3*wall_geom_id+2]
70 );
71 Eigen::Matrix3d wall_rot = Eigen::Map<Eigen::Matrix<double,3,3,Eigen::RowMajor>>(&d->geom_xmat[9*wall_geom_id]);
72
73 // Wall plane normal is x-axis of the geom frame (thin dimension)
74 u_ = -wall_rot.col(0);
75 point_ = wall_pos;
76
77 wallConstraint->setHorizon(10);
78 wallConstraint->setPlane(u_,point_);
79
80}
81
82void updateController() override
83{
84 for (int i=0; i<model->getNrOfDegreesOfFreedom() ; ++i)
85 {
86 robot_state.joint_position[i] = d->qpos[i];
87 robot_state.joint_velocity[i] = d->qvel[i];
88 }
89
90 model->setRobotState(robot_state);
91 Eigen::Affine3d target_pose;
92 target_pose.translation() = Eigen::Vector3d(d->mocap_pos[0],d->mocap_pos[1],d->mocap_pos[2]);
93 target_pose.linear() = Eigen::Quaterniond(d->mocap_quat[0],d->mocap_quat[1],d->mocap_quat[2],d->mocap_quat[3]).toRotationMatrix();
94 pinocchio::SE3 traj_pose(target_pose.matrix());
95
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();
99 Eigen::Matrix<double,6,1> p_gains;
100 p_gains << 10,10,10,10,10,10;
101 Eigen::Matrix<double,6,1> xd_star = p_gains.cwiseProduct(err);
102
103 main_task->setTargetVelocity(xd_star);
104 velocity_problem->update(m->opt.timestep);
105
106 if (velocity_problem->solutionFound())
107 {
108 sendJointVelocity(velocity_problem->getJointVelocityCommand());
109 }
110}
111
112};
113
114int main(int argc, const char** argv) {
115 MujocoQontrol mujoco_qontrol;
116 Qontrol::Log::Logger::parseArgv(argc, argv);
117
118 mjvCamera cam;
119 mjv_defaultCamera(&cam);
120
121 mjvOption opt;
122 mjv_defaultOption(&opt);
123
124 mjvPerturb pert;
125 mjv_defaultPerturb(&pert);
126
127 // simulate object encapsulates the UI
128 auto sim = std::make_unique<mj::Simulate>(
129 std::make_unique<mj::GlfwAdapter>(),
130 &cam, &opt, &pert, /* is_passive = */ false
131 );
132
133 std::string robot = argv[1];
134 std::string mujoco_model = "./resources/"+robot+"/scene_wallconstraint.xml";
135 mujoco_qontrol.resource_path = "./resources/"+robot+"/";
136
137 // start physics thread
138 std::thread physicsthreadhandle( &MujocoQontrol::PhysicsThread, mujoco_qontrol, sim.get(), mujoco_model.c_str());
139
140 // start simulation UI loop (blocking call)
141 sim->RenderLoop();
142 physicsthreadhandle.join();
143
144 return 0;
145}
—
Explanation of the code
Full Code:
You can find the source code of this example here.
1#!/usr/bin/env python3
2"""
3Interactive Velocity Control with Wall Constraint and MuJoCo
4
5This example demonstrates:
6- Loading a robot model in both MuJoCo and Qontrol
7- Joint velocity control with Cartesian velocity tracking
8- Cartesian plane constraint (wall constraint)
9- Interactive mocap target control
10
11The robot will track the mocap target while respecting a plane constraint
12that prevents the end-effector from crossing a virtual wall.
13
14Usage:
15 python velocity_qontrol_wall_constraint_interactive.py <robot_name>
16
17Example:
18 python velocity_qontrol_wall_constraint_interactive.py panda
19
20Controls:
21- Use the MuJoCo viewer to move the mocap body (red sphere)
22- The robot end-effector will track the mocap target
23- The robot will avoid crossing the virtual wall plane
24- Press ESC to exit
25"""
26
27import numpy as np
28import qontrol
29import pinocchio as pin
30import argparse
31import os
32import time
33from mujoco_helper import MujocoSimulator, compute_log6_error
34
35
36class QontrolVelocityControllerWithWall:
37 """Velocity controller with wall constraint using Qontrol"""
38
39 def __init__(self, robot_name: str, resource_path: str, mujoco_sim: MujocoSimulator):
40 """
41 Initialize the Qontrol velocity controller with wall constraint
42
43 Args:
44 robot_name: Name of the robot (e.g., 'panda')
45 resource_path: Path to robot resources folder
46 mujoco_sim: MuJoCo simulator instance
47 """
48 self.robot_name = robot_name
49 self.resource_path = resource_path
50 self.sim = mujoco_sim
51
52 # =================================================================
53 # Qontrol Setup - Robot Model and QP Problem
54 # =================================================================
55 urdf_path = os.path.join(resource_path, "robot.urdf")
56 if not os.path.exists(urdf_path):
57 raise FileNotFoundError(f"URDF file not found: {urdf_path}")
58
59 print(f"Loading Qontrol model from: {urdf_path}")
60 self.qontrol_model = qontrol.RobotModel.load_from_file(urdf_path)
61 self.ndof = self.qontrol_model.get_nr_of_degrees_of_freedom()
62 print(f"Robot has {self.ndof} degrees of freedom")
63
64 # Create QP solver
65 self.solver = qontrol.create_qpmad_solver()
66
67 # Create velocity-level problem
68 self.velocity_problem = qontrol.JointVelocityProblem(self.qontrol_model, self.solver)
69
70 # Add Cartesian velocity task for end-effector tracking
71 self.main_task = self.velocity_problem.task_set.add_cartesian_velocity("MainTask", 1.0)
72
73 # Add joint velocity regularization (minimize joint velocities)
74 self.regularization_task = self.velocity_problem.task_set.add_joint_velocity("RegularizationTask", 1e-5)
75
76 # Add constraints
77 self.joint_config_constraint = self.velocity_problem.constraint_set.add_joint_configuration("JointConfigurationConstraint")
78 self.joint_vel_constraint = self.velocity_problem.constraint_set.add_joint_velocity("JointVelocityConstraint")
79
80 # Add wall constraint (Cartesian plane constraint)
81 self.wall_constraint = self.velocity_problem.constraint_set.add_cartesian_plane("WallConstraint")
82
83 # Robot state object
84 self.robot_state = qontrol.RobotState()
85 self.robot_state.resize(self.ndof)
86
87 # Get tip and root frame names
88 self.tip_frame = self.qontrol_model.get_tip_frame_name()
89 self.root_frame = self.qontrol_model.get_root_frame_name()
90 print(f"Controlling frame: {self.tip_frame}")
91 print(f"Root frame: {self.root_frame}")
92
93 # Get initial joint state and update model
94 qpos, qvel = self.sim.get_joint_state()
95 self.robot_state.joint_position = qpos
96 self.robot_state.joint_velocity = qvel
97 self.qontrol_model.set_robot_state(self.robot_state)
98
99 # =================================================================
100 # Wall Constraint Setup
101 # =================================================================
102 # Define wall plane: u is the normal vector, point is a point on the plane
103 # Wall prevents end-effector from moving in the -x direction of the root frame
104
105 # Get initial tip and root poses
106 initial_tip_matrix = self.qontrol_model.get_frame_pose(self.tip_frame)
107 initial_root_matrix = self.qontrol_model.get_frame_pose(self.root_frame)
108
109 # Wall normal is -x direction of root frame
110 u_vector = -initial_root_matrix[:3, 0] # -x axis (first column)
111 self.u = u_vector.reshape(1, 3) # Shape (1, 3) for constraint
112
113 # Wall is located 10cm in front of initial tip position in the u direction
114 initial_tip_pos = initial_tip_matrix[:3, 3]
115 self.point = (initial_tip_pos + 0.1 * u_vector).reshape(1, 3)
116
117 print(f"Wall normal (u): {self.u}")
118 print(f"Wall point: {self.point}")
119
120 # Set wall constraint parameters
121 self.wall_constraint.set_horizon(10)
122 self.wall_constraint.set_plane(self.u, self.point)
123
124 # Initialize mocap to current end-effector pose
125 self.sim.init_mocap_to_frame(self.tip_frame)
126
127 print("dt:", self.sim.dt)
128
129 # =================================================================
130 # Control Parameters
131 # =================================================================
132 self.p_gains = np.array([10.0, 10.0, 10.0, 10.0, 10.0, 10.0]) # [position, orientation] gains
133
134 def update(self):
135 """Main control update: compute and apply joint velocity commands"""
136 # 1. Update Qontrol model with current state from simulation
137 qpos, qvel = self.sim.get_joint_state()
138 self.robot_state.joint_position = qpos
139 self.robot_state.joint_velocity = qvel
140 self.qontrol_model.set_robot_state(self.robot_state)
141
142 # 2. Get target and current poses
143 target_se3 = self.sim.get_mocap_pose_se3()
144 current_ee_matrix = self.qontrol_model.get_frame_pose(self.tip_frame)
145 current_se3 = pin.SE3(current_ee_matrix[:3, :3], current_ee_matrix[:3, 3])
146
147 # 3. Compute SE(3) log6 error
148 cartesian_error = compute_log6_error(target_se3, current_se3)
149
150 # 4. Compute desired Cartesian velocity (P control)
151 desired_velocity = self.p_gains * cartesian_error
152
153 # 5. Solve QP for joint velocities
154 self.main_task.set_target_velocity(desired_velocity)
155 self.velocity_problem.update(self.sim.dt)
156
157 # 6. Apply velocities to simulation
158 if self.velocity_problem.solution_found():
159 joint_velocities = self.velocity_problem.get_joint_velocity_command()
160 self.sim.set_joint_velocities(joint_velocities)
161 else:
162 print("Warning: No QP solution found!")
163 self.sim.set_joint_velocities(np.zeros(self.ndof))
164
165
166def main():
167 """Main entry point"""
168 parser = argparse.ArgumentParser(
169 description="Interactive velocity control with wall constraint using MuJoCo and Qontrol",
170 formatter_class=argparse.RawDescriptionHelpFormatter,
171 epilog="""
172Examples:
173 %(prog)s panda
174 %(prog)s ur5
175 """
176 )
177 parser.add_argument("robot", type=str, help="Robot name (e.g., panda)")
178
179 args = parser.parse_args()
180
181 # Get resource path
182 script_dir = os.path.dirname(os.path.abspath(__file__))
183 resource_path = os.path.join(script_dir, "..", "..", "..", "examples", "resources", args.robot)
184
185 if not os.path.exists(resource_path):
186 print(f"Error: Resource path not found: {resource_path}")
187 return 1
188
189 # MuJoCo scene path
190 scene_path = os.path.join(resource_path, "scene_interactive.xml")
191 if not os.path.exists(scene_path):
192 print(f"Error: Scene file not found: {scene_path}")
193 return 1
194
195 print(f"Loading MuJoCo scene from: {scene_path}")
196
197 # Initialize MuJoCo simulator
198 sim = MujocoSimulator(args.robot, resource_path)
199
200 # Initialize controller
201 controller = QontrolVelocityControllerWithWall(args.robot, resource_path, sim)
202
203 # Initialize mocap to robot's end-effector pose
204 sim.init_mocap_to_frame(controller.tip_frame)
205
206 print("\n" + "="*60)
207 print("Starting velocity control with wall constraint simulation")
208 print("Move the red mocap target to control the robot")
209 print("The robot will avoid crossing the virtual wall plane")
210 print("Press ESC to exit")
211 print("="*60 + "\n")
212
213 try:
214 # Run simulation with controller
215 sim.run_interactive(controller.update)
216 except KeyboardInterrupt:
217 print("\nInterrupted by user")
218 except Exception as e:
219 print(f"\nError during simulation: {e}")
220 import traceback
221 traceback.print_exc()
222 return 1
223
224 return 0
225
226
227if __name__ == "__main__":
228 exit(main())
—
Explanation of the code
Note: The following explanation refers to the logic implemented in C++, but the Python API directly mirrors this structure.