Chapter 14: Whole-Body Control Overview (Without Dynamics Re-derivation)

Lesson 5: Case Study: Simple Legged/ Humanoid Task Control

In this lesson we instantiate the whole-body task-space / QP control framework on a minimal legged / humanoid example: a biped robot that must balance on one foot, regulate its center of mass (CoM), and move the swing foot to a desired location while respecting contact and torque constraints. We focus on the equation-level structure, the QP formulation, and multi-language implementations at the acceleration / torque level, assuming familiarity with robot kinematics and dynamics from a previous robotics course and with task-space QP control from the earlier lessons of this chapter.

1. Scenario and High-Level Control Architecture

Consider a floating-base humanoid or planar biped with generalized coordinates \( q \in \mathbb{R}^n \) and generalized velocities \( \dot{q} \). We study a simple task:

  • Maintain balance while standing on one foot (stance foot in contact).
  • Regulate the center of mass (CoM) position above the stance foot.
  • Move the swing foot along a prescribed Cartesian trajectory.
  • Keep the posture close to a comfortable nominal configuration.

At the control level, we assume an acceleration-based whole-body controller that computes desired joint accelerations \( \ddot{q}^{\star} \) and contact forces \( \boldsymbol{\lambda} \), and then maps them into actuator torques \( \tau \) using the robot's known dynamics model (imported from the robotics course):

\[ \mathbf{M}(q)\ddot{q} + \mathbf{h}(q,\dot{q}) = \mathbf{S}^{\top} \boldsymbol{\tau} + \mathbf{J}_c(q)^{\top}\boldsymbol{\lambda}, \]

where \( \mathbf{M}(q) \) is the inertia matrix, \( \mathbf{h}(q,\dot{q}) \) collects Coriolis, centrifugal, and gravity terms, \( \mathbf{S} \) selects actuated joints, and \( \mathbf{J}_c(q) \) is the contact Jacobian for the stance foot.

The high-level structure of the controller is:

flowchart TD
  REF["Task references (com, swing foot, posture)"]
    --> TASK["Compute task errors and desired task accelerations"]
  TASK --> QP["Build quadratic program (H, g, Aeq, beq, Aineq, bineq)"]
  QP --> SOL["Solve QP for qdd, lambda, tau"]
  SOL --> TAU["Low-level torque command tau(t)"]
  TAU --> ROBOT["Legged robot dynamics"]
  ROBOT --> SENS["State estimation (q, qd, contacts)"]
  SENS --> TASK
        

Our case study is thus an example of the general whole-body QP control framework from previous lessons, but instantiated for a simple legged / humanoid task with explicit equations.

2. Task Definitions for a Simple Humanoid

We focus on four main tasks, expressed at the acceleration level for a whole-body controller:

  1. CoM regulation task.
  2. Stance foot contact consistency (holonomic constraint).
  3. Swing foot tracking task.
  4. Posture regularization task.

2.1 CoM regulation task

Let \( \mathbf{p}_{\text{com}}(q) \in \mathbb{R}^3 \) be the CoM position. Its acceleration is

\[ \ddot{\mathbf{p}}_{\text{com}} = \mathbf{J}_{\text{com}}(q)\ddot{q} + \dot{\mathbf{J}}_{\text{com}}(q,\dot{q})\dot{q}, \]

where \( \mathbf{J}_{\text{com}}(q) \) is the CoM Jacobian. A PD law in task space defines a desired CoM acceleration:

\[ \ddot{\mathbf{p}}_{\text{com}}^{\star} = \ddot{\mathbf{p}}_{\text{com,ref}} + \mathbf{K}_p^{\text{com}}\bigl( \mathbf{p}_{\text{com,ref}} - \mathbf{p}_{\text{com}}(q) \bigr) + \mathbf{K}_d^{\text{com}}\bigl( \dot{\mathbf{p}}_{\text{com,ref}} - \dot{\mathbf{p}}_{\text{com}}(q,\dot{q}) \bigr). \]

The CoM task then becomes a linear equation in \( \ddot{q} \):

\[ \mathbf{J}_{\text{com}}(q)\ddot{q} = \ddot{\mathbf{p}}_{\text{com}}^{\star} - \dot{\mathbf{J}}_{\text{com}}(q,\dot{q})\dot{q}. \]

2.2 Stance foot contact constraint

Let \( \mathbf{x}_c(q) \) denote the stance foot Cartesian pose. Assuming it is rigidly fixed to the ground (no slip, no lift-off), its velocity and acceleration must vanish:

\[ \dot{\mathbf{x}}_c(q,\dot{q}) = \mathbf{0}, \quad \ddot{\mathbf{x}}_c(q,\dot{q},\ddot{q}) = \mathbf{0}. \]

Since \( \dot{\mathbf{x}}_c = \mathbf{J}_c(q)\dot{q} \) and \( \ddot{\mathbf{x}}_c = \mathbf{J}_c(q)\ddot{q} + \dot{\mathbf{J}}_c(q,\dot{q})\dot{q} \), this yields the acceleration-level holonomic constraint

\[ \mathbf{J}_c(q)\ddot{q} + \dot{\mathbf{J}}_c(q,\dot{q})\dot{q} = \mathbf{0}. \]

2.3 Swing foot tracking task

For the swing foot pose \( \mathbf{x}_s(q) \), we define a desired acceleration based on a reference trajectory \( \mathbf{x}_{s,\text{ref}}(t) \):

\[ \ddot{\mathbf{x}}_s^{\star} = \ddot{\mathbf{x}}_{s,\text{ref}} + \mathbf{K}_p^{s}\bigl( \mathbf{x}_{s,\text{ref}} - \mathbf{x}_s(q) \bigr) + \mathbf{K}_d^{s}\bigl( \dot{\mathbf{x}}_{s,\text{ref}} - \dot{\mathbf{x}}_s(q,\dot{q}) \bigr), \]

and the corresponding task equation

\[ \mathbf{J}_s(q)\ddot{q} = \ddot{\mathbf{x}}_s^{\star} - \dot{\mathbf{J}}_s(q,\dot{q})\dot{q}. \]

2.4 Posture task

Finally, we define a joint-space posture task that attracts the robot toward a nominal configuration \( q_0 \), e.g. a comfortable upright stance. A simple choice is

\[ \ddot{q}^{\text{post},\star} = \ddot{q}_{0} + \mathbf{K}_p^{q}(q_0 - q) + \mathbf{K}_d^{q}(\dot{q}_0 - \dot{q}), \]

with task matrix \( \mathbf{J}_{\text{post}} = \mathbf{I}_n \):

\[ \mathbf{J}_{\text{post}}\ddot{q} = \ddot{q}^{\text{post},\star}. \]

In this case the posture task is directly an equality in \( \ddot{q} \).

3. Whole-Body QP Formulation for Legged/Humanoid Tasks

We now construct a quadratic program in the decision variable \( z \) defined as

\[ z = \begin{bmatrix} \ddot{q} \\ \boldsymbol{\tau} \\ \boldsymbol{\lambda} \end{bmatrix} \in \mathbb{R}^{n + n_a + n_{\lambda}}, \]

where \( n_a \) is the number of actuated joints and \( n_{\lambda} \) is the dimension of the contact wrench vector.

3.1 Quadratic cost from multiple tasks

Each acceleration-level task can be written in the affine form \( \mathbf{A}_k \ddot{q} - \mathbf{b}_k = \mathbf{0} \), e.g. for CoM:

\[ \mathbf{A}_{\text{com}} = \mathbf{J}_{\text{com}}(q),\quad \mathbf{b}_{\text{com}} = \ddot{\mathbf{p}}_{\text{com}}^{\star} - \dot{\mathbf{J}}_{\text{com}}\dot{q}. \]

We embed these into a quadratic cost

\[ J(z) = \sum_{k \in \{\text{com},s,\text{post}\}} \frac{1}{2} w_k \bigl\| \mathbf{A}_k \ddot{q} - \mathbf{b}_k \bigr\|^2 + \frac{1}{2} w_{\text{reg}}\|\ddot{q}\|^2, \]

where \( w_k > 0 \) are task weights and \( w_{\text{reg}} \) regularizes the solution. Stacking all tasks we obtain the standard QP form

\[ \min_{z} \;\; \frac{1}{2} z^{\top}\mathbf{H}z + \mathbf{g}^{\top}z. \]

In particular, if the cost depends only on \( \ddot{q} \), \( \mathbf{H} \) has a block-diagonal structure with a nonzero \( \ddot{q} \) block and zeros elsewhere (for \( \boldsymbol{\tau} \), \( \boldsymbol{\lambda} \)).

3.2 Equality and inequality constraints

We incorporate the dynamics and contact constraints as equalities:

  • Dynamics:

    \[ \mathbf{M}(q)\ddot{q} + \mathbf{h}(q,\dot{q}) - \mathbf{S}^{\top}\boldsymbol{\tau} - \mathbf{J}_c^{\top}\boldsymbol{\lambda} = \mathbf{0}. \]

  • Stance foot contact:

    \[ \mathbf{J}_c(q)\ddot{q} + \dot{\mathbf{J}}_c(q,\dot{q})\dot{q} = \mathbf{0}. \]

These equalities can be written compactly as \( \mathbf{A}_{\text{eq}} z = \mathbf{b}_{\text{eq}} \).

Inequality constraints encode actuation limits and friction cones:

  • Torque limits:

    \[ \boldsymbol{\tau}_{\min} \le \boldsymbol{\tau} \le \boldsymbol{\tau}_{\max}. \]

  • Linearized friction cone:

    \[ \mathbf{F}\boldsymbol{\lambda} \le \mathbf{0}, \]

    where \( \mathbf{F} \) encodes the facets of the friction pyramid at the stance foot.

In matrix form we have \( \mathbf{A}_{\text{ineq}} z \le \mathbf{b}_{\text{ineq}} \).

The resulting whole-body QP for the humanoid case study is therefore:

\[ \begin{aligned} &\min_{z} && \frac{1}{2} z^{\top}\mathbf{H}z + \mathbf{g}^{\top}z \\ &\text{subject to} && \mathbf{A}_{\text{eq}}z = \mathbf{b}_{\text{eq}}, \\ & & & \mathbf{A}_{\text{ineq}}z \le \mathbf{b}_{\text{ineq}}. \end{aligned} \]

From a control perspective, the QP acts as a multi-objective inverse kinematics and inverse dynamics solver that respects contact and actuation limits.

4. Python Implementation Sketch (Pinocchio + QP Solver)

We now sketch a Python implementation using pinocchio for rigid-body dynamics and qpsolvers (or any similar QP package) for solving the whole-body QP. The code illustrates how to build task matrices and constraints; it omits robot-specific details such as URDF paths.


import numpy as np
import pinocchio as pin
from qpsolvers import solve_qp

# Assume we have a Pinocchio model of a simple humanoid
# with data structure `model`, `data` already created.
# CoM and foot frames:
com_ref = np.array([0.0, 0.0, 0.8])   # desired CoM
swing_ref = np.array([0.1, 0.1, 0.4]) # desired swing foot position
posture_ref = None  # to be set to a nominal configuration q0

Kp_com = 50.0
Kd_com = 10.0
Kp_swing = 150.0
Kd_swing = 20.0
Kp_post = 20.0
Kd_post = 5.0

def build_whole_body_qp(model, data, q, qd, support_frame, swing_frame):
    """
    Build the matrices H, g, Aeq, beq, Aineq, bineq
    for a simple whole-body QP with CoM, swing foot,
    and posture tasks.
    """
    nv = model.nv  # velocity dimension
    na = model.nv  # assume all joints actuated for simplicity

    # 1) Compute dynamics terms
    pin.computeAllTerms(model, data, q, qd)
    M = data.M.copy()
    h = data.nle.copy()   # non-linear effects

    # Selection matrix for actuated joints (identity here)
    S = np.eye(na, nv)

    # 2) CoM quantities
    com_pos = pin.centerOfMass(model, data, q)
    com_vel = data.vcom[0]
    J_com = pin.jacobianCenterOfMass(model, data, q)

    com_pos_err = com_ref - com_pos
    com_vel_err = -com_vel
    com_acc_des = (0.0
                   + Kp_com * com_pos_err
                   + Kd_com * com_vel_err)

    # CoM task: J_com * qdd = com_acc_des - Jdot_com * qd
    # Pinocchio does not give Jdot_com directly; we can approximate with finite difference
    # or use a dedicated function in more complete code.
    Jdot_com_qd = np.zeros(3)  # placeholder for simplicity
    A_com = J_com
    b_com = com_acc_des - Jdot_com_qd

    # 3) Swing foot task
    # Frame placement and Jacobian
    swing_id = model.getFrameId(swing_frame)
    pin.updateFramePlacement(model, data, swing_id)
    x_swing = data.oMf[swing_id].translation
    J_swing = pin.computeFrameJacobian(
        model, data, q, swing_id, pin.LOCAL_WORLD_ALIGNED
    )[:3, :]

    # Approximate velocity and Jdot * qd
    swing_vel = J_swing.dot(qd)
    x_err = swing_ref - x_swing
    v_err = -swing_vel
    acc_des_swing = (0.0
                     + Kp_swing * x_err
                     + Kd_swing * v_err)
    Jdot_swing_qd = np.zeros(3)  # placeholder

    A_swing = J_swing
    b_swing = acc_des_swing - Jdot_swing_qd

    # 4) Posture task
    global posture_ref
    if posture_ref is None:
        posture_ref = q.copy()
    q_err = posture_ref - q
    qd_err = -qd
    acc_des_post = (0.0
                    + Kp_post * q_err
                    + Kd_post * qd_err)
    A_post = np.eye(nv)
    b_post = acc_des_post

    # Stack tasks into least-squares cost:
    w_com = 1.0
    w_swing = 1.0
    w_post = 0.1
    w_reg = 1e-4

    A_tasks = np.vstack([
        np.sqrt(w_com) * A_com,
        np.sqrt(w_swing) * A_swing,
        np.sqrt(w_post) * A_post
    ])
    b_tasks = np.concatenate([
        np.sqrt(w_com) * b_com,
        np.sqrt(w_swing) * b_swing,
        np.sqrt(w_post) * b_post
    ])

    # Cost: 0.5 * ||A_tasks * qdd - b_tasks||^2 + 0.5 * w_reg ||qdd||^2
    H_qdd = A_tasks.T @ A_tasks + w_reg * np.eye(nv)
    g_qdd = -A_tasks.T @ b_tasks

    # Decision vector: z = [qdd, tau] (ignore contact forces for simplicity)
    # Introduce approximate dynamics: M * qdd + h = S.T * tau
    # so tau = (S^-T) * (M * qdd + h); here S is identity.
    # We eliminate tau instead of including it in z.
    H = H_qdd
    g = g_qdd

    # Equality constraints: contact acceleration (stance foot)
    support_id = model.getFrameId(support_frame)
    pin.updateFramePlacement(model, data, support_id)
    Jc = pin.computeFrameJacobian(
        model, data, q, support_id, pin.LOCAL_WORLD_ALIGNED
    )[:3, :]
    Jcdot_qd = np.zeros(3)  # placeholder
    Aeq = Jc
    beq = -Jcdot_qd

    # Inequalities (e.g. joint acceleration bounds)
    qdd_max = 20.0 * np.ones(nv)
    qdd_min = -20.0 * np.ones(nv)
    Aineq = np.vstack([np.eye(nv), -np.eye(nv)])
    bineq = np.concatenate([qdd_max, -qdd_min])

    return H, g, Aeq, beq, Aineq, bineq

def solve_whole_body_qp(H, g, Aeq, beq, Aineq, bineq):
    # qpsolvers interface: solve_qp(P, q, G, h, A, b)
    qdd_star = solve_qp(H, g, Aineq, bineq, Aeq, beq, solver="osqp")
    return qdd_star
      

In a full implementation, one would: (i) use exact expressions for \( \dot{\mathbf{J}}_k\dot{q} \), (ii) keep \( \boldsymbol{\lambda} \) and \( \boldsymbol{\tau} \) as decision variables in the QP, (iii) include friction inequalities, and (iv) wrap this computation inside a real-time control loop.

5. C++ Implementation Sketch (Eigen + OSQP-Eigen)

In C++, it is natural to combine Eigen for linear algebra, pinocchio or another rigid-body library for dynamics, and OSQP-Eigen for QP solving. Below is a minimal sketch that assumes that the task matrices for the legged case have already been assembled into H, g, Aeq, and beq.


#include <Eigen/Dense>
#include <OsqpEigen/OsqpEigen.h>

// H, g, Aeq, beq, Aineq, bineq constructed elsewhere
struct WholeBodyQP {
  Eigen::MatrixXd H;
  Eigen::VectorXd g;
  Eigen::MatrixXd Aeq;
  Eigen::VectorXd beq;
  Eigen::MatrixXd Aineq;
  Eigen::VectorXd bineq;
};

Eigen::VectorXd solveWholeBodyQP(const WholeBodyQP& qp) {
  const int nv = static_cast<int>(qp.H.rows());

  OsqpEigen::Solver solver;
  solver.settings().setVerbosity(false);
  solver.settings().setWarmStart(true);

  // OSQP uses sparse matrices
  solver.data()->setNumberOfVariables(nv);
  solver.data()->setNumberOfConstraints(
      qp.Aeq.rows() + qp.Aineq.rows()
  );

  // Build constraint matrix and bounds:
  Eigen::MatrixXd Aall(qp.Aeq.rows() + qp.Aineq.rows(), nv);
  Aall << qp.Aeq,
          qp.Aineq;

  Eigen::VectorXd lower(qp.Aeq.rows() + qp.Aineq.rows());
  Eigen::VectorXd upper(qp.Aeq.rows() + qp.Aineq.rows());

  // Equalities: Aeq * z = beq
  lower.head(qp.Aeq.rows()) = qp.beq;
  upper.head(qp.Aeq.rows()) = qp.beq;

  // Inequalities: Aineq * z <= bineq
  lower.tail(qp.Aineq.rows()) =
      Eigen::VectorXd::Constant(qp.Aineq.rows(), -OsqpEigen::INFTY);
  upper.tail(qp.Aineq.rows()) = qp.bineq;

  solver.data()->setHessianMatrix(qp.H.sparseView());
  solver.data()->setGradient(qp.g);
  solver.data()->setLinearConstraintsMatrix(Aall.sparseView());
  solver.data()->setLowerBound(lower);
  solver.data()->setUpperBound(upper);

  if (!solver.initSolver()) {
    throw std::runtime_error("OSQP init failed");
  }

  if (!solver.solve()) {
    throw std::runtime_error("OSQP solve failed");
  }

  return solver.getSolution();
}
      

The robot-specific part is the construction of the WholeBodyQP matrices from CoM, swing-foot, posture tasks and contact/dynamics constraints, exactly as in the Python sketch but using C++ bindings of the robotics library.

6. Java Implementation Sketch (EJML + ojAlgo)

While Java is less common in low-level whole-body control, we can still implement the QP using libraries such as EJML (for linear algebra) and ojAlgo (for optimization). The following sketch assumes a simplified QP with only inequality bounds on joint accelerations and lumped tasks into H and g.


import org.ejml.simple.SimpleMatrix;
import org.ojalgo.optimisation.ExpressionsBasedModel;
import org.ojalgo.optimisation.Variable;
import org.ojalgo.optimisation.Optimisation.Result;

public class WholeBodyQPJava {

    public static double[] solveQP(SimpleMatrix H, SimpleMatrix g,
                                   double[] qddMin, double[] qddMax) {

        int n = H.numRows();
        ExpressionsBasedModel model = new ExpressionsBasedModel();

        Variable[] qddVars = new Variable[n];
        for (int i = 0; i < n; ++i) {
            qddVars[i] = Variable.make("qdd" + i)
                    .lower(qddMin[i])
                    .upper(qddMax[i]);
            model.addVariable(qddVars[i]);
        }

        // Quadratic objective: 0.5 * qdd^T H qdd + g^T qdd
        var expr = model.addExpression("cost");
        for (int i = 0; i < n; ++i) {
            expr.set(qddVars[i], g.get(i, 0));
            for (int j = 0; j < n; ++j) {
                double hij = 0.5 * H.get(i, j);
                expr.setQuadraticFactor(qddVars[i], qddVars[j], hij);
            }
        }
        expr.weight(1.0);

        Result result = model.minimise();
        double[] qdd = new double[n];
        for (int i = 0; i < n; ++i) {
            qdd[i] = result.get(i).doubleValue();
        }
        return qdd;
    }
}
      

The dynamics and task construction steps mirror the Python and C++ implementations; only the linear algebra and optimization libraries change. The state update and low-level torque commands would typically be handled in a real-time loop written in another language or via JNI.

7. MATLAB / Simulink Implementation

MATLAB is widely used for rapid prototyping of whole-body controllers. We can use the Robotics System Toolbox for kinematics/dynamics and quadprog for the QP. A simple script for the legged case is:


% Assume rigidBodyTree object 'humanoid' from Robotics System Toolbox
% and kinematics helpers have been defined.
function qdd_star = wholeBodyQP_step(humanoid, q, qd, supportBody, swingBody)

  % Compute dynamics
  [M, C, G] = manipulatorDynamics(humanoid, q, qd);
  h = C * qd.' + G;

  % CoM Jacobian and position
  Jcom = centerOfMassJacobian(humanoid, q);
  pcom = centerOfMass(humanoid, q);

  pcomRef = [0, 0, 0.8];
  Kp_com = 50; Kd_com = 10;

  vcom = Jcom * qd.';
  comErr = pcomRef - pcom;
  comVelErr = -vcom;
  comAccDes = Kp_com * comErr + Kd_com * comVelErr;
  JdotComQd = zeros(1, size(Jcom, 2)); % placeholder

  Acom = Jcom;
  bcom = comAccDes - JdotComQd;

  % Swing foot task
  swingId = bodyIndex(humanoid, swingBody);
  [Tswing, Jswing] = forwardKinematics(humanoid, q, swingId);
  xswing = tform2trvec(Tswing);
  vswing = Jswing * qd.';

  swingRef = [0.1, 0.1, 0.4];
  Kp_s = 150; Kd_s = 20;
  xErr = swingRef - xswing;
  vErr = -vswing;
  accSwingDes = Kp_s * xErr + Kd_s * vErr;
  JdotSwingQd = zeros(1, size(Jswing, 2)); % placeholder

  Aswing = Jswing(1:3, :);
  bswing = accSwingDes - JdotSwingQd(1:3);

  % Posture task
  q0 = q; % nominal posture
  Kp_q = 20; Kd_q = 5;
  qErr = q0 - q;
  qdErr = -qd;
  accPostDes = Kp_q * qErr + Kd_q * qdErr;

  Apost = eye(numel(q));
  bpost = accPostDes;

  % Stack tasks
  w_com = 1.0; w_swing = 1.0; w_post = 0.1; w_reg = 1e-4;

  At = [sqrt(w_com) * Acom;
        sqrt(w_swing) * Aswing;
        sqrt(w_post) * Apost];

  bt = [sqrt(w_com) * bcom.';
        sqrt(w_swing) * bswing.';
        sqrt(w_post) * bpost.'];

  H = At.' * At + w_reg * eye(size(At, 2));
  f = -At.' * bt;

  % Contact acceleration constraint (holonomic, stance foot)
  supportId = bodyIndex(humanoid, supportBody);
  [Tsup, Jsup] = forwardKinematics(humanoid, q, supportId);
  Jc = Jsup(1:3, :);
  JcdotQd = zeros(3, 1); % placeholder

  Aeq = Jc;
  beq = -JcdotQd;

  % Acceleration bounds
  n = numel(q);
  qddMax = 20 * ones(n, 1);
  qddMin = -20 * ones(n, 1);
  Aineq = [eye(n); -eye(n)];
  bineq = [qddMax; -qddMin];

  opts = optimoptions('quadprog', 'Display', 'off');
  qdd_star = quadprog(H, f, Aineq, bineq, Aeq, beq, [], [], [], opts).';
end
      

In Simulink, this function can be wrapped inside a MATLAB Function block that runs at the control rate; the block inputs are \( q \), \( \dot{q} \), contact information, and reference trajectories, while the outputs are \( \ddot{q}^{\star} \) or directly \( \boldsymbol{\tau} \) via \( \mathbf{M}(q)\ddot{q}^{\star} + \mathbf{h} \).

8. Wolfram Mathematica Implementation Sketch

In Wolfram Mathematica, the acceleration-level QP can be encoded using QuadraticOptimization or NMinimize. The following sketch considers only joint accelerations \( \ddot{q} \) as decision variables and aggregates CoM, swing, and posture tasks:


(* Dimensions *)
nv = 6; (* for example *)

(* Symbolic decision variables for joint accelerations *)
qdd = Array[qdd, nv];

(* Task matrices and vectors (numeric arrays from kinematics/dynamics) *)
Acom   = { {(* ... fill with J_com entries ... *)} };
bcom   = {(* ... com_acc_des - Jdot_com.qd ... *)};

Aswing = { {(* ... J_swing ... *)} };
bswing = {(* ... swing_acc_des - Jdot_swing.qd ... *)};

Apost  = IdentityMatrix[nv];
bpost  = {(* ... posture_acc_des ... *)};

wcom   = 1.0;
wswing = 1.0;
wpost  = 0.1;
wreg   = 10.^(-4);

At = Join[
  Sqrt[wcom]  Acom,
  Sqrt[wswing] Aswing,
  Sqrt[wpost] Apost
];

bt = Join[
  Sqrt[wcom]  bcom,
  Sqrt[wswing] bswing,
  Sqrt[wpost] bpost
];

H = Transpose[At].At + wreg IdentityMatrix[nv];
g = -Transpose[At].bt;

(* Contact constraint Jc qdd + Jcdot_qd == 0 *)
Jc        = { {(* ... stance foot Jacobian rows ... *)} };
Jcdot_qd  = {(* ... precomputed numeric vector ... *)};

eqConstr = {
  Jc.qdd + Jcdot_qd == ConstantArray[0, Length[Jcdot_qd]]
};

(* Acceleration bounds *)
qddMin = -20 ConstantArray[1, nv];
qddMax =  20 ConstantArray[1, nv];

ineqConstr = Thread[qddMin <= qdd <= qddMax];

(* Quadratic optimization *)
cost[qv_] := 1/2 qv.H.qv + g.qv;

sol = NMinimize[
  { cost[qdd], Join[eqConstr, ineqConstr] },
  qdd
];

qddStar = qdd /. Last[sol];
      

In a more advanced setup, one may represent the kinematics symbolically and generate optimized C code from Mathematica, but that lies beyond the scope of this case study.

9. Problems and Solutions

Problem 1 (Contact constraint derivation). For a stance foot point with Cartesian position \( \mathbf{x}_c(q) \), show that enforcing zero velocity and acceleration at the contact yields the constraint \( \mathbf{J}_c(q)\ddot{q} + \dot{\mathbf{J}}_c(q,\dot{q})\dot{q} = \mathbf{0} \). Explain its physical meaning.

Solution. The stance foot velocity is \( \dot{\mathbf{x}}_c = \mathbf{J}_c(q)\dot{q} \). Differentiating once more in time:

\[ \ddot{\mathbf{x}}_c = \frac{\mathrm{d}}{\mathrm{d}t} \bigl( \mathbf{J}_c(q)\dot{q} \bigr) = \dot{\mathbf{J}}_c(q,\dot{q})\dot{q} + \mathbf{J}_c(q)\ddot{q}. \]

Rigid contact with the environment requires \( \dot{\mathbf{x}}_c = \mathbf{0} \) and \( \ddot{\mathbf{x}}_c = \mathbf{0} \). Thus,

\[ \mathbf{J}_c(q)\ddot{q} + \dot{\mathbf{J}}_c(q,\dot{q})\dot{q} = \mathbf{0}, \]

which is the desired constraint. Physically, it ensures that the contact point neither moves nor accelerates relative to the ground, preventing slip and lift-off at the acceleration level.

Problem 2 (Closed-form solution for 1-DOF two-task QP). Consider a single joint with acceleration \( \ddot{q} \) and two tasks expressed as desired accelerations \( a_1^{\star} \) (e.g. CoM-related) and \( a_2^{\star} \) (e.g. posture-related). The QP is

\[ \min_{\ddot{q}} \; \frac{1}{2} w_1(\ddot{q} - a_1^{\star})^2 + \frac{1}{2} w_2(\ddot{q} - a_2^{\star})^2, \]

with \( w_1, w_2 > 0 \). Derive the optimal \( \ddot{q}^{\star} \) explicitly.

Solution. Differentiate the cost with respect to \( \ddot{q} \), set the derivative to zero:

\[ \frac{\partial}{\partial \ddot{q}} \left( \frac{1}{2} w_1(\ddot{q} - a_1^{\star})^2 + \frac{1}{2} w_2(\ddot{q} - a_2^{\star})^2 \right) = w_1(\ddot{q} - a_1^{\star}) + w_2(\ddot{q} - a_2^{\star}) = 0. \]

Solving for \( \ddot{q} \):

\[ (w_1 + w_2)\ddot{q} = w_1 a_1^{\star} + w_2 a_2^{\star} \quad\Rightarrow\quad \ddot{q}^{\star} = \frac{w_1 a_1^{\star} + w_2 a_2^{\star}}{w_1 + w_2}. \]

Thus the optimal acceleration is a weighted average of the two task accelerations, consistent with the general multi-task QP structure.

Problem 3 (Limit of large task weight and hierarchy). In the previous problem, assume that task 1 (e.g. CoM regulation) is more important than task 2. Show that, as \( w_1 \to \infty \) with \( w_2 \) fixed, the solution satisfies \( \ddot{q}^{\star} \to a_1^{\star} \). Interpret this as an approximation of strict task hierarchy.

Solution. We have

\[ \ddot{q}^{\star}(w_1, w_2) = \frac{w_1 a_1^{\star} + w_2 a_2^{\star}}{w_1 + w_2}. \]

Rewrite as

\[ \ddot{q}^{\star} = a_1^{\star} + \frac{w_2}{w_1 + w_2} (a_2^{\star} - a_1^{\star}). \]

As \( w_1 \to \infty \), the factor \( \frac{w_2}{w_1 + w_2} \to 0 \), hence

\[ \lim_{w_1 \to \infty} \ddot{q}^{\star} = a_1^{\star}. \]

In the limit of very large weight, the solution enforces task 1 exactly while task 2 only influences the solution when there is residual redundancy. This illustrates how large weights approximate strict task priority in a QP, though true hierarchical QPs are numerically more robust.

Problem 4 (Stacked task ordering for a legged humanoid). For the standing-on-one-foot case, propose a priority ordering of the following tasks and discuss the rationale:

  • (A) Contact consistency for the stance foot.
  • (B) CoM regulation.
  • (C) Swing foot tracking.
  • (D) Posture regularization.

Solution. A natural ordering from highest to lowest priority is (A) → (B) → (C) → (D). The reasoning can be summarized by the following schematic:

flowchart TD
  ROOT["Start"] --> T1["Highest: stance contact constraints (A)"]
  T1 --> T2["High: balance via com regulation (B)"]
  T2 --> T3["Medium: swing foot tracking (C)"]
  T3 --> T4["Low: posture regularization (D)"]
  T4 --> SOL["Solve QP and send torques"]
        

Contact consistency (A) is essential for physical feasibility; breaking it invalidates the whole model. CoM regulation (B) is critical for balance; a loss of CoM control leads to falls. Swing foot tracking (C) is important but can be sacrificed temporarily to avoid losing balance. Posture regularization (D) is purely aesthetic / comfort-related and should never compromise the higher-priority tasks.

10. Summary

In this lesson we applied the general whole-body QP control framework to a concrete legged / humanoid scenario: balancing on one foot while regulating the CoM, tracking a swing-foot trajectory, and maintaining a comfortable posture. We expressed each task as an acceleration-level linear relation in \( \ddot{q} \), aggregated them into a quadratic cost, and enforced dynamics, contact, and actuation limits as equality and inequality constraints.

We then showed how to implement this controller sketch in multiple programming environments (Python, C++, Java, MATLAB/Simulink, Wolfram Mathematica), emphasizing the construction of the QP matrices rather than library-specific details. Finally, we solved several analytical problems to deepen intuition about contact constraints, multi-task blending, and hierarchical priorities. These ideas provide a bridge between abstract whole-body control theory and practical implementations on legged and humanoid robots.

11. References

  1. Khatib, O. (1987). A unified approach for motion and force control of robot manipulators: The operational space formulation. IEEE Journal on Robotics and Automation, 3(1), 43–53.
  2. Sentis, L., & Khatib, O. (2006). A whole-body control framework for humanoids operating in human environments. IEEE International Conference on Robotics and Automation (ICRA), 2641–2648.
  3. Saab, L., Ramos, O., Mansard, N., Keith, F., Souères, P., & Fourquet, J.Y. (2013). Dynamic whole-body motion generation under rigid contacts and other unilateral constraints. IEEE Transactions on Robotics, 29(2), 346–362.
  4. Escande, A., Mansard, N., & Wieber, P.B. (2014). Hierarchical quadratic programming: Fast online humanoid-robot motion generation. International Journal of Robotics Research, 33(7), 1006–1028.
  5. Ott, C., Dietrich, A., & Albu-Schäffer, A. (2016). Prioritized multi-task compliance control of redundant manipulators. Automatica, 53, 416–423.
  6. Herzog, A., Righetti, L., Grimminger, F., Pastor, P., & Schaal, S. (2016). Balancing experiments on a torque-controlled humanoid with hierarchical inverse dynamics. IEEE/RSJ International Conference on Intelligent Robots and Systems (IROS), 981–988.
  7. Kim, J., & Sentis, L. (2014). Prioritized control of humanoid robots with task scaling under physical constraints. IEEE/RSJ International Conference on Intelligent Robots and Systems (IROS), 1449–1454.
  8. Wieber, P.B., Tedrake, R., & Kuindersma, S. (2016). Modeling and control of legged robots. In Springer Handbook of Robotics (2nd ed.), 1203–1234.
  9. Kuindersma, S., Deits, R., Fallon, M., Valenzuela, A., Dai, H., Permenter, F., Koolen, T., Marion, P., & Tedrake, R. (2016). Optimization-based locomotion planning, estimation, and control design for the Atlas humanoid robot. Autonomous Robots, 40(3), 429–455.
  10. Del Prete, A. (2021). Joint torque control with contact-stable optimization. IEEE Transactions on Robotics, 37(1), 219–237.