Inverse Kinematics for Articulated Robot Models (pink)
Pink (pin-pink) is a Python library for inverse kinematics (IK) of articulated robot models, built upon the Pinocchio robotics framework. It provides a flexible task-based IK solver, allowing users to define various objectives (e.g., end-effector tracking, joint limits, velocity control) that are resolved using a quadratic program (QP). The current version is 4.1.0, and it maintains an active development cycle with several releases per year.
Common errors
-
AttributeError: module 'pink.tasks' has no attribute 'BodyTask'
cause Using the old class name `BodyTask` after v2.0.0.fixThe `BodyTask` class was renamed to `FrameTask`. Update your code to `from pink.tasks import FrameTask`. -
TypeError: compute_qp_inequalities() missing 1 required positional argument: 'configuration'
cause After v4.0.0, `Limit.compute_qp_inequalities` expects a `pin.Configuration` object, not just a configuration vector.fixPass a full Pinocchio `Configuration` instance (e.g., `robot.q`) to the `compute_qp_inequalities` method. -
NameError: name 'PinkError' is not defined
cause Attempting to import `PinkError` directly from `pink` after v3.3.0.fixExceptions were moved to a submodule. Change `from pink import PinkError` to `from pink.exceptions import PinkError`. -
AssertionError: QP solver did not find a solution
cause Before v3.2.0, certain conditions where the IK solver failed to converge would raise an `AssertionError`. This is now handled by a specific exception.fixUpdate your exception handling to `except NoSolutionFound` (introduced in v3.2.0) instead of `except AssertionError` for solver non-convergence cases. Import it with `from pink.exceptions import NoSolutionFound`.
Warnings
- breaking The API for `Limit.compute_qp_inequalities` changed in v4.0.0. It now expects a full configuration instance as an argument, rather than just a configuration vector.
- breaking The `BodyTask` class was renamed to `FrameTask` in v2.0.0. Code using `BodyTask` will fail with an `AttributeError`.
- gotcha All custom exceptions were moved to the `pink.exceptions` submodule in v3.3.0. Direct imports like `from pink import PinkError` will no longer work.
- gotcha The `NoSolutionFound` exception was introduced in v3.2.0, replacing previous `AssertionError` instances when the QP solver converged but did not find a solution. Code relying on catching `AssertionError` for this specific case will need updating.
Install
-
pip install pin-pink -
conda install -c conda-forge pin-pink
Imports
- solve_ik
from pink import solve_ik
- FrameTask
from pink.tasks import BodyTask
from pink.tasks import FrameTask
- JointVelocityTask
from pink.tasks import JointVelocityTask
- RollingTask
from pink.tasks import RollingTask
- RelativeFrameTask
from pink.tasks import RelativeFrameTask
- PinkError
from pink import PinkError
from pink.exceptions import PinkError
- NoSolutionFound
raise AssertionError(...) for no solution
from pink.exceptions import NoSolutionFound
- FloatingBaseVelocityLimit
from pink.limits import FloatingBaseVelocityLimit
- JointLimit
from pink.limits import JointLimit
Quickstart
import numpy as np
import pinocchio as pin
from pink import solve_ik
from pink.tasks import FrameTask
# 1. Load a sample robot model from Pinocchio
model = pin.buildSampleModelManipulator()
robot = pin.RobotWrapper(model)
# 2. Initial configuration (q0 is usually the home configuration)
q = robot.q0
# 3. Define tasks
# Target an end-effector frame (e.g., the last frame in the model)
tip_name = robot.model.frames[-1].name
tip_id = robot.model.getFrameId(tip_name)
# Get initial end-effector pose
robot.data = robot.model.createData()
pin.framesForwardKinematics(robot.model, robot.data, q)
initial_tip_pose = robot.data.oMf[tip_id].copy()
# Define a target pose: move the tip 10cm upwards from its initial position
target_pose = initial_tip_pose.copy()
target_pose.translation += np.array([0.0, 0.0, 0.1])
# Create a FrameTask to track the target pose
task = FrameTask(
tip_name,
lm_damping=1e-6 # Levenberg-Marquardt damping for stability
)
task.set_target(target_pose)
tasks = [task]
# 4. Solve IK iteratively
dt = 0.001 # Integration timestep
num_iterations = 500
solution_found = False
print(f"Starting IK for {tip_name} to reach target position: {target_pose.translation}")
for i in range(num_iterations):
try:
# Solve for joint velocities to achieve tasks
velocity = solve_ik(robot.model, robot.data, q, tasks, dt)
except Exception as e:
# print(f"IK failed at iteration {i}: {e}")
break
# Integrate joint velocities to update configuration
q = pin.integrate(robot.model, q, velocity * dt)
# Update forward kinematics for error calculation
pin.framesForwardKinematics(robot.model, robot.data, q)
current_tip_pose = robot.data.oMf[tip_id]
# Calculate position error
error = np.linalg.norm(current_tip_pose.translation - target_pose.translation)
if error < 1e-4: # Tolerance for reaching target
solution_found = True
# print(f"Target reached in {i+1} iterations. Final error: {error:.6f}")
break
assert np.linalg.norm(current_tip_pose.translation - target_pose.translation) < 1e-3, \
"IK did not converge to target position within tolerance."
print("IK Quickstart Example: Success!")