Skip to content

Latest commit

 

History

29 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

horizon_rti

Real-Time Iteration (RTI) MPC pipeline for floating-base legged robots, built on top of Horizon and phase_manager.


Configuration (YAML)

The pipeline is driven entirely by a single YAML file (default path: ~/.ros/receding_kyon.yaml).

Top-level sections

model:            # URDF, floating base, fixed joints
problem:          # Horizon problem variables, costs, constraints
tasks:            # CartesianTask / ContactTask definitions
gait_manager:     # stance/flight phase timing per contact

Pipeline Walkthrough

1. Problem & Interfaces

Problem parses the YAML and builds the Horizon MPC problem (decision variables, parameters, cost terms, constraints). Only basic functionalities are ported from Python to C++.

Problem

Member Type Description
from_yaml(node) method Parse YAML → populate variables, parameters, costs, constraints, dynamics
var_map map<string, Variable::Ptr> All decision variables
param_map map<string, Parameter::Ptr> All parameters
cost_map map<string, Cost::Ptr> Cost terms
constr_map map<string, Constraint::Ptr> Constraints
N int Number of shooting nodes
dt VectorXd Time step(s)
dynamics casadi::Function Discrete dynamics

ProblemInterface

Initialization (call once, before the RTI loop):

Method Description
setModel(model) Attach a FullModelInverseDynamics
setFunctions() Register all costs and constraints with the iLQR solver
loadInitialGuess() Seed state/input trajectories from the problem's static x_ini / u_ini (from YAML)
updateBounds() Push initial state/input bounds to the solver
setParameterValues() Push initial parameter values (weights, references, …) to the solver
initWeightMasks() Set every *_weight_mask parameter to all-ones
setFunctionNodes() Set active node indices for every cost/constraint in the solver
bootstrap() Warm-up: run 100 iLQR iterations from the initial guess

RTI loop (call every cycle):

Method Description
loadSolutionAsInitialGuess(shift) Roll last solution by shift and use as warm-start; pins x0
setParameterValues() Push updated parameter values to the solver
updateBounds() Re-pin x0 in xlb/xub and push bounds to the solver
setFunctionNodes() Update active node indices (after pm->update())
rti() Single RTI step

2. Model

FullModelInverseDynamics builds a CasADi kinematics/dynamics object from the URDF.

Method Description
getKinDyn() Access the underlying CasadiKinDyn object
getJointNames() Ordered list of non-fixed joint names
isFloatingBase() true if the robot has a floating base
nq() / nv() Configuration / velocity space size
fk(frame) Forward kinematics CasADi function for a frame
jacobian(frame) Jacobian CasADi function for a frame
getContacts() List of contact frame names
getContactMap() Map from contact frame → associated force variable names
getForceMap() Map from force variable name → contact frame
getInitialState(var) Initial state vector for a given variable
getInitialInput(var) Initial input vector for a given variable
getQInit() Per-joint initial configuration (map<name, value>)
getBaseInit() 7-element base pose initial value (position + quaternion)

3. Task Interface

TaskInterface owns the task registry. Built-in task types:

YAML type C++ class Description
Cartesian CartesianTask Cartesian position/orientation tracking
Contact ContactTask Contact force / no-slip constraint
Method Description
from_yaml(config) Instantiate all tasks from the tasks: YAML section
getTask(name) Retrieve a task by name
getTasks() Return the full ordered task list
getContactList() Ordered list of contact frame names (from ContactTask entries)
getProblemInterface() Access the underlying ProblemInterface
printSolverState() Dump active costs/constraints and their node sets

Initialization (call once, before the RTI loop):

Method Description
bootstrap() Delegate to ProblemInterface::bootstrap()
loadSolutionAsInitialGuess(shift) Shift and reseed warm-start from bootstrap solution

RTI loop (call every cycle):

Method Description
loadSolutionAsInitialGuess(shift) Roll last solution by shift and reseed warm-start; pins x0
updateParameters() Delegate to ProblemInterface::setParameterValues()
updateBounds() Delegate to ProblemInterface::updateBounds()
updateIndices() Rebuild solver per-node cost/constraint index tables
rti() Delegate to ProblemInterface::rti()
getSolution() Return last solution as map<name, MatrixXd>

4. Gait Manager

GaitManager reads from the YAML file to populate phases of the PhaseManager. In particular:

  • One Timeline per contact, each holding a stance phase and a flight phase.
  • A ContactSwing object per contact that generates smooth arc trajectories.
  • Base-velocity reference state (setBaseVelocity()).
Method Description
fromYaml(node) Load stance/flight timing and initial mode from the gait_manager: YAML section
initializeTimeline() Populate all timelines with their initial phases
run() Dispatch current mode (append phases) and update base-velocity parameters — call once per cycle
switchMode(mode, enable) Programmatically activate / deactivate an OperationMode
getOperationMode() Current OperationMode
setBaseVelocity(vel) Set 6-DOF base velocity reference [vx,vy,vz,wx,wy,wz]
setParams(action, params) Override gait parameters (step_duration, step_height, double_stance)
getParams(action) Read current gait parameters for an action
getContactTimelines() Map of contact name → Timeline::Ptr
getContactTimeline(c) Single timeline for contact c
getStancePhase(c) / getFlightPhase(c) Phase objects for a contact
hasSwing() Whether any swing task is configured
getContactSwing(c) ContactSwing object for contact c
print_phases() Print current timeline state to stdout

GaitManager::run() dispatches the current OperationMode:

Mode Behaviour
STAND Fills all timelines with stance phases
TROT Alternates diagonal pairs (LF+RH, RF+LH)
CRAWL Sequential single-foot swing
WALK Slow, overlapping stance transitions
STEP Single step on demand
DRAG One contact drags along the ground
IDLE No phase modifications

Gait parameters (step_duration, step_height, double_stance) are per-mode and can be changed at runtime via setParams() or the ROS parameter server.

ROS Layer

The ROS layer is optional and sits entirely on top of the core pipeline. It is composed of two classes that handle all I/O with the ROS ecosystem, leaving the solver loop free of any ROS dependency.

GaitManagerROSInterface

GaitManagerROSInterface is a zero-logic wrapper around GaitManager.
All it does is translate ROS messages/services into calls on GaitManager.

Services

Service Type Description
/horizon/stand/switch std_srvs/SetBool Enable/disable STAND mode
/horizon/trot/switch std_srvs/SetBool Enable/disable TROT mode
/horizon/crawl/switch std_srvs/SetBool Enable/disable CRAWL mode
/horizon/walk/switch std_srvs/SetBool Enable/disable WALK mode
/horizon/step/switch std_srvs/SetBool Enable/disable STEP mode
/horizon/drag/switch std_srvs/SetBool Enable/disable DRAG mode
/horizon/idle/switch std_srvs/SetBool Enable/disable IDLE mode

Topics

Topic Type Direction Description
/horizon/base_velocity/reference geometry_msgs/Twist Sub Sets base velocity reference (linear x/y, angular z)
/horizon/operation_mode horizon_msgs/OperationMode Pub Current operation mode (if horizon_msgs available)

ROS Parameters (per gait mode)

/horizon/<mode>/step_duration   (int)
/horizon/<mode>/step_height     (double)
/horizon/<mode>/double_stance   (int)

These are read at each service call and pushed to GaitManager.


JoyCommands

JoyCommands subscribes to /joy and:

  • Converts stick axes to a geometry_msgs/Twist published on /horizon/base_velocity/reference.
  • Calls gait-switch services on button edge events (non-blocking, detached thread).

Topics

Topic Type Direction Description
/joy sensor_msgs/Joy Sub Raw joystick input
/horizon/base_velocity/cmd geometry_msgs/Twist Pub Commanded base velocity

Default button mapping

Button Action
btn[4] (hold) CRAWL on / off
btn[5] (hold) TROT on / off
btn[6] (momentary) STAND
btn[7] (momentary) IDLE
btn[0] Raise COM (+0.01 m)
btn[2] Lower COM (−0.01 m)

Default axis mapping

Axis Effect
axes[1] Linear X (forward / backward)
axes[0] Linear Y (strafe)
axes[3] Angular Z (yaw)

Scaling is set via JoyCommandsOptions::lin_vel_scale (default 0.2 m/s) and ang_vel_scale (default 0.3 rad/s).


RTI Benchmark

Per-section timing comparison between the Python and C++ RTI pipelines, measured over 455 iterations (STAND → TROT forward → TROT rotate → STAND), 1 iLQR step per iteration.
Both pipelines run the same problem (kyon quadruped, same YAML config).
The original problem can be found in the kyon_controller repository, benchmarking branch — specifically the file python/receding_horizon.py.

Python

Python RTI Benchmark

C++

C++ RTI Benchmark

Summary

Section Python (mean) C++ (mean) Speedup
loadSolutionAsInitialGuess 0.33 ms 0.023 ms ~14×
pm.shift() 0.72 ms 0.172 ms ~4×
gm.run() 0.39 ms 0.102 ms ~4×
pm.update() 7.22 ms 1.325 ms ~5×
updateParams+Bounds+Indices 5.33 ms 0.614 ms ~9×
rti (solver) 8.38 ms 9.84 ms
TOTAL 22.64 ms 12.16 ms ~1.9×

The solver step is comparable between the two (same underlying C++ iLQR), while all surrounding pipeline overhead is substantially lower in C++.

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages