Last active
November 25, 2024 13:20
-
-
Save mlaves/a60cbc5541bd6c9974358fbaad9e4c51 to your computer and use it in GitHub Desktop.
Incremental Inverse Kinematics for Franka Emika Panda 7DoF Robot
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"nbformat": 4, | |
"nbformat_minor": 5, | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3", | |
"language": "python", | |
"name": "python3" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.8.8" | |
}, | |
"colab": { | |
"name": "incremental_ik-panda.ipynb", | |
"provenance": [], | |
"include_colab_link": true | |
} | |
}, | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "view-in-github", | |
"colab_type": "text" | |
}, | |
"source": [ | |
"<a href=\"https://colab.research.google.com/gist/mlaves/a60cbc5541bd6c9974358fbaad9e4c51/incremental_ik-panda.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "4f74e0ab" | |
}, | |
"source": [ | |
"# Incremental Inverse Kinematics for Franka Emika Panda 7DoF Robot\n", | |
"\n", | |
"The 3D pose $ \\mathbf{x} \\in \\mathbb{R}^{6} $ of the end effector (EE) is given by\n", | |
"\n", | |
"$$\n", | |
"\\mathbf{x} = \\mathbf{f}(\\mathbf{q}) ~ ,\n", | |
"$$\n", | |
"\n", | |
"with $ n $ joint angles $ \\mathbf{q} $ and direct kinematics $\\mathbf{f} : \\mathbb{R}^{n} \\to \\mathbb{R}^{6} $.\n", | |
"We are interested in solving the inverse kinematics $\\mathbf{q} = \\mathbf{f}^{-1}(\\mathbf{x})$, which we can do analytically (if possible), numerically (e.g., solve non-linear least-squares with Levenberg-Marquardt algorithm) or, as described here, using *incremental inverse kinematics* (IIK).\n", | |
"\n", | |
"In IIK, we linearize the direct kinematics at the current joint angle configuration $\\mathbf{q}^{\\ast}$\n", | |
"\n", | |
"$$\n", | |
"\\Delta \\left. \\mathbf{x}\\right|_{\\mathbf{x}^{\\ast}} \\propto \\Delta \\left. \\mathbf{q}\\right|_{\\mathbf{q}^{\\ast}} ~ ,\n", | |
"$$\n", | |
"\n", | |
"which we can easily solve around $ (\\mathbf{x}^{\\ast}, \\mathbf{q}^{\\ast}) $ using the Jacobian $ \\mathbf{J}_{\\mathbf{f}} := ( \\partial f_{i} / \\partial q_{j} )_{i,j} $\n", | |
"\n", | |
"$$\n", | |
"\\Delta \\left. \\mathbf{x}\\right|_{\\mathbf{x}^{\\ast}} = \\mathbf{J}_{\\mathbf{f}}(\\mathbf{q}^{\\ast}) \\Delta \\left. \\mathbf{q}\\right|_{\\mathbf{q}^{\\ast}} ~ .\n", | |
"$$" | |
], | |
"id": "4f74e0ab" | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "86a6d43c" | |
}, | |
"source": [ | |
"## Steps of Incremental Inverse Kinematics\n", | |
"\n", | |
"Given: target pose $ \\mathbf{x}^{(t)} $ \n", | |
"Sought: joint angles $ \\mathbf{q}^{(t)} $\n", | |
"\n", | |
"0. Define starting pose $ (\\mathbf{x}^{(0)}, \\mathbf{q}^{(0)}) $ and set up IIK: $ \\Delta \\left. \\mathbf{x}\\right|_{\\mathbf{x}^{(0)}} = \\mathbf{J}(\\mathbf{q}^{(0)}) \\Delta \\left. \\mathbf{q}\\right|_{\\mathbf{q}^{(0)}} $.\n", | |
"1. Determine the deviation $ \\Delta \\mathbf{x}^{(k)} $ relative to the target pose; e.g. $ (\\mathbf{x}^{(t)} - \\mathbf{x}^{(0)}) $.\n", | |
"2. Check for termination; e.g., $ \\max ( \\vert \\Delta x^{(k)}_{i,j} \\vert ) \\leq \\epsilon $.\n", | |
"3. Solve $ \\Delta \\mathbf{x}^{(k)} = \\mathbf{J}(\\mathbf{q}^{(k)}) \\Delta \\mathbf{q}^{(k)} $ for $ \\Delta \\mathbf{q}^{(k)} $, e.g., by $ \\Delta \\mathbf{x}^{(k)} = (\\mathbf{J}(\\mathbf{q}^{(k)}) \\mathbf{J}(\\mathbf{q}^{(k)})^{T})^{-1} \\Delta \\mathbf{q}^{(k)} $.\n", | |
"4. Calculate new joint angles $ \\mathbf{q}^{(k+1)} = \\mathbf{q}^{(k)} + \\Delta \\mathbf{q}^{(k)} $.\n", | |
"5. $ k \\leftarrow k + 1 $, go to 2." | |
], | |
"id": "86a6d43c" | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "f4ebb171" | |
}, | |
"source": [ | |
"## Set Up Direct Kinematics and Solve Jacobian Symbolically\n", | |
"\n", | |
"In case of the Panda robot, we have $ n=7 $ active joint angles $ \\mathbf{q} = \\left[ \\theta_{1}, \\ldots, \\theta_{7} \\right]^{\\mathsf{T}} $.\n", | |
"We use `sympy` to solve the Jacobian symbollically." | |
], | |
"id": "f4ebb171" | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "0bd23a9d" | |
}, | |
"source": [ | |
"from sympy import symbols, init_printing, Matrix, eye, sin, cos, pi\n", | |
"init_printing(use_unicode=True)" | |
], | |
"id": "0bd23a9d", | |
"execution_count": 1, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "6c652242" | |
}, | |
"source": [ | |
"# create joint angles as symbols\n", | |
"\n", | |
"q1, q2, q3, q4, q5, q6, q7 = symbols('theta_1 theta_2 theta_3 theta_4 theta_5 theta_6 theta_7')\n", | |
"joint_angles = [q1, q2, q3, q4, q5, q6, q7]" | |
], | |
"id": "6c652242", | |
"execution_count": 2, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "9b6430cb" | |
}, | |
"source": [ | |
"# construct symbolic direct kinematics from Craig's DH parameters\n", | |
"# see https://frankaemika.github.io/docs/control_parameters.html\n", | |
"\n", | |
"dh_craig = [\n", | |
" {'a': 0, 'd': 0.333, 'alpha': 0, },\n", | |
" {'a': 0, 'd': 0, 'alpha': -pi/2},\n", | |
" {'a': 0, 'd': 0.316, 'alpha': pi/2},\n", | |
" {'a': 0.0825, 'd': 0, 'alpha': pi/2},\n", | |
" {'a': -0.0825, 'd': 0.384, 'alpha': -pi/2},\n", | |
" {'a': 0, 'd': 0, 'alpha': pi/2},\n", | |
" {'a': 0.088, 'd': 0.107, 'alpha': pi/2},\n", | |
"]\n", | |
"\n", | |
"DK = eye(4)\n", | |
"\n", | |
"for i, (p, q) in enumerate(zip(reversed(dh_craig), reversed(joint_angles))):\n", | |
" d = p['d']\n", | |
" a = p['a']\n", | |
" alpha = p['alpha']\n", | |
"\n", | |
" ca = cos(alpha)\n", | |
" sa = sin(alpha)\n", | |
" cq = cos(q)\n", | |
" sq = sin(q)\n", | |
"\n", | |
" transform = Matrix(\n", | |
" [\n", | |
" [cq, -sq, 0, a],\n", | |
" [ca * sq, ca * cq, -sa, -d * sa],\n", | |
" [sa * sq, cq * sa, ca, d * ca],\n", | |
" [0, 0, 0, 1],\n", | |
" ]\n", | |
" )\n", | |
"\n", | |
" DK = transform @ DK" | |
], | |
"id": "9b6430cb", | |
"execution_count": 3, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"colab": { | |
"base_uri": "https://localhost:8080/", | |
"height": 98 | |
}, | |
"id": "80eb83ca", | |
"outputId": "1755a270-194c-44a0-a6b5-7269dbc99908" | |
}, | |
"source": [ | |
"# test direct kinematics\n", | |
"\n", | |
"DK.evalf(subs={\n", | |
" 'theta_1': 0,\n", | |
" 'theta_2': 0,\n", | |
" 'theta_3': 0,\n", | |
" 'theta_4': 0,\n", | |
" 'theta_5': 0,\n", | |
" 'theta_6': 0,\n", | |
" 'theta_7': 0,\n", | |
"})" | |
], | |
"id": "80eb83ca", | |
"execution_count": 4, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"data": { | |
"text/latex": "$\\displaystyle \\left[\\begin{matrix}1.0 & 0 & 0 & 0.088\\\\0 & -1.0 & 0 & 0\\\\0 & 0 & -1.0 & 0.926\\\\0 & 0 & 0 & 1.0\\end{matrix}\\right]$", | |
"text/plain": [ | |
"⎡1.0 0 0 0.088⎤\n", | |
"⎢ ⎥\n", | |
"⎢ 0 -1.0 0 0 ⎥\n", | |
"⎢ ⎥\n", | |
"⎢ 0 0 -1.0 0.926⎥\n", | |
"⎢ ⎥\n", | |
"⎣ 0 0 0 1.0 ⎦" | |
] | |
}, | |
"metadata": { | |
"tags": [] | |
}, | |
"execution_count": 4 | |
} | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "2082f0ad" | |
}, | |
"source": [ | |
"## Reshape Direct Kinematics\n", | |
"\n", | |
"Let $ \\mathrm{DK} : \\mathbb{R}^{n} \\to \\mathbb{R}^{4 \\times 4} $ be the direct kinematics transformation matrix (EE pose matrix), with\n", | |
"\n", | |
"$$\n", | |
"\\mathrm{DK} =\n", | |
"\\left[\n", | |
"\\begin{matrix}\n", | |
"a_{11} & a_{12} & a_{13} & a_{14} \\\\\n", | |
"a_{21} & a_{22} & a_{23} & a_{24} \\\\\n", | |
"a_{31} & a_{32} & a_{33} & a_{34} \\\\\n", | |
"0 & 0 & 0 & 1\n", | |
"\\end{matrix}\n", | |
"\\right] ~ .\n", | |
"$$\n", | |
"\n", | |
"Instead of computing Euler angles from $ \\mathrm{DK} $, we simply crop the last row and flatten column-wise:\n", | |
"$$\n", | |
"\\mathrm{A} = \\left[ a_{11}, a_{21}, a_{31}, \\ldots, a_{34} \\right]^{\\mathsf{T}}, A \\in \\mathbb{R}^{12 \\times 1} ~ .\n", | |
"$$" | |
], | |
"id": "2082f0ad" | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "c7bab991" | |
}, | |
"source": [ | |
"A = DK[0:3, 0:4] # crop last row\n", | |
"A = A.transpose().reshape(12,1) # reshape to column vector A = [a11, a21, a31, ..., a34]\n", | |
"\n", | |
"Q = Matrix(joint_angles)\n", | |
"J = A.jacobian(Q) # compute Jacobian symbolically" | |
], | |
"id": "c7bab991", | |
"execution_count": 5, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "eaf8b589" | |
}, | |
"source": [ | |
"## From Symbolical to Numerical\n", | |
"\n", | |
"We can now move to numerical computation for speed reasons.\n", | |
"With `lambdify`, we can create `numpy` functions from `sympy` expressions.\n", | |
"`numba` JIT compiles those functions to native code (optional)." | |
], | |
"id": "eaf8b589" | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "c7378849" | |
}, | |
"source": [ | |
"import numpy as np\n", | |
"from sympy import lambdify\n", | |
"from numba import jit\n", | |
"\n", | |
"A_lamb = jit(lambdify((q1, q2, q3, q4, q5, q6, q7), A, 'numpy'))\n", | |
"J_lamb = jit(lambdify((q1, q2, q3, q4, q5, q6, q7), J, 'numpy'))" | |
], | |
"id": "c7378849", | |
"execution_count": 6, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "c2e7fd9b" | |
}, | |
"source": [ | |
"## IIK Implementation\n", | |
"\n", | |
"We are now ready to implement the actual IIK." | |
], | |
"id": "c2e7fd9b" | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "7ad80c11" | |
}, | |
"source": [ | |
"@jit\n", | |
"def incremental_ik(q, A, A_final, step=0.1, atol=1e-4):\n", | |
" while True:\n", | |
" delta_A = (A_final - A)\n", | |
" if np.max(np.abs(delta_A)) <= atol:\n", | |
" break\n", | |
" J_q = J_lamb(q[0,0], q[1,0], q[2,0], q[3,0], q[4,0], q[5,0], q[6,0])\n", | |
" J_q = J_q / np.linalg.norm(J_q) # normalize Jacobian\n", | |
" \n", | |
" # multiply by step to interpolate between current and target pose\n", | |
" delta_q = np.linalg.pinv(J_q) @ (delta_A*step)\n", | |
" \n", | |
" q = q + delta_q\n", | |
" A = A_lamb(q[0,0], q[1,0],q[2,0],q[3,0],q[4,0],q[5,0],q[6,0])\n", | |
" return q, np.max(np.abs(delta_A))" | |
], | |
"id": "7ad80c11", | |
"execution_count": 7, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"colab": { | |
"base_uri": "https://localhost:8080/" | |
}, | |
"id": "ae2bc32d", | |
"outputId": "a379fd92-db14-49d5-e675-91db5b27a71e" | |
}, | |
"source": [ | |
"# define joint limits for the Panda robot\n", | |
"limits = [\n", | |
" (-2.8973, 2.8973),\n", | |
" (-1.7628, 1.7628),\n", | |
" (-2.8973, 2.8973),\n", | |
" (-3.0718, -0.0698),\n", | |
" (-2.8973, 2.8973),\n", | |
" (-0.0175, 3.7525),\n", | |
" (-2.8973, 2.8973)\n", | |
"]\n", | |
"\n", | |
"# create initial pose\n", | |
"q_init = np.array([l+(u-l)/2 for l, u in limits], dtype=np.float64).reshape(7, 1)\n", | |
"A_init = A_lamb(*(q_init.flatten()))\n", | |
"print(A_init.reshape(3, 4, order='F'))" | |
], | |
"id": "ae2bc32d", | |
"execution_count": 8, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": [ | |
"[[ 0.9563065 0. 0.29236599 0.58193844]\n", | |
" [ 0. -1. 0. 0. ]\n", | |
" [ 0.29236599 -0. -0.9563065 0.654902 ]]\n" | |
], | |
"name": "stdout" | |
} | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"colab": { | |
"base_uri": "https://localhost:8080/" | |
}, | |
"id": "c24599a8", | |
"outputId": "24ed80a7-c8cf-43d3-fe38-d79933f9f6db" | |
}, | |
"source": [ | |
"# generate random final pose within joint limits\n", | |
"\n", | |
"np.random.seed(0)\n", | |
"\n", | |
"q_rand = np.array([np.random.uniform(l, u) for l, u in limits], dtype=np.float64).reshape(7, 1)\n", | |
"A_final = A_lamb(*(q_rand).flatten())\n", | |
"print(A_final.reshape(3, 4, order='F'))" | |
], | |
"id": "c24599a8", | |
"execution_count": 9, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": [ | |
"[[ 0.21582232 0.95638546 0.19684405 0.55544547]\n", | |
" [ 0.91784019 -0.26748917 0.29328985 0.50407938]\n", | |
" [ 0.3331518 0.11737288 -0.93553914 0.33267676]]\n" | |
], | |
"name": "stdout" | |
} | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"colab": { | |
"base_uri": "https://localhost:8080/" | |
}, | |
"id": "bad82d63", | |
"outputId": "9c06c29e-9d20-456f-adb7-dd86ad9951be" | |
}, | |
"source": [ | |
"q_final, _ = incremental_ik(q_init, A_init, A_final, atol=1e-6)\n", | |
"q_final.flatten()" | |
], | |
"id": "bad82d63", | |
"execution_count": 10, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"data": { | |
"text/plain": [ | |
"array([ 0.44462097, 0.69891914, 0.37442864, -1.44809794, -0.21841531,\n", | |
" 2.45359754, -0.47850648])" | |
] | |
}, | |
"metadata": { | |
"tags": [] | |
}, | |
"execution_count": 10 | |
} | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "a3823360" | |
}, | |
"source": [ | |
"## Test Runtime and Convergence Robustness\n", | |
"\n", | |
"Randomly sample 10k target poses within joint limits to test runtime and convergence robustness." | |
], | |
"id": "a3823360" | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"colab": { | |
"base_uri": "https://localhost:8080/" | |
}, | |
"id": "463ec2f2", | |
"outputId": "6084f280-245f-49f9-c4b5-83a1bb484671" | |
}, | |
"source": [ | |
"%%timeit -n10000\n", | |
"q_rand = np.array([np.random.uniform(l, u) for l, u in limits], dtype=np.float64).reshape(7, 1)\n", | |
"incremental_ik(q_rand, A_init, A_final)" | |
], | |
"id": "463ec2f2", | |
"execution_count": 14, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": [ | |
"10000 loops, best of 5: 1.12 ms per loop\n" | |
], | |
"name": "stdout" | |
} | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "2773c0f0" | |
}, | |
"source": [ | |
"" | |
], | |
"id": "2773c0f0", | |
"execution_count": 12, | |
"outputs": [] | |
} | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment