Convex MPC   Whole Body Control   RL Policy   2026

BHEEMA
Making a humanoid
walk from scratch

A convex MPC locomotion controller and an RL policy for the Unitree G1 humanoid, built from scratch in MuJoCo. I run classical control and learned locomotion on the same platform.

1.4ms
MPC solve
16 Hz
MPC rate
200 Hz
WBC rate
50 Hz
RL policy
20s+
Stable walk
Walking demo
Unitree G1 bipedal walking   MuJoCo   Convex MPC + Whole Body Control
What this is

Convex MPC for
bipedal locomotion
on the Unitree G1

BHEEMA is a model predictive control based locomotion system for the Unitree G1 humanoid. I built the whole control stack myself, following the MIT Cheetah 3 convex MPC framework and adapting it for a bipedal platform with passive ankles.

The G1 has no actuated ankles, and this matters a lot. Most bipedal MPC formulations assume 12 degrees of force control per step. The G1 only gives you 6, because each foot is basically a point contact. So I had to build the whole MPC around that constraint.

The system runs in MuJoCo with hardware matched actuator limits. The MPC plans at 16 Hz, the whole body controller runs at 200 Hz, and the physics runs at 2000 Hz. The robot stands as long as I want and walks forward with an alternating gait for over 20 seconds.

Architecture

Three rates.
One control loop.
Full stack integration.

The MPC solves a convex QP over a finite horizon using single rigid body dynamics. It outputs contact forces for each stance leg. The whole body controller maps those forces to joint torques through Jacobian transposes, then adds gravity compensation and joint level PD feedback.

The swing leg controller uses 6D operational space impedance control to track foot trajectories from a Raibert heuristic footstep planner. The gait scheduler decides which legs are in stance and which are in swing at each moment.

16Hz
Centroidal MPC
SRBD dynamics, OSQP solver, friction cones, contact force output per stance leg
OSQP
↓   contact forces
200Hz
Whole Body Controller
Stance: J^T force mapping + gravity comp + PD. Swing: impedance control
Pinocchio
↓   joint torques
2000Hz
MuJoCo Physics
Full rigid body simulation, hardware matched actuator limits (88/139/50 Nm)
MuJoCo
MPC Formulation

Single rigid body
dynamics

The centroidal MPC models the G1 as a single rigid body with external contact forces at the feet. I linearize the rotation dynamics around the current orientation to keep the optimization convex, so the QP solves fast and reliably every cycle.

I approximate the friction cone constraints as pyramids and enforce them directly in the QP. The cost function penalizes deviations from the desired body pose and velocity, and I weight pitch and roll highest to keep the linearization valid.

  • Single rigid body dynamics with linearized rotation
  • 6D contact force formulation for passive ankles
  • OSQP as QP solver with warm starting
  • Friction cone constraints as linearized pyramids
  • Pitch/roll weighted highest in Q matrix for linearization validity
  • Horizontal force bounds at 400N per stance leg
  • 1.4 ms average solve time, well within 62.5 ms budget
  • Raibert heuristic footstep planner with gait scheduler
Whole Body Control

From contact forces
to joint torques

The MPC outputs desired contact forces. The whole body controller turns those into motor commands that keep the robot balanced. Here is how it works at 200 Hz.

01
Jacobian Transpose Force Mapping
I map the MPC contact forces to joint torques through the foot Jacobian transpose. This is the main torque signal for stance legs.
Pinocchio   J^T
02
Gravity Compensation
I add the full Coriolis and gravity vector from Pinocchio to the feedforward torques. Without this, every joint has to fight gravity only through the MPC force signal.
C(q,dq) + g(q)
03
Joint PD Feedback
A proportional derivative feedback layer (Kp=175, Kd=20) runs on top of the feedforward torques. This keeps the system stable under disturbances that pure feedforward cannot handle.
Kp=175   Kd=20
04
Swing Leg Impedance Control
Swing legs track foot trajectories using 6D operational space impedance control. The Raibert planner generates the foot path and the controller handles the tracking.
6D Impedance
System

Every component
accounted for

MPC Solver
OSQP
Convex QP for centroidal dynamics. Sparse formulation, warm-started between solves, 1.4 ms average.
Dynamics
Pinocchio
Analytical Jacobians, gravity vectors, forward kinematics, center of mass computations. Runs at 200 Hz.
Simulator
MuJoCo
Full rigid body physics at 2000 Hz. Hardware matched actuator limits: hip 88 Nm, knee 139 Nm, ankle 50 Nm.
Stance Control
J^T + Gravity + PD
Feedforward force mapping plus gravity compensation plus joint space PD feedback for disturbance rejection.
Swing Control
6D Impedance
Operational space impedance controller tracking foot trajectories from the Raibert footstep planner.
Footstep Plan
Raibert Heuristic
Velocity based footstep placement with gait scheduler coordinating stance and swing phase timing.
Robot
Unitree G1
29 DoF humanoid with passive ankles. Point contact feet. MJCF model with hardware matched actuator specs.
Reference
MIT Cheetah 3
Convex MPC for legged robots. Adapted from quadruped to bipedal with a passive ankle 6D force formulation.
RL Policy Demo
Unitree G1 bipedal walking   MuJoCo   RL Policy + Keyboard Teleop
Reinforcement Learning

Learning to walk
from rewards

Next to the classical MPC stack, I trained an RL locomotion policy in MjLab as a second approach to bipedal walking. The policy learns straight from reward signals like velocity tracking, energy efficiency, gait quality, and posture stability, instead of from an explicit dynamics model.

The environment forks MjLab's well tested G1 velocity task and I tune the reward weights to shape the walking behavior I wanted. The observations, actions, terminations, and domain randomization come from the base config. The reward function is what makes the policy different.

I deploy the trained checkpoint through a pure NumPy inference bridge that handles the joint mapping between the 29 joint training model and the 43 joint MJCF, which includes BrainCo hands. The legs run PD torque control and the upper body runs position targets, which matches the actuator types in the physical hardware.

2048Envs
Parallel Training
PPO policy trained in MjLab across 2048 parallel environments on flat terrain
MjLab
↓   reward shaping
99Obs dim
Observation Space
Base velocities, projected gravity, joint pos/vel relative to default, previous action, velocity command
29 Joints
↓   512 → 256 → 128
50Hz
Policy Inference
4 layer MLP with ELU activations, running normalized observation through pure NumPy forward pass
NumPy
↓   joint targets
1000Hz
PD Torque Control
Per joint Kp/Kd from MjLab config. Legs: explicit PD torque. Upper body: position actuator targets
MuJoCo
Reward Design

Shaping the gait
through reward weights

The reward function is where the locomotion knowledge lives. Each term already exists in MjLab's base config, so I tuned the weights to favor stable, efficient bipedal walking over general purpose locomotion.

01
Velocity Tracking
Linear XY tracking at 1.5× (aggressive), angular Z at 0.8×. This is the main task signal, follow the commanded velocity.
w = 1.5 / 0.8
+
02
Energy & Smoothness
Joint torque penalty (−1e-4), action rate (−0.01), action acceleration (−0.005). Prevents jerky, motor burning gaits.
Negative
+
03
Gait Quality
Foot clearance (0.5) to prevent shuffling, air time (0.5) for proper swing duration, foot slip penalty (−0.1) during stance.
w = 0.5 / −0.1
+
04
Body Stability
Upright penalty (−1.0), angular momentum (−0.02), body angular velocity (−0.05). Keeps the torso stable and the linearization valid.
Negative
+
05
Safety Constraints
Default pose tracking (−0.5), self-collision penalty (−1.0), joint limit penalty (−1.0). Prevents weird stances and hardware damage.
Negative
Deployment Bridge

29 joints in training.
43 joints on the robot.

I trained the policy on 29 joints (no hands). The deployment MJCF has 43 joints, which is 29 body joints plus 14 BrainCo hand joints interleaved through the kinematic tree.

The RL bridge handles this by building explicit qpos/qvel index maps at startup. For each of the 29 policy joints, it looks up the real address in the full MJCF instead of assuming naive sequential slicing. So the same bridge works no matter how many extra joints your MJCF has.

I split control by actuator type. The first 12 joints (legs) use motor actuators, so the bridge computes PD torques directly at 1000 Hz. The remaining 17 joints (waist, arms, wrists) use MuJoCo's built in position actuators and get joint angle targets directly.

Observation
99 dim
Body velocity (3), angular velocity (3), projected gravity (3), joint pos (29), joint vel (29), prev action (29), command (3)
Action Space
29 dim
Per joint position offsets from default pose, scaled by joint specific action scale factors from MjLab config
Network
MLP 4 layer
99 → 512 → 256 → 128 → 29 with ELU activations. Pure NumPy inference, no PyTorch dependency at deploy time
Normalization
Running stats
Observation mean and std from training checkpoint. Applied before every forward pass for distribution matching
Leg Control
PD Torque
Explicit Kp/Kd per joint from MjLab actuator config. Computed at sim rate (1000 Hz) for close tracking
Upper Body
Position Target
Joint angle targets written directly to MuJoCo position actuators. Built in PD handles the tracking
Teleop
Arrow Keys
Non blocking keyboard listener with velocity ramping. Forward/strafe/turn commands with emergency stop
Checkpoint
HuggingFace
Model hosted at Siddarth09/bheema_locomotion. Auto downloaded and cached via huggingface_hub at startup