Skip to article frontmatterSkip to article content
%%capture
'''
(C) Copyright 2020-2025 Murilo Marques Marinho (murilomarinho@ieee.org)

     This file is licensed in the terms of the
     Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)
     license.

 Derivative work of:
 https://github.com/dqrobotics/learning-dqrobotics-in-matlab/tree/master/robotic_manipulators
 Contributors to this file:
     Murilo Marques Marinho (murilomarinho@ieee.org)
'''

DQ8 Optimization-based Robot Control

I found an issue

Thank you! Please report it at https://github.com/MarinhoLab/OpenExecutableBooksRobotics/issues

Introduction

In the last lesson, you learned the concept of task-space singularities and a particular way to address them.

In this lesson, we will revisit the topic of robot control using quadratic optimization formalism.

%%capture
%pip install dqrobotics quadprog dqrobotics-pyplot
%pip install dqrobotics quadprog dqrobotics-pyplot --break-system-packages
from math import pi
import numpy as np
from dqrobotics import *
from dqrobotics.utils import DQ_Geometry
from dqrobotics.robot_control import ControlObjective
from dqrobotics.robot_modeling import DQ_Kinematics
from dqrobotics_extensions.pyplot import plot

import matplotlib.pyplot as plt
plt.rcParams["animation.html"] = "jshtml" # Need to output animation's videos
import matplotlib.animation as anm
from functools import partial # Need to call functions correctly for matplotlib animations

Notation

Keep these in mind (we will also use this notation when writing papers for conferences and journals):

  • hHh\in \mathbb{H}: a quaternion. (Bold-face, lowercase character)
  • hH\underline{h} \in \mathcal{H}: a dual quaternion. (Bold-face, underlined, lowercase character)
  • p,t,Hpp,t,\cdots \in {\mathbb{H}}_p: pure quaternions. They represent points, positions, and translations. They are quaternions for which Re(h)=0\textrm{Re}\left(h\right)=0.
  • rS3r\in {\mathbb{S}}^3: unit quaternions. They represent orientations and rotations. They are quaternions for which h=1||h||=1.
  • xS\underline{x} \in \underline{\mathcal{S}}: unit dual quaternions. They represent poses and pose transformations. They are dual quaternions for which h=1||h||=1.
  • lHp\underline{l} \in {\mathcal{H}}_p \cap S\underline{{\mathcal{S}}} : a Plücker line.
  • π{P(π)Hp}S\underline{{{\pi }}} \in \left\lbrace P\left(\underline{{{\pi }}} \right)\in {\mathbb{H}}_p \right\rbrace \cap \underline{{\mathcal{S}}} : a plane.
  • θ,a,b,R\theta, a,b,\cdots \in \mathbb{R}: real numbers.
  • q,Rn:{{q,\cdots }}\in {\mathbb{R}}^n: real vectors.
  • J,A,B,Rm×n:J, A, B,\cdots \in {\mathbb{R}}^{m\times n}: real matrices.

Robot definition

The concepts of this lesson apply to any manipulator robot. However, to have a more concrete understanding using DQ Robotics, consider the same robot as used in the past lesson.

image_0.png

  1. Let the robot RR be a 7-DoF planar robot, as drawn in Fig.1.
  2. Let FW{\mathcal{F}}_W be the world-reference frame.
  3. Let xRW(t)xR{\underline{x} }_R^W (t)\triangleq {\underline{x} }_R \in S\underline{\mathcal{S}} represent the pose of the end effector.
  4. Let RR be composed of seven rotational joints that rotate about their z-axis, composed in the joint-space vector q(t)q=[θ1 θ2 θ3 θ4 θ5 θ6 θ7]Tq\left(t\right)\triangleq q={\left\lbrack \theta_1 ~\theta_2 ~\theta_3 ~\theta_4 ~\theta_5 ~\theta_6 ~\theta_7 \right\rbrack }^T with θi(t)θiR\theta_i \left(t\right)\triangleq \theta_i \in \mathbb{R} for i=1,2,,7i=1,2,\ldots,7. The rotation of the reference frames of each joint coincides with the rotation of FW{\mathcal{F}}_W when θi=0\theta_i =0. The lengths of the joints are lil_i \in R+{0}{\mathbb{R}}^+ -\lbrace 0\rbrace.
  5. Consider that we can freely control the joint vector qq.

This robot can be modeled by the following class.

from seven_dof_planar_robot_dh import SevenDofPlanarRobotDH
help(SevenDofPlanarRobotDH.kinematics)
Help on function kinematics in module seven_dof_planar_robot_dh:

kinematics()
    Returns the kinematics of the SevenDoFPlanarRobot as DQ_SerialManipulatorDH.

Let us instantiate our robot as follows

seven_dof_planar_robot = SevenDofPlanarRobotDH.kinematics()

A brief review of quadratic optimization

At some point in your education, you might have been introduced to optimization problems. There are many types of optimization problems, and in this lesson, we will address only two types, which are enough to solve a large class of robot control problems.

Unconstrained quadratic optimization

The first type is called unconstrained quadratic optimization

uarg  minq˙   F(q˙)u\in \underset{\dot{q} }{\arg \;\min } ~~~\mathcal{F}\left(\dot{q} \right)

where F(q˙)R+\mathcal{F}\left(\dot{q} \right)\in {\mathbb{R}}^+ is a quadratic objective function that depends on the current joint configuration. Note that it is called an optimization problem, and NOT an equation (note that there is no equality sign). In mathematical optimization terminology, q˙\dot{q} is called the decision variable.

You can read that as “find all q˙\dot{q} that minimize F(q˙)\mathcal{F}\left(\dot{q} \right) and return one of them as uu.” The solution for an unconstrained quadratic optimization problem can, in general, be found analytically. In this lesson we will call that minimizer as uu.

Quadratic programming

The second type of optimization problem that we will address is called quadratic programming (QP), which is a linearly constrained quadratic optimization problem, in the form

uarg  minq˙   F(q˙)    subject  to Wq˙w\begin{array}{l} u\in \underset{\dot{q} }{\arg \;\min } ~~~\mathcal{F}\left(\dot{q} \right)\\ ~~~~\textrm{subject}\;\textrm{to}~W\dot{q} \preceq w \end{array}

where WRn×pW\in {\mathbb{R}}^{n\times p} and wRpw\in {\mathbb{R}}^p represent pp linear constraints related to the nn degrees-of-freedom of the robot. Note that we use \preceq and not \le because we are representing element-wise inequalities.

You can read that QP as “find all q˙\dot{q} that minimize F(q˙)\mathcal{F}\left(\dot{q} \right), subject to Wq˙W\dot{q} less or equal to ww, and return one of them as uu.” In general, numerical methods are required to obtain a solution for a QP. Similarly to the unconstrained case, in this lesson we will call that minimizer as uu.

The damped pseudo-inverse as the solution of an unconstrained quadratic optimization problem

In the last lesson, the meaning of the damping factor was introduced in the point-of-view of the SVD of the robot’s task Jacobian.

The damping factor can be trivially understood when we re-write the robot control problem in the following way

uarg  minq˙ Jq˙+ηx~2+λ2q˙2u\in \underset{\dot{q} }{\arg \;\min } ~||J\dot{q} +\eta \tilde{x} ||^2 +\lambda^2 ||\dot{q} ||^2

where JRm×nJ\in {\mathbb{R}}^{m\times n} is the task Jacobian for an nn- DoF robot and an mm- DoF task. In addition, ηR+{0}\eta \in {\mathbb{R}}^+ -\lbrace 0\rbrace is the proportional gain of the controller and x~Rm\tilde{x} \in {\mathbb{R}}^m is the task error. Lastly, λR\lambda \in \mathbb{R} is the damping factor.

Note that we are balancing two terms in the objective function. The first term, Jq˙+ηx~2||J\dot{q} +\eta \tilde{x} ||^2, will be high when the task error is high and zero when the task error is zero. The second term, λ2q˙2\lambda^2 ||\dot{q} ||^2, will penalize high joint velocities depending on the size of the damping factor. As we saw in practice in the last lesson, the damping factor is used as a term to balance task error convergence and the norm of the joint velocities.

Given that we have an unconstrained quadratic optimization problem which is, by definition, convex, the solution is straightforward. The optimum is where the first-order partial derivative of the objective function with respect to the decision variable equals to zero. Hence

q˙(Jq˙+ηx~2)=0q˙[(Jq˙+ηx~)T(Jq˙+ηx~)]=0q˙(q˙TJTJq˙+2q˙TJTx~+η2x~Tx~+λ2q˙Tq˙)=02JTJq˙+2JTx~+2λ2q˙=0(JTJ+λ2I)q˙=JTx~u(JTJ+λ2I)1JTx~\begin{align} \frac{\partial }{\partial \dot{q} }\left(||J\dot{q} +\eta \tilde{x} ||^2 \right) &= 0 \\ \frac{\partial }{\partial \dot{q} }\left\lbrack (J\dot{q} +\eta \tilde{x} )^T (J\dot{q} +\eta \tilde{x} )\right\rbrack &= 0 \\ \frac{\partial }{\partial \dot{q} }\left({\dot{q} }^T J^T J\dot{q} +2{\dot{q} }^T J^T \tilde{x} +\eta^2 {\tilde{x} }^T \tilde{x} +\lambda^2 {\dot{q} }^T \dot{q} \right) &= 0 \\ 2J^T J\dot{q} +2J^T \tilde{x} +2\lambda^2 \dot{q} &= 0 \\ \left(J^T J+\lambda^2 I\right)\dot{q} &= -J^T \tilde{x} \\ \therefore u &\triangleq -{\left(J^T J+\lambda^2 I\right)}^{-1} J^T \tilde{x} \\ \end{align}

Notice that this solution is exactly the same as the damped pseudo-inverse used in the last lesson.

Joint limits

Using unconstrained optimization to solve robot-control tasks is useful in some cases, but most tasks in practice have what we call hard limits. They are called that way in the sense that those limits are joint-space or task-space limits that cannot be trespassed. For instance, joint limits are a physical type of hard limit. The controller cannot command the robot to move over its joint limits.

When performing kinematic control, the decision variables are the joint velocities and not the joint positions. Therefore, we need to write the joint limits as a linear inequality that depends on the joint velocities.

Suppose that the lower joint limits are qRnq^- \in {\mathbb{R}}^n and the upper joint limits are q+Rnq^+ \in {\mathbb{R}}^n. The most common way of implementing joint limits using a QP is as follows

uarg  minq˙   Jq˙+ηx~2+λ2q˙2    subject  to WJLq˙wJL\begin{array}{l} u\in \underset{\dot{q} }{\arg \;\min } ~~~||J\dot{q} +\eta \tilde{x} ||^2 +\lambda^2 ||\dot{q} ||^2 \\ ~~~~\textrm{subject}\;\textrm{to}~W_{JL} \dot{q} \preceq w_{JL} \end{array}

where

WJL=[II]W_{JL} =\left\lbrack \begin{array}{c} -I\\ I \end{array}\right\rbrack

and

wJL=[ηJL(qq)ηJL(q+q)].w_{JL} =\left\lbrack \begin{array}{c} -\eta_{JL} \left(q^- -q\right)\\ \eta_{JL} \left(q^+ -q\right) \end{array}\right\rbrack .

where ηJLR+{0}\eta_{JL} \in {\mathbb{R}}^+ -\lbrace 0\rbrace is a configurable gain. In general, we choose ηJL=1\eta_{JL} =1.

The main idea behind this constraint is to constrain the velocity of the joint in the direction of the joint limit.

DQ Robotics Example

To solve QPs as described in this lesson using DQ Robotics, use the following class

from dqrobotics.robot_control import DQ_ClassicQPController
help(DQ_ClassicQPController)
Help on class DQ_ClassicQPController in module dqrobotics._dqrobotics._robot_control:

class DQ_ClassicQPController(DQ_QuadraticProgrammingController)
 |  Method resolution order:
 |      DQ_ClassicQPController
 |      DQ_QuadraticProgrammingController
 |      DQ_KinematicConstrainedController
 |      DQ_KinematicController
 |      pybind11_builtins.pybind11_object
 |      builtins.object
 |
 |  Methods defined here:
 |
 |  __init__(...)
 |      __init__(self: dqrobotics._dqrobotics._robot_control.DQ_ClassicQPController, arg0: dqrobotics._dqrobotics._robot_modeling.DQ_Kinematics, arg1: dqrobotics._dqrobotics._solvers.DQ_QuadraticProgrammingSolver) -> None
 |
 |  compute_objective_function_linear_component(...)
 |      compute_objective_function_linear_component(self: dqrobotics._dqrobotics._robot_control.DQ_ClassicQPController, arg0: numpy.ndarray[numpy.float64[m, n]], arg1: numpy.ndarray[numpy.float64[m, 1]]) -> numpy.ndarray[numpy.float64[m, 1]]
 |
 |      Compute the objective function.
 |
 |  compute_objective_function_symmetric_matrix(...)
 |      compute_objective_function_symmetric_matrix(self: dqrobotics._dqrobotics._robot_control.DQ_ClassicQPController, arg0: numpy.ndarray[numpy.float64[m, n]], arg1: numpy.ndarray[numpy.float64[m, 1]]) -> numpy.ndarray[numpy.float64[m, n]]
 |
 |      Compute symmetric matrix.
 |
 |  ----------------------------------------------------------------------
 |  Methods inherited from DQ_QuadraticProgrammingController:
 |
 |  compute_setpoint_control_signal(...)
 |      compute_setpoint_control_signal(self: dqrobotics._dqrobotics._robot_control.DQ_QuadraticProgrammingController, arg0: numpy.ndarray[numpy.float64[m, 1]], arg1: numpy.ndarray[numpy.float64[m, 1]]) -> numpy.ndarray[numpy.float64[m, 1]]
 |
 |      Compute the setpoint control signal.
 |
 |  compute_tracking_control_signal(...)
 |      compute_tracking_control_signal(self: dqrobotics._dqrobotics._robot_control.DQ_QuadraticProgrammingController, arg0: numpy.ndarray[numpy.float64[m, 1]], arg1: numpy.ndarray[numpy.float64[m, 1]], arg2: numpy.ndarray[numpy.float64[m, 1]]) -> numpy.ndarray[numpy.float64[m, 1]]
 |
 |      Compute the tracking control signal.
 |
 |  ----------------------------------------------------------------------
 |  Methods inherited from DQ_KinematicConstrainedController:
 |
 |  set_equality_constraint(...)
 |      set_equality_constraint(self: dqrobotics._dqrobotics._robot_control.DQ_KinematicConstrainedController, arg0: numpy.ndarray[numpy.float64[m, n]], arg1: numpy.ndarray[numpy.float64[m, 1]]) -> None
 |
 |      Sets equality constraints.
 |
 |  set_inequality_constraint(...)
 |      set_inequality_constraint(self: dqrobotics._dqrobotics._robot_control.DQ_KinematicConstrainedController, arg0: numpy.ndarray[numpy.float64[m, n]], arg1: numpy.ndarray[numpy.float64[m, 1]]) -> None
 |
 |      Sets inequality constraints.
 |
 |  ----------------------------------------------------------------------
 |  Methods inherited from DQ_KinematicController:
 |
 |  get_control_objective(...)
 |      get_control_objective(self: dqrobotics._dqrobotics._robot_control.DQ_KinematicController) -> dqrobotics._dqrobotics._robot_control.ControlObjective
 |
 |      Gets the control objective
 |
 |  get_damping(...)
 |      get_damping(self: dqrobotics._dqrobotics._robot_control.DQ_KinematicController) -> float
 |
 |      Gets the damping.
 |
 |  get_gain(...)
 |      get_gain(self: dqrobotics._dqrobotics._robot_control.DQ_KinematicController) -> float
 |
 |      Gets the controller gain
 |
 |  get_jacobian(...)
 |      get_jacobian(self: dqrobotics._dqrobotics._robot_control.DQ_KinematicController, arg0: numpy.ndarray[numpy.float64[m, 1]]) -> numpy.ndarray[numpy.float64[m, n]]
 |
 |      Gets the Jacobian
 |
 |  get_last_error_signal(...)
 |      get_last_error_signal(self: dqrobotics._dqrobotics._robot_control.DQ_KinematicController) -> numpy.ndarray[numpy.float64[m, 1]]
 |
 |      Gets the last error signal
 |
 |  get_task_variable(...)
 |      get_task_variable(self: dqrobotics._dqrobotics._robot_control.DQ_KinematicController, arg0: numpy.ndarray[numpy.float64[m, 1]]) -> numpy.ndarray[numpy.float64[m, 1]]
 |
 |      Gets the task variable
 |
 |  is_set(...)
 |      is_set(self: dqrobotics._dqrobotics._robot_control.DQ_KinematicController) -> bool
 |
 |      Checks if the controller's objective has been set
 |
 |  reset_stability_counter(...)
 |      reset_stability_counter(self: dqrobotics._dqrobotics._robot_control.DQ_KinematicController) -> None
 |
 |      Resets the stability counter
 |
 |  set_control_objective(...)
 |      set_control_objective(self: dqrobotics._dqrobotics._robot_control.DQ_KinematicController, arg0: dqrobotics._dqrobotics._robot_control.ControlObjective) -> None
 |
 |      Sets the control objective
 |
 |  set_damping(...)
 |      set_damping(self: dqrobotics._dqrobotics._robot_control.DQ_KinematicController, arg0: float) -> None
 |
 |      Sets the damping.
 |
 |  set_gain(...)
 |      set_gain(self: dqrobotics._dqrobotics._robot_control.DQ_KinematicController, arg0: float) -> None
 |
 |      Sets the controller gain
 |
 |  set_primitive_to_effector(...)
 |      set_primitive_to_effector(self: dqrobotics._dqrobotics._robot_control.DQ_KinematicController, arg0: dqrobotics._dqrobotics.DQ) -> None
 |
 |      Sets the effector primitive
 |
 |  set_stability_counter_max(...)
 |      set_stability_counter_max(self: dqrobotics._dqrobotics._robot_control.DQ_KinematicController, arg0: int) -> None
 |
 |      Sets the maximum of the stability counter
 |
 |  set_stability_threshold(...)
 |      set_stability_threshold(self: dqrobotics._dqrobotics._robot_control.DQ_KinematicController, arg0: float) -> None
 |
 |      Sets the stability threshold
 |
 |  set_target_primitive(...)
 |      set_target_primitive(self: dqrobotics._dqrobotics._robot_control.DQ_KinematicController, arg0: dqrobotics._dqrobotics.DQ) -> None
 |
 |      Sets the target primitive
 |
 |  system_reached_stable_region(...)
 |      system_reached_stable_region(self: dqrobotics._dqrobotics._robot_control.DQ_KinematicController) -> bool
 |
 |      Checks if the controller has stabilized
 |
 |  ----------------------------------------------------------------------
 |  Static methods inherited from pybind11_builtins.pybind11_object:
 |
 |  __new__(*args, **kwargs) class method of pybind11_builtins.pybind11_object
 |      Create and return a new object.  See help(type) for accurate signature.

To solve a QP, you have to choose the numerical solver that will be used by the controller. MATLAB has a solver in its optimization toolbox called “quadprog.” To use it, first instantiate the robot and the solver

from dqrobotics.solvers import DQ_QuadprogSolver
# Solver definition
qp_solver = DQ_QuadprogSolver()

and then instantiate the controller and set the control objective, the gain, and the damping factor

# Controller definition
translation_controller = DQ_ClassicQPController(seven_dof_planar_robot, qp_solver)
translation_controller.set_control_objective(ControlObjective.Translation)
translation_controller.set_gain(10)
translation_controller.set_damping(1)

Let us define the following joint limits

# Joint limits
q_minus = -(pi/8) * np.ones(7)
q_plus  =  (pi/8) * np.ones(7)

print(f"q_minus = {q_minus}")
print(f"q_plus = {q_plus}")
q_minus = [-0.39269908 -0.39269908 -0.39269908 -0.39269908 -0.39269908 -0.39269908
 -0.39269908]
q_plus = [0.39269908 0.39269908 0.39269908 0.39269908 0.39269908 0.39269908
 0.39269908]

We define the other control-related variables as in the last lesson.

# Desired translation (pure quaternion)
td = 7 * j_
# Sampling time [s]
tau = 0.01
# Simulation time [s]
time_final = 4
# Initial joint values [rad]
q = np.zeros(7)
# Store the joint values
stored_q = []

# Translation controller loop.
for time in np.arange(0, time_final + tau,tau):
    # Store q for posterior animation
    stored_q.append(q)

    # The linear inequalities depend on the current joint values, so we calculate them inside the control loop.
    # Define the linear inequality matrix and the linear inequality vector
    Wjl = np.vstack((-np.eye(7), np.eye(7)))
    wjl = np.hstack((-1 * (q_minus-q), 1 * (q_plus-q)))

    # We then add them to the controller using the following method
    # Update the linear inequalities in the controller
    translation_controller.set_inequality_constraint(Wjl, wjl)

    # The rest of the control loop is unchanged with respect to prior lessons.
    # Get the next control signal [rad/s]
    u = translation_controller.compute_setpoint_control_signal(q, vec4(td))

    # Move the robot
    q = q + u * tau

Plot related code with reusable functions animate_robot and setup_plot.

# Animation function
def animate_robot(n, robot, stored_q, xd):
    dof = robot.get_dim_configuration_space()

    plt.cla()
    plot(robot, q=stored_q[n])
    plot(xd)
    plt.xlabel('x [m]')
    plt.xlim([-dof, dof])
    plt.ylabel('y [m]')
    plt.ylim([-dof, dof])
    ax = plt.gca()
    ax.axes.zaxis.set_ticklabels([])
    plt.title(f'Translation control time={time} s out of {time_final} s')

def setup_plot():
    fig = plt.figure()
    ax = plt.axes(projection='3d')
    ax.set_proj_type('ortho')
    ax.view_init(azim=0, elev=90) #https://stackoverflow.com/questions/33084853/set-matplotlib-view-to-be-normal-to-the-x-y-plane-in-python
    return fig, ax

fig, ax = setup_plot()
anm.FuncAnimation(fig, 
                  partial(animate_robot, robot=seven_dof_planar_robot, stored_q=stored_q, xd = 1 + 0.5*E_*td), 
                  frames=len(stored_q))
Loading...

We can then verify that the joint limits have been kept.

fig = plt.figure()
for i in range(0, 7):
    plt.subplot(3,3,i+1)
    np_stored_q = np.array(stored_q)
    plt.plot(np_stored_q[:,i])
    plt.plot(np.ones(np_stored_q.shape[0]) * q_minus[i],'k')
    plt.plot(np.ones(np_stored_q.shape[0]) * q_plus[i],'k')
    plt.title(f'$q_{i}$')
    plt.ylim([-1, 1])
    plt.ylabel('[rad]')
    plt.xlabel('Iteration')
fig.tight_layout()
<Figure size 640x480 with 7 Axes>

Note that the robot was unable to reach the desired task-space translation due to the joint limits, as shown by the black lines representing the joint limits. This is exactly what we wanted: a controller that will get as close as possible to the target while maintaining the robot’s joint limits.

Vector-field inequalities: task-space collision avoidance

We have just now seen how to use linear inequalities to prevent the robot from trespassing on its joint limits. The joint limits are one of the few applications of joint-space constraints. In most cases, we are interested in having task-space constraints. That is, constraints that are defined at the task space. The difficulty in doing so is writing the task-space constraint as linear constraints in joint space.

An effective way of doing so is called vector-field inequalities (VFIs), presented in detail in the following paper.

[1] Marinho, M. M; Adorno, B. V; Harada, K.; and Mitsuishi, M. Dynamic Active Constraints for Surgical Robots using Vector Field Inequalities. IEEE Transactions on Robotics (T-RO), 35(5): 1166–1185. October 2019.

The VFIs are described in detail in [1], so to simplify the discussion for this lesson, consider the (signed) distance dd(q)Rd\triangleq d\left(q\right)\in \mathbb{R} to be the relation between some entity in the robot, such as a point, line, or plane, and another entity in the workspace.

Keeping the robot entity outside a restricted zone

Consider the situation of keeping the robot entity outside a restricted zone. We define the distance error in such a way that the distance error is positive whenever the robot is outside the restricted zone, zero when at the border, and negative when inside the restricted zone. The distance error is then d~ddsafe\tilde{d} \triangleq d-d_{\textrm{safe}}, where dsafeRd_{\textrm{safe}} \in \mathbb{R} is the safe distance, that we suppose to be constant in time in this lesson.

To obtain the general formulation of the VFIs, we start with the following task-space inequality, that will keep our robot outside a restricted zone

d˙~ηdd~,\tilde{\dot{d} } \ge -\eta_d \tilde{d} ,

where ηdR+{0}\eta_d \in {\mathbb{R}}^+ -\lbrace 0\rbrace is a proportional gain. To be used in our QP, we need to re-write this as a joint-space linear constraint, which is what the VFI method does.

The VFIs are then given by

d˙~=d˙d˙safe=d˙ηdd~.\tilde{\dot{d} } =\dot{d} -{\dot{d} }_{\textrm{safe}} =\dot{d} \ge -\eta_d \tilde{d} .

Given that the safe distance is constant in time. Now, notice that if the distance depends on the joint values, we can find a distance Jacobian such that the following holds

d˙=Jdq˙,\dot{d} =J_d \dot{q} ,

where JdR1×nJ_d \in {\mathbb{R}}^{1\times n} is the distance Jacobian for that specific pair of entities.

The linear constraint for that pair of entities, with respect to the joint velocities, then becomes

Jdq˙ηdd~.J_d \dot{q} \ge -\eta_d \tilde{d} .

That constraint can be re-written to be compatible with our QP definition as

Jdq˙ηdd~.-J_d \dot{q} \le \eta_d \tilde{d} .

Keeping the robot entity inside a safe zone

The reasoning is basically the same of the restricted zone case, but the distance error is redefined as d~dsafed\tilde{d} \triangleq d_{\textrm{safe}} -d. This causes the linear constraint to change to

Jdq˙ηdd~.J_d \dot{q} \le \eta_d \tilde{d} .

Square distances instead of distances

Calculating the distance Jacobians of the distance of some primitives introduces algorithmic singularities, as shown in [2]. To handle that problem, we often calculate the Jacobian of the square distance instead [1]. The notation for square distances is the upper-case letter DD.

[2] Marinho, M. M; Adorno, B. V; Harada, K.; and Mitsuishi, M. Active Constraints using Vector Field Inequalities for Surgical Robots. In 2018 IEEE International Conference on Robotics and Automation (ICRA), pages 5364–5371, May 2018.

Distance Jacobians

In lesson 4, we learned about distance functions using dual quaternions, and in lesson 6 you learned how to calculate a few Jacobians. Using that knowledge, it is easy to calculate the distance Jacobians related to the distance functions.

We will go through how to calculate all the distance Jacobians described in [1] using DQ Robotics, but we will skip most of the mathematical derivations because they are already described in detail in the paper.

Preliminaries

To calculate the distance Jacobian, the following two definitions are useful.

First, the cross-product between a,bHpa,b\in {\mathbb{H}}_p can be mapped into R4{\mathbb{R}}^4 using the operator Sˉ(.)\bar{S} \left(.\right), as follows [1, Eq. (3)]

vec4(a×b)=Sˉ(a)vec4(b)=Sˉ(b)Tvec4(a){\textrm{vec}}_4 \left(a\times b\right)=\bar{S} \left(a\right){\textrm{vec}}_4 \left(b\right)=\bar{S} {\left(b\right)}^T {\textrm{vec}}_4 \left(a\right) ,

Second, the time derivative of the squared norm of a time-varying quaternion h(t)Hph\left(t\right)\in {\mathbb{H}}_p is given by [1, Eq. (4)]

ddt(h2)=h˙h+hh˙=2<h˙,h>.\frac{d}{dt}\left(||h||^2 \right)=\dot{h} h^* +h{\dot{h} }^* =2<\dot{h} ,h>.

Two new task Jacobians are also needed. Similarly, to how we obtained the translation, rotation, and pose Jacobians, we can also obtain the Jacobians related to lines and planes in the robot.

Line Jacobian

Suppose that we want the Jacobian related to line with direction lvHpS3l_v \in {\mathbb{H}}_p \cap {\mathbb{S}}^3 that passes through the end-effector. The line will be given by [1, Eq. (24)]

lvlv(q)=lv+εmv.{\underline{l} }_v \triangleq {\underline{l} }_v \left(q\right)=l_v +\varepsilon m_v .

Given our definition of line, we have the following [1, Eq. (28)]

vec8l˙v=Jlvq˙.{\textrm{vec}}_8 {\dot{\underline{l} } }_v =J_{l_v } \dot{q}.

Using DQ Robotics, we can obtain the line Jacobian as follows

# Get robot's pose and pose Jacobian
x = seven_dof_planar_robot.fkm(q)
Jx = seven_dof_planar_robot.pose_jacobian(q)

# Define the line direction
lv = k_

# Get the line Jacobian
Jlv = DQ_Kinematics.line_jacobian(Jx, x, lv)

print(f"Jlv = {Jlv}")
Jlv = [[ 0.00000000e+00  0.00000000e+00  0.00000000e+00  0.00000000e+00
   0.00000000e+00  0.00000000e+00  0.00000000e+00]
 [ 0.00000000e+00  0.00000000e+00  0.00000000e+00  0.00000000e+00
   0.00000000e+00  0.00000000e+00  0.00000000e+00]
 [ 0.00000000e+00  0.00000000e+00  0.00000000e+00  0.00000000e+00
   0.00000000e+00  0.00000000e+00  0.00000000e+00]
 [ 0.00000000e+00 -5.55111512e-17 -5.55111512e-17  0.00000000e+00
   0.00000000e+00  0.00000000e+00  0.00000000e+00]
 [ 0.00000000e+00  0.00000000e+00  0.00000000e+00  0.00000000e+00
   0.00000000e+00  0.00000000e+00  0.00000000e+00]
 [ 7.45479631e-01 -1.81048005e-01 -8.97954928e-01 -1.29989544e+00
  -1.32780652e+00 -9.77586757e-01 -4.86904393e-01]
 [ 5.67013315e+00  5.29390644e+00  4.59673754e+00  3.68107176e+00
   2.68146135e+00  1.74479379e+00  8.73455272e-01]
 [ 0.00000000e+00  0.00000000e+00  0.00000000e+00  0.00000000e+00
   0.00000000e+00  0.00000000e+00  0.00000000e+00]]

where JlrR8×nJ_{l_r } \in {\mathbb{R}}^{8\times n} is the line Jacobian. It can be decomposed into

Jlv=[JrvJmv],J_{l_v } =\left\lbrack \begin{array}{c} J_{r_v } \\ J_{m_v } \end{array}\right\rbrack ,

where JrvR4×nJ_{r_v } \in {\mathbb{R}}^{4\times n} is composed of the first four rows of the line Jacobian, which are related to the direction of the line. In addition, JmvR4×nJ_{m_v } \in {\mathbb{R}}^{4\times n} is composed of the last four rows of the line Jacobian, which are related to the line moment.

Plane Jacobian

Lastly, the Jacobian related to the plane in the end-effector with normal nπvHpS3n_{\pi_v } \in {\mathbb{H}}_p \cap {\mathbb{S}}^3 will be given by

vec8π˙v=Jπvq˙,{\textrm{vec}}_8 {\dot{\underline{{{\pi }}} } }_v =J_{\pi_v } \dot{q},

where

Jπv=[JrvJdπv],J_{\pi_v } =\left\lbrack \begin{array}{c} J_{r_v } \\ J_{d_{\pi_v } } \end{array}\right\rbrack ,

and

Jdπv=vec4(nπv)TJt+vec4(t)TJrvJ_{d_{\pi_v } } ={\textrm{vec}}_4 {\left(n_{\pi_v } \right)}^T J_t +{\textrm{vec}}_4 {\left(t\right)}^T J_{r_v }
# Get robot's pose and pose Jacobian
x = seven_dof_planar_robot.fkm(q)
Jx = seven_dof_planar_robot.pose_jacobian(q)

# Define the line direction
piv = k_

# Get the line Jacobian
Jpiv = DQ_Kinematics.plane_jacobian(Jx, x, piv)

print(f"Jpiv = {Jpiv}")
Jpiv = [[ 0.00000000e+00  0.00000000e+00  0.00000000e+00  0.00000000e+00
   0.00000000e+00  0.00000000e+00  0.00000000e+00]
 [ 0.00000000e+00  0.00000000e+00  0.00000000e+00  0.00000000e+00
   0.00000000e+00  0.00000000e+00  0.00000000e+00]
 [ 0.00000000e+00  0.00000000e+00  0.00000000e+00  0.00000000e+00
   0.00000000e+00  0.00000000e+00  0.00000000e+00]
 [ 0.00000000e+00 -5.55111512e-17 -5.55111512e-17  0.00000000e+00
   0.00000000e+00  0.00000000e+00  0.00000000e+00]
 [ 0.00000000e+00  0.00000000e+00  0.00000000e+00  0.00000000e+00
   0.00000000e+00  0.00000000e+00  0.00000000e+00]
 [ 0.00000000e+00  0.00000000e+00  0.00000000e+00  0.00000000e+00
   0.00000000e+00  0.00000000e+00  0.00000000e+00]
 [ 0.00000000e+00  0.00000000e+00  0.00000000e+00  0.00000000e+00
   0.00000000e+00  0.00000000e+00  0.00000000e+00]
 [ 0.00000000e+00  0.00000000e+00  0.00000000e+00  0.00000000e+00
   0.00000000e+00  0.00000000e+00  0.00000000e+00]]

Point-to-point distance Jacobian

Let the translation of the robot’s point entity be t(q)tHpt\left(q\right)\triangleq t\in {\mathbb{H}}_p and the position of a point in the workspace be pHpp\in {\mathbb{H}}_p. The squared-distance between those two points is, as mentioned in lesson 4, is [1, Eq. (21)]

Dt,p=tp2.D_{t,p} =\left|\right|\mathit{\mathbf{t}}-\mathit{\mathbf{p}}\left|{\left|\right.}^2 \ldotp \right.

The derivative of the squared-distance is, therefore [1, Eq. (22)]

  D˙t,p=2vec4(tp)Tvec4(  t˙).{\dot{\;D} }_{t,p} =2{\textrm{vec}}_4 {\left(\mathit{\mathbf{t}}-\mathit{\mathbf{p}}\right)}^T {\textrm{vec}}_4 \left(\dot{\;\mathit{\mathbf{t}}} \right)\ldotp

This means that the point-to-point distance Jacobian is given by

Jt,p=2vec4(tp)TJt.{\mathit{\mathbf{J}}}_{t,p} =2{\textrm{vec}}_4 {\left(\mathit{\mathbf{t}}-\mathit{\mathbf{p}}\right)}^T {\mathit{\mathbf{J}}}_t \ldotp

Using DQ Robotics, it can be calculated as

# Point in the workspace
p = 2 * j_

# Get robot's pose and pose Jacobian
x = seven_dof_planar_robot.fkm(q)
Jx = seven_dof_planar_robot.pose_jacobian(q)

# Get the robot's translation and translation Jacobian
t = translation(x)
Jt = DQ_Kinematics.translation_jacobian(Jx, x)

# Get the distance Jacobian
Jp_p = DQ_Kinematics.point_to_point_distance_jacobian(Jt, t, p)

print(f"Jp_p = {Jp_p}")
Jp_p = [[ -2.98191852  -9.22193941 -13.44477671 -15.02990676 -13.74440306
   -9.77716359  -4.87629414]]

Point-to-line distance Jacobian

Let lHpS\underline{l} \in {\mathcal{H}}_p \cap \underline{{\mathcal{S}}} be a line entity in the workspace. The square distance between a point entity in the robot and that line is [1, Eq. (29)]

Dt,l=t×lm2.D_{t,l} =\left|\right|\mathit{\mathbf{t}}\times \mathit{\mathbf{l}}-\mathit{\mathbf{m}}\left|{\left|\right.}^2 \ldotp \right.

The point-to-line distance Jacobian is, therefore [1, Eq. (32)]

Jt,l=2vec4(t×lm)TSˉ(l)TJt.{\mathit{\mathbf{J}}}_{t,l} =2{\textrm{vec}}_4 {\left(\mathit{\mathbf{t}}\times \mathit{\mathbf{l}}-\mathit{\mathbf{m}}\right)}^T \bar{\mathit{\mathbf{S}}} {\left(\mathit{\mathbf{l}}\right)}^T {\mathit{\mathbf{J}}}_t \ldotp

Using DQ Robotics, it can be calculated as

# Line in the workspace
l_dq = j_

# Get robot's pose and pose Jacobian
x = seven_dof_planar_robot.fkm(q)
Jx = seven_dof_planar_robot.pose_jacobian(q)

# Get the robot's translation and translation Jacobian
t = translation(x)
Jt = DQ_Kinematics.translation_jacobian(Jx, x)

# Get the distance Jacobian
Jt_l = DQ_Kinematics.point_to_line_distance_jacobian(Jt, t, l_dq)

print(f"Jt_l = {Jt_l}")
Jt_l = [[-8.45393754 -7.89299883 -6.85354842 -5.48832804 -3.99794964 -2.60141647
  -1.30228623]]

Line-to-point distance Jacobian

Let lv(q)lvHpS{\underline{l} }_v \left(q\right)\triangleq {\underline{l} }_v \in {\mathcal{H}}_p \cap \underline{{\mathcal{S}}} be a line entity related to the robot. The square distance between that line and a point in the workspace is given by [1, Eq. (33)]

Dlv,p=p×lvmv2D_{l_v, p} =\left|\right|\mathit{\mathbf{p}}\times {\mathit{\mathbf{l}}}_v -{\mathit{\mathbf{m}}}_v \left|{\left|\right.}^2 \right.

The line-to-point distance Jacobian is a bit more involved, as described in [1, Eq. (34)].

Using DQ Robotics, it can be calculated as

# Point in the workspace
p = 2 * j_

# Get robot's pose and pose Jacobian
x = seven_dof_planar_robot.fkm(q)
Jx = seven_dof_planar_robot.pose_jacobian(q)

# Get the robot's translation and translation Jacobian
t = translation(x)
Jt = DQ_Kinematics.translation_jacobian(Jx, x)

# Define the direction of the line passing through the end-effector and get
# the line Jacobian
lv = k_
Jl = DQ_Kinematics.line_jacobian(Jx, x, lv)

# Get the robot's rotation
r = rotation(x)
# Get the robot line
lv_dq = Ad(r,lv) + E_*cross(t, Ad(r,lv))

# Get the distance Jacobian
Jlv_p = DQ_Kinematics.line_to_point_distance_jacobian(Jl, lv_dq, p)

print(f"Jlv_p = {Jlv_p}")
Jlv_p = [[ -2.98191852  -9.22193941 -13.44477671 -15.02990676 -13.74440306
   -9.77716359  -4.87629414]]

Line-to-line distance Jacobian

Suppose that we want to calculate the Jacobian that relates the distance between a line entity in the robot, lv{\underline{l} }_v, and a line in the workspace l.\underline{l} . The mathematical derivation is described in detail in [1, Eq. (48)]. To obtain the line-to-line distance Jacobian using DQ Robotics, we do

# Line in the workspace
l_dq = j_

# Get robot's pose and pose Jacobian
x = seven_dof_planar_robot.fkm(q)
Jx = seven_dof_planar_robot.pose_jacobian(q)

# Get the robot's translation
t = translation(x)

# Define the direction of the line passing through the end-effector and get
# the line Jacobian
lv_e = k_ # From the point-of-view of the end-effector
Jl = DQ_Kinematics.line_jacobian(Jx, x, lv_e)

# Get the robot's rotation
r = rotation(x)
# Get the robot line direction
lv = Ad(r,lv) #  (in the point-of-view of the base)
lv_dq = lv + E_*cross(t, lv) #  (in the point-of-view of the base)

# Get the distance Jacobian
Jlv_p = DQ_Kinematics.line_to_line_distance_jacobian(Jl, lv_dq, l_dq)

print(f"Jlv_p = {Jlv_p}")
Jlv_p = [[-8.45393754 -7.89299883 -6.85354842 -5.48832804 -3.99794964 -2.60141647
  -1.30228623]]

Point-to-plane distance Jacobian

Let π{P(π)Hp}S\underline{{{\pi }}} \in \left\lbrace P\left(\underline{{{\pi }}} \right)\in {\mathbb{H}}_p \right\rbrace \cap \underline{{\mathcal{S}}} be a plane entity in the workspace. The signed distance between the robot’s translation, tt, and the plane, in the point of view of the plane is given by [1, Eq. (57)].

dt,π  π  =<t,nπ  >dπ    d_{t,\pi \;}^{\pi \;} =<\mathit{\mathbf{t}},{\mathit{\mathbf{n}}}_{\pi \;} >-d_{\pi \;\;}

The point-to-plane distance Jacobian will therefore be given by [1, Eq. (59)]

Jt,π=vec4(nπ)TJt.J_{t,\pi } ={\textrm{vec}}_4 {\left(n_{\pi } \right)}^T J_t .

Using DQ Robotics, it can be obtained as

# Define the plane in the workspace
pi_w = i_

# Get robot's pose and pose Jacobian
x = seven_dof_planar_robot.fkm(q)
Jx = seven_dof_planar_robot.pose_jacobian(q)

# Get the robot's translation and translation Jacobian
t = translation(x)
Jt = DQ_Kinematics.translation_jacobian(Jx, x)

# Get the distance Jacobian
Jt_pi = DQ_Kinematics.point_to_plane_distance_jacobian(Jt, t, pi_w)

print(f"Jt_pi = {Jt_pi}")
Jt_pi = [[-5.67013315 -5.29390644 -4.59673754 -3.68107176 -2.68146135 -1.74479379
  -0.87345527]]

Plane-to-point distance Jacobian

Let πv(q)πv{P(πv)Hp}S{\underline{{{\pi }}} }_v \left(q\right)\triangleq {\underline{{{\pi }}} }_v \in \left\lbrace P\left({\underline{{{\pi }}} }_v \right)\in {\mathbb{H}}_p \right\rbrace \cap \underline{{\mathcal{S}}} be a plane entity in the robot. The signed distance between the robot’s plane entity, πv{\underline{{{\pi }}} }_v, and a point in the workspace, pp, in the point of view of the plane is given by [1, Eq. (57)]

dπ,p  π  =<p,nπv  >dπv.    d_{\pi ,p\;}^{\pi \;} =<\mathit{\mathbf{p}},{\mathit{\mathbf{n}}}_{\pi_v \;} >-d_{\pi_{v\ldotp } \;\;}

The plane-to-point distance Jacobian will be given by

Jπv,p=vec4(p)TJrvJdπv.J_{\pi_v ,p} ={\textrm{vec}}_4 {\left(p\right)}^T J_{r_v } -J_{d_{\pi_v } } .

Using DQ Robotics, it can be obtained as

# Point in the workspace
p = 2 * j_

# Get robot's pose and pose Jacobian
x = seven_dof_planar_robot.fkm(q)
Jx = seven_dof_planar_robot.pose_jacobian(q)

# Get the robot's translation and translation Jacobian
t = translation(x)
Jt = DQ_Kinematics.translation_jacobian(Jx, x)

# Get the robot rotation (to calculate the plane)
r = rotation(x)

# Get the robot's plane
n_pi_v_e = i_ # Plane's normal with respect to the end-effector
Jpi = DQ_Kinematics.plane_jacobian(Jx, x, n_pi_v_e)

# Get the distance Jacobian
Jpi_p = DQ_Kinematics.plane_to_point_distance_jacobian(Jpi, p)

print(f"Jpi_p = {Jpi_p}")
Jpi_p = [[-0.97380879  0.0186581   0.98429883  1.78121758  2.29231136  2.44247761
   2.43814707]]

Vector-field inequalities in action

We saw how to calculate several types of distances and distance Jacobians. Now we need to put this knowledge to good use.

Preventing collision with walls

Suppose that there is one wall above and one wall below our 7-DoF planar robot, and we want to prevent any collisions between the robot and those walls.

The top wall is given by

pi_top = -j_ - E_ * 2

and the bottom wall is given by

pi_bottom = j_ + -E_ * 2

Note that the normals of those planes are pointing towards the robot. This is important because it means that the distance of any point of the robot will be positive with respect to those planes. Mistaking the direction of the plane’s normal is a common error, so be careful.

Let us plot the two walls.

fig = plt.figure()
ax = plt.axes(projection='3d')
plot(pi_top, plane=True, scale=14)
plot(pi_bottom, plane=True, scale=14)
plt.title('Plane constraints')
<Figure size 640x480 with 1 Axes>

To prevent the collision between the robot and those walls, one strategy is to create one point-to-plane constraint for each of the robot’s joints with respect to those planes. This corresponds to a total of fourteen linear constraints.

Using DQ Robotics, they can be implemented as follows.

# Define a list of planes
plane_list = [pi_top, pi_bottom]

# Define the robot
seven_dof_planar_robot = SevenDofPlanarRobotDH.kinematics()

# Solver definition
qp_solver = DQ_QuadprogSolver()

# Controller definition
translation_controller = DQ_ClassicQPController(seven_dof_planar_robot, qp_solver)
translation_controller.set_control_objective(ControlObjective.Translation)
translation_controller.set_gain(10)
translation_controller.set_damping(1)

# VFI gain
eta_d = 1

# Desired translation (pure quaternion)
td = 7 * j_
# Sampling time [s]
tau = 0.01
# Simulation time [s]
final_time = 4
# Initial joint values [rad]
q = 0.01 * np.ones(7)

# Store the signals
stored_q = []

# Translation controller loop.
for time in np.arange(0, time_final + tau,tau):

    # Store q for posterior animation
    stored_q.append(q)

    # The inequality matrix and vector
    W = None
    w = None
    n = seven_dof_planar_robot.get_dim_configuration_space()

    for joint_counter in range(0, n):

        # Get the pose Jacobian and pose of the current joint
        Jx = seven_dof_planar_robot.pose_jacobian(q, joint_counter)
        x = seven_dof_planar_robot.fkm(q, joint_counter)

        # Get the translation Jacobian and the translation of the current
        # joint
        Jt = DQ_Kinematics.translation_jacobian(Jx, x)
        t = translation(x)

        for plane_counter in range (0, len(plane_list)):

            # Get the current plane
            workspace_plane = plane_list[plane_counter]

            # Calculate the point-to-plane distance Jacobian
            # We have to augment the Jp_pi with zeros
            Jp_pi = np.hstack((DQ_Kinematics.point_to_plane_distance_jacobian(Jt, t, workspace_plane), np.zeros((1, n-joint_counter-1))))

            # Calculate the point-to-plane distance
            dp_pi = DQ_Geometry.point_to_plane_distance(t, workspace_plane)

            # Wq <= w
            if W is None:
                W = -Jp_pi
            else:
                W = np.vstack((W, -Jp_pi))
            
            if w is None:
                w = np.array(eta_d * dp_pi)
            else:
                w = np.vstack((w, eta_d * dp_pi))


    # Update the linear inequalities in the controller
    translation_controller.set_inequality_constraint(W, w)

    # Get the next control signal [rad/s]
    u = translation_controller.compute_setpoint_control_signal(q, vec4(td))

    # Move the robot
    q = q + u * tau

Plot related code with reusable functions animate_robot_with_planes and setup_plot.

# Animation function
def animate_robot_with_planes(n, 
                  robot, 
                  stored_q, 
                  xd, 
                  pi_top, 
                  pi_bottom):
    dof = robot.get_dim_configuration_space()

    plt.cla()
    plot(robot, q=stored_q[n])
    plot(xd)
    plot(pi_top, plane=True, scale=14)
    plot(pi_bottom, plane=True, scale=14)
    plt.xlabel('x [m]')
    plt.xlim([-dof, dof])
    plt.ylabel('y [m]')
    plt.ylim([-dof, dof])
    ax = plt.gca()
    ax.axes.zaxis.set_ticklabels([])
    plt.title(f'Translation control time={time} s out of {time_final} s')

def setup_plot():
    fig = plt.figure()
    ax = plt.axes(projection='3d')
    ax.set_proj_type('ortho')
    ax.view_init(azim=0, elev=90) #https://stackoverflow.com/questions/33084853/set-matplotlib-view-to-be-normal-to-the-x-y-plane-in-python
    return fig, ax

fig, ax = setup_plot()
anm.FuncAnimation(fig, 
                  partial(animate_robot_with_planes, 
                          robot=seven_dof_planar_robot, 
                          stored_q=stored_q,
                          xd = 1 + 0.5*E_*td,
                          pi_top = pi_top,
                          pi_bottom = pi_bottom), 
                  frames=len(stored_q),
                  interval=int(tau*1000))
Loading...

Entry-sphere constraint

A widespread type of constraint in minimally invasive surgery is the entry-sphere constraint. Suppose there is an incision in the skin of the patient, positioned at

p = 5 * j_

Let us define the sphere to have a radius of 0.25 m. That is, we choose

d_safe = 0.25

Moreover, let us attach to our planar robot a 2-meter-long shaft whose axis is collinear with the xx- axis of the last link, as follows

# Define the initial joint configurations [rad]
q = (pi / 8.) * np.ones(7)

# Define the robot
seven_dof_planar_robot = SevenDofPlanarRobotDH.kinematics()

# Add the shaft
seven_dof_planar_robot.set_effector(1 + 0.5 * E_ * (2 * i_))
1 + E*(1i)

Let us control the robot to the following desired translation

# Desired translation
td = -1 * i_ + 4 * j_

In a way that the shaft never goes outside the entry-sphere. Using DQ Robotics, this can be done as follows

# Solver definition
qp_solver = DQ_QuadprogSolver()

# Controller definition
translation_controller = DQ_ClassicQPController(seven_dof_planar_robot, qp_solver)
translation_controller.set_control_objective(ControlObjective.Translation)
translation_controller.set_gain(10)
translation_controller.set_damping(1)

# VFI gain
eta_d = 1

# Sampling time [s]
tau = 0.01
# Simulation time [s]
final_time = 4

# Store the signals
stored_q = []

# Translation controller loop.
for time in np.arange(0, time_final + tau,tau):

    # Store q for posterior animation
    stored_q.append(q)

    # Get the pose Jacobian and the pose
    Jx = seven_dof_planar_robot.pose_jacobian(q)
    x = seven_dof_planar_robot.fkm(q)

    # Get the line Jacobian for the x-axis
    Jl = DQ_Kinematics.line_jacobian(Jx, x, i_)

    # Get the line with respect to the base
    t = translation(x)
    r = rotation(x)
    l = Ad(r, i_)
    l_dq = l + E_ * cross(t, l)

    # Get the line-to-point distance Jacobian
    Jl_p = DQ_Kinematics.line_to_point_distance_jacobian(Jl, l_dq, p)

    # Get the line-to-point square distance
    Dl_p = DQ_Geometry.point_to_line_squared_distance(p, l_dq)

    # Get the distance error
    D_safe = d_safe ** 2
    D_tilde = D_safe - Dl_p

    # The inequality matrix and vector
    W = np.array(Jl_p)
    w = np.array([eta_d * D_tilde])

    # Update the linear inequalities in the controller
    translation_controller.set_inequality_constraint(W, w)

    # Get the next control signal [rad/s]
    u = translation_controller.compute_setpoint_control_signal(q, vec4(td))

    # Move the robot
    q = q + u * tau

We define a plot function animate_robot_with_entry_sphere and reuse setup_plot defined earlier.

# Animation function
def animate_robot_with_entry_sphere(n, 
                  robot,
                  tau,
                  stored_q, 
                  xd, 
                  p_dq):
    dof = robot.get_dim_configuration_space()

    plt.cla()
    plot(robot, q=stored_q[n])
    plot(xd)
    plot(p_dq)
    plt.xlabel('x [m]')
    plt.xlim([-dof, dof])
    plt.ylabel('y [m]')
    plt.ylim([-dof, dof])
    ax = plt.gca()
    ax.axes.zaxis.set_ticklabels([])
    plt.title(f'Translation control time={n*tau} s out of {time_final} s')

fig, ax = setup_plot()
anm.FuncAnimation(fig, 
                  partial(animate_robot_with_entry_sphere, 
                          robot=seven_dof_planar_robot, 
                          tau=tau,
                          stored_q=stored_q,
                          xd = 1 + 0.5*E_*td,
                          p_dq = 1 + 0.5*E_*p), 
                  frames=len(stored_q),
                  interval=int(tau*1000))
Loading...

Homework

Create a class called VS050RobotDH that represents the Denso VS050 robot, whose DH parameters are described in the following table and is composed entirely of revolute joints. (Remember lesson 6)

[θdaαπ0.3450π2π200.250π200.01π200.2550π2π00π200.0700]\left\lbrack \begin{array}{cccc} \theta & d & a & \alpha \\ -\pi & 0.345 & 0 & \frac{\pi }{2}\\ \frac{\pi }{2} & 0 & 0.25 & 0\\ -\frac{\pi }{2} & 0 & 0.01 & -\frac{\pi }{2}\\ 0 & 0.255 & 0 & \frac{\pi }{2}\\ \pi & 0 & 0 & \frac{\pi }{2}\\ 0 & 0.07 & 0 & 0 \end{array}\right\rbrack

End-effector: attach to the robot a 20cm-long-shaft along the zz- axis of the last joint.

Initial configuration: q(0)=[0π3π30π30]T.q(0)={\left\lbrack \begin{array}{cccccc} 0 & \frac{\pi }{3} & \frac{\pi }{3} & 0 & \frac{\pi }{3} & 0 \end{array}\right\rbrack }^T .

  1. Create a file called vs050_plane_constraints.py and do the following:
    1. Create a translation controller with τ=0.01\tau =0.01. Choose the other controller parameters so that the control is smooth (see this and prior lessons for examples).
    2. Create 6 planes centered around the point p1=0.45ı^+0.08k^p_1 =0.45\hat{\imath} +0.08\hat{k}, so that all normals are pointed inwards, to make a 5 cm cube. (remember lesson 4)
    3. During control, use point-to-plane VFIs to keep the tooltip always inside the cubic region.
    4. Move the end-effector, in order, to the following four desired positions td={0.60ı^+0.08k^,0.20ı^+0.15k^,0.45ı^+0.08k^,0.45ı^+0.0k^}t_d =\left\lbrace 0.60\hat{\imath} +0.08\hat{k}, 0.20\hat{\imath} +0.15\hat{k}, 0.45\hat{\imath} +0.08\hat{k}, 0.45\hat{\imath} +0.0\hat{k} \right\rbrace, for 4 simulation-time seconds each.
    5. Plot the task error in one figure
    6. Plot the distance of the end-effector to all the six planes using subplots in another figure.
  2. Create a file called vs050_entry_sphere_constraint.py and do the following:
    1. Create a translation controller with τ=0.01\tau =0.01. Choose the other controller parameters so that the control is smooth (see this and prior lessons for examples).
    2. Consider the entry-sphere to be centered at p2=0.45ı^+0.2k^p_2 =0.45\hat{\imath} +0.2\hat{k}. Consider the entry sphere to have a radius of 5 mm.
    3. During control, use a line-to-point VFI to keep the shaft always inside the entry-sphere.
    4. Move the end-effector, in order, to the following four desired positions td={0.45ı^+0.03k^,0.48ı^+0.03k^,0.41ı^+0.03k^,0.40ı^+0.01k^}t_d =\left\lbrace 0.45\hat{\imath} +0.03\hat{k}, 0.48\hat{\imath} +0.03\hat{k}, 0.41\hat{\imath} +0.03\hat{k}, 0.40\hat{\imath} +0.01\hat{k} \right\rbrace, for 4 simulation-time seconds each.
    5. Plot the task error in one figure
    6. Plot the distance of the shaft to the center of the entry-sphere in another figure.