Inverse Kinematics: Analytical & Numerical Solutions
Which joint angles put the tool where we want? Learn the inverse kinematics (IK) problem, existence (workspace) and multiplicity of solutions (elbow up/down, wrist flip, shoulder left/right), the analytical 2R solution with the law of cosines and atan2, kinematic decoupling for 6-axis arms with spherical wrists, numerical IK (Newton–Raphson with the Jacobian, gradient descent, damped least squares/Levenberg–Marquardt), joint limits, redundancy and null-space optimisation — with NumPy code comparing the analytical and numerical solutions of a 2R arm.
Tasks are specified in Cartesian space (put the gripper here, oriented like this), but motors are commanded in joint space. Inverse kinematics bridges the two, and it's harder than FK: there may be no solution, several, or infinitely many.
Analytical solutions are fast and give all solutions; numerical solvers handle any geometry, redundancy and constraints.
An analogy
IK is like reaching for a cup: your brain chooses shoulder and elbow angles to put your hand on it. You could reach with the elbow down or awkwardly up — both work (multiple solutions); if the cup is too far, no posture works (outside the workspace).
Solvability
A solution exists only if the target is inside the workspace (reachable positions) and dexterous workspace (reachable with the required orientation). A 6-DOF arm typically has up to 8 (sometimes 16) solutions for one pose. Joint limits and obstacles remove some; continuity with the current posture selects one.
Analytical IK
Closed-form approaches:
- Geometric — triangles and law of cosines (2R, 3R planar, SCARA)
- Algebraic — solve the FK equations symbolically
- Kinematic decoupling — wrist centre p_w = p − d₆·R·ẑ gives q₁–q₃; R₃⁶ = (R₀³)ᵀR gives q₄–q₆
- Always use atan2 (quadrant-correct), never plain atan
- IKFast generates analytical solvers automatically for many robots
Numerical IK
Iteratively reduce the error e between desired and current pose: Newton–Raphson with J⁻¹ (fast near the solution, unstable near singularities), Jacobian transpose (simple, slow), damped least squares (robust, most used), or optimisation with constraints (joint limits, collision) as in TRAC-IK, KDL, MoveIt. Results depend on the initial guess — seed with the current joint state for continuity.
Redundancy
Robots with more DOF than the task needs (7-DOF arms, mobile manipulators, humanoids) have infinitely many solutions. The pseudo-inverse J⁺ gives the minimum-norm solution; the null-space term (I − J⁺J)z optimises secondary objectives (avoid joint limits, obstacles and singularities, maximise manipulability) without disturbing the task. Task-priority and whole-body controllers stack multiple objectives.
Formulas
- 2R elbow angle:
cos q₂ = (x² + y² − L₁² − L₂²)/(2L₁L₂); q₂ = atan2(±√(1 − c₂²), c₂) - 2R shoulder angle:
q₁ = atan2(y, x) − atan2(L₂ sin q₂, L₁ + L₂ cos q₂) - Newton–Raphson:
q ← q + J⁻¹(q)·e - Damped least squares:
Δq = Jᵀ(JJᵀ + λ²I)⁻¹e - Redundancy resolution:
Δq = J⁺e + (I − J⁺J)z
Worked example: Reachability
Given: 2R arm 0.4/0.3, target (0.8, 0)
Find: Solvable?
- r = 0.8 > 0.7
Answer: No — outside workspace
Worked example: Elbow angle
Given: Target (0.45, 0.30)
Find: cos q₂.
- (0.2025 + 0.09 − 0.16 − 0.09)/0.24
Answer: 0.177 → q₂ = ±79.8°
Worked example: Wrist centre
Given: Tool at (0.8, 0, 0.5), tool z pointing down, d₆ = 0.1 m
Find: p_w.
- p − 0.1 × (0, 0, −1)
Answer: (0.8, 0, 0.6)
Analytical and numerical IK of a 2R arm
import numpy as np
# Inverse kinematics: analytical 2R solution (elbow up/down) and numerical damped least squares
L1, L2 = 0.40, 0.30
x, y = 0.45, 0.30
c2 = (x*x + y*y - L1*L1 - L2*L2) / (2 * L1 * L2)
for name, s2 in (("elbow down", np.sqrt(1 - c2*c2)), ("elbow up", -np.sqrt(1 - c2*c2))):
q2 = np.arctan2(s2, c2)
q1 = np.arctan2(y, x) - np.arctan2(L2 * s2, L1 + L2 * c2)
print(f"{name}: q1 = {np.degrees(q1):7.2f} deg, q2 = {np.degrees(q2):7.2f} deg")
def fk(q): return np.array([L1*np.cos(q[0]) + L2*np.cos(q[0]+q[1]), L1*np.sin(q[0]) + L2*np.sin(q[0]+q[1])])
def jac(q):
s1, c1, s12, c12 = np.sin(q[0]), np.cos(q[0]), np.sin(q[0]+q[1]), np.cos(q[0]+q[1])
return np.array([[-L1*s1 - L2*s12, -L2*s12], [L1*c1 + L2*c12, L2*c12]])
q, lam = np.array([0.1, 0.1]), 0.01
for it in range(50):
e = np.array([x, y]) - fk(q)
if np.linalg.norm(e) < 1e-9: break
J = jac(q)
q = q + J.T @ np.linalg.solve(J @ J.T + lam**2 * np.eye(2), e)
q = (q + np.pi) % (2 * np.pi) - np.pi # wrap angles to [-180, 180)
print(f"damped least squares converged in {it} iterations: q = {np.round(np.degrees(q), 2).tolist()} deg, error {np.linalg.norm(e):.1e} m")
# elbow down: q1 = 0.60 deg, q2 = 79.80 deg
# elbow up: q1 = 66.78 deg, q2 = -79.80 deg
# damped least squares converged in 14 iterations: q = [0.6, 79.8] deg, error 1.5e-11 mTwo postures reach the same point — the controller must pick one consistently (robot 'configuration' flags) to avoid sudden flips. The numerical solver, starting near q = (0.1, 0.1) rad, converges to the elbow-down solution in 14 iterations; its raw angles wrap around several turns, so they're normalised to ±180°. Damping (λ) keeps steps bounded near singularities where J⁻¹ blows up.
Common mistakes
- Wrong: Using atan instead of atan2. — Right: atan2 handles all quadrants.
- Wrong: Switching solution branches between waypoints. — Right: Keep configuration consistent; seed from current joints.
- Wrong: Ignoring joint limits in numerical IK. — Right: Clamp or optimise with limits and wrap angles.
Applications
- Cartesian jogging: Moving the TCP along x/y/z with the teach pendant.
- Motion planning: MoveIt IK for pick poses.
- Animation & VR: IK for character limbs.
- Humanoids: Whole-body IK with balance constraints.
Key takeaways
- IK: pose → joints; check reachability first.
- 2R: law of cosines + atan2; elbow up/down.
- Numerical: damped least squares with good seeds.
- Redundancy: null-space for secondary goals.
Going further
- IKFast: Automatic analytical solver generation.
- Optimisation-based IK: Constraints, collisions and priorities.
- Learning-based IK: Neural networks for fast approximations.