How to Fix Simulation Instability in DART: Common Causes and Solutions

Simulation instability in DART typically stems from oversized timesteps, stiff explicit controllers, or aggressive contact handling, and can be resolved by reducing the timestep, adding implicit damping, and tuning constraint solver parameters.

The Dynamic Animation and Robotics Toolkit (DART) is a popular open-source physics engine for robotics and biomechanics simulation. Despite its robust constraint solver, users frequently encounter simulation instability in DART when configuring complex contact scenarios or high-gain controllers. This guide examines the root causes of these instabilities and provides concrete fixes using the actual dartsim/dart source code.

Common Symptoms and Root Causes of Simulation Instability in DART

Understanding the symptom pattern helps identify whether you are facing a numerical stability issue or a modeling error.

Explosive Velocity Divergence

When joint velocities rapidly approach infinity within a few simulation steps, the root cause is typically an oversized timestep (dt) relative to the system stiffness. In dart/simulation/world.hpp, the World::setTimeStep() method sets this parameter, and values above 0.01 seconds often trigger instability with stiff springs or high-gain PD controllers.

High-Frequency Jitter in Contact Stacks

Rapid oscillations or vibration when objects are in contact usually indicate insufficient damping or overly aggressive contact constraints. This occurs in the constraint solver phase, where large timesteps combined with high-friction contacts can push the Linear Complementarity Problem (LCP) solver out of its convergence region.

Slow Positional Drift

Gradual deviation from expected trajectories without velocity explosions typically stems from discretization error or energy injection through contacts. This accumulates when the semi-implicit Euler integrator used in World::step() operates with inadequate damping.

First-Line Fixes for DART Simulation Stability

Before adjusting complex solver parameters, apply these four fixes in order:

  1. Reduce the timestep – Call World::setTimeStep(dt) with a smaller value (start with 0.001s). This is the most effective single change for resolving explosive instability.

  2. Lower controller stiffness – Decrease PD gains or replace explicit torque control with implicit joint stiffness via DegreeOfFreedom::setSpringStiffness().

  3. Add damping – Apply velocity-dependent damping through DegreeOfFreedom::setDampingCoefficient() or moderate Kd gains in your controller.

  4. Clamp actuation – Saturate torque commands to realistic bounds before calling Joint::setCommand() to prevent impulsive force spikes.

Advanced Stabilization Techniques

When basic fixes prove insufficient, target specific subsystems using DART's deeper configuration APIs.

Optimizing Timestep Settings

In dart/simulation/world.hpp, the World::setTimeStep(double) method (line 191) controls the integration period. For systems with stiff springs or high-frequency contact dynamics, reduce dt to 0.001 seconds or smaller. The semi-implicit Euler integrator in World::step() is only conditionally stable, and stability improves linearly with smaller timesteps.

Configuring Implicit Joint Springs and Damping

Rather than simulating springs through high-gain PD control, use DART's built-in implicit spring and damping models in dart/dynamics/degree_of_freedom.hpp. The DegreeOfFreedom::setSpringStiffness(double) method (line 330) adds stiffness to the joint dynamics matrix solved implicitly during the LCP step, remaining stable at larger timesteps than explicit controllers. Similarly, DegreeOfFreedom::setDampingCoefficient(double) adds velocity-dependent dissipation directly to the dynamics.

Tuning Constraint Solver Parameters

For contact-related jitter, adjust the constraint solver settings in dart/constraint/constraint_solver.hpp. Access the solver via World::getConstraintSolver() and modify:

  • setIterationLimit(int) – Increase from default (usually 20) to 50-200 for complex contact stacks.
  • setTolerance(double) – Tighten to 1e-6 for better convergence.
  • Time step consistency – Ensure ConstraintSolver::setTimeStep() matches the world timestep.

Collision Detector Selection

If instability persists with specific geometry types, experiment with different collision detectors. In dart/collision/, switch between FCL, Bullet, or ODE backends:

auto detector = dart::collision::FCLCollisionDetector::create();
world->getConstraintSolver()->setCollisionDetector(detector);

Different detectors handle edge-case contacts (thin objects, high-speed collisions) with varying stability characteristics.

Code Examples for Stabilizing DART Simulations

1. Reduce Timestep and Verify

// Create world and set a safe timestep
dart::simulation::WorldPtr world = dart::simulation::World::create();
world->setTimeStep(0.001);   // 1 ms – start small and increase as needed

Source: World::setTimeStep definition in dart/simulation/world.hpp (line 191).

2. Use Implicit Joint Stiffness & Damping

// Assume you have a pointer to a joint or a DoF
auto* dof = joint->getDof(0);

// Set spring stiffness (implicit)
dof->setSpringStiffness(500.0);   // N·m/rad

// Set damping coefficient (implicit)
dof->setDampingCoefficient(5.0); // N·m·s/rad

Source: DegreeOfFreedom::setSpringStiffness in dart/dynamics/degree_of_freedom.hpp (line 330); setDampingCoefficient is analogous.

3. Clamp Controller Commands

double rawTorque = controller.computeTorque(...);
double maxTorque = 100.0;          // realistic limit
double torque = std::clamp(rawTorque, -maxTorque, maxTorque);
joint->setCommand(torque);

4. Adjust Constraint Solver Settings

auto* solver = world->getConstraintSolver();
solver->setIterationLimit(200);   // increase if contacts jitter
solver->setTolerance(1e-6);      // tighter convergence

Source: ConstraintSolver API in dart/constraint/constraint_solver.hpp (line 144).

5. Switch Collision Detector

auto detector = dart::collision::FCLCollisionDetector::create();
world->getConstraintSolver()->setCollisionDetector(detector);

Summary

  • Simulation instability in DART most often originates from oversized timesteps, stiff explicit controllers, or aggressive contact configurations rather than solver bugs.
  • Reduce the timestep using World::setTimeStep() as the first and most effective intervention.
  • Prefer implicit joint springs and damping via DegreeOfFreedom::setSpringStiffness() and setDampingCoefficient() over high-gain PD control for stiff behaviors.
  • Tune the constraint solver by increasing iteration limits and tightening tolerances through World::getConstraintSolver() when dealing with complex contact stacks.
  • Clamp actuation forces and verify mass properties to prevent numerical explosions from unrealistic force spikes or extreme inertia ratios.

Frequently Asked Questions

What is the most common cause of simulation instability in DART?

The most common cause is an oversized timestep relative to the system stiffness. DART uses a semi-implicit Euler integrator that is only conditionally stable. When the timestep is too large for stiff springs, high-gain controllers, or rapid contact events, the integration error accumulates exponentially, causing velocities to diverge toward infinity within a few steps.

How do I fix exploding velocities in my DART simulation?

Start by reducing the timestep via World::setTimeStep(0.001) or smaller. Next, replace any high-gain PD controllers with implicit joint springs using DegreeOfFreedom::setSpringStiffness(), which remains stable at larger timesteps. Finally, add damping via DegreeOfFreedom::setDampingCoefficient() or clamp your torque commands to prevent impulsive force spikes that trigger numerical explosions.

What timestep should I use for stable DART simulations?

Begin with 1 ms (0.001 s) for systems involving contacts, stiff springs, or high-gain control. You can increase the timestep gradually up to 0.01 s only after verifying stability with implicit damping and moderate controller gains. For extremely stiff systems or high-speed collisions, you may need to reduce the timestep to 0.0005 s or smaller.

Can switching collision detectors fix simulation jitter?

Yes, different collision detectors handle edge-case contacts with varying stability characteristics. If you experience jitter with thin objects or high-speed collisions, experiment with alternative backends such as FCL, Bullet, or ODE by creating the detector and passing it to World::getConstraintSolver()->setCollisionDetector(). This can resolve instability arising from specific geometric configurations without requiring a smaller global timestep.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →