Humanoid Operational-Space and Whole-Body Control¶
Background¶
Operational-space control (OSC) describes a robot task in Cartesian space, while whole-body control (WBC) uses redundant joint motion to satisfy lower priority goals without disturbing the primary task. This example gives a small, inspectable version of that hierarchy: a planar humanoid upper body tracks a moving right-hand target while the left hand approaches a separate target and the torso returns upright.
This is deliberately a teaching example at the acceleration level. It uses identity joint-space acceleration dynamics in a stabilized shoulder frame. It is not a production floating-base/contact-force WBC model: it does not include a rigid-body mass matrix, gravity compensation, torque limits, contacts, friction cones, center-of-pressure constraints, or balance.
Kinematic model¶
The five joint coordinates are
representing torso posture, right shoulder and elbow, and left shoulder and elbow. The shoulder origin is fixed at \(\boldsymbol s=[0,L_T]^{\mathsf T}\), with
The right- and left-hand positions are
Because the hand tasks are expressed in the stabilized shoulder frame, \(q_T\) does not enter either hand kinematics. This deliberate simplification makes the null-space structure visible.
Primary right-hand task¶
The fixed horizon is \(T=2.5\ \mathrm s\). Define
Starting from
the desired right-hand trajectory is
Numerically, the hand moves from approximately \([0.605445,0.578541]^{\mathsf T}\ \mathrm m\) to \([0.645445,0.658541]^{\mathsf T}\ \mathrm m\). Its desired velocity and acceleration use \(\dot h\) and \(\ddot h\).
Let
The feedback acceleration reference is
The exact task hierarchy used by the state dynamics is
The Pockit state is \(\boldsymbol x=[\boldsymbol q^{\mathsf T}, \dot{\boldsymbol q}^{\mathsf T}]^{\mathsf T}\), the control is the five-dimensional null-space command \(\boldsymbol z\), and
For this model, the right-hand Jacobian only uses \(q_{RS}\) and \(q_{RE}\). Its analytical pseudoinverse therefore gives
so torso and left-arm accelerations cannot alter the primary right-hand acceleration.
Null-space objective and constraints¶
The left-hand target is generated by \((q_{LS},q_{LE})=(-0.45,1.00)\), giving
With \(J_L=\partial\boldsymbol p_L/\partial\boldsymbol q\), the secondary objective is
The joint ranges are
All joint speeds satisfy \(|\dot q_i|\le3\ \mathrm{rad/s}\), and all null-space commands satisfy \(|z_i|\le10\ \mathrm{rad/s^2}\). The positive right-elbow lower bound keeps the \(2\times2\) arm Jacobian away from its straight-arm singularity. The terminal joint state is free.
Variables and units¶
| Symbol | Meaning | Unit |
|---|---|---|
| \(q_T\) | Torso posture angle | rad |
| \(q_{RS},q_{RE}\) | Right shoulder and elbow angles | rad |
| \(q_{LS},q_{LE}\) | Left shoulder and elbow angles | rad |
| \(\dot{\boldsymbol q}\) | Joint velocities | rad/s |
| \(\boldsymbol z\) | Null-space joint-acceleration command | rad/s² |
| \(\boldsymbol p_R,\boldsymbol p_L\) | Hand positions | m |
| \(J,J_L\) | Hand Jacobians | m/rad |
Modeling choices¶
Planar revolute coordinates avoid the redundant quaternion and unit-norm constraint that a spatial attitude model would require. The stabilized shoulder frame and identity acceleration dynamics isolate the task-priority idea from rigid-body dynamics. The script evaluates the closed form of \(J^\#=J^{\mathsf T}(JJ^{\mathsf T})^{-1}\); this is mathematically identical to the matrix expression but substantially reduces symbolic Hessian generation time.
The problem uses one fixed-time Lobatto phase with 10 mesh intervals and four points per interval. A quintic joint-space initial guess moves toward the secondary targets with zero endpoint velocity. After optimization, all states and controls are reconstructed at 4,001 uniformly spaced physical times. The script checks every joint-angle, joint-speed, and null-acceleration bound on that dense grid in addition to evaluating the task hierarchy.
Run the example¶
From the repository root, run:
Symbolic differentiation for this nonlinear hierarchy makes the first run slower than the other introductory examples. To save the figure without opening an interactive window:
Key implementation¶
joint_angles = sp.Matrix(phase.x[:5])
joint_velocities = sp.Matrix(phase.x[5:])
z = sp.Matrix(phase.u)
right_hand, left_hand = _symbolic_hand_positions(joint_angles)
J = right_hand.jacobian(joint_angles)
J_left = left_hand.jacobian(joint_angles)
J_dot = sp.zeros(2, 5)
for i in range(5):
J_dot += J.diff(joint_angles[i]) * joint_velocities[i]
# Closed form of J.T * (J * J.T)^-1 for the nonsingular right 2R arm.
J_arm = J[:, 1:3]
det_J_arm = (
J_arm[0, 0] * J_arm[1, 1] - J_arm[0, 1] * J_arm[1, 0]
)
J_arm_inverse = sp.Matrix(
[[J_arm[1, 1], -J_arm[0, 1]],
[-J_arm[1, 0], J_arm[0, 0]]]
) / det_J_arm
J_pinv = sp.zeros(5, 2)
J_pinv[1:3, :] = J_arm_inverse
N = sp.diag(1.0, 0.0, 0.0, 1.0, 1.0)
a_ref = (
desired_acceleration
+ 36.0 * (desired_position - right_hand)
+ 12.0 * (desired_velocity - J * joint_velocities)
)
qdd = J_pinv * (a_ref - J_dot * joint_velocities) + N * z
phase.set_dynamics([*joint_velocities, *qdd])
The full source also defines the secondary integral cost, joint bounds, dynamics-informed initial guess, dense numerical hierarchy and bound checks, and all plots.
Verified result¶
The script verifies the hierarchy numerically after every successful solve:
| Check | Verified value |
|---|---|
| Maximum null-space leakage \(\max_t\lVert JN\rVert_F\) | \(4.286\times10^{-16}\) |
| Maximum right-hand tracking error | \(1.535\times10^{-6}\ \mathrm m\) |
| Initial left-hand target error | \(0.0820\ \mathrm m\) |
| Final left-hand target error | \(0.0055\ \mathrm m\) |
| Maximum dense path-bound violation | \(0\) |
Thus the lower-priority motion improves the left-hand task by more than an order of magnitude while preserving the right-hand trajectory to numerical precision. The figure shows sampled arm poses, both hand trajectories, task errors, torso regulation, and the measured null-space leakage from the dense history without clipping the plotted diagnostic values.

Source code¶
See the complete runnable example:
examples/humanoid_whole_body_control.py.