Library / SDK
pink-kinematics/pink avatar
pink-kinematics/pink

Pink: inverse kinematics as a weighted task problem solved every control cycle

Python inverse kinematics using Pinocchio and QP solvers

865 stars70 forksPythonApache-2.0

At a glance

What is it?
Pink is a Python inverse kinematics library built on Pinocchio that expresses control goals as tasks with costs and targets, and solves a quadratic program under the robot's own configuration and velocity limits. It outputs a velocity rather than a pose, which is what makes it usable on a real control loop.
Who is it for?
Adopt Pink if you are doing whole-body control on an articulated robot and want task weights, joint limits and a floating base limit handled in one place, since the solver reads the limits from the robot model and returns a velocity you can integrate directly. Do not adopt it if you want a one-shot pose solver, because the output is a velocity and the API is differential, so a static target still needs the integration loop.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 25 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem is stated as a quadratic program, not a solver call

Pink solves differential inverse kinematics by weighted tasks, and the README sets out the mathematics before the API, which is the right order for understanding the library.

A task is defined by a residual function of the robot configuration, a quantity to be driven to zero. The example given is a foot position, where the residual is the difference between a target position and the foot's current position. In differential inverse kinematics the library computes a velocity that satisfies the first-order differential equation in which the task Jacobian times the velocity equals the negative residual scaled by a gain.

The interesting part is what happens with several tasks. They can conflict, and the README says the conflicts are resolved by casting all objectives to a common unit and weighing the normalised objectives relative to each other. Configuration and velocity limits are included, so the whole thing becomes a quadratic program: minimise the weighted sum of the task errors subject to the velocity bounds read from the robot model.

That last clause is the practical point. The constraints do not come from a separate configuration object the user maintains, they come from the model, so the solver cannot propose a velocity the robot could not execute. The residual formulation also explains why costs are expressed in physical units: a cost per metre and a cost per radian are only comparable after normalisation, and that is exactly what the library does.

Pink's stated role is to let you describe the problem as tasks with targets and have it build and solve the underlying quadratic program automatically.

A task is a cost, a target and a type

The task API has three moving parts, and the README's example makes all three visible.

The cost is what a task's error is worth. For a frame task there is a position cost and an orientation cost, and the README says these can be scalars or three-dimensional vectors. A scalar says every metre of position error costs the same. A vector says otherwise, and the components weight the error anisotropically along each axis of the body frame, which is how you express that sideways error matters more than vertical error for a particular contact.

The target is what the task aims at. A frame task aims for a target transform, a posture task aims for a target configuration vector, and both are set through the set_target function. The posture target in the example is a concatenation of a floating base quaternion, a floating base position and the joint angles, which tells you the configuration vector layout that Pink expects.

The type is the task class, and the README's example is a biped with four tasks: a base frame task controlling the position and orientation of the base, a left contact frame task, a right contact frame task, and a posture task that gives a preferred angle for each joint. The posture task is there for regularisation, with a small cost of 1e-3, and the comment next to it explains the unit. The contact tasks have position costs but zero orientation cost, because a foot on the ground is constrained in position and free in orientation.

A task can also be initialised from the robot's neutral configuration, which the README shows by loading a robot description and setting each frame task's target from the transform the frame has in that configuration. That is a small convenience with a large effect: it means the initial targets are consistent with the robot's own zero pose rather than with a hand-written guess.

The output is a velocity, so the loop is yours to run

This is the part that decides whether Pink fits your controller, and the README is unambiguous about it.

Pink solves differential inverse kinematics, meaning it outputs a velocity that steers the robot towards achieving all tasks at best. If you keep integrating that velocity and the task targets do not change, the robot converges to a stationary configuration:

python
dt = 6e-3  # [s]
for t in np.arange(0.0, 42.0, dt):
    velocity = solve_ik(configuration, tasks.values(), dt, solver="quadprog")
    configuration.integrate_inplace(velocity, dt)
    time.sleep(dt)

The loop is small on purpose. The solver is called with the configuration, the tasks, the time step and the solver name, the velocity is integrated into the configuration in place, and the loop sleeps for the step. There is no internal thread and no internal timer, which means the control rate is yours to decide and the loop can be driven by a real-time scheduler.

The other case is the one most robotics work involves. If the task targets are continuously updated, there is no stationary solution to converge to, and the model keeps tracking each target as well as it can. The targets are set through the same set_target call, so a moving target is a matter of updating it in the loop.

The README also notes that solve_ik takes care of both the configuration and the velocity limits read from the robot model, which is the detail that makes the loop above safe to run without additional clamping.

One more thing to notice in the loop: the solver is passed by name. The example uses quadprog, and the dependency on the qpsolvers library is what makes that a choice rather than a hardcoded call.

Floating base velocity limits are a separate, attachable object

Robots with planar or free flyer bases need a limit that is not a joint limit, because the base twist is a separate entity in the model. Pink handles it with an object you attach to the configuration's model.

python
from pink.limits import FloatingBaseVelocityLimit

floating_limit = FloatingBaseVelocityLimit(
    configuration.model,
    base_frame="base_link",
    max_linear_velocity=[0.3, 0.3, 0.2],    # [m] / [s]
    max_angular_velocity=[1.0, 1.0, 1.0],   # [rad] / [s]
)
configuration.model.floating_base_velocity_limit = floating_limit

The limits are per axis for both the linear and the angular part, and the comments record the units, which is the same care about units that runs through the task costs. Attaching the object to the model is enough: the README says the limit is then automatically taken into account when building the IK constraints, so there is no second place to wire it up and no way to forget it.

The base frame is a parameter, and the README explains the default. Passing base_frame=None lets Pink pick the frame attached to the root_joint automatically, which is the sensible choice when the robot description is consistent, and the explicit form is there for descriptions where it is not.

This is a good example of the library's overall approach. Constraints live in objects that are attached to the model, and the solver reads them from there, so a limit cannot drift out of sync with the problem being solved.

The dependency chain is Pinocchio plus a solver library

Pink's dependencies are few and each one is load bearing.

The pin package, the Python binding of Pinocchio, does the kinematics: the model, the data, the Jacobians and the configuration objects that the tasks and the solver work with. The qpsolvers package is the quadratic program solver interface, which is what turns the solver argument into a choice rather than a hardcoded dependency. numpy is the array library underneath, loop-rate-limiters provides a rate limiter, and typing-extensions is there for the typing support.

Installation comes in two flavours, and the README recommends the first for performance. From conda-forge, the package is called pink:

bash
conda install -c conda-forge pink

From PyPI it is called pin-pink:

bash
pip install pin-pink

The two names differ because the conda-forge package and the PyPI distribution are named differently, while the importable module is pink in both cases. The project file sets the module name explicitly for the build backend, which is why the flit configuration has a module entry naming pink even though the distribution is pin-pink.

The build backend is flit_core, the project requires Python 3.10 or newer, and the classifiers list Python 3.10 through 3.14. The development status is marked as production and stable, which is a stronger claim than most robotics libraries make, and the framework classifier for robot frameworks is there as well. The intended audience is declared as developers and science and research, and the topic is scientific and engineering software.

The repository is careful about reproducible environments, with both a pixi.lock and a uv.lock at the root, and it keeps a CITATION.cff and a CHANGELOG.md so the package can be cited and its history read.

The examples double as the test suite for what the library can do

The examples directory is the most informative part of the repository, because it shows which robot morphologies the library has actually been exercised on.

There are arm examples for the UR5, the UR3, the Panda, the Kinova Gen2 and the Z1, and several of them are not just a straight solve. One covers the UR5 with end-effector limits and lives in a barriers/ directory, which tells you the library can treat the end effector as a barrier rather than a target. Another uses a sparse solver for the UR3, another smooths joint velocity, and another compares manipulability metrics for the Panda.

The humanoid examples cover several robots, including DRACO3, the G1, the JVRC and SIGMAban, and the humanoid G1 example is a whole-body control case with a com task. The mobile examples cover an omni-wheeled robot and the Stretch, and there is a floating base velocity limit example, a one degree of freedom configuration limit example, a double pendulum example for an unactuated degree of freedom, an example that loads a custom URDF, and one that draws shapes through meshcat.

The breadth is the point. A library that claims to handle a floating base, joint limits, task weighting and end-effector barriers is making a claim that only one robot would not test, and the examples directory is the evidence.

Two smaller details are worth noting for anyone reading the repository. The documentation link in the README points to the pink-kinematics organisation pages while the project metadata points to the author's own pages, so the canonical documentation location is not obvious from a single file. And the releases are on a steady minor cadence, v4.2.0 in April 2026, v4.3.0 in July and v4.4.0 in September, with the last push on 2026-09-27, which is a healthy pace for a library in the production and stable category.

Editorial conclusion

Adopt Pink if you are doing whole-body control on an articulated robot and want task weights, joint limits and a floating base limit handled in one place, since the solver reads the limits from the robot model and returns a velocity you can integrate directly. Do not adopt it if you want a one-shot pose solver, because the output is a velocity and the API is differential, so a static target still needs the integration loop. Before you commit, check the solver you want is available on your platform, since the example passes the quadprog solver explicitly, and confirm the robot description you load exposes the configuration and velocity limits the model needs, because the constraints come from the model rather than from the tasks.

Frequently asked questions

How do I install Pink for inverse kinematics?

The README recommends conda install -c conda-forge pink for the best performance, and notes that you can also install it from PyPI with pip install pin-pink. The conda-forge package is called pink and the PyPI distribution pin-pink, while the importable module is pink in both cases, and the project requires Python 3.10 or newer.

What does a Pink task consist of?

A task has a type, a cost and a target. The example builds FrameTask objects for the base and the two contact frames of a biped, with position and orientation costs that may be scalars or three-dimensional vectors for anisotropic weighting, plus a PostureTask for regularisation. Targets are set with the set_target function, and a frame task target can be initialised from the robot's neutral configuration.

Does Pink compute a joint configuration or a velocity?

A velocity. Pink solves differential inverse kinematics and returns a velocity that steers the robot towards the tasks, and you integrate it into the configuration yourself in a loop, for example with solve_ik followed by configuration.integrate_inplace. With fixed targets the loop converges to a stationary configuration, and with continuously updated targets it keeps tracking.

How are joint and base velocity limits handled?

The configuration and velocity limits are read from the robot model, and solve_ik takes them into account when building the constraints. For robots with planar or free flyer bases there is a separate FloatingBaseVelocityLimit object that you attach to the model's floating_base_velocity_limit attribute, with per-axis linear and angular limits and a base frame that can be left as None to be picked automatically.

Which robots does Pink have examples for?

The examples directory covers arms such as the UR5, UR3, Panda, Kinova Gen2 and Z1, including a UR5 with end-effector limits, a sparse solver example and velocity smoothing, humanoids including DRACO3, G1, JVRC and SIGMAban, and mobile platforms such as an omni-wheeled robot and the Stretch, plus a custom URDF loader and a meshcat drawing example.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. pink-kinematics/pink on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/pink-kinematics-pink.svg)](https://hysenlabs.com/projects/pink-kinematics-pink)

Was this useful?