Skip to content

fix: make enhanced-determinism cover the math calls that go to the platform's libm under std - #1074

Open
iancarscadden wants to merge 1 commit into
dimforge:masterfrom
iancarscadden:enhanced-determinism-libm-under-std
Open

iancarscadden wants to merge 1 commit into
dimforge:masterfrom
iancarscadden:enhanced-determinism-libm-under-std

Conversation

@iancarscadden

Copy link
Copy Markdown

Refs #1054.

Problem

With enhanced-determinism, transcendental functions are meant to go through libm (simba/libm_force). That only happens for calls made through simba's traits. A method call such as x.sin() on a Real resolves to the inherent f32::sin whenever the std feature is on (the default, and what serde-serialize needs), and that calls the platform's math library. Platform libms round these functions differently, so results differ across platforms even with enhanced-determinism on.

Fix

Call each of these sites through simba::scalar::ComplexField / RealField, like the rest of the solver already does (simd_asin, ComplexField::cos, ...). Without enhanced-determinism they still resolve to the standard library, so nothing changes for other users.

Site Function Runs when
joint_constraint_helper.rs, AngularLimitParams::new sin_cos every joint with an angular limit (2D and 3D)
generic_joint_constraint_builder.rs, motor_angular_generic sin ×2 position-target motor of an impulse joint attached to a multibody link
revolute_joint.rs, RevoluteJoint::angle asin ×2 3D
soft_element_constraint_assembly.rs exp, powf soft bodies: deformation damping, cell size
soft_constraint_plasticity.rs powf ×2 soft bodies: plasticity
soft_scalar_constraint.rs acos soft bodies: dihedral constraints (3D)
soft_body_shape_matching.rs atan2 soft bodies: shape matching (2D)
soft_body_material.rs exp soft bodies: tear smoothing
soft_body_builder_shapes.rs sin/cos ×2 soft-body tube (3D) and disk (2D) builders
soft_body_builder_mesh_helpers.rs acos soft-body dihedral rest angle (3D)

The list comes from a grep for every transcendental method call on a concrete float, cross-checked with clippy's disallowed-methods over f32::/f64::. What is left after this PR is powi(2) (one multiplication), Rot2::sin()/cos() (accessors), and test code.

Repro

The new platform_math_determinism tests (2D and 3D) step the joint paths and compare the result bit for bit with a golden. On master (cherry-pick only the two test files):

cargo test -p rapier3d --release --features enhanced-determinism --test platform_math_determinism
cargo test -p rapier3d --release --features enhanced-determinism --test platform_math_determinism --target wasm32-wasip1
macOS arm64, master wasm32-wasip1, master every target, this PR
3D revolute_joint_angle 0xdd919012d0f49c14 0x384ef971c0fe5a21 0x384ef971c0fe5a21
3D motor_position_on_multibody_link 0x1c9bbfec0e740d88 0xec521711c406c841 0xec521711c406c841
2D motor_position_on_multibody_link 0xc067a7ae165a7432 0x4260ecae0c6ed6bd 0x4260ecae0c6ed6bd

angular_limits happens to agree between these two targets for the ranges I tried (Apple's sinf/cosf rarely differ from libm for small arguments); it is there as a golden for the other platforms. #1054's repro repository shows that path diverging on macOS in 2D.

Testing

  • New tests added to the tests-determinism, tests-determinism-arm64 and wasm-determinism jobs and to run-ci-checks.sh.
  • Checked locally with this PR: the goldens hold on macOS arm64, Linux arm64, Linux x86_64 (emulated) and wasm32-wasip1 (Node 22).
  • cargo fmt -- --check, cargo clippy with -D warnings, cargo test -p rapier2d -p rapier2d-f64 -p rapier3d -p rapier3d-f64 (799 passed), cargo check --features enhanced-determinism for both crates, the no_std checks on the host target. The existing snapshot_portability (native and wasm32) and simd_backend_determinism goldens are unchanged.

Not in this PR

For context: with these three fixes applied, 600-tick 2D and 3D scenes in our engine give identical state hashes and byte-identical snapshots on macOS arm64, Linux arm64 and x86_64, Android arm64, wasm32-wasip1 and the iOS Simulator, and the test binaries import no platform math symbols.

With the `std` feature on, `x.sin()`, `x.acos()`, `x.exp()`... on a `Real` resolve
to the inherent float methods, which call the platform's math library even when
`enhanced-determinism` (`simba/libm_force`) is enabled. The results then differ
across platforms. Call them through simba's `ComplexField`/`RealField` instead:

- `AngularLimitParams::new` (`sin_cos`): every joint with an angular limit
- `motor_angular_generic` (`sin`): position-target motors of impulse joints
  attached to a multibody link
- `RevoluteJoint::angle` (`asin`, 3D)
- soft bodies: deformation damping and tear smoothing (`exp`), cell size and
  plasticity (`powf`), dihedral angles (`acos`), 2D shape matching (`atan2`),
  the tube and disk builders (`sin`/`cos`)

The new `platform_math_determinism` tests pin the joint paths to goldens that
the native, aarch64 and wasm32 CI jobs all check.

Refs dimforge#1054

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant