Skip to main content

Determinism

By default, Rapier is locally deterministic, meaning that running the exact same simulation (with the same initial conditions) twice with the same machine, using the same build of the bindings, will result in the exact same simulation results. However, doing this on two different computers may result in completely different results.

note

Two simulations run with the same initial conditions if their worlds are configured with the same values (gravity, integration parameters, etc.), rigid-bodies/colliders/joints are constructed the same way, and they are inserted into and removed from the world in the exact same order. Note that this includes the timestep length: the simulation must be advanced by the same sequence of timesteps on every machine, which excludes a timestep length depending on the frame rate of the application.

It is possible to make Rapier cross-platform deterministic, meaning that running a simulation with two different computers (including different OS and/or different processors) will result in the exact same results. In order to achieve this, both computers must start the simulation with the same initial conditions as discussed above, and the following additional conditions must be met:

  • The bindings are built with the determinism feature, e.g., with maturin develop --release -F determinism -m bindings/python/rapier-py-3d/Cargo.toml (see building from source). The parallelism, which is always enabled, can be combined with it: the results of the parallel solver are identical to the results of the sequential one, and don't depend on the number of threads (nor, therefore, on the number of cores of the machine running the simulation).
  • The target platforms must strictly comply to the IEEE 754-2008 floating-points standard. This ensures that floating-point computations behave the same on all platforms. This include most modern mainstream processors.
  • If some of the values used to initialize Rapier structures are computed using floating points functions (sin, cos, tan, exp, etc.) other than addition/subtraction/multiplication/division/square root, then you need to make sure the functions being used are the ones provided by the rapier3d.math module, e.g., rapier3d.math.sin, instead of the functions of the math module of Python, which rely on the C standard library of the platform. This applies to NumPy as well: its vectorized functions (e.g. numpy.sin) may even give different results on two processors of the same platform, depending on the SIMD instructions they support:
# WRONG version:
# The following will not work cross-platform-deterministically because the functions of the
# `math` module (and of NumPy) give different results on different platforms.
collider = rp.Collider.ball(0.5).translation((math.exp(1.0), math.sin(2.0), math.cos(3.0))).build()
import rapier3d.math as rpm

# CORRECT version:
# The following will work cross-platform-deterministically because we use the functions
# of Rapier.
collider = rp.Collider.ball(0.5).translation((rpm.exp(1.0), rpm.sin(2.0), rpm.cos(3.0))).build()

It is recommended to check that the bindings actually loaded by your application are built with the determinism feature, which is indicated by the enhanced_determinism property of rapier3d.build_features():

# Make sure the loaded bindings are built with the `determinism` feature.
if not rp.build_features().enhanced_determinism:
print("Warning: the rapier3d bindings aren't cross-platform deterministic.")

A simple way of checking that two simulations are in the exact same state is to compare their snapshots, e.g., with a hash of the bytes given by PhysicsWorld.snapshot:

# Two simulations are in the exact same state if their snapshots are identical.
digest = hashlib.sha256(world.snapshot()).hexdigest()
print("State after 100 timesteps:", digest)