Getting Started¶
This page introduces mink’s core concepts through a simple example: driving a robot arm’s end-effector to a target pose.
Setup¶
We’ll use the Panda arm from the examples/ directory in the mink repository.
Load the model and create a configuration:
import mujoco
import numpy as np
from mink import Configuration
model = mujoco.MjModel.from_xml_path("franka_emika_panda/mjx_scene.xml")
configuration = Configuration(model)
configuration.update_from_keyframe("home")
Configuration wraps a MuJoCo model and provides the kinematic
quantities needed for IK—frame poses, Jacobians, and integration.
See MuJoCo Basics for details.
Defining a Target Pose¶
mink represents rigid body transformations using SE3 (poses) and
SO3 (rotations). These classes support composition via @ and
provide convenient constructors. See Rotations and Poses for details.
To define a target, start from the current end-effector pose and apply a transformation:
from mink import SE3, SO3
# Get current end-effector pose.
ee_pose = configuration.get_transform_frame_to_world("attachment_site", "site")
# Define target: translate in world frame, then rotate in local frame.
translation = np.array([0.0, -0.4, -0.2])
rotation = SO3.from_y_radians(-np.pi / 2)
target = SE3.from_translation(translation) @ ee_pose # World frame.
target = target @ SE3.from_rotation(rotation) # Local frame.
Left-multiplying applies a transformation in the world frame—the translation moves the pose along world axes. Right-multiplying applies it in the local frame—the rotation is about the gripper’s own y-axis, not the world’s.
This creates a target that is offset from the current pose and rotated 90°. By decoupling translation and rotation, it’s easy to reason about each independently.
Creating a Task¶
A FrameTask drives a frame toward a target pose:
from mink import FrameTask
task = FrameTask(
frame_name="attachment_site",
frame_type="site",
position_cost=1.0,
orientation_cost=1.0,
)
task.set_target(target)
Setting both position_cost and orientation_cost to non-zero values
creates a full 6-DOF pose task. Set orientation_cost=0.0 for a position-only
task where the end-effector can rotate freely.
Controlling Convergence Speed¶
The gain parameter sets the desired rate at which the task error should decay.
With gain \(g \in [0, 1]\), the error after \(n\) steps is approximately:
To reach 1% of the initial error after \(n\) frames, solve for:
For example, to converge over 2 seconds at 60 fps (120 frames):
duration = 2.0 # seconds
fps = 60
n_frames = int(duration * fps)
gain = 1.0 - 0.01 ** (1.0 / n_frames) # ≈ 0.038
task = FrameTask(
frame_name="attachment_site",
frame_type="site",
position_cost=1.0,
orientation_cost=1.0,
gain=gain,
)
Lower gain values produce slower, smoother motion. The default gain=1.0
converges as fast as possible.
Note
This formula assumes first-order error dynamics, which holds when the Jacobian is well-conditioned and the target is reachable.
The IK Loop¶
At each step, solve_ik() computes a joint velocity intended to reduce
task error:
from mink import solve_ik
dt = 1.0 / fps
for _ in range(n_frames):
vel = solve_ik(configuration, [task], dt)
configuration.integrate_inplace(vel, dt)
The dt parameter affects the mapping between the solved step \(\|\Delta q\|\)
and the commanded velocity \(\|v\|\), and it affects limits that are expressed as
rates (velocity bounds). In typical usage you keep dt matched to your control loop.
Larger dt values result in bigger configuration updates per step, which can
lead to degraded accuracy or instability if the linearization becomes invalid
over that update.
In practice, the effective convergence rate depends on both gain and dt:
larger gains or larger timesteps result in more aggressive updates.
How to set dt
Match dt to your control loop rate. For example, at 60 Hz use
dt = 1/60; at 1000 Hz use dt = 0.001.
integrate_inplace() applies the velocity to update
joint positions: q ← q + vel * dt. For quaternion-based joints (ball and
free), it uses proper quaternion integration.
Complete Example¶
import mujoco
import numpy as np
from mink import Configuration, FrameTask, SE3, SO3, solve_ik
# Load robot.
model = mujoco.MjModel.from_xml_path("franka_emika_panda/mjx_scene.xml")
configuration = Configuration(model)
configuration.update_from_keyframe("home")
# Define target pose relative to current end-effector.
ee_pose = configuration.get_transform_frame_to_world("attachment_site", "site")
translation = np.array([0.0, -0.4, -0.2])
rotation = SO3.from_y_radians(-np.pi / 2)
target = SE3.from_translation(translation) @ ee_pose
target = target @ SE3.from_rotation(rotation)
# Create task with gain for smooth 2-second convergence.
duration, fps = 2.0, 60
n_frames = int(duration * fps)
gain = 1.0 - 0.01 ** (1.0 / n_frames)
task = FrameTask(
frame_name="attachment_site",
frame_type="site",
position_cost=1.0,
orientation_cost=1.0,
gain=gain,
)
task.set_target(target)
# Run IK loop.
dt = 1.0 / fps
for _ in range(n_frames):
vel = solve_ik(configuration, [task], dt)
configuration.integrate_inplace(vel, dt)
# Check result.
final = configuration.get_transform_frame_to_world("attachment_site", "site")
print(f"Position error: {np.linalg.norm(final.translation() - target.translation()):.2e} m")
What’s Next¶
This example used a single pose task with controlled convergence. To build more robust IK systems with regularization and limits, continue to Tasks and Limits.