diff --git a/python/mujoco/bindings_test.py b/python/mujoco/bindings_test.py index 9f1f61d0..c4544d7a 100644 --- a/python/mujoco/bindings_test.py +++ b/python/mujoco/bindings_test.py @@ -737,6 +737,7 @@ Euler integrator, semi-implicit in velocity. def test_float_constant(self): self.assertEqual(mujoco.mjMAXVAL, 1e10) + self.assertEqual(mujoco.mjMINVAL, 1e-15) def test_string_constants(self): self.assertLen(mujoco.mjDISABLESTRING, mujoco.mjtDisableBit.mjNDISABLE) diff --git a/python/mujoco/constants.cc b/python/mujoco/constants.cc index 2ffe73bb..b8327c75 100644 --- a/python/mujoco/constants.cc +++ b/python/mujoco/constants.cc @@ -78,6 +78,9 @@ PYBIND11_MODULE(_constants, pymodule) { // from mujoco.h X(mjVERSION_HEADER); + // from mjtnum.h + X(mjMINVAL); + #undef X pymodule.attr("mjDISABLESTRING") = MakeTuple(mjDISABLESTRING); pymodule.attr("mjENABLESTRING") = MakeTuple(mjENABLESTRING); diff --git a/python/mujoco/viewer.py b/python/mujoco/viewer.py index 0d0aa942..0c6d7c3b 100644 --- a/python/mujoco/viewer.py +++ b/python/mujoco/viewer.py @@ -136,7 +136,8 @@ def _physics_loop(simulate: Simulate, loader: Optional[_InternalLoaderType]): # Inject noise. if simulate.ctrlnoisestd != 0.0: # Convert rate and scale to discrete time (Ornstein–Uhlenbeck). - rate = math.exp(-m.opt.timestep / simulate.ctrlnoiserate) + rate = math.exp(-m.opt.timestep / + max(simulate.ctrlnoiserate, mujoco.mjMINVAL)) scale = simulate.ctrlnoisestd * math.sqrt(1 - rate * rate) for i in range(m.nu): diff --git a/simulate/main.cc b/simulate/main.cc index 704b38f5..d26481e4 100644 --- a/simulate/main.cc +++ b/simulate/main.cc @@ -330,7 +330,7 @@ void PhysicsLoop(mj::Simulate& sim) { // inject noise if (sim.ctrlnoisestd) { // convert rate and scale to discrete time (Ornstein–Uhlenbeck) - mjtNum rate = mju_exp(-m->opt.timestep / sim.ctrlnoiserate); + mjtNum rate = mju_exp(-m->opt.timestep / mju_max(sim.ctrlnoiserate, mjMINVAL)); mjtNum scale = sim.ctrlnoisestd * mju_sqrt(1-rate*rate); for (int i=0; inu; i++) {