Tutorial 5: Using the MPC Planner

📋 Implementation Status: This tutorial describes the planned MPC planner functionality. The MPC planner architecture is fully specified and documented, but the implementation is currently in progress. This document serves as both a specification for developers and a preview of upcoming features for users.

Model Predictive Control (MPC) enables real-time reactive trajectory planning that can adapt to dynamic obstacles and disturbances.


What is MPC?

MPC (Model Predictive Control) is a closed-loop control strategy that:

  • Predicts future states over a time horizon

  • Optimizes control actions to reach the goal

  • Executes only the first control step

  • Repeats continuously until goal is reached

MPC vs Classic Planning

Aspect

Classic Planner

MPC Planner

Loop

Open-loop

Closed-loop

Planning

Once before execution

Continuous during execution

Adaptability

Fixed trajectory

Real-time adaptation

Obstacles

Static only

Static + dynamic

Disturbances

Requires replanning

Automatic correction

GPU Usage

Burst

Sustained

Latency

50-200ms

1-10ms per iteration


Prerequisites

Before starting this tutorial:

  1. Complete Tutorial 1: First Trajectory

  2. Ensure you have:

    • NVIDIA GPU with CUDA support

    • curobo_ros installed and configured

    • Robot simulator or real robot


Step 1: Launch with MPC Planner

Using Launch File

Create a launch file that uses MPC by default:

# mpc_demo.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
 
def generate_launch_description():
    return LaunchDescription([
        Node(
            package='curobo_ros',
            executable='curobo_trajectory_planner',
            name='trajectory_planner',
            parameters=[{
                'default_planner': 'mpc',  # Use MPC planner
                'robot_type': 'doosan_m1013',
                'time_dilation_factor': 0.5,
 
                # MPC-specific parameters
                'mpc_convergence_threshold': 0.01,  # meters
                'mpc_max_iterations': 1000,
                'mpc_horizon': 10,
                'mpc_control_frequency': 100  # Hz
            }],
            output='screen'
        )
    ])

Launch it:

ros2 launch curobo_ros mpc_demo.launch.py

Using Command Line

Alternatively, launch with default settings and switch to MPC:

# Terminal 1: Launch unified planner
ros2 run curobo_ros curobo_trajectory_planner
 
# Terminal 2: Switch to MPC
ros2 service call /unified_planner/set_planner curobo_msgs/srv/SetPlanner "{planner_type: 1}"

Step 2: Configure MPC Parameters

MPC has several tunable parameters:

Convergence Threshold

How close to the goal before stopping (in meters):

ros2 param set /unified_planner mpc_convergence_threshold 0.01
  • Smaller values (0.001): Higher precision, longer execution

  • Larger values (0.05): Faster completion, lower precision

  • Recommended: 0.01-0.02 for most tasks

Max Iterations

Maximum number of MPC iterations:

ros2 param set /unified_planner mpc_max_iterations 1000
  • More iterations: Can handle complex scenes, slower

  • Fewer iterations: Faster but may not converge

  • Recommended: 500-1000 for dynamic environments

MPC Horizon

Planning horizon length (number of future steps):

ros2 param set /unified_planner mpc_horizon 10
  • Longer horizon: Better long-term planning, more computation

  • Shorter horizon: Reactive, less computational cost

  • Recommended: 5-15 steps

Control Frequency

How often MPC replans (Hz):

ros2 param set /unified_planner mpc_control_frequency 100
  • Higher frequency: More reactive, more GPU usage

  • Lower frequency: Less smooth, more efficient

  • Recommended: 50-100 Hz for real-time control


Step 3: Generate and Execute with MPC

Using Python API

#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from curobo_ros.planners import PlannerFactory
from geometry_msgs.msg import Pose
import numpy as np
 
class MPCDemoNode(Node):
    def __init__(self):
        super().__init__('mpc_demo_node')
 
        # Create MPC planner
        self.planner = PlannerFactory.create_planner(
            'mpc',
            self,
            config_wrapper
        )
 
        self.get_logger().info('MPC planner initialized')
 
    def plan_and_execute(self):
        # Define start state (current robot position)
        start_state = self.get_current_joint_state()
 
        # Define goal pose
        goal_pose = Pose()
        goal_pose.position.x = 0.5
        goal_pose.position.y = 0.3
        goal_pose.position.z = 0.4
        goal_pose.orientation.w = 1.0
 
        # Plan with MPC
        config = {
            'convergence_threshold': 0.01,
            'max_iterations': 1000
        }
 
        result = self.planner.plan(start_state, goal_pose, config)
 
        if result.success:
            self.get_logger().info('MPC planning succeeded')
 
            # Execute (MPC will continuously replan)
            success = self.planner.execute(robot_context, goal_handle)
 
            if success:
                self.get_logger().info('MPC execution completed')
        else:
            self.get_logger().error(f'MPC planning failed: {result.message}')
 
def main():
    rclpy.init()
    node = MPCDemoNode()
    node.plan_and_execute()
    rclpy.shutdown()
 
if __name__ == '__main__':
    main()

Using ROS 2 Services/Actions

# 1. Generate trajectory with MPC
ros2 service call /unified_planner/generate_trajectory curobo_msgs/srv/TrajectoryGeneration \
  "{
    goal_pose: {
      position: {x: 0.5, y: 0.3, z: 0.4},
      orientation: {w: 1.0}
    },
    use_current_state: true
  }"
 
# 2. Execute with MPC (will continuously replan)
ros2 action send_goal /unified_planner/execute_trajectory curobo_msgs/action/SendTrajectory "{}"

Step 4: Using the MpcMove Action

What is MpcMove?

The MpcMove action provides a streamlined interface for MPC-based motion execution. Unlike the two-step process of generate_trajectory + execute_trajectory, MpcMove combines planning and execution into a single action call.

Key Features:

  • Single Action Call: No need for separate planning and execution

  • Real-time Feedback: Provides continuous joint command updates and progress

  • Closed-loop Control: MPC continuously replans during execution

  • Reactive: Automatically adapts to obstacles and disturbances

MpcMove vs generate_trajectory + execute_trajectory

Aspect

generate_trajectory + execute_trajectory

MpcMove Action

API Calls

Two separate calls

Single action call

Planning

Generates full trajectory upfront

Continuous replanning during execution

Feedback

Trajectory waypoints, then execution status

Real-time joint commands + progression

Use Case

Static environments, reproducible paths

Dynamic environments, reactive control

Complexity

More steps to coordinate

Simpler single-action interface

MpcMove Action Interface

Action Name: /unified_planner/mpc_move Action Type: curobo_msgs/action/MpcMove

Goal:

geometry_msgs/Pose target_pose  # Target end-effector pose

Result:

bool success                     # True if goal reached
string message                   # Status message or error details

Feedback:

sensor_msgs/JointState joint_command  # Current joint target
float32 step_progression              # Progress toward goal (0.0 to 1.0)

Example 1: Basic MpcMove Usage

# Send goal to MPC action server
ros2 action send_goal /unified_planner/mpc_move curobo_msgs/action/MpcMove \
  "{target_pose: {position: {x: 0.5, y: 0.3, z: 0.4}, orientation: {w: 1.0}}}" \
  --feedback

Expected Feedback Output:

# Feedback updates continuously during execution
feedback:
  joint_command:
    position: [-0.234, 0.567, -1.234, 0.890, -0.456, 0.123]
  step_progression: 0.15  # 15% complete

feedback:
  joint_command:
    position: [-0.245, 0.578, -1.245, 0.901, -0.467, 0.134]
  step_progression: 0.32  # 32% complete

...

feedback:
  joint_command:
    position: [-0.289, 0.634, -1.312, 0.978, -0.523, 0.189]
  step_progression: 0.98  # 98% complete

Result:

result:
  success: true
  message: 'MPC reached goal pose successfully'

Example 2: MpcMove with Different Orientations

# Move with end-effector pointing downward
ros2 action send_goal /unified_planner/mpc_move curobo_msgs/action/MpcMove \
  "{target_pose: {position: {x: 0.6, y: 0.0, z: 0.3}, \
    orientation: {x: 0.0, y: 1.0, z: 0.0, w: 0.0}}}" \
  --feedback

Example 3: Monitor Progress Without Feedback

# Send goal without continuous feedback (simpler output)
ros2 action send_goal /unified_planner/mpc_move curobo_msgs/action/MpcMove \
  "{target_pose: {position: {x: 0.45, y: -0.2, z: 0.5}, orientation: {w: 1.0}}}"

# In another terminal, monitor action status
ros2 action list
ros2 action info /unified_planner/mpc_move

Example 4: Cancel MpcMove During Execution

# Terminal 1: Start MPC move
ros2 action send_goal /unified_planner/mpc_move curobo_msgs/action/MpcMove \
  "{target_pose: {position: {x: 0.8, y: 0.4, z: 0.6}, orientation: {w: 1.0}}}" \
  --feedback

# Terminal 2: Cancel the action (e.g., if obstacle appears)
# Get the goal ID from Terminal 1 output, then:
ros2 action send_goal --cancel /unified_planner/mpc_move

Understanding Feedback: step_progression

The step_progression field indicates how close the robot is to the goal:

  • 0.0: Starting position

  • 0.5: Halfway to goal

  • 1.0: Goal reached

Progress Interpretation:

  • Linear motion: Progression reflects Cartesian distance traveled

  • Complex paths: Progression may be non-linear due to obstacle avoidance

  • Near obstacles: Progression may slow down (more MPC iterations needed)

Understanding Feedback: joint_command

The joint_command field provides the current joint targets being sent to the robot:

joint_command:
  position: [j1, j2, j3, j4, j5, j6]  # Joint positions in radians
  velocity: []                         # Not populated
  effort: []                           # Not populated

Use Cases for joint_command Feedback:

  • Monitor robot joint state during execution

  • Verify robot is following MPC commands

  • Log joint trajectories for analysis

  • Detect anomalies (e.g., joint limits)

When to Use MpcMove

Use MpcMove when:

  • You need simple, single-call interface for motion

  • Environment has dynamic obstacles

  • You want real-time feedback on progress

  • Reactive control is more important than trajectory reproducibility

Use generate_trajectory + execute_trajectory when:

  • You need to inspect/modify trajectory before execution

  • Trajectory must be saved or replayed

  • Environment is static (more efficient with classic planner)

  • You need full trajectory waypoints upfront

Combining MpcMove with Obstacle Management

MpcMove automatically adapts to obstacles in the world configuration:

# 1. Add obstacle
ros2 service call /unified_planner/add_object curobo_msgs/srv/AddObject \
  "{name: 'wall', type: 0, dimensions: {x: 0.1, y: 1.0, z: 1.5}, \
    pose: {position: {x: 0.4, y: 0.0, z: 0.5}, orientation: {w: 1.0}}}"

# 2. Send MpcMove goal (will automatically avoid wall)
ros2 action send_goal /unified_planner/mpc_move curobo_msgs/action/MpcMove \
  "{target_pose: {position: {x: 0.6, y: 0.3, z: 0.4}, orientation: {w: 1.0}}}" \
  --feedback

# 3. MPC continuously replans to avoid the wall during execution

Adding Obstacles Mid-Execution

One of MPC’s key strengths is handling obstacles that appear during motion:

# Terminal 1: Start MPC move to distant goal
ros2 action send_goal /unified_planner/mpc_move curobo_msgs/action/MpcMove \
  "{target_pose: {position: {x: 0.7, y: 0.5, z: 0.5}, orientation: {w: 1.0}}}" \
  --feedback

# Terminal 2: Add obstacle mid-execution (after 2 seconds)
sleep 2
ros2 service call /unified_planner/add_object curobo_msgs/srv/AddObject \
  "{name: 'dynamic_box', type: 0, dimensions: {x: 0.15, y: 0.15, z: 0.15}, \
    pose: {position: {x: 0.5, y: 0.3, z: 0.4}, orientation: {w: 1.0}}}"

# Observe: MPC automatically avoids the new obstacle without stopping!

Troubleshooting MpcMove

Issue 1: Action Immediately Fails

Symptoms:

result:
  success: false
  message: 'Goal pose unreachable or violates constraints'

Solutions:

# Check if goal is reachable using IK service (warmup first if not done)
ros2 service call /unified_planner/warmup_ik curobo_msgs/srv/WarmupIK "{batch_size: 1}"
ros2 service call /unified_planner/ik curobo_msgs/srv/Ik \
  "{pose: {position: {x: 0.5, y: 0.3, z: 0.4}, orientation: {w: 1.0}}}"

# If IK fails, adjust goal pose to be within workspace

Issue 2: MPC Not Converging

Symptoms:

result:
  success: false
  message: 'MPC failed to converge after max iterations'

Solutions:

# Increase max iterations
ros2 param set /unified_planner mpc_max_iterations 2000

# Relax convergence threshold
ros2 param set /unified_planner mpc_convergence_threshold 0.02

# Try again
ros2 action send_goal /unified_planner/mpc_move curobo_msgs/action/MpcMove "{...}"

Issue 3: Slow Progress (step_progression not advancing)

Symptoms: Feedback shows progression stuck at same value

Possible Causes:

  • Collision with obstacle blocking path

  • Joint limits preventing motion

  • Goal pose nearly unreachable

Solutions:

# Check for collision distance
ros2 service call /unified_planner/get_collision_distance curobo_msgs/srv/GetCollisionDistance \
  "{joint_state: {position: [j1, j2, j3, j4, j5, j6]}}"

# If collision detected, remove or adjust obstacles
ros2 service call /unified_planner/remove_object curobo_msgs/srv/RemoveObject "{name: 'obstacle_name'}"

# Or adjust goal pose

Issue 4: Action Never Completes

Symptoms: Progression reaches 0.95-0.98 but never completes

Possible Causes:

  • Convergence threshold too tight

  • Goal pose requires precision beyond MPC capability

Solutions:

# Relax convergence threshold
ros2 param set /unified_planner mpc_convergence_threshold 0.02

# Or use classic planner for final precise positioning
ros2 service call /unified_planner/set_planner curobo_msgs/srv/SetPlanner "{planner_type: 0}"
ros2 service call /unified_planner/generate_trajectory ...

Monitoring MpcMove Performance

View Action Status

# List all active actions
ros2 action list -t

# Get detailed info about MpcMove action
ros2 action info /unified_planner/mpc_move

# Output shows:
# - Action server status (ACTIVE/IDLE)
# - Number of active goals
# - Clients connected

Echo Feedback in Real-Time

# View feedback without sending a goal
ros2 action send_goal /unified_planner/mpc_move curobo_msgs/action/MpcMove \
  "{target_pose: {position: {x: 0.5, y: 0.3, z: 0.4}, orientation: {w: 1.0}}}" \
  --feedback | tee mpc_feedback.log

# Logs all feedback to file for later analysis

Monitor Joint Commands

# In another terminal during MpcMove execution:
ros2 topic echo /joint_states

# Compare commanded joints (from feedback) vs actual joints (from topic)

Step 5: Observe MPC Behavior

In RViz

  1. Visualization:

    • Ghost robot shows predicted trajectory

    • Trajectory updates in real-time

    • Can see MPC adapting to obstacles

  2. Add dynamic obstacle while robot is moving:

    ros2 service call /unified_planner/add_object curobo_msgs/srv/AddObject \
      "{name: 'dynamic_box', type: 0, dimensions: {x: 0.2, y: 0.2, z: 0.2}, \
        pose: {position: {x: 0.4, y: 0.2, z: 0.3}, orientation: {w: 1.0}}}"
    
  3. Observe: MPC automatically avoids new obstacle without stopping!

Monitor MPC Performance

# Monitor MPC iterations
ros2 topic echo /unified_planner/mpc_stats
 
# Expected output:
# iterations: 87
# convergence: 0.009  # meters from goal
# computation_time: 8.5  # ms
# success: true

Step 6: Advanced MPC Usage

Dynamic Obstacle Avoidance

MPC excels at avoiding obstacles that appear during execution:

def demo_dynamic_avoidance():
    # Start MPC execution
    planner.execute(robot_context, goal_handle)
 
    # Simulate dynamic obstacle appearing
    time.sleep(2.0)  # Let robot start moving
 
    # Add obstacle in robot's path
    add_collision_object("moving_obstacle", [0.5, 0.3, 0.4])
 
    # MPC automatically avoids it without stopping!

Disturbance Rejection

MPC can correct for external disturbances:

# Start MPC execution
planner.execute(robot_context, goal_handle)
 
# Simulate disturbance (push robot)
def apply_disturbance():
    # MPC detects deviation and corrects automatically
    pass

Tracking Moving Targets

MPC can track moving goal positions:

class MovingTargetMPC:
    def __init__(self):
        self.planner = PlannerFactory.create_planner('mpc', node, config)
        self.timer = node.create_timer(0.1, self.update_goal)
 
    def update_goal(self):
        # Update goal position (e.g., tracking moving object)
        new_goal = self.get_moving_target_pose()
 
        # MPC will automatically adjust trajectory
        self.planner.update_goal(new_goal)

Step 7: Tuning MPC for Your Application

High-Speed Reactive Control

For fast, reactive movements:

config = {
    'convergence_threshold': 0.02,  # Slightly relaxed
    'max_iterations': 500,          # Fewer iterations
    'control_frequency': 200,       # Very high frequency
    'horizon': 5                    # Short horizon
}

High-Precision Tasks

For precise positioning:

config = {
    'convergence_threshold': 0.005,  # Tight tolerance
    'max_iterations': 2000,          # More iterations
    'control_frequency': 50,         # Lower frequency OK
    'horizon': 15                    # Longer horizon
}

Constrained Environments

For cluttered spaces with many obstacles:

config = {
    'convergence_threshold': 0.01,
    'max_iterations': 1500,
    'voxel_size': 0.005,  # Fine collision checking
    'horizon': 10
}

Common Issues and Solutions

Issue 1: MPC Not Converging

Symptom:

Warning: MPC failed to converge after 1000 iterations

Solutions:

  1. Increase max_iterations:

    ros2 param set /unified_planner mpc_max_iterations 2000
    
  2. Check goal is reachable:

    # Test with classic planner first
    ros2 service call /unified_planner/set_planner curobo_msgs/srv/SetPlanner "{planner_type: 0}"
    
  3. Relax convergence threshold:

    ros2 param set /unified_planner mpc_convergence_threshold 0.02
    

Issue 2: Jerky Motion

Symptom: Robot movement is not smooth, oscillates

Solutions:

  1. Decrease control frequency:

    ros2 param set /unified_planner mpc_control_frequency 50
    
  2. Increase horizon length:

    ros2 param set /unified_planner mpc_horizon 15
    
  3. Tune time_dilation_factor:

    ros2 param set /unified_planner time_dilation_factor 0.3  # Slower, smoother
    

Issue 3: High GPU Usage

Symptom: GPU at 100%, system slow

Solutions:

  1. Reduce control frequency:

    ros2 param set /unified_planner mpc_control_frequency 30
    
  2. Shorten horizon:

    ros2 param set /unified_planner mpc_horizon 5
    
  3. Use classic planner for static environments:

    ros2 service call /unified_planner/set_planner curobo_msgs/srv/SetPlanner "{planner_type: 0}"
    

Issue 4: Slow Reaction to Obstacles

Symptom: MPC doesn’t avoid obstacles quickly enough

Solutions:

  1. Increase control frequency:

    ros2 param set /unified_planner mpc_control_frequency 150
    
  2. Reduce collision voxel size (finer detection):

    ros2 param set /unified_planner voxel_size 0.005
    
  3. Adjust MPC weights (favor obstacle avoidance):

    config['obstacle_weight'] = 10.0  # Higher priority
    

Comparing MPC vs Classic

Experiment: Static Environment

# Test 1: Classic planner
ros2 service call /unified_planner/set_planner curobo_msgs/srv/SetPlanner "{planner_type: 0}"
ros2 service call /unified_planner/generate_trajectory ...
# Observe: Fast planning, smooth execution, predictable
 
# Test 2: MPC planner
ros2 service call /unified_planner/set_planner curobo_msgs/srv/SetPlanner "{planner_type: 1}"
ros2 service call /unified_planner/generate_trajectory ...
# Observe: Continuous replanning, slightly slower, adaptive

Result: In static environments, Classic is more efficient

Experiment: Dynamic Environment

# Add obstacle mid-execution
 
# With Classic planner:
# - Robot must stop
# - Replan required
# - Execution resumes
 
# With MPC planner:
# - No stop needed
# - Automatic avoidance
# - Continuous execution

Result: In dynamic environments, MPC is superior


Best Practices

1. Choose the Right Planner

  • Use Classic when:

    • Environment is static

    • Speed/efficiency is critical

    • Trajectory must be reproducible

    • Limited GPU resources

  • Use MPC when:

    • Environment has moving obstacles

    • Real-time adaptation needed

    • Disturbance rejection required

    • High-precision tracking

2. Start with Default Parameters

# Good defaults for most applications
config = {
    'convergence_threshold': 0.01,
    'max_iterations': 1000,
    'control_frequency': 100,
    'horizon': 10
}

Tune only if needed!

3. Monitor Performance

Always monitor MPC performance:

ros2 topic echo /unified_planner/mpc_stats

Look for:

  • Iterations: Should be < max_iterations

  • Convergence: Should approach threshold

  • Computation time: Should be < 1/frequency

4. Warm-Up the Planner

Pre-warm MPC for faster first execution:

# At startup
planner = PlannerFactory.create_planner('mpc', node, config)
planner.warmup()  # Initializes GPU kernels

5. Fallback to Classic

If MPC fails, fallback to Classic:

result = mpc_planner.plan(start, goal, config)
if not result.success:
    self.get_logger().warn('MPC failed, falling back to Classic')
    classic_planner = PlannerFactory.create_planner('classic', node, config)
    result = classic_planner.plan(start, goal, config)

Next Steps


References

For Developers: If you’re implementing the MPC planner, see the MPC Implementation Guide for detailed technical specifications, code templates, and testing strategies.