Basic simulation

Loading

To perform basic simulation with MuJoCo, create a MjModel struct by calling one of the following methods:

For example:

use mujoco_rs::prelude::*;

fn main() {
    let model = MjModel::from_xml("model.xml").expect("could not load the model");
}

MjModel represents the compiled model from an XML file and contains everything from basic metadata to physics parameters.

For managing actual simulation state, MjData is used. It can be created either through the MjData::new method or through the MjModel::make_data method. For example:

use mujoco_rs::prelude::*;

fn main() {
    let model = MjModel::from_xml("model.xml").expect("could not load the model");
    let mut data = MjData::new(&model);  // or model.make_data()
}

Using the MjData::new method is far more flexible than using the MjModel::make_data method. The former allows parameters to be of any type as long as they implement ModelType (e.g., Box<MjModel>). For example:

use mujoco_rs::prelude::*;

fn main() {
    let model = Box::new(MjModel::from_xml("model.xml").expect("could not load the model"));
    let mut data = MjData::new(model);  // move model into the data
    let model_ref = data.model();  // obtain a reference to the model
}

The model-handle type M must implement ModelType — a MuJoCo-rs trait that is pre-implemented for most standard Deref containers, such as Rc and Arc. ModelTypeMut implements the same but for DerefMut standard containers. For shared ownership, use Rc<MjModel> (single-threaded) or Arc<MjModel> when thread-sharing is needed. For non-shared usage, Box<MjModel> is the most appropriate, as it also allows changes to model parameters.

Using Box or Rc (instead of direct references) allows usage in environments with lifetime restrictions. One such example is Python bindings created with PyO3. The standalone/pyo3_application example shows how to create a simple MuJoCo-rs based application for use from the Python programming language.

Implementing a custom ModelType

Note

This section is optional for most users as the standard containers inside the Rust standard library should suffice.

ModelType and ModelTypeMut are unsafe traits with two rules: every deref call returns the same MjModel, and that model stays valid and in place for as long as the container lives. Plain Deref does not guarantee the rules on its own.

To use a container of your own, implement Deref and then add the unsafe impl:

use mujoco_rs::wrappers::mj_model::traits::ModelType;
use mujoco_rs::prelude::*;
use std::ops::Deref;

struct MyHandle(Box<MjModel>);

impl Deref for MyHandle {
    type Target = MjModel;
    fn deref(&self) -> &MjModel { &self.0 }
}

// SAFETY: deref returns the model that the handle owns, on every call.
unsafe impl ModelType for MyHandle {}

Add DerefMut and ModelTypeMut the same way when the container also gives mutable access to the model.

Asset cache

MuJoCo keeps the file-loaded assets (either from disk or virtual file system), such as meshes, in a global asset cache to avoid reprocessing.

A handle to the global cache is represented by MjCache and can be obtained by calling MjCache::current (wrapper around MuJoCo’s mj_getCache).

use mujoco_rs::prelude::*;

fn main() {
    let cache = MjCache::current();
    let model = MjModel::from_xml("model.xml").expect("could not load the model");
    println!("cached bytes: {}", cache.size());

    // Loading the same file again takes the meshes from the cache.
    let model = MjModel::from_xml("model.xml").expect("could not load the model");

    cache.clear();
    cache.set_capacity(0);  // disable the cache
}

Running

Then to run/step the simulation, just call the MjData::step method like so:

use mujoco_rs::prelude::*;

fn main() {
    let model = MjModel::from_xml("model.xml").expect("could not load the model");
    let mut data = MjData::new(&model);
    loop {
        data.step();
    }
}

The method MjData::step is just a wrapper around the mujoco_c::mj_step FFI function. Similarly, MjData::step1 and MjData::step2 wrap mujoco_c::mj_step1 and mujoco_c::mj_step2, respectively.

For more information about specific MuJoCo functions, see the MuJoCo documentation.

Real-time

The example above runs the simulation as fast as possible. To slow it down, you can either add a call to std::thread::sleep for an approximate delay, or use std::time::Instant with Instant::elapsed for precise timing.

use std::time::Duration;
use std::thread;

use mujoco_rs::prelude::*;

fn main() {
    let model = MjModel::from_xml("model.xml").expect("could not load the model");
    let mut data = MjData::new(&model);
    let timestep = model.opt().timestep;
    loop {
        data.step();
        /* Approximate-time delay */
        thread::sleep(Duration::from_secs_f64(timestep))
    }
}

Changing model’s parameters

For purposes of transfer from simulation to reality in reinforcement learning it is beneficial to perform domain randomization [Tobin2017]. This requires modifying different aspects of the environment, such as physics parameters — part of MjModel.

Danger

Not all parameters of MjModel are safe to change. See MuJoCo’s documentation for a list of parameters that are safe to change.

Direct mutation with model_opt_mut / model_vis_mut / model_stat_mut

When the model-handle type M implements ModelTypeMut (e.g., Box<MjModel>), the safe accessors MjData::model_opt_mut, MjData::model_vis_mut, and MjData::model_stat_mut give direct mutable access to the model’s physics options, visualization settings, and statistics respectively.

1use mujoco_rs::prelude::*;
2
3fn main() {
4    let model = Box::new(MjModel::from_xml("model.xml").expect("could not load the model"));
5    let mut data = MjData::new(model);
6    data.model_opt_mut().timestep = 0.004;
7    data.model_opt_mut().gravity[2] = -5.0;
8}

Note

For full mutable access to the entire MjModel (e.g., to replace the model in-place), use the unsafe MjData::model_mut method. It requires M: ModelTypeMut and is unsafe because swapping to an incompatible model can violate internal invariants.

model_opt_mut and its siblings are not available when M is a shared-ownership type (e.g., Arc<MjModel>, &MjModel). In those cases, use MjData::swap_model or its fallible twin MjData::try_swap_model below. The latter two methods are not unsafe as failure results in a panic or in a returned error, respectively.

See the simulation/model_parameters.rs example for a complete runnable comparison of both approaches (model_opt_mut and swap_model).

Swapping models with swap_model

When M does not implement ModelTypeMut (e.g., Arc<MjModel>), or when you need to swap in an entirely different model instance, use the swap_model method or the try_swap_model method, which swap the MjModel owned by MjData for the MjModel given as parameter.

 1use mujoco_rs::prelude::*;
 2
 3fn main() {
 4    let mut model_template = Box::new(MjModel::from_xml("model.xml").expect("could not load the model"));
 5
 6    let model = model_template.clone();
 7    let mut data = MjData::new(model);
 8
 9    // Modify simulation timestep
10    model_template.opt_mut().timestep = 0.004;
11    model_template = data.swap_model(model_template);
12}

Note that the above example uses MjModel as the model_template only as an illustration — usually this would be something like MjSpec, however that is reserved for the Model editing chapter.

[Tobin2017]

J. Tobin, R. Fong, A. Ray, J. Schneider, W. Zaremba, and P. Abbeel, “Domain randomization for transferring deep neural networks from simulation to the real world,” in 2017 IEEE/RSJ International Conference on Intelligent Robots and Systems (IROS), Vancouver, BC, Canada, Sep. 2017, pp. 23–30. doi: 10.1109/IROS.2017.8202133