ExMujoco (ExMujoco v0.1.1)

Copy Markdown View Source

MuJoCo, from Elixir.

{:ok, model} = ExMujoco.load_xml("model/scene.xml")
data = ExMujoco.data(model)

ExMujoco.forward(data)
ExMujoco.step(data)

ExMujoco.Data.qpos(data) |> ExMujoco.Tensor.to_list()
ExMujoco.Model.geom_size(model) |> ExMujoco.Tensor.to_list()

A model is compiled and immutable; a data block is the state that moves. Both are resources, released by the garbage collector, so there is no lifetime to get wrong: a Data holds the Model it came from, and neither outlives its user.

Fields are read one at a time — ExMujoco.Model and ExMujoco.Data have a function per field of mjModel and mjData, named as MuJoCo names them, and answering either a number or an ExMujoco.Tensor. Reading copies, so what you hold does not change when the next step runs.

Where the code comes from

The NIF is generated from MuJoCo's own headers: one binding per field and per C function in c_src/manifest.exs. docs/coverage.md says what is bound and what is not, and why. The hand-written half — c_src/ex_mujoco_nif.cpp — is the part where each call needs a decision: which buffer, whose lifetime.

What is not here yet

mjSpec — building a model in memory, rather than compiling XML — and offscreen rendering. Both are in docs/plan.md with the reason they are their own phase: a scene needs a GL context per platform, and that is a different job from stepping physics.

Errors

MuJoCo reports a fatal error by not returning, which inside a NIF would abort the VM. It is intercepted and arrives as ExMujoco.Error with MuJoCo's own message, so a bad model is an exception in one process and not a dead node.

Summary

Types

An mjData and the model it belongs to.

A compiled mjModel.

Functions

The six numbers of one contact's force, as an ExMujoco.Tensor.

A fresh mjData for a model, at the model's initial state.

Position and velocity kinematics without stepping — mj_forward.

The version of the headers this NIF was generated from.

Compiles a model from MJCF in memory.

Compiles a model from an MJCF file.

Writes a state read by state/2 back into the data.

Puts the data back to the model's initial state.

The whole integration state, as a binary of float64.

How many float64 values a state signature holds.

One simulation step — mj_step.

The MuJoCo library's version, as MuJoCo numbers it: 4000001 is 4.0.1.

Types

data()

@type data() :: ExMujoco.Data.t()

An mjData and the model it belongs to.

model()

@type model() :: ExMujoco.Model.t()

A compiled mjModel.

Functions

contact_force(data, index)

@spec contact_force(data(), non_neg_integer()) :: ExMujoco.Tensor.t()

The six numbers of one contact's force, as an ExMujoco.Tensor.

MuJoCo writes them into an array the caller owns, which is why this is not a generated binding: there is no array to hand it.

data(model)

@spec data(model()) :: data()

A fresh mjData for a model, at the model's initial state.

forward(data)

@spec forward(data()) :: :ok

Position and velocity kinematics without stepping — mj_forward.

What to call after writing qpos, qvel or ctrl and before reading anything that depends on them.

header_version()

@spec header_version() :: non_neg_integer()

The version of the headers this NIF was generated from.

The two are compared at load; if they disagree the NIF does not load.

load_string(xml)

@spec load_string(String.t()) :: {:ok, model()} | {:error, ExMujoco.Error.t()}

Compiles a model from MJCF in memory.

load_xml/1 is the better choice when the model can name its own files; this one has no directory to resolve includes against.

load_xml(path)

@spec load_xml(Path.t()) :: {:ok, model()} | {:error, ExMujoco.Error.t()}

Compiles a model from an MJCF file.

Every file it includes is resolved relative to its own directory, as MuJoCo does.

put_state(data, signature, values)

@spec put_state(data(), integer(), binary()) :: :ok

Writes a state read by state/2 back into the data.

The binary has to be the size that signature implies; anything else is refused with an ArgumentError naming both sizes, rather than written past the end of it.

reset(data)

@spec reset(data()) :: :ok

Puts the data back to the model's initial state.

state(data, signature \\ Types.State.integration())

@spec state(data(), integer()) :: binary()

The whole integration state, as a binary of float64.

The default signature is mjSTATE_INTEGRATION, which is every element MuJoCo has a slot for; state/2 takes any other mjtState combination, e.g. ExMujoco.Types.State.physics() for just the generalized coordinates.

state_size(model, signature \\ Types.State.integration())

@spec state_size(model(), integer()) :: non_neg_integer()

How many float64 values a state signature holds.

step(data)

@spec step(data()) :: :ok

One simulation step — mj_step.

version()

@spec version() :: non_neg_integer()

The MuJoCo library's version, as MuJoCo numbers it: 4000001 is 4.0.1.