Level 6 (after Lessons 6.1 to 6.3) · Time 3 to 4 hours · Difficulty 3 of 5
Six-dimensional inverse kinematics for the ee site of models/arm7.xml: position and orientation, by damped least squares, inside the joint limits, with a null-space term that picks the solution closest to a preferred posture. The solver must also say when it has failed. An IK routine that returns a wrong answer with a success flag is worse than one that returns nothing.
mj_jacSite), Lesson 6.2 (damped least squares), Lesson 6.3 (redundancy and the null space).pose_error(model, data, site, target_pos, target_quat): the 6-vector of position error and rotation-vector error, both in the world frame.solve(model, site, target_pos, target_quat, q_init, posture=None): returns (q, success).“Reachable” is defined without the solver: the tests generate targets by forward kinematics of random joint vectors inside the limits, so every target has at least one exact solution. The tests use seed 2026. The numbers below are from seed 123, which the reference was developed on.
home start, 85 with up to 2 random restarts, 98 with 5 and 100 with 20, in 2.7 s on one CPU core. The worst final errors are 0.05 mm and 0.0005 degree, far inside the 1 mm and 1 degree acceptance, because the solver keeps iterating until the posture term stops moving.success = False for positions beyond the 1.05 m reach and below the floor. It also returns False for a position 5 cm under the top of the workspace with the tool pointing down. That position is reachable pointing up, but pointing down would need the wrist 21 cm higher than it can go.home pose as target, a start 0.3 rad away on every joint and posture = home, the solver returns home to $7 \times 10^{-6}$ rad. The null-space term chose that solution among the arm’s one-parameter family of solutions.MJC_IMPL=starter pytest projects/p05_ik
pytest projects/p05_ik
Nine tests:
Each common failure below is a mutant in mutants.json; python tools/mutate_project.py p05_ik checks that each one fails at least one test.
mju_quat2Vel already wraps angles above $\pi$ (engine_util_spatial.c), so with it the flip is redundant but harmless.mj_jacSite reads cdof and subtree_com, which mj_comPos computes, not mj_kinematics. On a fresh MjData the Jacobian is then all zeros and the solver does not move.arm2.xml) and a comparison with your solver: accuracy, iterations, and what happens at the elbow singularity.mj_collision reports arm self-contact, and measure how many targets that removes.solution.py, about 60 lines.