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:
-
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. -
Lower controller stiffness – Decrease PD gains or replace explicit torque control with implicit joint stiffness via
DegreeOfFreedom::setSpringStiffness(). -
Add damping – Apply velocity-dependent damping through
DegreeOfFreedom::setDampingCoefficient()or moderateKdgains in your controller. -
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()andsetDampingCoefficient()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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →