Inverse Kinematics
RoboPlan provides two inverse kinematics solvers: a simple Jacobian-based solver and an optimal solver based on quadratic programming.
SimpleIK: Jacobian-Based Solver
SimpleIK is a lightweight inverse kinematics solver using the damped least squares (DLS) method, also known as the Levenberg-Marquardt algorithm.
Algorithm
At each iteration, SimpleIK computes joint velocities that minimize the Cartesian error:
Where:
\(e = \log_6(T_{\text{goal}}^{-1} T_{\text{current}})\) — 6D Cartesian error (position + orientation)
\(J\) — Frame Jacobian in
LOCALreference frame\(\lambda\) — Damping factor for regularization
The joint configuration is updated via integration:
Properties:
Simple and efficient — minimal computational overhead
Supports multiple simultaneous goal frames
Collision checking with random restarts on failure
Convergence monitoring based on separate linear and angular error thresholds
Configuration
Parameter |
Description |
Default |
|---|---|---|
|
Joint group to control |
“” |
|
Maximum iterations per attempt |
100 |
|
Maximum computation time (seconds) |
0.01 |
|
Number of random restarts on failure |
2 |
|
Integration step size |
0.01 |
|
Damping factor \(\lambda\) |
0.001 |
|
Convergence threshold (meters) |
0.001 |
|
Convergence threshold (radians) |
0.001 |
|
Enable collision checking |
true |
Usage Example
import numpy as np
from roboplan.core import Scene, JointConfiguration, CartesianConfiguration
from roboplan.simple_ik import SimpleIkOptions, SimpleIk
# Setup
scene = Scene("robot", urdf_path, srdf_path, package_paths)
options = SimpleIkOptions(
group_name="arm",
step_size=0.25,
max_linear_error_norm=0.001,
max_angular_error_norm=0.001,
check_collisions=True
)
ik_solver = SimpleIk(scene, options)
# Define goal
goal = CartesianConfiguration()
goal.base_frame = "base"
goal.tip_frame = "tool0"
goal.tform = target_transform # 4x4 SE(3) matrix
# Solve
start = JointConfiguration()
start.positions = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) # initial configuration
solution = JointConfiguration()
success = ik_solver.solveIk(goal, start, solution)
OInK: Optimal Inverse Kinematics
The OInK solver uses Quadratic Programming (QP) to compute joint displacements that achieve multiple objectives while respecting constraints and safety barriers.
QP Problem Formulation
OInK solves the following QP at each control step:
Subject to:
Reformulated as:
Where:
Symbol |
Description |
Source |
|---|---|---|
\(\Delta q\) |
Joint displacement (decision variable) |
— |
\(J_k, e_k,\) \(W_k\) |
Task Jacobian, error, weight matrix |
Tasks |
\(\alpha_k\) |
Task gain (low-pass filter) |
Tasks |
\(\mu_k\) |
Levenberg-Marquardt damping |
Tasks |
\(\lambda\) |
Tikhonov regularization |
Solver |
\(G_c, l, u\) |
Hard constraint matrix and bounds |
Constraints |
\(G_b, h_b\) |
Barrier constraint matrix and bounds |
Barriers |
\(r_b, J_b\) |
Safe displacement gain and barrier Jacobian |
Barriers |
Tasks
Tasks define optimization objectives through Jacobians and errors.
FrameTask
Tracks a target 6-DOF pose (position + orientation).
Error computation:
Error saturation (prevents large jumps that invalidate CBF linearization):
Jacobian: Frame Jacobian in LOCAL_WORLD_ALIGNED coordinates, negated so QP moves toward target.
Weight matrix:
Parameter |
Description |
Default |
|---|---|---|
|
Position error weight |
1.0 |
|
Orientation error weight |
1.0 |
|
Low-pass filter gain \(\alpha\) |
1.0 |
|
Levenberg-Marquardt damping |
0.0 |
|
Saturation limit (meters) |
∞ |
|
Saturation limit (radians) |
∞ |
ConfigurationTask
Drives toward a target joint configuration (null-space regularization).
Error: Manifold-aware difference: \(e = \text{difference}(q, q_{\text{target}})\)
Jacobian: \(J = -I\) (negative identity)
Weight matrix: \(W = \text{diag}(\sqrt{w_1}, \ldots, \sqrt{w_{n_v}})\)
Constraints vs Barriers
Hard Constraints
Hard constraints enforce strict bounds that the QP solver cannot violate:
Properties:
Exact enforcement — the solution always satisfies the constraint
State-independent bounds — same restriction regardless of distance to limit
No Jacobian needed for joint-space constraints
QP becomes infeasible if constraints conflict
Use for: Physical actuator limits, joint position/velocity bounds
Control Barrier Functions (Barriers)
Barriers enforce forward invariance of a safe set through a differential condition:
In discrete time:
Properties:
State-dependent bounds — more freedom far from boundary, tighter near it
Smooth behavior — graceful slowdown instead of abrupt stop
Requires Jacobian computation
Always feasible (soft constraint via class-K function)
Subject to linearization error in discrete time
Use for: Cartesian position bounds, collision avoidance, workspace constraints
Comparison
Aspect |
Hard Constraint |
Barrier (CBF) |
|---|---|---|
Formulation |
\(\Delta q \leq\) constant |
\(J \cdot \Delta q \leq\) \(f(\text{distance})\) |
Near boundary |
Same restriction |
Tighter (slows down) |
Far from boundary |
Same restriction |
Looser (more freedom) |
Enforcement |
Exact |
Approximate (linearization) |
Feasibility |
Can fail |
Always feasible |
Behavior |
Abrupt at limit |
Smooth approach |
Computation |
Simple |
Requires Jacobian |
Constraint Details
VelocityLimit
Enforces maximum joint velocities as hard bounds:
PositionLimit
Restricts motion based on distance to joint limits:
The gain \(\gamma \in (0, 1]\) controls aggressiveness. As \(q \to q_{\max}\), the upper bound \(\to 0\).
Barrier Details
PositionBarrier
Keeps a frame within an axis-aligned bounding box using CBF constraints.
Barrier function (for lower bound on axis \(i\)):
Barrier Jacobian:
QP constraint (using saturating class-K function):
Where:
\(\gamma\) — barrier gain (aggressiveness)
\(m\) — safety margin (conservative buffer for linearization error)
Safe displacement regularization adds to the objective:
This encourages motion toward a safe configuration when near boundaries.
Parameter |
Description |
Default |
|---|---|---|
|
Class-K function gain \(\gamma\) |
1.0 |
|
Control timestep |
required |
|
Regularization weight \(r\) |
1.0 |
|
Conservative buffer \(m\) |
0.0 |
|
Enable/disable per-axis constraints |
all |
Linearization Error and enforceBarriers()
The CBF constraint is based on a first-order Taylor expansion:
This has \(O(\|\Delta q\|^2)\) error. Near boundaries with large commands, the linearized constraint can be satisfied while the actual barrier is violated.
enforceBarriers() provides a post-solve safety check using forward kinematics:
// After solving QP
oink.solveIk(tasks, constraints, barriers, scene, delta_q);
// Validate using FK: if h(q + delta_q) < -tolerance, set delta_q = 0
oink.enforceBarriers(barriers, scene, delta_q, tolerance);
Implementation Notes
Numerical Properties
Positive definiteness: Guaranteed by Tikhonov regularization \(\lambda I\)
Convexity: Quadratic objective + linear constraints → unique global optimum
Weight scaling: Applied as \(\sqrt{w}\) for better conditioning
Solver
OInK uses OSQP with:
Dense accumulation of \(H\) and \(c\)
Sparse conversion for solving
Warm-starting between iterations
Workspace caching for constraints
Usage Example
import numpy as np
from roboplan_ext import Scene
from roboplan_ext.optimal_ik import (
Oink, FrameTask, FrameTaskOptions,
VelocityLimit, PositionLimit, PositionBarrier
)
# Setup
scene = Scene("robot", urdf_path, srdf_path, package_paths)
nv = scene.model.nv
dt = 0.01
# Tasks
task = FrameTask(target_pose, nv, FrameTaskOptions(
position_cost=1.0,
orientation_cost=1.0,
max_position_error=0.1 # Prevents large jumps
))
# Hard constraints (exact enforcement)
vel_limit = VelocityLimit(nv, dt, v_max=np.ones(nv))
pos_limit = PositionLimit(nv, gain=0.95)
# Barriers (smooth task-space safety)
barrier = PositionBarrier(
frame_name="tool0",
p_min=np.array([-0.5, -0.5, 0.1]),
p_max=np.array([0.5, 0.5, 1.0]),
num_variables=nv,
dt=dt,
gain=5.0,
safety_margin=0.01
)
# Solve
oink = Oink(nv)
delta_q = np.zeros(nv)
oink.solveIk([task], [vel_limit, pos_limit], [barrier], scene, delta_q)
oink.enforceBarriers([barrier], scene, delta_q) # FK validation
q = scene.integrate(q, delta_q)