Merge branch 'google-deepmind:main' into vidur/3.3.0-2

This commit is contained in:
vidurvij-apptronik
2025-03-24 11:19:07 -05:00
committed by GitHub
330 changed files with 50638 additions and 13092 deletions
+1 -2
View File
@@ -96,7 +96,7 @@ jobs:
-DCMAKE_CXX_COMPILER:STRING=clang++-10
-DMUJOCO_HARDEN:BOOL=ON
tmpdir: "/tmp"
- os: macos-12
- os: macos-13
cmake_args: >-
-G Ninja
-DMUJOCO_HARDEN:BOOL=ON
@@ -239,7 +239,6 @@ jobs:
- name: Test Python bindings
if: ${{ runner.os != 'Windows' }}
shell: bash
working-directory: python/dist
env:
MUJOCO_GL: disable
run: >
+1 -1
View File
@@ -28,7 +28,7 @@ set(MSVC_INCREMENTAL_DEFAULT ON)
project(
mujoco
VERSION 3.2.5
VERSION 3.3.1
DESCRIPTION "MuJoCo Physics Simulator"
HOMEPAGE_URL "https://mujoco.org"
)
+16 -9
View File
@@ -33,8 +33,9 @@ We also provide [Python bindings] and a plug-in for the [Unity] game engine.
## Documentation
MuJoCo's documentation can be found at [mujoco.readthedocs.io]. Upcoming features due for the next
release can be found in the [changelog] in the latest branch.
MuJoCo's documentation can be found at [mujoco.readthedocs.io]. Upcoming
features due for the next release can be found in the [changelog] in the
"latest" branch.
## Getting Started
@@ -52,14 +53,19 @@ running on Google Colab:
- The **introductory** tutorial teaches MuJoCo basics:
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/google-deepmind/mujoco/blob/main/python/tutorial.ipynb)
- The **LQR** tutorial synthesizes a linear-quadratic controller, balancing a humanoid on one leg:
- The **rollout** tutorial shows how to use the multithreaded `rollout` module:
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/google-deepmind/mujoco/blob/main/python/rollout.ipynb)
- The **LQR** tutorial synthesizes a linear-quadratic controller, balancing a
humanoid on one leg:
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/google-deepmind/mujoco/blob/main/python/LQR.ipynb)
- The **least-squares** tutorial explains how to use the Python-based nonlinear least-squares solver:
- The **least-squares** tutorial explains how to use the Python-based nonlinear
least-squares solver:
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/google-deepmind/mujoco/blob/main/python/least_squares.ipynb)
- The **MJX** tutorial provides usage examples of
[MuJoCo XLA](https://mujoco.readthedocs.io/en/stable/mjx.html), a branch of MuJoCo written in JAX:
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/google-deepmind/mujoco/blob/main/mjx/tutorial.ipynb)
- The **differentiable physics** tutorial trains locomotion policies with analytical gradients automatically derived from MuJoCo's physics step:
- The **differentiable physics** tutorial trains locomotion policies with
analytical gradients automatically derived from MuJoCo's physics step:
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/google-deepmind/mujoco/blob/main/mjx/training_apg.ipynb)
## Installation
@@ -73,7 +79,7 @@ and macOS (universal). This is the recommended way to use the software.
### Building from source
Users who wish to build MuJoCo from source should consult the [build from
source] section of the documentation. However, please note that the commit at
source] section of the documentation. However, note that the commit at
the tip of the `main` branch may be unstable.
### Python (>= 3.9)
@@ -109,7 +115,8 @@ GitHub [Issues](https://github.com/google-deepmind/mujoco/issues) are reserved
for bug reports, feature requests and other development-related subjects.
## Related software
MuJoCo is the backbone for numerous environment packages. Below we list several bindings and converters.
MuJoCo is the backbone for numerous environment packages. Below we list several
bindings and converters.
### Bindings
@@ -137,7 +144,6 @@ These packages give users of various languages access to MuJoCo functionality:
- **Java**: [mujoco-java](https://github.com/CommonWealthRobotics/mujoco-java)
- **Julia**: [MuJoCo.jl](https://github.com/JamieMair/MuJoCo.jl)
### Converters
- **OpenSim**: [MyoConverter](https://github.com/MyoHub/myoconverter) converts
@@ -146,6 +152,8 @@ These packages give users of various languages access to MuJoCo functionality:
two-way SDFormat <-> MJCF conversion tool.
- **OBJ**: [obj2mjcf](https://github.com/kevinzakka/obj2mjcf)
a script for converting composite OBJ files into a loadable MJCF model.
- **onshape**: [Onshape to Robot](https://github.com/rhoban/onshape-to-robot)
Converts [onshape](https://www.onshape.com/en/) CAD assemblies to MJCF.
## Citation
@@ -184,7 +192,6 @@ This is not an officially supported Google product.
[Getting Started]: https://mujoco.readthedocs.io/en/latest/programming#getting-started
[Unity]: https://unity.com/
[releases page]: https://github.com/google-deepmind/mujoco/releases
[GitHub Issues]: https://github.com/google-deepmind/mujoco/issues
[mujoco.readthedocs.io]: https://mujoco.readthedocs.io
[changelog]: https://mujoco.readthedocs.io/en/latest/changelog.html
[Python bindings]: https://mujoco.readthedocs.io/en/stable/python.html#python-bindings
+5 -1
View File
@@ -6,7 +6,7 @@ possible in your code contributions.
### Scope of this guide
MuJoCo has three main code categories:
Most of this guide involves C/C++ code. For Python, jump to the section [below](#python-code). For MuJoCo C/C++, code has three main categories:
1. **C code:** MuJoCo's core codebase. It consists of public headers under
`include/` and C source files and internal headers under `src/`. This style
@@ -158,3 +158,7 @@ example above.
New code should use the C99 convention. When editing an existing function,
please move existing variable declarations into local scope. Pull requests
helping us to complete the migration are very welcome.
### [Python code](#python-code)
For Python code, run `pyink foo.py` to adhere to Google's [Python style guide](https://google.github.io/styleguide/pyguide.html). For sorting and cleaning imports, run `isort foo.py`. Both `pyink` and `isort` can be pip installed via `pip install pyink isort`.
+4 -4
View File
@@ -39,22 +39,22 @@ set(MUJOCO_DEP_VERSION_qhull
CACHE STRING "Version of `qhull` to be fetched."
)
set(MUJOCO_DEP_VERSION_Eigen3
b396a6fbb2e173f52edb3360485dedf3389ef830
66f7f51b7e069d0a03a21157fa60b24aece69aeb
CACHE STRING "Version of `Eigen3` to be fetched."
)
set(MUJOCO_DEP_VERSION_abseil
4447c7562e3bc702ade25105912dce503f0c4010 # LTS 20240722.0
9ac7062b1860d895fb5a8cbf58c3e9ef8f674b5f # LTS 20250127.0
CACHE STRING "Version of `abseil` to be fetched."
)
set(MUJOCO_DEP_VERSION_gtest
b514bdc898e2951020cbdca1304b75f5950d1f59 # v1.15.2
6910c9d9165801d8827d628cb72eb7ea9dd538c5 # v1.16.0
CACHE STRING "Version of `gtest` to be fetched."
)
set(MUJOCO_DEP_VERSION_benchmark
7c8ed6b082aa3c7a3402f18e50da4480421d08fd # v1.8.4
049f6e79cc3e8636cec21bbd94ed185b4a5f2653
CACHE STRING "Version of `benchmark` to be fetched."
)
+4 -4
View File
@@ -1,6 +1,6 @@
1 VERSIONINFO
FILEVERSION 3,2,5,0
PRODUCTVERSION 3,2,5,0
FILEVERSION 3,3,1,0
PRODUCTVERSION 3,3,1,0
FILEOS 0x4
FILETYPE 0x1
{
@@ -9,9 +9,9 @@ FILETYPE 0x1
BLOCK "040904b0"
{
VALUE "ProductName", "MuJoCo"
VALUE "ProductVersion", "3.2.5"
VALUE "ProductVersion", "3.3.1"
VALUE "FileDescription", "MuJoCo"
VALUE "FileVersion", "3.2.5"
VALUE "FileVersion", "3.3.1"
VALUE "InternalName", "mujoco.dll"
VALUE "OriginalFilename", "mujoco.dll"
VALUE "CompanyName", "Google DeepMind"
+4 -4
View File
@@ -1,8 +1,8 @@
MUJOCO ICON "mujoco.ico"
1 VERSIONINFO
FILEVERSION 3,2,5,0
PRODUCTVERSION 3,2,5,0
FILEVERSION 3,3,1,0
PRODUCTVERSION 3,3,1,0
FILEOS 0x4
FILETYPE 0x1
{
@@ -11,9 +11,9 @@ FILETYPE 0x1
BLOCK "040904b0"
{
VALUE "ProductName", "MuJoCo"
VALUE "ProductVersion", "3.2.5"
VALUE "ProductVersion", "3.3.1"
VALUE "FileDescription", "MuJoCo"
VALUE "FileVersion", "3.2.5"
VALUE "FileVersion", "3.3.1"
VALUE "InternalName", "simulate.exe"
VALUE "OriginalFilename", "simulate.exe"
VALUE "CompanyName", "Google DeepMind"
+5
View File
@@ -1,3 +1,8 @@
.. raw:: html
<div id="fetchlines"/>
.. _API:
=========
+1 -1
View File
@@ -517,7 +517,7 @@ shown in the table below. Their names are in the format ``mjKEY_XXX``. They corr
- Maximum number of UI rectangles.
Defined in `mjui.h <https://github.com/google-deepmind/mujoco/blob/main/include/mujoco/mjui.h>`_.
* - ``mjVERSION_HEADER``
- 325
- 331
- The version of the MuJoCo headers; changes with every release. This is an integer equal to 100x the software
version, so 210 corresponds to version 2.1. Defined in mujoco.h. The API function :ref:`mj_version` returns a
number with the same meaning but for the compiled library.
+12 -2
View File
@@ -28,7 +28,7 @@ MuJoCo defines a large number of types:
- :ref:`mjOption` (embedded in :ref:`mjModel`).
- :ref:`mjData`.
- :ref:`Auxillary struct types<tyAuxStructure>`, also used by the engine.
- :ref:`Auxiliary struct types<tyAuxStructure>`, also used by the engine.
- Structs for collecting :ref:`simulation statistics<tyStatStructure>`.
- Structs for :ref:`abstract visualization<tyVisStructure>`.
- Structs used by the :ref:`openGL renderer<tyRenderStructure>`.
@@ -752,7 +752,7 @@ modifiable inputs and write their outputs.
.. _tyAuxStructure:
Auxillary
Auxiliary
^^^^^^^^^
These struct types are used in the engine and their names are prefixed with ``mj``. :ref:`mjVisual`
@@ -1140,6 +1140,16 @@ behavior.
.. mujoco-include:: mjsElement
.. _mjsCompiler:
mjsCompiler
~~~~~~~~~~~
Compiler options.
.. mujoco-include:: mjsCompiler
.. _mjsBody:
mjsBody
File diff suppressed because it is too large Load Diff
+25 -10
View File
@@ -47,13 +47,23 @@ struct pointers while editing the model programmatically.
:ref:`mj_recompile` returns 0 if compilation succeed. In the case of failure, the given :ref:`mjModel` and :ref:`mjData`
instances will be deleted; as in :ref:`mj_compile`, the compilation error can be read with :ref:`mjs_getError`.
.. _mj_saveLastXML:
Update XML data structures with info from low-level model created with :ref:`mj_loadXML`, save as MJCF.
If error is not NULL, it must have size error_sz.
Note that this function only saves models that have been loaded with :ref:`mj_loadXML`, the legacy loading mechanism.
See the :ref:`model editing<meOverview>` chapter to understand the difference between the old and new model loading and
saving mechanisms.
.. _mj_saveXMLString:
Save spec to XML string, return 1 on success, 0 otherwise. XML saving requires that the spec first be compiled.
Save spec to XML string, return 0 on success, -1 on failure. If the length of the output buffer is too small, returns
the required size. XML saving automatically compiles the spec before saving.
.. _mj_saveXML:
Save spec to XML file, return 1 on success, 0 otherwise. XML saving requires that the spec first be compiled.
Save spec to XML file, return 0 on success, -1 otherwise. XML saving requires that the spec first be compiled.
.. _Mainsimulation:
@@ -221,16 +231,16 @@ Returns the smallest signed distance between two geoms and optionally the segmen
Returned distances are bounded from above by ``distmax``. |br| If no collision of distance smaller than ``distmax`` is
found, the function will return ``distmax`` and ``fromto``, if given, will be set to (0, 0, 0, 0, 0, 0).
.. admonition:: Positive ``distmax`` values
:class: note
.. admonition:: different (correct) behavior under `nativeccd`
:class: note
.. TODO: b/339596989 - Improve mjc_Convex.
As explained in :ref:`Collision Detection<coDistance>`, distances are inaccurate when using the
:ref:`legacy CCD pipeline<coCCD>`, and its use is discouraged.
For some colliders, a large, positive ``distmax`` will result in an accurate measurement. However, for collision
pairs which use the general ``mjc_Convex`` collider, the result will be approximate and likely innacurate.
This is considered a bug to be fixed in a future release.
In order to determine whether a geom pair uses ``mjc_Convex``, inspect the table at the top of
`engine_collision_driver.c <https://github.com/google-deepmind/mujoco/blob/main/src/engine/engine_collision_driver.c>`__.
.. _mj_fullM:
Convert sparse inertia matrix ``M`` into full (i.e. dense) matrix.
|br| ``dst`` must be of size ``nv x nv``, ``M`` must be of the same size as ``mjData.qM``.
.. _mj_mulM:
@@ -694,3 +704,8 @@ to the inputs. Below, :math:`\bar q` denotes the pre-modified quaternion:
Note that derivatives depend only on :math:`h` and :math:`v` (in fact, on :math:`s = h v`).
All outputs are optional.
.. _mjs_delete:
Delete object corresponding to the given element, return 0 on success. This function should only be used for element
types that cannot have children, i.e. excluding bodies and default classes.
+264 -386
View File
@@ -264,6 +264,11 @@ how to use includes and how to modularize large files if desired.
The name of the XML file to be included. The file location is relative to the directory of the main MJCF file. If the
file is not in the same directory, it should be prefixed with a relative path.
.. admonition:: Prefer attach to include
:class: note
While some use cases for :ref:`include<include>` remain valid, it is recommended to use the
:ref:`attach<body-attach>` element instead, where applicable.
.. _mujoco:
@@ -577,6 +582,12 @@ from its default.
This flag disables the mid-phase collision filtering using a static AABB bounding volume hierarchy (a BVH binary
tree). If disabled, all geoms pairs that are allowed to collide are checked for collisions.
.. _option-flag-nativeccd:
:at:`nativeccd`: :at-val:`[disable, enable], "enable"`
This flag enables the native convex collision detection pipeline instead of using the
`libccd library <https://github.com/danfis/libccd>`__, see :ref:`convex collisions<coCCD>` for more details.
.. _option-flag-eulerdamp:
:at:`eulerdamp`: :at-val:`[disable, enable], "enable"`
@@ -586,7 +597,7 @@ from its default.
.. _option-flag-autoreset:
:at:`autoreset`: :at-val:`[disable, enable], "enable"`
This flag disables the automatic reseting of the simulation state when numerical issues are detected.
This flag disables the automatic resetting of the simulation state when numerical issues are detected.
.. _option-flag-override:
@@ -596,9 +607,17 @@ from its default.
.. _option-flag-energy:
:at:`energy`: :at-val:`[disable, enable], "disable"`
This flag enables the computation of kinetic and potential energy, stored in mjData.energy and displayed in the GUI.
This feature adds some CPU time but it is usually negligible. Monitoring energy for a system that is supposed to be
energy-conserving is one of the best ways to assess the accuracy of a complex simulation.
This flag enables the computation of potential and kinetic energy in ``mjData.energy[0, 1]`` respectively,
and displayed in the simulate GUI info overlay. Potential energy includes the gravitational component summed over
all bodies :math:`\sum_b m_b g h` and energy stored in passive springs in joints, tendons and flexes
:math:`\tfrac{1}{2} k x^2`, where :math:`x` is the displacement and :math:`k` is the spring constant. Kinetic
energy is given by :math:`\tfrac{1}{2} v^T M v`, where :math:`v` is the velocity and :math:`M` is the
mass matrix. Note that potential and kinetic energy in constraints is not accounted for.
The extra computation (also triggered by :ref:`potential<sensor-e_potential>` and
:ref:`kinetic<sensor-e_kinetic>` energy sensors) adds some CPU time but it is usually negligible. Monitoring energy
for a system that is supposed to be energy-conserving is one of the best ways to assess the accuracy of a complex
simulation.
.. _option-flag-fwdinv:
@@ -622,14 +641,12 @@ from its default.
.. _option-flag-multiccd:
:at:`multiccd`: :at-val:`[disable, enable], "disable"` |nbsp| |nbsp| |nbsp| (experimental feature)
:at:`multiccd`: :at-val:`[disable, enable], "disable"`
This flag enables multiple-contact collision detection for geom pairs that use a general-purpose convex-convex
collider e.g., mesh-mesh collisions. This can be useful when the contacting geoms have a flat surface, and the
collider e.g., mesh-mesh collisions. This can be useful when the contacting geoms have a flat surface and the
single contact point generated by the convex-convex collider cannot accurately capture the surface contact, leading
to instabilities that typically manifest as sliding or wobbling. Multiple contact points are found by rotating the
two geoms by ±1e-3 radians around the tangential axes and re-running the collision function. If a new contact is
detected it is added, allowing for up to 4 additional contact points. This feature is currently considered
experimental, and both the behavior and the way it is activated may change in the future.
to instabilities that typically manifest as sliding or wobbling. The implementation of this feature depends on the
selected convex collision pipeline, see :ref:`convex collisions<coCCD>` for more details.
.. _option-flag-island:
@@ -639,12 +656,6 @@ from its default.
allows for `island visualization <https://youtu.be/Vc1tq0fFvQA>`__.
In a future release, the constraint solver will exploit the disjoint nature of constraint islands.
.. _option-flag-nativeccd:
:at:`nativeccd`: :at-val:`[disable, enable], "disable"`
This flag enables the native convex collision detection pipeline instead of using the
`libccd library <https://github.com/danfis/libccd>`__.
.. _compiler:
**compiler** (*)
@@ -772,23 +783,11 @@ has any effect. The settings here are global and apply to the entire model.
models compiled with this flag, it is important to remember that collision geoms are often placed in a
:ref:`group<body-geom-group>` which is invisible by default.
.. _compiler-convexhull:
:at:`convexhull`: :at-val:`[false, true], "true"`
If this attribute is "true", the compiler will automatically generate a convex hull for every mesh that is used in at
least one non-visual geom (in the sense of the discardvisual attribute above). This is done to speed up collision
detection; recall :ref:`Collision` section in the Computation chapter. Even if the mesh is already convex, the hull
contains edge information that is not present in the mesh file, so it needs to be constructed. The only reason to
disable this feature is to speed up re-loading of a model with large meshes during model editing (since the convex
hull computation is the slowest operation performed by the compiler). However once model design is finished, this
feature should be enabled, because the availability of convex hulls substantially speeds up collision detection with
large meshes.
.. _compiler-usethread:
:at:`usethread`: :at-val:`[false, true], "true"`
If this attribute is "true", the model compiler will run in multi-threaded mode. Currently multi-threading is used
for computing the length ranges of actuators and for loading meshes.
for computing the length ranges of actuators and for parallel loading of meshes.
.. _compiler-fusestatic:
@@ -1245,21 +1244,21 @@ The full list of processing steps applied by the compiler to each mesh is as fol
.. _asset-mesh-inertia:
:at:`inertia`: :at-val:`[convex, exact, legacy], "legacy"`
:at:`inertia`: :at-val:`[convex, exact, legacy, shell], "legacy"`
This attribute controls how the mesh is used when mass and inertia are
:ref:`inferred from geometry<_compiler-inertiafromgeom>`. The current default value :at-val:`legacy` will be changed
:ref:`inferred from geometry<compiler-inertiafromgeom>`. The current default value :at-val:`legacy` will be changed
to :at-val:`convex` in a future release.
:at-val:`convex`
Use the mesh's convex hull to compute volume and inertia.
:at-val:`convex`: Use the mesh's convex hull to compute volume and inertia, assuming uniform density.
:at-val:`exact`
Use an exact algorithm to compute volume and inertia. This algorithm requires a well-oriented, watertight mesh and
will error otherwise.
:at-val:`exact`: Compute volume and inertia exactly, even for non-convex meshes. This algorithm requires a
well-oriented, watertight mesh and will error otherwise.
:at-val:`legacy`
Use the legacy algorithm, which is similar to :at-val:`convex`, but leads to volume overcounting for non-convex
meshes.
:at-val:`legacy`: Use the legacy algorithm, leads to volume overcounting for non-convex meshes. Though currently the
default to avoid breakages, it is not recommended.
:at-val:`shell`: Assume mass is concentrated on the surface of the mesh. Use the mesh's surface to compute
the inertia, assuming uniform surface density.
.. _asset-mesh-smoothnormal:
@@ -1273,7 +1272,7 @@ The full list of processing steps applied by the compiler to each mesh is as fol
:at:`maxhullvert`: :at-val:`int, "-1"`
Maximum number of vertices in a mesh's convex hull. Currently this is implemented by asking qhull
`to teminate <http://www.qhull.org/html/qh-optt.htm#TAn>`__ after :at:`maxhullvert` vertices. The default
`to terminate <http://www.qhull.org/html/qh-optt.htm#TAn>`__ after :at:`maxhullvert` vertices. The default
value of -1 means "unlimited". Positive values must be larger than 3.
.. _asset-mesh-vertex:
@@ -1710,9 +1709,9 @@ properties are grouped together.
loaded explicitly via the :ref:`texture <asset-texture>` element and then referenced here. The texture referenced
here is used for specifying the RGB values. For advanced rendering (e.g., Physics-Based Rendering), more texture
types need to be specified (e.g., roughness, metallic). In this case, this texture attribute should be omitted, and
the texture types should be specified explicitly via the specific role child elements, e.g.,
:ref:`texture <material-orm>`. Note however that the built-in renderer does not support PBR properties, so these
advanced rendering features are only available when using an external renderer.
the texture types should be specified using :ref:`layer <material-layer>` child elements. Note however that the
built-in renderer does not support PBR properties, so these advanced rendering features are only available when using
an external renderer.
.. _asset-material-texrepeat:
@@ -1784,116 +1783,59 @@ properties are grouped together.
model element which defines its own local rgba attribute, the local definition has precedence. Note that this "local"
definition could in fact come from a defaults class. The remaining material properties always apply.
.. _material-rgb:
.. _material-layer:
:el-prefix:`material/` |-| **rgb** (?)
''''''''''''''''''''''''''''''''''''''
:el-prefix:`material/` |-| **layer** (?)
''''''''''''''''''''''''''''''''''''''''
This element references a texture asset used to specify base color / albedo values.
If multiple textures are needed to specify the appearance of a material, the :ref:`texture <asset-material-texture>`
attribute cannot be used, and :el:`layer` child elements must be used instead. Specifying both the :at:`texture`
attribute and :el:`layer` child elements is an error.
.. _material-rgb-texture:
.. _material-layer-texture:
:at:`texture`: :at-val:`string, required`
Name of the texture, expected to have exactly 3 channels.
Name of the texture, like the :ref:`texture <asset-material-texture>` attribute.
.. _material-normal:
.. _material-layer-role:
:el-prefix:`material/` |-| **normal** (?)
'''''''''''''''''''''''''''''''''''''''''
:at:`role`: :at-val:`string, required`
Role of the texture. The valid values, expected number of channels, and the role semantics are:
This element references a texture asset used to specify the bump map (surface normals).
.. _material-normal-texture:
:at:`texture`: :at-val:`string, required`
Name of the texture, expected to have exactly 3 channels.
.. _material-occlusion:
:el-prefix:`material/` |-| **occlusion** (?)
''''''''''''''''''''''''''''''''''''''''''''
This element references a texture asset used to specify ambient occlusion.
.. _material-occlusion-texture:
:at:`texture`: :at-val:`string, required`
Name of the texture, expected to have exactly one channel.
.. _material-roughness:
:el-prefix:`material/` |-| **roughness** (?)
''''''''''''''''''''''''''''''''''''''''''''
This element references a texture asset used to specify the roughness map.
.. _material-roughness-texture:
:at:`texture`: :at-val:`string, required`
Name of the texture, expected to have exactly one channel.
.. _material-metallic:
:el-prefix:`material/` |-| **metallic** (?)
'''''''''''''''''''''''''''''''''''''''''''
This element references a texture asset used to specify the metallic map.
.. _material-metallic-texture:
:at:`texture`: :at-val:`string, required`
Name of the texture, expected to have exactly one channel.
.. _material-opacity:
:el-prefix:`material/` |-| **opacity** (?)
''''''''''''''''''''''''''''''''''''''''''
This element references a texture asset used to specify the opacity map (alpha channel, transparency).
.. _material-opacity-texture:
:at:`texture`: :at-val:`string, required`
Name of the texture, expected to have exactly one channel.
.. _material-emissive:
:el-prefix:`material/` |-| **emissive** (?)
'''''''''''''''''''''''''''''''''''''''''''
This element references a texture asset used to specify light emission.
.. _material-emissive-texture:
:at:`texture`: :at-val:`string, required`
Name of the texture, expected to have exactly 4 channels.
.. _material-orm:
:el-prefix:`material/` |-| **orm** (?)
''''''''''''''''''''''''''''''''''''''
This element references a texture asset used to specify a packed ORM map, where occlusion, roughness, and metallic
are joined into the corresponding RGB values of a single texture.
.. _material-orm-texture:
:at:`texture`: :at-val:`string, required`
Name of the texture, expected to have exactly 3 channels.
.. _material-rgba:
:el-prefix:`material/` |-| **rgba** (?)
'''''''''''''''''''''''''''''''''''''''
This element references a texture asset used to specify a packed map where albedo and opacity are joined into the same
4-channel texture.
.. _material-rgba-texture:
:at:`texture`: :at-val:`string, required`
Name of the texture, expected to have exactly 4 channels.
.. list-table::
:widths: 1 1 8
:header-rows: 1
* - value
- channels
- description
* - :at:`rgb`
- 3
- base color / albedo [red, green, blue]
* - :at:`normal`
- 3
- bump map (surface normals)
* - :at:`occlusion`
- 1
- ambient occlusion
* - :at:`roughness`
- 1
- roughness
* - :at:`metallic`
- 1
- metallicity
* - :at:`opacity`
- 1
- opacity (alpha channel)
* - :at:`emissive`
- 4
- RGB light emmision intensity, exposure weight in 4th channel
* - :at:`orm`
- 3
- packed 3 channel [occlusion, roughness, metallic]
* - :at:`rgba`
- 4
- packed 4 channel [red, green, blue, alpha]
.. _asset-model:
@@ -1946,11 +1888,11 @@ defined. Its body name is automatically defined as "world".
:at:`mocap`: :at-val:`[false, true], "false"`
If this attribute is "true", the body is labeled as a mocap body. This is allowed only for bodies that are children
of the world body and have no joints. Such bodies are fixed from the viewpoint of the dynamics, but nevertheless the
forward kinematics set their position and orientation from the fields mjData.mocap_pos and mjData.mocap_quat at each
forward kinematics set their position and orientation from the fields ``mjData.mocap_{pos,quat}`` at each
time step. The size of these arrays is adjusted by the compiler so as to match the number of mocap bodies in the
model. This mechanism can be used to stream motion capture data into the simulation. Mocap bodies can also be moved
via mouse perturbations in the interactive visualizer, even in dynamic simulation mode. This can be useful for
creating props with adjustable position and orientation. See also the mocap attribute of :ref:`flag <option-flag>`.
creating props with adjustable position and orientation.
.. _body-pos:
@@ -2181,7 +2123,7 @@ rotations as unit quaternions.
(``mjData.qfrc_actuator``) rather than passive forces (``mjData.qfrc_passive``). Notionally, this means that gravity
compensation is the result of a control system rather than natural buoyancy. In practice, enabling this flag is
useful when joint-level actuator force clamping is used. In this case, the total actuation force applied on a joint,
including gravity compensation, is guaranteed to not exceeed the specified limits. See :ref:`CForceRange` and
including gravity compensation, is guaranteed to not exceed the specified limits. See :ref:`CForceRange` and
:ref:`actuatorfrcrange<body-joint-actuatorfrcrange>` for more details on this type of force limit.
.. _body-joint-margin:
@@ -2426,7 +2368,8 @@ helps clarify the role of bodies and geoms in MuJoCo.
This attribute specifies an integer group to which the geom belongs. The only effect on the physics is at compile
time, when body masses and inertias are inferred from geoms selected based on their group; see inertiagrouprange
attribute of :ref:`compiler <compiler>`. At runtime this attribute is used by the visualizer to enable and disable
the rendering of entire geom groups. It can also be used as a tag for custom computations.
the rendering of entire geom groups. By default, groups 0, 1 and 2 are visible, while all other groups are invisible.
The group attribute can also be used as a tag for custom computations.
.. _body-geom-priority:
@@ -2515,8 +2458,10 @@ helps clarify the role of bodies and geoms in MuJoCo.
.. _body-geom-shellinertia:
:at:`shellinertia` :at-val:`[false, true], "false"`
If true, the geom's inertia is computed assuming that all the mass is concentrated on the boundary. In this case
:at:`density` is interpreted as surface density rather than volumetric density.
If true, the geom's inertia is computed assuming that all the mass is concentrated on the surface. In this case
:at:`density` is interpreted as surface rather than volumetric density. This attribute only applies to primitive
geoms and is ignored for meshes. Surface inertia for meshes can be specified by setting the
:ref:`asset/mesh/inertia<asset-mesh-inertia>` attribute to :at-val:`"shell"`.
.. _body-geom-solmix:
@@ -2563,7 +2508,7 @@ helps clarify the role of bodies and geoms in MuJoCo.
<COrientation>`. The frame position is in the middle between the end points. If this attribute is specified, the
remaining position and orientation-related attributes are ignored. The image on the right demonstrates use of
:at:`fromto` with the four supported geoms, using identical Z values. The model is `here <_static/fromto.xml>`__.
Note that the :at:`fromto` semantics of *capsule* are unique: the two end points specify the segement around which
Note that the :at:`fromto` semantics of *capsule* are unique: the two end points specify the segment around which
the radius defines the capsule surface.
.. _body-geom-pos:
@@ -2794,7 +2739,7 @@ and the +Y axis points up. Thus the frame position and orientation are the key a
:at:`mode`: :at-val:`[fixed, track, trackcom, targetbody, targetbodycom], "fixed"`
This attribute specifies how the camera position and orientation in world coordinates are computed in forward
kinematics (which in turn determine what the camera sees). "fixed" means that the position and orientation specified
below are fixed relative to the the body where the camera is defined. "track" means that the camera position is at a
below are fixed relative to the body where the camera is defined. "track" means that the camera position is at a
constant offset from the body in world coordinates, while the camera orientation is constant in world coordinates.
These constants are determined by applying forward kinematics in qpos0 and treating the camera as fixed. Tracking can
be used for example to position a camera above a body, point it down so it sees the body, and have it always remain
@@ -3017,14 +2962,14 @@ the direction specified by the dir attribute. It does not have a full spatial fr
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This is not a model element, but rather a macro which expands into multiple model elements representing a composite
object. These elements are bodies (with their own joints, geoms and sites) that become children of the parent body
containing the macro, as well as tendons and equality constraints added to the corresponding model sections. The
automatically-generated bodies are laid out in a regular grid in 1D, 2D or 3D depending on the object type and count
attributes. The macro expansion is done by the model compiler. If the resulting model is then saved, the macro will be
object. These elements are bodies (with their own joints and geoms) that become children of the parent body containing
the macro. The macro expansion is done by the model compiler. If the resulting model is then saved, the macro will be
replaced with the actual model elements. The defaults mechanism used in the rest of MJCF does not apply here, even if
the parent body has a childclass attribute defined. Instead there are internal defaults adjusted automatically for each
composite object type. Composite objects can only be defined if the model is in local coordinates. Using them in global
coordinates results in compiler error. See :ref:`CComposite` in the modeling guide for more detailed explanation.
composite object type. See :ref:`CComposite` in the modeling guide for more detailed explanation. Note that there used
to be several composite types, but they have incrementally replaced by :ref:`replicate<replicate>` (for repeated
objects) and :ref:`flexcomp<body-flexcomp>` (for soft objects). Therefore, the only supported composite type is now
cable, which produces an inextensible chain of bodies connected with ball joints.
.. _body-composite-prefix:
@@ -3035,37 +2980,8 @@ coordinates results in compiler error. See :ref:`CComposite` in the modeling gui
.. _body-composite-type:
:at:`type`: :at-val:`[particle, grid, cable, rope, loop, cloth, box, cylinder, ellipsoid], required`
This attribute determines the type of composite object. The remaining attributes and sub-elements are then
interpreted according to the type. Default settings are also adjusted depending on the type.
The **particle** type creates a 1D, 2D or 3D grid of equally-spaced bodies. By default, each body has a single sphere
geom and 3 orthogonal sliding joints, allowing translation but not rotation. The geom condim and priority attributes
are set to 1 by default. This makes the spheres have frictionless contacts with all other geoms (unless the priority
of some frictional geom is higher). The user can replace the default sliders with multiple joints of kind="particle"
and replace the default sphere with a custom geom. Note that the particle composite type is deprecated and might be
removed in a future version. Instead of particle, it is recommended to use :ref:`replicate`.
The **grid** type creates a 1D or 2D grid of bodies, each having a sphere geom, a sphere site, and 3 orthogonal
sliding joints by default. The :el:`pin` sub-element can be used to specify that some bodies should not have joints,
and instead should be pinned to the parent body. Unlike the particle type, here each two neighboring bodies are
connected with a spatial tendon whose length is equality-constrained to its initial value (the sites are needed to
define the tendons). The "main" tendons are parallel to the axes of the grid. In addition one can create diagonal
"shear" tendons, using the :el:`tendon` sub-element. This type is suitable for simulating strings as well as cloth.
The **rope** type creates a 1D grid of bodies, each having a geom with user-defined type (sphere, capsule or
ellipsoid) and 2 hinge joints with axes orthogonal to the grid, creating a universal joint with the previous body.
This corresponds to a kinematic chain which can bend but cannot stretch or twist. In addition, one can specify
stretch and twist joints (slide and hinge respectively) with the :el:`joint` sub-element. When specified, these extra
joints are equality-constrained, but the constraint is soft by default so that some stretch and twist are possible.
The rope can extend in one or both directions from the parent body. To specify the origin of the rope, the parent
body *must* be named so that it fits the automatic naming convention. For example, to make the parent be the first
body in the chain, and assuming we have prefix="C", the parent body should be named "CB0". When the parent is not at
the end, the rope consists of two kinematic chains starting at the parent and extending in opposite directions.
The **loop** type is the same as the rope type except the elements are arranged in a circle, and the first and last
elements are equality-constrained to remain connected (using the "connect" constraint type). The softness of this
equality constraint is adjusted with the attributes solrefsmooth and solimpsmooth.
:at:`type`: :at-val:`[cable], required`
This attribute determines the type of composite object. The only supported type is cable.
The **cable** type creates a 1D chain of bodies connected with ball joints, each having a geom with user-defined type
(cylinder, capsule or box). The geometry can either be defined with an array of 3D vertex coordinates :at:`vertex`
@@ -3074,34 +2990,6 @@ coordinates results in compiler error. See :ref:`CComposite` in the modeling gui
:at:`size`, resulting in :math:`f(s)=\{\text{size}[1]\cdot\cos(2\pi\cdot\text{size}[2]),\;
\text{size}[1]\cdot\sin(2\pi\cdot\text{size}[2]),\; \text{size}[0]\cdot s\}`.
The **cloth** type is a different way to model cloth, beyond type="grid". Here the elements are connected with
universal joints and form a kinematic spanning tree. The root of the tree is the parent body, and its coordinates in
the grid are inferred from its name - similar to rope but here the naming format is "CB2_0". Neighboring bodies that
are not connected with joints are then connected with equality-constrained spatial tendons. The resulting cloth is
non-homogeneous, because the kinematic constraints cannot be violated while the tendon equality constraints are soft.
One can make it more homogeneous by adding stretch and twist joints (similar to rope) and adjusting the strength of
their equality constraints. Shear tendons can also be added. In addition to the different physics, cloth can do
things that a 2D grid cannot do. This is because the elements of cloth have both position and orientation, while the
elements of grid can only translate. The geoms used in cloth can be ellipsoids and capsules in addition to spheres.
When elongated geoms are used, they are rotated and interleaved in a pattern that fills the holes, preventing objects
from penetrating the cloth. Furthermore the inertia of the cloth elements can be modified with the flatinertia
attribute, and can then be used with lift and drag forces to simulate ripple effects.
The **box** type creates a 3D arrangement of bodies forming the outer shell of a (soft) box. The parent body is at
the center of the box. Each element body has a geom (sphere, ellipsoid or capsule) and a single sliding joint
pointing away from the center of the box. The sliding joints are equality-constrained to their initial value.
Furthermore, to achieve smooth deformations of the sides of the box, each joint is equality-constrained to remain
equal to its neighbor joints. To preserve the volume of the soft box approximately, a fixed tendon is used to
constrain the sum of all joints to remain constant. When the user specifies elongated geoms (capsules or ellipsoids)
their long axis is aligned with the sliding joint axis. This makes the shell thicker for collision detection
purposes, preventing objects from penetrating the box. It is important to disable contacts between the elements of
the box. This is done by setting the default geom contype to 0. The user can change it of course, but if the geoms
comprising the soft box are allowed to contact each other the model will not work as intended.
The **cylinder** and **ellipsoid** types are the same as box, except the elements are projected on the surface of an
ellipsoid or a cylinder respectively. Thus the composite soft body shape is different, while everything else is the
same as in the box type.
.. _body-composite-count:
:at:`count`: :at-val:`int(3), required`
@@ -3112,61 +3000,33 @@ coordinates results in compiler error. See :ref:`CComposite` in the modeling gui
parent body. Note that some types imply a grid of certain dimensionality, so the requirements for this attribute
depend on the specified type.
.. _body-composite-spacing:
:at:`spacing`: :at-val:`real, required`
The spacing between the centers of the grid elements. This spacing is the same in all dimensions. It should normally
be set to a value larger than the geom size, otherwise there will be a lot of contacts in the reference model
configuration (which is allowed but rarely desirable).
.. _body-composite-offset:
:at:`offset`: :at-val:`real(3), "0 0 0"`
This attribute affects particle and grid types, and is ignored for all other types. It specifies a 3D offset from the
center of the parent body to the center of the grid of elements. The offset is expressed in the local coordinate
frame of the parent body.
.. _body-composite-flatinertia:
:at:`flatinertia`: :at-val:`real, "0"`
This attribute affects the cloth type and is ignored for all other types. The default value 0 disables this
mechanism. When the value is positive, it specifies the ratio of the small-to-large axes of the modified diagonal
inertia. The idea is to set it to a small value, say 0.01, in which case the inertias of the body elements will
corresponds to flat boxes aligned with the cloth (which can then be used for lift forces). This will not change the
geom shapes, but instead will set the body inertias directly and disable the automatic computation of inertia from
geom shape for the composite body only.
.. _body-composite-solrefsmooth:
.. _body-composite-solimpsmooth:
:at:`solrefsmooth`, :at:`solimpsmooth`
These are the solref and solimp attributes of the loop-closure equality constraint for loop types, and the
smoothness-preserving equality constraint for box, cylinder and ellipsoid types. For all other types they have no
effect. They obey the same rules as all other solref and solimp attributes in MJCF, except their defaults here are
adjusted depending on the composite type. See :ref:`CSolver`.
It specifies a 3D offset from the center of the parent body to the center of the grid of elements. The offset is
expressed in the local coordinate frame of the parent body.
.. _body-composite-vertex:
:at:`vertex`: :at-val:`real(3*nvert), optional`
Vertex 3D positions in global coordinates (cable only).
Vertex 3D positions in global coordinates.
.. _body-composite-initial:
:at:`initial`: :at-val:`[free, ball, none], "0"`
Behavior of the first point (cable only). Free: free joint. Ball: ball joint. None: no dof.
Behavior of the first point. Free: free joint. Ball: ball joint. None: no dof.
.. _body-composite-curve:
:at:`curve`: :at-val:`string(3), optional`
Functions specifying the vertex positions (cable only). Available functions are `s`, `cos(s)`, and `sin(s)`, where
`s` is the arc length parameter.
Functions specifying the vertex positions. Available functions are `s`, `cos(s)`, and `sin(s)`, where `s` is the arc
length parameter.
.. _body-composite-size:
:at:`size`: :at-val:`int(3), optional`
Scaling of the curve functions (cable only). `size[0]` is the scaling of `s`, `size[1]` is the radius of `\cos(s)`
and `\sin(s)`, and `size[2]` is the speed of the argument (i.e. `\cos(2*\pi*size[2]*s)`).
Scaling of the curve functions. `size[0]` is the scaling of `s`, `size[1]` is the radius of `\cos(s)` and `\sin(s)`,
and `size[2]` is the speed of the argument (i.e. `\cos(2*\pi*size[2]*s)`).
.. _composite-joint:
@@ -3180,7 +3040,7 @@ joints should be created, as well as to adjust the attributes of both automatic
.. _composite-joint-kind:
:at:`kind`: :at-val:`[main, twist, stretch, particle], required`
:at:`kind`: :at-val:`[main], required`
The joint kind here is orthogonal to the joint type in the rest of MJCF. The joint kind refers to the function of the
joint within the mechanism comprising the composite body, while the joint type (hinge or slide) is implied by the
joint kind and composite body type.
@@ -3190,18 +3050,6 @@ joints should be created, as well as to adjust the attributes of both automatic
sliders for box, cylinder and rope; universal joints for cloth, rope and loop. Even though the main joints are
included automatically, this sub-element is still useful for adjusting their attributes.
The **twist** kind corresponds to hinge joints enabling rope, loop and cloth objects to twist. These are optional
joints and are only created if this sub-element is present. This sub-element is also used to adjust the attributes of
the optional twist joints. For other composite object types this sub-element has no effect.
The **stretch** kind corresponds to slide joints enabling rope, loop and cloth objects to stretch. These are optional
joints and are only created if this sub-element is present. This sub-element is also used to adjust the attributes of
the optional stretch joints. For other composite object types this sub-element has no effect.
The **particle** kind can only be used with the particle composite type. As opposed to all previous kinds, this kind
*replaces* the default 3 sliders with user-defined joints. User-defined joints can be repeated, for example
to create planar particles with two sliders and a hinge.
.. _composite-joint-solreffix:
.. _composite-joint-solimpfix:
@@ -3249,73 +3097,6 @@ joints should be created, as well as to adjust the attributes of both automatic
Same meaning as regular :ref:`joint <body-joint>` attributes.
.. _composite-tendon:
:el-prefix:`composite/` |-| **tendon** (*)
''''''''''''''''''''''''''''''''''''''''''
Tendons are treated similarly to joints in composite objects. The tendon kind specified here together with the composite
body type imply the tendon type as used in the rest of MJCF. This sub-element is used to both create optional tendons,
and adjust the attributes of automatic and optional tendons. One difference from joints is that all tendons used in
composite objects are equality-constrained.
.. _composite-tendon-kind:
:at:`kind`: :at-val:`[main, shear], required`
The **main** kind corresponds to tendons holding the composite body together. These are the spatial tendons that
connect neighboring bodies in grid and cloth, and the fixed tendon used to preserve the volume of box, cylinder and
ellipsoid. For other composite types this sub-element has no effect.
The **shear** kind corresponds to diagonal tendons that prevent shear (as opposed to enabling - which is the function
of optional joints). Such tendons can be created in 2D grid objects and cloth objects. For all other composite object
types this sub-element has no effect.
.. _composite-tendon-solreffix:
.. _composite-tendon-solimpfix:
:at:`solreffix`, :at:`solimpfix`
These are the solref and solimp attributes used to equality-constrain the tendon. The defaults are adjusted depending
on the composite type. Otherwise these attributes obey the same rules as all other solref and solimp attributes in
MJCF. See :ref:`CSolver`.
.. _composite-tendon-group:
.. _composite-tendon-stiffness:
.. _composite-tendon-damping:
.. _composite-tendon-limited:
.. _composite-tendon-range:
.. _composite-tendon-margin:
.. _composite-tendon-solreflimit:
.. _composite-tendon-solimplimit:
.. _composite-tendon-frictionloss:
.. _composite-tendon-solreffriction:
.. _composite-tendon-solimpfriction:
.. _composite-tendon-material:
.. _composite-tendon-rgba:
.. _composite-tendon-width:
.. |body/composite/tendon attrib list| replace::
:at:`group`, :at:`stiffness`, :at:`damping`, :at:`limited`, :at:`range`, :at:`margin`, :at:`solreflimit`,
:at:`solimplimit`, :at:`frictionloss`, :at:`solreffriction`, :at:`solimpfriction`, :at:`material`, :at:`rgba`,
:at:`width`
|body/composite/tendon attrib list|
Same meaning as regular :ref:`tendon <tendon>` attributes.
.. _composite-geom:
:el-prefix:`composite/` |-| **geom** (?)
@@ -3437,22 +3218,6 @@ automatically-generated skin.
bi-cubic interpolation. This increases the quality of the rendering (especially in the absence of textures) but also
slows down the renderer, so use it with caution. Values above 3 are unlikely to be needed.
.. _composite-pin:
:el-prefix:`composite/` |-| **pin** (*)
'''''''''''''''''''''''''''''''''''''''
This sub-element can be used to pin some of the element bodies in grid objects (both 1D and 2D). Pinning means that the
corresponding body has no joints, and therefore it is rigidly fixed to the parent body. When the parent is the world,
this has the effect of hanging a string or a cloth in space. If the parent body is moving, this can be used to model a
handle where the composite object is attached. For other composite types this sub-element has no effect.
.. _composite-pin-coord:
:at:`coord`: :at-val:`int(2), required`
The grid coordinates of the element body which should be pinned. The coordinates are zero-based. For 1D grids this
attribute can have only one number, in which case the second number is automatically set to 0.
.. _composite-plugin:
@@ -3559,9 +3324,48 @@ saving the XML:
radius in 2D, and tetrahedra with radius in 3D. Certain flexcomp types imply a dimensionality, in which case the
value specified here is ignored.
.. youtube:: uNt3i8hrJu4
:align: right
:width: 240px
.. _body-flexcomp-dof:
:at:`dof`: :at-val:`[full, radial, trilinear], "full"`
The parametrization of the flex's degrees of freedom (dofs). See the video on the right illustrating the
different parametrizations with deformable spheres. The three models in the video are respectively
`sphere_full <https://github.com/google-deepmind/mujoco/blob/main/model/flex/sphere_full.xml>`__,
`sphere_radial <https://github.com/google-deepmind/mujoco/blob/main/model/flex/sphere_radial.xml>`__
and `sphere_trilinear <https://github.com/google-deepmind/mujoco/blob/main/model/flex/sphere_trilinear.xml>`__.
**full**
Three translational dofs per vertex. This is the most expressive but also the most expensive option.
**radial**
A single radial translational dof per vertex. Note that unlike in the "full" case, the radial parametrization
requires a free joint at the flex's parent in order for free body motion to be possible. This type of
parametrization is appropriate for shapes that are relatively spherical.
**trilinear**
Three translational dofs at each corner of the bounding box of the flex, for a total of 24 dofs for the entire
flex, independent of the number of vertices. The positions of the vertices are updated using trilinear
interpolation over the bounding box.
.. youtube:: qJFbx-FR7Bc
:align: right
:width: 240px
Trilinear flexes are much faster than the previous two options, and are the preferred choice if the expected
deformations can be captured by the reduced parametriation. For example, see the video on the right comparing `full
<https://github.com/google-deepmind/mujoco/blob/main/model/flex/gripper.xml>`__ and `trilinear
<https://github.com/google-deepmind/mujoco/blob/main/model/flex/gripper_trilinear.xml>`__ flexes for modeling
deformable gripper pads.
Note that the choice of dof parametrization affects the deformation modes of the flex but has no effect on the
accuracy of the collision geometry, which always takes into account the high-resolution mesh of the flex.
.. _body-flexcomp-type:
:at:`type`: :at-val:`[grid, box, cylinder, ellipsoid, mesh, gmsh, direct], "grid"`
:at:`type`: :at-val:`[grid, box, cylinder, ellipsoid, disc, circle, mesh, gmsh, direct], "grid"`
This attribute determines the type of :el:`flexcomp` object. The remaining attributes and sub-elements are then
interpreted according to the type. Default settings are also adjusted depending on the type. Different types
correspond to different methods for specifying the flexcomp points and the stretchable elements that connect them.
@@ -3585,11 +3389,19 @@ saving the XML:
**ellipsoid** is the same as **box**, except the points are projected on the surface of an ellipsoid.
**disc** is the same as **box**, except the points are projected on the surface of a disc. It is only compatible
with :at:`dim=2`.
**circle** is the same as **grid**, except the points are sampled along a circle so that the first and last points
are the same. The radius of the circle is computed such that each segment has the requested spacing. It is only
compatible with :at:`dim=1`.
**mesh** loads the flexcomp points and elements (i.e. triangles) from a mesh file, in the same file formats as mesh
assets. A mesh asset is not actually added to the model. Instead the vertex and face data from the mesh file are used
to populate the point and element data of the flexcomp. :at:`dim` is automatically set to 2. Recall that a mesh asset
in MuJoCo can be used as a rigid geom attached to a single body. In contrast, the flex generated here corresponds to
a soft mesh with the same initial shape, where each vertex is a separate moving body (unless pinned).
assets, excluding the legacy .msh format. A mesh asset is not actually added to the model. Instead the vertex and
face data from the mesh file are used to populate the point and element data of the flexcomp. :at:`dim` is
automatically set to 2. Recall that a mesh asset in MuJoCo can be used as a rigid geom attached to a single body. In
contrast, the flex generated here corresponds to a soft mesh with the same initial shape, where each vertex is a
separate moving body (unless pinned).
.. _gmsh-file-docs:
@@ -3638,9 +3450,10 @@ saving the XML:
:at:`texcoord`: :at-val:`real(2*npoint), optional`
Texture coordinates of each point, passed through to the automatically-generated flex. Note that flexcomp does not
generate texture coordinates automatically, except for 2D grids. For all other types, the user can specify explicit
texture coordinates here, even if the points themselves were generated automatically. This requires understanding of
the layout of the automatically-generated points and how they correspond to the texture referenced by the material.
generate texture coordinates automatically, except for 2D grids, box, cylinder and ellipsoid. For all other types,
the user can specify explicit texture coordinates here, even if the points themselves were generated automatically.
This requires understanding of the layout of the automatically-generated points and how they correspond to the
texture referenced by the material.
.. _body-flexcomp-mass:
@@ -3659,9 +3472,9 @@ saving the XML:
:at:`file`: :at-val:`string, optional`
The name of the file from which a **surface** (triangular) or **volumetric** (tetrahedral) mesh is loaded. For
surface meshes, the file extension is used to determine the file format. Supported formats are the same as in
:ref:`mesh assets<asset-mesh>` and also including GMSH. Volumetric meshes are supported only in GMSH format.
See :ref:`here<gmsh-file-docs>` for more information on GMSH files.
surface meshes, the file extension is used to determine the file format. Supported formats are GMSH and the formats
specified in :ref:`mesh assets<asset-mesh>`, excluding the legacy .msh format. Volumetric meshes are supported only
in GMSH format. See :ref:`here<gmsh-file-docs>` for more information on GMSH files.
.. _body-flexcomp-rigid:
@@ -3705,6 +3518,13 @@ saving the XML:
These attributes are directly passed through to the automatically-generated :ref:`flex<deformable-flex>` object and
have the same meaning.
.. _body-flexcomp-origin:
:at:`origin`: :at-val:`real(3), "0 0 0"`
The origin of the flexcomp. Used for generating a volumetric mesh from an OBJ surface mesh. Each surface triangle is
connected to the origin to create a tetrahedron, so the resulting volumetric mesh is guaranteed to be well-formed
only for convex shapes.
.. _flexcomp-contact:
:el-prefix:`flexcomp/` |-| **contact** (*)
@@ -4097,9 +3917,14 @@ cases, the user will specify a :el:`flexcomp` which will then automatically cons
.. _deformable-flex-texcoord:
:at:`texcoord`: :at-val:`real(2*nvert), optional`
Texture coordinates for each vertex. If omitted, texture mapping for this flex is disabled, even if a texture is
specified in the material.
:at:`texcoord`: :at-val:`real(2*vert or ntexcoord), optional`
Texture coordinates. If omitted, texture mapping for this flex is disabled, even if a texture is specified in the
material.
.. _deformable-flex-elemtexcoord:
:at:`elemtexcoord`: :at-val:`int((dim+1)*nelem), optional`
Texture indices for each face. If omitted, texture are assumed to be vertex-based.
.. _deformable-flex-element:
@@ -4109,7 +3934,7 @@ cases, the user will specify a :el:`flexcomp` which will then automatically cons
which is why the number of indices equals (dim+1) times the number of elements. In 2D, the vertices should be listed
in counter-clockwise order. In 1D and 3D the order is irrelevant; in 3D the model compiler will rearrange the
vertices as needed. Repeated vertex indices within a flex element are not allowed. The topology of the flex is not
enforced; it could corespond to a continuous soft body, or a collection of disconnected stretchable elements, or
enforced; it could correspond to a continuous soft body, or a collection of disconnected stretchable elements, or
anything in-between.
.. _deformable-flex-flatskin:
@@ -4138,6 +3963,12 @@ cases, the user will specify a :el:`flexcomp` which will then automatically cons
Integer group to which the flex belongs. This attribute can be used for custom tags. It is also used by the
visualizer to enable and disable the rendering of entire groups of flexes.
.. _deformable-flex-node:
:at:`node`: :at-val:`string(nnode), optional`
The degrees-of-freedom of the flex.
An array of MuJoCo body names (separated by white space) to which each node belongs. The number of body names
should equal the number of nodes (nnode). See the flexcomp :ref:`dof<body-flexcomp-dof>` attribute for more details.
.. _flex-edge:
@@ -4197,7 +4028,7 @@ stress-strain relationship.. See also :ref:`deformable <CDeformable>` objects.
:at:`thickness`: :at-val:`real(1), "-1"`
Shell thickness, units of length; only for used 2D flexes. Used to scale the stretching stiffness.
This thickness can be set equal to 2 times the :ref:`radius <deformable-flex-radius>` in order to match the geometry,
but is exposed seperately since the radius might be constrained by considerations related to collision detection.
but is exposed separately since the radius might be constrained by considerations related to collision detection.
.. _flex-contact:
@@ -4210,7 +4041,7 @@ extensions specific to flexes.
.. _flex-contact-internal:
:at:`internal`: :at-val:`[true, false], "true"`
:at:`internal`: :at-val:`[true, false], "false"`
Enables or disables internal collisions which prevent flex self-penetration and element inversion. Note that flex
elements that have shared vertices cannot collide (or else there will be permanent contacts). In 1D and 2D, internal
collision checks rely on predefined vertex-element pairs, where the vertex is treated as a sphere with the same
@@ -4218,7 +4049,9 @@ extensions specific to flexes.
flex. The pre-defined vertex-element pairs are generated by the model compiler automatically. In 3D, internal
collision checks are performed within each tetraheron: each vertex is collided with the plane corresponding to the
opposing triangle face (again using the flex radius). The resulting contacts are always created with condim 1, gap 0,
margin 0.
margin 0. Note that internal contacts modify the behavior implied by the :ref:`elasticity
parameters<flex-elasticity>` and is recommended only for flexes where element inversion cannot be prevented. The
default value of this attribute was changed from "true" to "false" in version 3.3.1.
.. _flex-contact-selfcollide:
@@ -4438,7 +4271,7 @@ ball joint outside the kinematic tree. Connect constraints can be specified in o
- Using :ref:`body1<equality-connect-body1>` and :ref:`anchor<equality-connect-anchor>` (both required) and
optionally :ref:`body2<equality-connect-body2>`. When using this specification, the constraint is assumed to be
satisfied in the configuration in which the model is defined.
satisfied at the configuration in which the model is defined (``mjData.qpos0``).
- :ref:`site1<equality-connect-site1>` and :ref:`site2<equality-connect-site2>` (both required). When using this
specification, the two sites will be pulled together by the constraint, regardless of their position in the default
configuration. An example of this specification is shown in
@@ -4484,8 +4317,8 @@ ball joint outside the kinematic tree. Connect constraints can be specified in o
:at:`anchor`: :at-val:`real(3), optional`
Coordinates of the 3D anchor point where the two bodies are connected, in the local coordinate frame of :at:`body1`.
The constraint is assumed to be satisfied in the configuration in which the model is defined, which lets the compiler
compute the associated anchor point for :at:`body2`.
The constraint is assumed to be satisfied in the configuration at which the model is defined (``mjData.qpos0``),
which lets the compiler compute the associated anchor point for :at:`body2`.
.. _equality-connect-site1:
@@ -4739,6 +4572,18 @@ A second form of wrapping is where the tendon is constrained to pass *through* a
wrap around it. This is enabled automatically when a sidesite is specified and its position is inside the volume of
the obstacle geom.
.. youtube:: I2q7D0Vda-A
:width: 300px
:align: right
**Visualization:** Tendon paths are visualized as in the image above, respecting the :ref:`width<tendon-spatial-width>`,
:ref:`material<tendon-spatial-material>` and :ref:`rgba<tendon-spatial-rgba>` attributes below. A special kind of
visualization is used for unactuated 2-point tendons with :ref:`range<tendon-spatial-range>` or
:ref:`springlength<tendon-spatial-springlength>` of the form :at-val:`[0 X]`, with positive X. Such tendons act like a
cable, applying force only when stretched. Therefore when not stretched, they are drawn as a catenary of
length X, as in the clip on the right of `this example model
<https://github.com/google-deepmind/mujoco/blob/main/test/engine/testdata/catenary.xml>`__.
.. _tendon-spatial-name:
:at:`name`: :at-val:`string, optional`
@@ -6255,13 +6100,12 @@ excluded; this is because sensor calculations are independent of the visualizer.
:el-prefix:`sensor/` |-| **camprojection** (*)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element creates a camprojection sensor, which returns the location of a target site, projected onto a camera image
in pixel coordinates. The origin of this system is located at the top-left corner of the first pixel, so a target
which projects exactly onto the corner of the image, will have value (0, 0). Values are not clipped, so targets which
fall outside the camera image will take values above or below the pixel limits. Moreover, points behind the camera
are also projected onto the image, so it is up to the user to filter out such points, if desired. This can be done using
a `framepos<sensor-framepos>` sensor with the camera as reference frame, then a negative/positive value in the
z-coordinate indicates (respectively) a location in the front/back of the camera.
This element creates a camera projection sensor: the location of a target site, projected onto a camera image in pixel
coordinates. The pixel origin (0, 0) is located at the top-left corner. Values are not clipped, so targets which fall
outside the camera image will take values above or below the pixel range limits. Moreover, points behind the camera are
also projected onto the image, so it is up to the user to filter out such points, if desired. This can be done using a
:ref:`framepos<sensor-framepos>` sensor with the camera as a reference frame: a negative/positive value in the
z-coordinate indicates a location in front of/behind the camera plane, respectively.
.. _sensor-camprojection-site:
@@ -6471,7 +6315,7 @@ contributed by all actuators to a single scalar joint (hinge or slider). If the
:ref:`actuatorgravcomp<body-joint-actuatorgravcomp>` attribute is "true", this sensor will also measure contributions by
gravity compensation forces (which are added directly to the joint and would *not* register in the
:ref:`actuatorfrc<sensor-actuatorfrc>`) sensor. This type of sensor is important when multiple actuators act on a single
joint or when a single actuator act on multiple joints. See :ref:`CForceRange` for details.
joint or when a single actuator acts on multiple joints. See :ref:`CForceRange` for details.
.. _sensor-jointactuatorfrc-name:
@@ -7139,16 +6983,11 @@ pipeline. These 3 sensors share some common properties:
to geom-geom penetration) will be reported by :ref:`sensor/distance<sensor-distance>`.
In order to determine collision properties of non-penetrating geom pairs, a positive :at:`cutoff` is required.
.. admonition:: Positive cutoff values
.. admonition:: different (correct) behavior under `nativeccd`
:class: note
.. TODO: b/339596989 - Improve mjc_Convex.
For some colliders, a positive :at:`cutoff` will result in an accurate measurement. However, for collision
pairs which use the general ``mjc_Convex`` collider, the result will be approximate and likely innacurate.
This is considered a bug to be fixed in a future release.
In order to determine whether a geom pair uses ``mjc_Convex``, inspect the table at the top of
`engine_collision_driver.c <https://github.com/google-deepmind/mujoco/blob/main/src/engine/engine_collision_driver.c>`__.
As explained in :ref:`Collision Detection<coDistance>`, distances are inaccurate when using the
:ref:`legacy CCD pipeline<coCCD>`, and its use is discouraged.
:at:`geom1`, :at:`geom2`, :at:`body1`, :at:`body2`
For all 3 collision sensor types, the two colliding geoms can be specified explicitly using the :at:`geom1` and
@@ -7300,6 +7139,45 @@ See :ref:`collision-sensors` for more details about sensors of this type.
:at:`name`, :at:`noise`, :at:`user`
See :ref:`CSensor`.
.. _sensor-e_potential:
:el-prefix:`sensor/` |-| **e_potential** (*)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element creates sensor that returns the potential energy.
.. _sensor-e_potential-name:
.. _sensor-e_potential-noise:
.. _sensor-e_potential-cutoff:
.. _sensor-e_potential-user:
:at:`name`, :at:`noise`, :at:`cutoff`, :at:`user`
See :ref:`CSensor`.
.. _sensor-e_kinetic:
:el-prefix:`sensor/` |-| **e_kinetic** (*)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element creates sensor that returns the kinetic energy.
.. _sensor-e_kinetic-name:
.. _sensor-e_kinetic-noise:
.. _sensor-e_kinetic-cutoff:
.. _sensor-e_kinetic-user:
:at:`name`, :at:`noise`, :at:`cutoff`, :at:`user`
See :ref:`CSensor`.
.. _sensor-clock:
:el-prefix:`sensor/` |-| **clock** (*)
+30 -92
View File
@@ -52,9 +52,9 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`fitaabb<compiler-fitaabb>` | :ref:`eulerseq<compiler-eulerseq>` | :ref:`meshdir<compiler-meshdir>` | :ref:`texturedir<compiler-texturedir>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`discardvisual<compiler-discardvisual>` | :ref:`convexhull<compiler-convexhull>` | :ref:`usethread<compiler-usethread>` | :ref:`fusestatic<compiler-fusestatic>` | |
| | | | :ref:`discardvisual<compiler-discardvisual>` | :ref:`usethread<compiler-usethread>` | :ref:`fusestatic<compiler-fusestatic>` | :ref:`inertiafromgeom<compiler-inertiafromgeom>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`inertiafromgeom<compiler-inertiafromgeom>` | :ref:`inertiagrouprange<compiler-inertiagrouprange>` | :ref:`assetdir<compiler-assetdir>` | :ref:`alignfree<compiler-alignfree>` | |
| | | | :ref:`inertiagrouprange<compiler-inertiagrouprange>` | :ref:`assetdir<compiler-assetdir>` | :ref:`alignfree<compiler-alignfree>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| compiler |br| |_| |L| | | .. table:: |
@@ -178,66 +178,10 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| material |br| |_2| |L| | | .. table:: |
| :ref:`rgb | ? | :class: mjcf-attributes |
| <material-rgb>` | | |
| :ref:`layer | \* | :class: mjcf-attributes |
| <material-layer>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texture<material-rgb-texture>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| material |br| |_2| |L| | | .. table:: |
| :ref:`occlusion | ? | :class: mjcf-attributes |
| <material-occlusion>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texture<material-occlusion-texture>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| material |br| |_2| |L| | | .. table:: |
| :ref:`roughness | ? | :class: mjcf-attributes |
| <material-roughness>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texture<material-roughness-texture>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| material |br| |_2| |L| | | .. table:: |
| :ref:`metallic | ? | :class: mjcf-attributes |
| <material-metallic>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texture<material-metallic-texture>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| material |br| |_2| |L| | | .. table:: |
| :ref:`normal | ? | :class: mjcf-attributes |
| <material-normal>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texture<material-normal-texture>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| material |br| |_2| |L| | | .. table:: |
| :ref:`opacity | ? | :class: mjcf-attributes |
| <material-opacity>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texture<material-opacity-texture>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| material |br| |_2| |L| | | .. table:: |
| :ref:`emissive | ? | :class: mjcf-attributes |
| <material-emissive>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texture<material-emissive-texture>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| material |br| |_2| |L| | | .. table:: |
| :ref:`rgba | ? | :class: mjcf-attributes |
| <material-rgba>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texture<material-rgba-texture>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| material |br| |_2| |L| | | .. table:: |
| :ref:`orm | ? | :class: mjcf-attributes |
| <material-orm>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texture<material-orm-texture>` | | | | |
| | | | :ref:`texture<material-layer-texture>` | :ref:`role<material-layer-role>` | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| asset |br| |_| |L| | | .. table:: |
@@ -396,9 +340,7 @@
| :ref:`composite | \* | :class: mjcf-attributes |
| <body-composite>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`prefix<body-composite-prefix>` | :ref:`type<body-composite-type>` | :ref:`count<body-composite-count>` | :ref:`spacing<body-composite-spacing>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`offset<body-composite-offset>` | :ref:`flatinertia<body-composite-flatinertia>` | :ref:`solrefsmooth<body-composite-solrefsmooth>` | :ref:`solimpsmooth<body-composite-solimpsmooth>` | |
| | | | :ref:`prefix<body-composite-prefix>` | :ref:`type<body-composite-type>` | :ref:`count<body-composite-count>` | :ref:`offset<body-composite-offset>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`vertex<body-composite-vertex>` | :ref:`initial<body-composite-initial>` | :ref:`curve<body-composite-curve>` | :ref:`size<body-composite-size>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
@@ -419,21 +361,6 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| composite |br| |_2| |L| | | .. table:: |
| :ref:`tendon | \* | :class: mjcf-attributes |
| <composite-tendon>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`kind<composite-tendon-kind>` | :ref:`group<composite-tendon-group>` | :ref:`stiffness<composite-tendon-stiffness>` | :ref:`damping<composite-tendon-damping>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`solreffix<composite-tendon-solreffix>` | :ref:`solimpfix<composite-tendon-solimpfix>` | :ref:`limited<composite-tendon-limited>` | :ref:`range<composite-tendon-range>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`margin<composite-tendon-margin>` | :ref:`solreflimit<composite-tendon-solreflimit>` | :ref:`solimplimit<composite-tendon-solimplimit>` | :ref:`frictionloss<composite-tendon-frictionloss>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`solreffriction<composite-tendon-solreffriction>` | :ref:`solimpfriction<composite-tendon-solimpfriction>` | :ref:`material<composite-tendon-material>` | :ref:`rgba<composite-tendon-rgba>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`width<composite-tendon-width>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| composite |br| |_2| |L| | | .. table:: |
| :ref:`skin | ? | :class: mjcf-attributes |
| <composite-skin>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
@@ -465,13 +392,6 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| composite |br| |_2| |L| | | .. table:: |
| :ref:`pin | \* | :class: mjcf-attributes |
| <composite-pin>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`coord<composite-pin-coord>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| composite |br| |_2| |L| | | .. table:: |
| :ref:`plugin | \* | :class: mjcf-attributes |
| <composite-plugin>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
@@ -491,15 +411,17 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`name<body-flexcomp-name>` | :ref:`type<body-flexcomp-type>` | :ref:`group<body-flexcomp-group>` | :ref:`dim<body-flexcomp-dim>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`count<body-flexcomp-count>` | :ref:`spacing<body-flexcomp-spacing>` | :ref:`radius<body-flexcomp-radius>` | :ref:`rigid<body-flexcomp-rigid>` | |
| | | | :ref:`dof<body-flexcomp-dof>` | :ref:`count<body-flexcomp-count>` | :ref:`spacing<body-flexcomp-spacing>` | :ref:`radius<body-flexcomp-radius>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`mass<body-flexcomp-mass>` | :ref:`inertiabox<body-flexcomp-inertiabox>` | :ref:`scale<body-flexcomp-scale>` | :ref:`file<body-flexcomp-file>` | |
| | | | :ref:`rigid<body-flexcomp-rigid>` | :ref:`mass<body-flexcomp-mass>` | :ref:`inertiabox<body-flexcomp-inertiabox>` | :ref:`scale<body-flexcomp-scale>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`point<body-flexcomp-point>` | :ref:`element<body-flexcomp-element>` | :ref:`texcoord<body-flexcomp-texcoord>` | :ref:`material<body-flexcomp-material>` | |
| | | | :ref:`file<body-flexcomp-file>` | :ref:`point<body-flexcomp-point>` | :ref:`element<body-flexcomp-element>` | :ref:`texcoord<body-flexcomp-texcoord>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`rgba<body-flexcomp-rgba>` | :ref:`flatskin<body-flexcomp-flatskin>` | :ref:`pos<body-flexcomp-pos>` | :ref:`quat<body-flexcomp-quat>` | |
| | | | :ref:`material<body-flexcomp-material>` | :ref:`rgba<body-flexcomp-rgba>` | :ref:`flatskin<body-flexcomp-flatskin>` | :ref:`pos<body-flexcomp-pos>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`axisangle<body-flexcomp-axisangle>` | :ref:`xyaxes<body-flexcomp-xyaxes>` | :ref:`zaxis<body-flexcomp-zaxis>` | :ref:`euler<body-flexcomp-euler>` | |
| | | | :ref:`quat<body-flexcomp-quat>` | :ref:`axisangle<body-flexcomp-axisangle>` | :ref:`xyaxes<body-flexcomp-xyaxes>` | :ref:`zaxis<body-flexcomp-zaxis>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`euler<body-flexcomp-euler>` | :ref:`origin<body-flexcomp-origin>` | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| flexcomp |br| |_2| |L| | | .. table:: |
@@ -563,7 +485,9 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`material<deformable-flex-material>` | :ref:`rgba<deformable-flex-rgba>` | :ref:`flatskin<deformable-flex-flatskin>` | :ref:`body<deformable-flex-body>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`vertex<deformable-flex-vertex>` | :ref:`element<deformable-flex-element>` | :ref:`texcoord<deformable-flex-texcoord>` | | |
| | | | :ref:`vertex<deformable-flex-vertex>` | :ref:`element<deformable-flex-element>` | :ref:`texcoord<deformable-flex-texcoord>` | :ref:`elemtexcoord<deformable-flex-elemtexcoord>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`node<deformable-flex-node>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| flex |br| |_2| |L| | | .. table:: |
@@ -1285,6 +1209,20 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| sensor |br| |_| |L| | | .. table:: |
| :ref:`e_potential | \* | :class: mjcf-attributes |
| <sensor-e_potential>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`name<sensor-e_potential-name>` | :ref:`cutoff<sensor-e_potential-cutoff>` | :ref:`noise<sensor-e_potential-noise>` | :ref:`user<sensor-e_potential-user>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| sensor |br| |_| |L| | | .. table:: |
| :ref:`e_kinetic | \* | :class: mjcf-attributes |
| <sensor-e_kinetic>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`name<sensor-e_kinetic-name>` | :ref:`cutoff<sensor-e_kinetic-cutoff>` | :ref:`noise<sensor-e_kinetic-noise>` | :ref:`user<sensor-e_kinetic-user>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| sensor |br| |_| |L| | | .. table:: |
| :ref:`clock | \* | :class: mjcf-attributes |
| <sensor-clock>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+223 -20
View File
@@ -5,19 +5,206 @@ Changelog
Upcoming version (not yet released)
-----------------------------------
General
^^^^^^^
- The global compiler flag ``exactmeshinertia`` has been removed and replaced with the mesh-specific
:ref:`inertia<asset-mesh-inertia>` attribute.
.. admonition:: Breaking API changes
:class: attention
MJX
^^^
- Added ``apply_ft``, ``jac``, and ``xfrc_accumulate`` as public functions.
- Added ``TOUCH`` sensor.
- The default value of the flag for toggling :ref:`internal flex contacts<flex-contact-internal>` was changed from
"true" to "false". This feature has proven to be counterintuitive for users.
Bug fixes
^^^^^^^^^
- Fixed a bug in slider-crank ref:`transmission<geTransmission>`. The bug was introduced in 3.0.0.
- :ref:`mj_jacDot` was missing a term that accounts for the motion of the point with respect to
which the Jacobian is computed, now fixed.
- Fixed a bug that caused the parent frame of elements in the child worldbody to be incorrectly set when attaching an
mjSpec to a frame or a site.
Version 3.3.0 (Feb 26, 2025)
----------------------------
Feature promotion
^^^^^^^^^^^^^^^^^
.. youtube:: qJFbx-FR7Bc
:aspect: 16:7
:align: right
:width: 240px
1. Introduced a new kind of **fast deformable body**, activated by setting :ref:`flexcomp/dof<body-flexcomp-dof>` to
"trilinear". This type of :ref:`deformable<CDeformable>` flex object has the same collision geometry as a regular
flex, but has far fewer degrees of freedom. Instead of 3 dofs per vertex, only the corners of the bounding box are
free to move, with the positions of the interior vertices computed with trilinear interpolation of the 8 corners, for
a total of 24 dofs for the entire flex object (or less, if some of the corners are pinned). This limits the types of
deformation achievable by the flex, but allows for much faster simulation. For example, see the video on the right
comparing `full <https://github.com/google-deepmind/mujoco/blob/main/model/flex/gripper.xml>`__ and `trilinear
<https://github.com/google-deepmind/mujoco/blob/main/model/flex/gripper_trilinear.xml>`__ flexes for modeling
deformable gripper pads.
.. image:: images/computation/ccd_light.gif
:width: 20%
:align: right
:class: only-light
.. image:: images/computation/ccd_dark.gif
:width: 20%
:align: right
:class: only-dark
2. The native convex collision detection pipeline introduced in 3.2.3 and enabled by the
:ref:`nativeccd<option-flag-nativeccd>` flag, is now the default. See the section on
:ref:`Convex Collision Detection<coCCD>` for more details.
**Migration:** If the new pipeline breaks your workflow, set :ref:`nativeccd<option-flag-nativeccd>` to "disable".
General
^^^^^^^
3. Add support for custom plots in the MuJoCo viewer by exposing a ``viewport`` property, a ``set_figures`` method,
and a ``clear_figures`` method.
4. Separate collision and deformation meshes for :ref:`flex<deformable-flex>`. This enables a fixed cost for the soft
body computations, while preserving the fidelity of high-resolution collisions.
5. Added :ref:`potential<sensor-e_potential>` and :ref:`kinetic<sensor-e_kinetic>` energy sensors.
6. Improved shadow rendering in the native renderer.
7. Moved ``introspect`` to ``python/introspect``.
.. admonition:: Breaking API changes
:class: attention
8. As mentioned above, the native convex collision detection pipeline is now the default, which may break some
workflows. In this case, set :ref:`nativeccd<option-flag-nativeccd>` to "disable" to restore the old behavior.
9. Added :ref:`mjs_setDeepCopy` API function. When the deep copy flag is 0, attaching a model will not copy it to the
parent, so the original references to the child can be used to modify the parent after attachment. The default
behavior is to perform such a shallow copy. The old behavior of creating a deep copy of the child model while
attaching can be restored by setting the deep copy flag to 1.
10. Changes to inertia inference from meshes:
Previously, in order to specify that the mass lies on the surface, :ref:`geom/shellinertia<body-geom-shellinertia>`
could be used for any geom type. Now this attribute is ignored if the geom is a mesh; instead, inertia inference
for meshes is specified in the asset, using the :ref:`asset/mesh/inertia<asset-mesh-inertia>` attribute.
Previously, if the volumetric inertia computation failed (for example due to a very flat mesh), the compiler
would silently fall back to surface inertia computation. Now, the compiler will throw an informative error.
11. Removed the composite type ``grid``. Users should instead use :ref:`flexcomp<body-flexcomp>`.
12. Removed the ``particle`` composite type. It is recommended to use the more generic :ref:`replicate<replicate>`
instead, see for example `this model
<https://github.com/google-deepmind/mujoco/blob/main/model/replicate/particle.xml>`__.
MJX
^^^
13. Added support for spatial tendons with internal sphere and cylinder wrapping.
14. Fix a bug with box-box collisions :github:issue:`2356`.
Python bindings
^^^^^^^^^^^^^^^
15. Added a pedagogical colab notebook for ``mujoco.rollout``, a Python module for multithreaded simulation rollouts.
It is available here |rollout_colab|.
|br| Contribution by :github:user:`aftersomemath`.
.. |rollout_colab| image:: https://colab.research.google.com/assets/colab-badge.svg
:target: https://colab.research.google.com/github/google-deepmind/mujoco/blob/main/python/rollout.ipynb
Version 3.2.7 (Jan 14, 2025)
----------------------------
Python bindings
^^^^^^^^^^^^^^^
1. :ref:`rollout<PyRollout>` now features native multi-threading. If a sequence of ``MjData`` instances
of length ``nthread`` is passed in, ``rollout`` will automatically create a thread pool and parallelize
the computation. The thread pool can be reused across calls, but then the function cannot be called simultaneously
from multiple threads. To run multiple threaded rollouts simultaneously, use the new class ``Rollout`` which
encapsulates the thread pool. Contribution by :github:user:`aftersomemath`.
2. Fix global namespace pollution when using ``mjpython`` (:github:issue:`2265`).
General
^^^^^^^
.. admonition:: Breaking API changes (minor)
:class: attention
3. The field ``mjData.qLDiagSqrtInv`` has been removed. This field is only required for the dual solvers. It is now
computed as-needed rather than unconditionally. Relatedly, added the corresponding argument to :ref:`mj_solveM2`.
4. Reduced the memory footprint of the PGS solver's :ref:`A matrix<soDual>`. This was the last remaining dense-memory
allocation in MuJoCo, allowing for a significant reduction of the :ref:`dynamic memory allocation heuristic<CSize>`.
Bug fixes
^^^^^^^^^
5. Fixed a bug in the box-sphere collider, depth was incorrect for deep penetrations (:github:issue:`2206`).
6. Fixed a bug in :ref:`mj_mulM2` and added a test.
Version 3.2.6 (Dec 2, 2024)
---------------------------
General
^^^^^^^
1. Removed rope and loop from :ref:`composite<body-composite>`. The user is encouraged to instead use the :at:`cable`
plugin or :ref:`flexcomp<body-flexcomp>`, respectively.
MJX
^^^
2. Added muscle actuators.
Python bindings
^^^^^^^^^^^^^^^
3. Provide prebuilt wheels for Python 3.13.
4. Added ``bind`` method and removed id attribute from :ref:`mjSpec` objects. Using ids is error prone in scenarios of
repeated attachment and detachment. Python users are encouraged to use names for unique identification of model
elements.
5. :ref:`rollout<PyRollout>` can now accept sequences of MjModel of length ``nroll``. Also removed the ``nroll``
argument because its value can always be inferred.
Bug fixes
^^^^^^^^^
6. Fixed :github:issue:`2212`, type error in ``mjx.get_data``.
7. Fixed bug introduced in 3.2.0 in handling of :ref:`texrepeat<asset-material-texrepeat>` attribute, was mistakenly
cast from ``float`` to ``int``, (fixed :github:issue:`2223`).
Version 3.2.5 (Nov 4, 2024)
---------------------------
Feature promotion
^^^^^^^^^^^^^^^^^
1. The :doc:`Model Editing<programming/modeledit>` framework afforded by :ref:`mjSpec`, introduced in 3.2.0 as an
in-development feature, is now stable and recommended for general use.
2. The native convex collision detection pipeline introduced in 3.2.3 and enabled by the
:ref:`nativeccd<option-flag-nativeccd>` flag, is not yet the default but is already recommended for general use.
Please try it when encountering collision-related problems and report any issues you encounter.
General
^^^^^^^
3. The global compiler flag ``exactmeshinertia`` has been removed and replaced with the mesh-specific
:ref:`inertia<asset-mesh-inertia>` attribute.
4. The not-useful ``convexhull`` compiler option (to disable computation of mesh convex hulls) has been removed.
5. Removed the deprecated ``mju_rotVecMat``, ``mju_rotVecMatT`` and ``mjv_makeConnector`` functions.
6. Sorting now uses a faster, native sort function (fixes :github:issue:`1638`).
7. The PBR texture layers introduced in 3.2.1 were refactored from separate sub-elements to a single
:ref:`layer<material-layer>` sub-element.
8. The composite types box, cylinder, and sphere have been removed. Users should instead use the equivalent types
available in :ref:`flexcomp<body-flexcomp>`.
MJX
^^^
9. Added ``apply_ft``, ``jac``, and ``xfrc_accumulate`` as public functions.
10. Added ``TOUCH`` sensor.
11. Added support for ``eq_active``. Fixes :github:issue:`2173`.
12. Added ray intersection with ellipsoid.
Bug fixes
^^^^^^^^^
13. Fixed several bugs related to connect and weld constraints with site semantics (fixes :github:issue:`2179`, reported
by :github:user:`yinfanyi`). The introduction of site specification to connects and welds in 3.2.3 conditionally
changed the semantics of `mjData.eq_obj1id` and `mjData.eq_obj2id`, but these changes were not properly propagated
in several places leading to incorrect computations of constraint inertia, readings of affected force/torque sensors
and runtime enabling/disabling of such constraints.
14. Fixed a bug in slider-crank :ref:`transmission<geTransmission>`. The bug was introduced in 3.0.0.
15. Fixed a bug in flex texture coordinates that prevented the correct allocation of textures in mjModel.
Documentation
^^^^^^^^^^^^^
16. Function headers in the :doc:`API reference <../APIreference/APIfunctions>` now link to their source definitions
in GitHub.
Version 3.2.4 (Oct 15, 2024)
----------------------------
@@ -26,13 +213,14 @@ General
^^^^^^^
.. youtube:: e8lUuykQPGs
:aspect: 16:7
:align: right
:width: 240px
1. The Newton solver no longer requires ``nv*nv`` memory allocation, allowing for much larger models. See e.g.,
`100_humanoids.xml <https://github.com/google-deepmind/mujoco/blob/main/model/humanoid/100_humanoids.xml>`__.
Two quadratic-memory allocations still remain to be fully sparsified: ``mjData.actuator_moment`` and the matrices used
by the PGS solver.
Two quadratic-memory allocations still remain to be fully sparsified: ``mjData.actuator_moment`` and the matrices
used by the PGS solver.
2. Removed the :at:`solid` and :at:`membrane` plugins and moved the associated computations into the engine. See `3D
example model <https://github.com/google-deepmind/mujoco/blob/main/model/flex/floppy.xml>`__ and `2D example model
<https://github.com/google-deepmind/mujoco/blob/main/model/flex/trampoline.xml>`__ for examples of flex objects
@@ -81,10 +269,11 @@ General
4. Added the :ref:`nativeccd<option-flag-nativeccd>` flag. When this flag is enabled, general convex collision
detection is handled with a new native code path, rather than `libccd <https://github.com/danfis/libccd>`__.
This feature is in early stages of testing, but users who've experienced issues related to collsion detection are
This feature is in early stages of testing, but users who've experienced issues related to collision detection are
welcome to experiment with it and report any issues.
.. youtube:: kcM_oauk3ZA
:aspect: 16:7
:align: right
:width: 240px
@@ -167,7 +356,7 @@ General
2. Added a new :ref:`autoreset<option-flag-autoreset>` flag to disable automatic reset when NaNs or infinities are
detected.
3. Added sub-elements to the MJCF :ref:`material<asset-material>` element, to allow specification of multiple textures
for rendering (e.g., :ref:`occlusion-roughness-metallic<material-orm>`). Note that the MuJoCo renderer doesn't
for rendering (e.g., ``occlusion, roughness, metallic``). Note that the MuJoCo renderer doesn't
support these new features, and they are made available for use with external renderers.
4. Sorting (``mjQUICKSORT``) now calls ``std::sort`` when building with C++ (:github:issue:`1638`).
@@ -214,12 +403,12 @@ General
3. Calls to :ref:`mj_defaultVFS` may allocate memory inside VFS, and the corresponding
:ref:`mj_deleteVFS` must be called to deallocate any internal allocated memory.
4. Deprecated :ref:`mju_rotVecMat` and :ref:`mju_rotVecMatT` in favor of :ref:`mju_mulMatVec3` and
4. Deprecated ``mju_rotVecMat`` and ``mju_rotVecMatT`` in favor of :ref:`mju_mulMatVec3` and
:ref:`mju_mulMatTVec3`. These function names and argument order are more consistent with the rest of the API.
The older functions have been removed from the Python bindings and will be removed from the C API in the next
release.
5. Removed the ``actuator_actdim`` callback from actuator plugins. They now have the ``actdim`` attribute, which
must be used with actuators that write state to the ``act`` array. This fixed a crash which happend when
must be used with actuators that write state to the ``act`` array. This fixed a crash which happened when
keyframes were used in a model with stateful actuator plugins. The PID plugin will give an error when the wrong
value of actdim is provided.
@@ -229,6 +418,7 @@ General
speed and memory footprint.
.. youtube:: ZXBTEIDWHhs
:aspect: 16:7
:align: right
:width: 240px
@@ -317,6 +507,7 @@ General
^^^^^^^
.. youtube:: 5k0_wsIRAFc
:aspect: 16:7
:align: right
:width: 240px
@@ -421,6 +612,7 @@ Python bindings
^^^^^^^^^^^^^^^
.. youtube:: xHDS0n5DpqM
:aspect: 16:7
:align: right
:width: 240px
@@ -480,7 +672,7 @@ General
1. Improved the :ref:`discardvisual<compiler-discardvisual>` compiler flag, which now discards all visual-only assets.
See :ref:`discardvisual<compiler-discardvisual>` for details.
2. Removed the :ref:`timer<mjtTimer>` for midphase colllision detection, it is now folded in with the narrowphase
timer. This is because timing the two phases seperately required fine-grained timers inside the collision
timer. This is because timing the two phases separately required fine-grained timers inside the collision
functions; these functions are so small and fast that the timer itself was incurring a measurable cost.
3. Added the flag :ref:`bvactive<visual-global-bvactive>` to ``visual/global``, allowing users to turn off
visualisation of active bounding volumes (the red/green boxes in this :ref:`this changelog item<midphase>`). For
@@ -585,6 +777,7 @@ General
``qfrc_{spring, damper, gravcomp, fluid}``. The sum of these vectors equals ``qfrc_passive``.
.. youtube:: H9qG9Zf2W44
:aspect: 16:7
:align: right
:width: 240px
@@ -648,6 +841,7 @@ Documentation
^^^^^^^^^^^^^
.. youtube:: cE3s_IfO4g4
:aspect: 16:7
:align: right
:width: 240px
@@ -679,6 +873,7 @@ New features
:target: https://colab.research.google.com/github/google-deepmind/mujoco/blob/main/mjx/tutorial.ipynb
.. youtube:: QewlEqIZi1o
:aspect: 16:7
:align: right
:width: 240px
@@ -689,6 +884,7 @@ New features
gradient at query points. See the :ref:`documentation<exWriting>` for more details.
.. youtube:: ra2bTiZHGlw
:aspect: 16:7
:align: right
:width: 240px
@@ -709,6 +905,7 @@ New features
this functionality to be unified in the future.
.. youtube:: Vc1tq0fFvQA
:aspect: 16:7
:align: right
:width: 240px
@@ -837,6 +1034,7 @@ Simulate
^^^^^^^^
.. youtube:: YSvWn_poqWs
:aspect: 16:7
:align: right
:width: 240px
@@ -851,6 +1049,7 @@ Documentation
^^^^^^^^^^^^^
.. youtube:: nljr0X79vI0
:aspect: 16:7
:align: right
:width: 240px
@@ -884,8 +1083,8 @@ General
used to determine the type of the asset file without resorting to pulling the type from the file extension.
#. Added analytic derivatives for quaternion :ref:`subtraction<mjd_subQuat>` and :ref:`integration<mjd_quatIntegrate>`
(rotation with an angular velocity). Derivatives are in the 3D tangent space.
#. Added :ref:`mjv_connector` which has identical functionality to :ref:`mjv_makeConnector`, but with more convenient
"from-to" argument parametrization. :ref:`mjv_makeConnector` is now deprecated.
#. Added :ref:`mjv_connector` which has identical functionality to ``mjv_makeConnector``, but with more convenient
"from-to" argument parametrization. ``mjv_makeConnector`` is now deprecated.
#. Bumped oldest supported MacOS from version 10.12 to 11. MacOS 11 is the oldest version still maintained by Apple.
Python bindings
@@ -949,11 +1148,12 @@ Simulate
^^^^^^^^
.. youtube:: mXVPbppGk5I
:aspect: 16:7
:align: right
:width: 240px
6. Added Visualization tab to simulate UI, corresponding to elements of the :ref:`visual<visual>` MJCF element. After
modifying values in the GUI, a saved XML will contain the new values. The modifyable members of
modifying values in the GUI, a saved XML will contain the new values. The modifiable members of
:ref:`mjStatistic` (:ref:`extent<statistic-extent>`, :ref:`meansize<statistic-meansize>` and
:ref:`center<statistic-center>`) are computed by the compiler and therefore do not have defaults. In order for these
attributes to appear in the saved XML, a value must be specified in the loaded XML.
@@ -1072,7 +1272,7 @@ Python bindings
passive viewer now also requires an explicit call to ``sync`` on its handle to pick up any update to the physics
state. This is to avoid race conditions that can result in visual artifacts. See
:ref:`documentation<PyViewerPassive>` for details.
#. The ``viewer.launch_repl`` function has been removed since its functionality is superceded by ``launch_passive``.
#. The ``viewer.launch_repl`` function has been removed since its functionality is superseded by ``launch_passive``.
#. Added a small number of missing struct fields discovered through the new ``introspect`` metadata.
Bug fixes
@@ -1322,6 +1522,7 @@ General
See the :ref:`Memory allocation <CSize>` section for details.
.. youtube:: RHnXD6uO3Mg
:aspect: 16:7
:align: right
:height: 150px
@@ -1409,6 +1610,7 @@ General
^^^^^^^
.. youtube:: BcHZ5BFeTmU
:aspect: 16:7
:align: right
:height: 150px
@@ -1425,6 +1627,7 @@ General
different uses of new weld attributes.
.. youtube:: s-0JHanqV1A
:aspect: 16:7
:align: right
:height: 150px
+2 -2
View File
@@ -135,7 +135,7 @@ also disables the inertia-based model for the parent body. The
Elements of the model are a generalization of :cite:t:`andersen2005b` to 3 dimensions.
The force :math:`\mathbf{f}_{\text{ellipsoid}}` and torque
:math:`\mathbf{g}_{\text{ellipsoid}}` exerted by the fluid onto the solid are
the sum of of the terms
the sum of the terms
.. math::
\begin{align*}
@@ -308,7 +308,7 @@ We present the following result.
:math:`\mathcal{E}` with :math:`\Pi_{\mathbf{u}}` (denoted :math:`\mathcal{E}^{\cap}_{\mathbf{u}}`).
An important property of :math:`\mathcal{E}^{\mathrm{proj}}_{\mathbf{u}}` is that :math:`\mathbf{u}` is tangent
tangent to the ellipsoid :math:`\mathcal{E}` at every point on :math:`\mathcal{E}^{\mathrm{proj}}_{\mathbf{u}}`.
to the ellipsoid :math:`\mathcal{E}` at every point on :math:`\mathcal{E}^{\mathrm{proj}}_{\mathbf{u}}`.
We can regard :math:`\mathcal{E}` as the image of the unit sphere :math:`\mathcal{S}` under a stretching
transformation :math:`T = \mathrm{diag}(r_x, r_y, r_z)`. Furthermore, if :math:`\mathbf{\tilde{u}}` is a vector
+135 -42
View File
@@ -241,7 +241,7 @@ The computation of the constraint force is the hard part and will be described l
description of the general framework by summarizing how the above quantities up to the constraint Jacobian are computed.
- The applied force :math:`\tau` includes :ref:`passive <gePassive>` forces from spring-dampers and fluid dynamics,
:ref:`actuation <geActuation>` forces, and additonal forces specified by the user.
:ref:`actuation <geActuation>` forces, and additional forces specified by the user.
- The bias force :math:`c` includes Coriolis, centrifugal and gravitational forces. Their sum is computed using the
Recursive Newton-Euler (RNE) algorithm with acceleration set to 0.
- The joint-space inertia matrix :math:`M` is computed using the Composite Rigid-Body (CRB) algorithm. This matrix is
@@ -414,7 +414,7 @@ with MuJoCo's operation as long as such user forces depend only on position and
MuJoCo can compute three types of passive forces:
- Spring-dampers in joints and tendons. See the following attribues for details.
- Spring-dampers in joints and tendons. See the following attributes for details.
|br| **Joints:**
:ref:`stiffness<body-joint-stiffness>`, :ref:`springref<body-joint-springref>`,
:ref:`damping<body-joint-damping>`, :ref:`springdamper<body-joint-springdamper>`.
@@ -484,24 +484,27 @@ acceleration as a function of velocity: :math:`a_t = a(v_t)`, the velocity updat
This is a non-linear equation in the unknown vector :math:`v_{t+h}` and can be solved numerically at each time step
using a first-order expansion of :math:`a(v_{t+h})` around :math:`v_t`. Recall that the forward dynamics are
.. math:: a(v) = M^{-1} \big(\tau(v) - c(v) + J^T f(v)\big)
.. math::
:label: eq_forward
a(v) = M^{-1} \big(\tau(v) - c(v) + J^T f(v)\big)
Thus we define the derivative
.. math::
\begin{aligned}
{\partial a(v) \over \partial v} &= M^{-1} D \\
D &\equiv {\partial \over \partial v} \Big(\tau(v) - c (v) + J^T f(v)\Big)
\end{aligned}
\begin{aligned}
{\partial a(v) \over \partial v} &= M^{-1} D \\
D &\equiv {\partial \over \partial v} \Big(\tau(v) - c (v) + J^T f(v)\Big)
\end{aligned}
The velocity update corresponding to Newton's method is as follows. First, we expand the right hand side to first order
.. math::
\begin{aligned}
v_{t+h} &= v_t + h a(v_{t+h}) \\
&\approx v_t + h \big( a(v_t) + {\partial a(v) \over \partial v} \cdot (v_{t+h}-v_t) \big) \\
&= v_t + h a(v_t) + h M^{-1} D \cdot (v_{t+h}-v_t)
\end{aligned}
\begin{aligned}
v_{t+h} &= v_t + h a(v_{t+h}) \\
&\approx v_t + h \big( a(v_t) + {\partial a(v) \over \partial v} \cdot (v_{t+h}-v_t) \big) \\
&= v_t + h a(v_t) + h M^{-1} D \cdot (v_{t+h}-v_t)
\end{aligned}
Premultiplying by :math:`M` and rearranging yields
@@ -512,16 +515,27 @@ Solving for :math:`v_{t+h}`, we obtain the implicit-in-velocity update
.. math::
:label: eq_implicit_update
v_{t+h} = v_t + h (M-h D)^{-1} M a(v_t)
\begin{aligned}
v_{t+h} &= v_t + h \widehat{M}^{-1} M a(v_t) \\
\widehat{M} &\equiv M-h D
\end{aligned}
.. _geIntegrators:
Integrators
^^^^^^^^^^^
MuJoCo supports four integrators: three single-step integrators and the multi-step 4th order Runge-Kutta integrator.
All three single-step integrators in MuJoCo use the update :eq:`eq_implicit_update`, with different definitions of the
:math:`D` matrix, which is always computed analytically.
Semi-implicit with implicit joint damping (``Euler``)
For this method, :math:`D` only includes derivatives of joint damping. Note that in this case :math:`D` is diagonal
and :math:`M-h D` is symmetric, so Cholesky decomposition can be used. If the model has no joint damping or the
and :math:`\widehat{M}` is symmetric, so :math:`L^TL` decomposition (a variant of Cholesky) can be used. This
factorization is stored ``mjData.qLD``. If the model has no joint damping or the
:ref:`eulerdamp<option-flag-eulerdamp>` disable-flag is set, implicit damping is disabled and the semi-implicit
update :eq:`eq_semimplicit` is used, rather than :eq:`eq_implicit_update`.
update :eq:`eq_semimplicit` is used, rather than :eq:`eq_implicit_update`, avoiding the additional factorization of
:math:`\widehat{M}` (*additional* because :math:`M` is already factorized for the acceleration update
:eq:`eq_forward`).
Implicit-in-velocity (``implicit``)
For this method, :math:`D` includes derivatives of all forces except the constraint forces :math:`J^T f(v)`. These
@@ -530,8 +544,8 @@ Implicit-in-velocity (``implicit``)
future version. Additionally, we restrict :math:`D` to have the same sparsity pattern as :math:`M`, for computational
efficiency. This restriction will exclude damping in tendons which connect bodies that are on different branches of
the kinematic tree. Since :math:`D` is not symmetric, we cannot use Cholesky factorization, but because :math:`D` and
:math:`M` have the same sparsity pattern corresponding to the topology of the kinematic tree, reverse-order LU
factorization of :math:`M-h D` is `guaranteed to have no fill-in
:math:`M` have the same sparsity pattern corresponding to the topology of the kinematic tree, reverse-order
:math:`LU` factorization of :math:`\widehat{M}` is guaranteed to have `no fill-in
<https://link.springer.com/book/10.1007/978-1-4899-7560-7>`_. This factorization is stored ``mjData.qLU``.
Fast implicit-in-velocity (``implicitfast``)
@@ -541,7 +555,7 @@ Fast implicit-in-velocity (``implicitfast``)
Second, these forces change rapidly only at high rotational velocities of complex pendula and spinning bodies,
scenarios which are not common and already well-handled by the Runge-Kutta integrator (see below). Because the RNE
derivatives are also the main source of asymmetry of :math:`D`, by dropping them and symmetrizing, we can use the
faster Cholesky rather than LU decomposition.
faster :math:`L^TL` rather than :math:`LU` decomposition.
4th-order Runge-Kutta (``RK4``)
One advantage of our continuous-time formulation is that we can use higher order integrators such as Runge-Kutta or
@@ -576,8 +590,8 @@ Fast implicit-in-velocity (``implicitfast``)
increased stability, and is therefore a strict improvement. It is the recommended integrator for most models.
**implicit**:
The benefit over ``implicitfast`` is the implicit integration of Coriolis and centripetal forces, including
gyroscopic forces. The most common case where integrating such forces implicitly leads to noticable improvement is
when free objects with assymetric inertia are spinning quickly. `gyroscopic.xml <../_static/gyroscopic.xml>`__
gyroscopic forces. The most common case where integrating such forces implicitly leads to noticeable improvement is
when free objects with asymmetric inertia are spinning quickly. `gyroscopic.xml <../_static/gyroscopic.xml>`__
shows an ellipsoid rolling on an inclined plane which quickly diverges with ``implicitfast`` but is stable with
``implicit``.
**RK4**:
@@ -646,7 +660,7 @@ Control: ``ctrl``
generalized forces directly (stateless actuators), or affect the actuator activations in ``mjData.act``, which then
produce forces.
Auxillary Controls: ``qfrc_applied`` and ``xfrc_applied``
Auxiliary Controls: ``qfrc_applied`` and ``xfrc_applied``
| ``mjData.qfrc_applied`` are directly applied generalized forces.
| ``mjData.xfrc_applied`` are Cartesian wrenches applied to the CoM of individual bodies. This field is used for
example, by the :ref:`native viewer<saSimulate>` to apply mouse perturbations.
@@ -1525,27 +1539,106 @@ Filtering
Checking
~~~~~~~~
Detailed collision checking, also known as *near-phase* or narrow-phase_ collision detection, is performed by functions
that depend on the geom types in the pair. The table of narrow-phase collision functions can be inspected at the top of
`engine_collision_driver.c <https://github.com/google-deepmind/mujoco/blob/main/src/engine/engine_collision_driver.c>`__
and exposed to users who wish to install their own colliders as :ref:`mjCOLLISIONFUNC`. MuJoCo supports several
primitive geometric shapes: plane, sphere, capsule, cylinder, ellipsoid, and box. It also supports triangulated meshes and
height-fields.
Detailed collision checking is performed by functions that depend on the geom types in the pair. MuJoCo supports several
primitive geometric shapes: plane, sphere, capsule, cylinder, ellipsoid, box. It also supports triangulated meshes and
height fields.
.. _narrow-phase: https://en.wikipedia.org/wiki/Collision_detection#Narrow_phase
We have chosen to limit collision detection to *convex* geoms. All primitive types are convex. Height fields are not
convex but internally they are treated as unions of triangular prisms (using custom collision pruning beyond the filters
described above). Meshes specified by the user can be non-convex, and are rendered as such. For collision purposes
however they are replaced with their convex hulls. Mesh collisions are based on the Minkowski Portal Refinement (MPR)
algorithm as implemented in `libccd <https://github.com/danfis/libccd>`__. It has tolerance and maximum iteration
parameters exposed as ``mjModel.opt.ccd_tolerance`` and ``mjModel.opt.ccd_iterations`` respectively. MPR operates on the
convex hull implicitly, however pre-computing that hull can substantially improve performance for large meshes. The
model compiler does that by default, using the `qhull <http://www.qhull.org/>`__ library.
With the notable exception of :ref:`SDF plugins<exSDF>` (see documentation therein), collision detection is limited to
*convex* geoms. All primitive types are convex. Height-fields are not convex but internally they are treated as a
collection of triangular prisms (using custom collision pruning beyond the filters described above). Meshes specified by
the user can be non-convex, and are rendered as such. For collision purposes however they are replaced with their convex
hulls (visualized with the 'H' key in :ref:`simulate <saSimulate>`), computed by the `qhull <http://www.qhull.org/>`__
library.
.. _coCCD:
Convex collisions
^^^^^^^^^^^^^^^^^
All collisions involving pairs of geoms that do not have an analytic collider (e.g., meshes), are handled by one of two
general-purpose convex collision detection (CCD) pipelines:
native pipeline (default)
The native CCD pipeline ("nativeccd") is implemented natively in MuJoCo, based on the Gilbert-Johnson-Keerthi and
Expanding Polytope algorithms (GJK_ / EPA_). The native pipeline is both faster and more robust than the MPR-based
pipeline.
libccd pipeline (legacy)
This legacy pipeline is based on the libccd_ library, and uses Minkowski Portal Refinement (MPR_). It is activated by
disabling the :ref:`nativeccd<option-flag-nativeccd>` flag.
.. _libccd: https://github.com/danfis/libccd
.. _MPR: https://en.wikipedia.org/wiki/Minkowski_Portal_Refinement
.. _GJK: https://en.wikipedia.org/wiki/Gilbert%E2%80%93Johnson%E2%80%93Keerthi_distance_algorithm
.. _EPA: http://scroll.stanford.edu/courses/cs468-01-fall/Papers/van-den-bergen.pdf
Both pipelines are controlled by a tolerance (in units of distance) and maximum iteration parameters exposed as
``mjOption.ccd_tolerance`` (:ref:`ccd_tolerance<option-ccd_tolerance>`) and ``mjOption.ccd_iterations``
(:ref:`ccd_iterations<option-ccd_iterations>`), respectively.
.. _coMultiCCD:
Multiple contacts
^^^^^^^^^^^^^^^^^
Some colliders can return more than one contact per colliding pair to model line or surface contacts, as when two flat
objects touch. For example the capsule-plane and box-plane colliders can return up to two or four contacts,
respectively. Standard general-purpose convex collision algorithms like MPR and GJK always return a single contact
point, which is problematic for surface contact scenarios (e.g., box-stacking). Both of MuJoCo's CCD pipelines can
return multiple points per contacting pair ("multiccd"). This behavior is controlled by the
:ref:`multiccd<option-flag-multiccd>` flag, but is implemented in different ways with different trade-offs:
libccd pipeline (legacy)
Multiple contact points are found by rotating the two geoms by ±1e-3 radians around the tangential axes and
re-running the collision routine. If a new contact is detected it is added, allowing for up to 4 additional contact
points. This method is effective, but increases the cost of each collision call by a factor of 5.
native pipeline
Native multiccd discovers multiple contacts using a novel analysis of the contacting surfaces at the solution,
avoiding full re-runs of the collision routine, and is thus effectively "free". Note that native multiccd currently
does not support positive contact margins. If one of the two geoms has a positive margin, native multiccd will fall
back to legacy algorithm.
.. _coDistance:
Geom distance
^^^^^^^^^^^^^
.. image:: ../images/computation/ccd_light.gif
:width: 25%
:align: right
:class: only-light
.. image:: ../images/computation/ccd_dark.gif
:width: 25%
:align: right
:class: only-dark
The narrow-phase collision functions described :ref:`above<coChecking>` drive the :ref:`mj_geomDistance` function and
associated :ref:`collision-sensors`. Due to the limitations of MPR, the legacy pipeline will return incorrect values
(top) except at very small distances relative to the geom sizes, and is discouraged for this use case. In
contrast, the GJK-based native pipeline (bottom), computes the correct values at all distances.
Convex decomposition
^^^^^^^^^^^^^^^^^^^^
In order to model a non-convex object other than a height field, the user must decompose it into a union of convex geoms
(which can be primitive shapes or meshes) and attach them to the same body. Open tools like the `CoACD library
<https://github.com/SarahWeiii/CoACD>`__ can be used outside MuJoCo to automate this process. Finally, all built-in
collision functions can be replaced with custom callbacks. This can be used to incorporate a general-purpose "triangle
soup" collision detector for example. However we do not recommend such an approach. Pre-processing the geometry and
representing it as a union of convex geoms takes some work, but it pays off at runtime and yields both faster and more
stable simulation.
(which can be primitive shapes or meshes) and attach them to the same body. A height-field is essentially a shape that
is automatically-decomposed into prisms
Open mesh-decomposition tools like the
`CoACD library <https://github.com/SarahWeiii/CoACD>`__ can be used outside MuJoCo to automate this process. Finally,
all built-in collision functions can be replaced with custom callbacks. This can be used to incorporate a
general-purpose "triangle soup" collision detector for example. However we do not recommend such an approach.
Pre-processing the geometry and representing it as a union of convex geoms takes some work, but it pays off at runtime
and yields both faster and more stable simulation.
The exception to this rule are :ref:`SDF plugins<exSDF>` (see documentation therein), which in
`certain cases <https://github.com/google-deepmind/mujoco/blob/main/plugin/sdf/README.md#gear>`__ can be efficient,
but have other requirements and limitations.
.. _Pipeline:
@@ -1597,14 +1690,14 @@ The stages below compute quantities that depend on the generalized positions ``m
3. Compute the body inertias and joint axes, in global frames centered at the centers of mass of the corresponding
kinematic subtrees: :ref:`mj_comPos`
4. Compute quantities related to :ref:`flex<deformable-flex>` objects: :ref:`mj_flex`
5. Compute the actuator lengths and moment arms: :ref:`mj_tendon`
5. Compute the tendon lengths and moment arms. This includes the computation of minimal-length paths for spatial
tendons: :ref:`mj_tendon`
6. Compute the composite rigid body inertias and joint-space inertia matrix: :ref:`mj_crb`
7. Compute the sparse factorization of the joint-space inertia matrix: :ref:`mj_factorM`
8. Construct the list of active contacts. This includes both broad-phase and near-phase collision detection:
:ref:`mj_collision`
9. Construct the constraint Jacobian and compute the constraint residuals: :ref:`mj_makeConstraint`
10. Compute the tendon lengths and moment arms. This includes the computation of minimal-length paths for spatial
tendons: :ref:`mj_transmission`
10. Compute the actuator lengths and moment arms: :ref:`mj_transmission`
11. Compute the matrices and vectors needed by the constraint solvers: :ref:`mj_projectConstraint`
12. Compute sensor data that only depends on position, and the potential energy if enabled: :ref:`mj_sensorPos`,
:ref:`mj_energyPos`
@@ -1722,7 +1815,7 @@ The top-level function :ref:`mj_inverse` invokes the following sequence of compu
Derivatives
-----------
MuJoCo's entire computational pipline including its constraint solver are analytically differentiable in principle.
MuJoCo's entire computational pipeline including its constraint solver are analytically differentiable in principle.
Writing efficient implementations of these derivatives is a long term goal of the development team. Analytic derivatives
of the smooth dynamics (excluding constraints) with respect to velocity are already computed and enable the two
:ref:`implicit integrators<geIntegration>`.
+4
View File
@@ -159,10 +159,14 @@ pygments_dark_style = 'monokai'
html_static_path = [
'_static',
'css',
'js',
]
html_css_files = [
'theme_overrides.css',
]
html_js_files = [
'linenumbers.js',
]
favicons = [
{
+6 -6
View File
@@ -72,7 +72,7 @@ h4 {
/* Paragraph margins don't apply to table cell contents. */
.rst-content table.docutils td>p {
margin-top: 0px;
margin-top: 0;
}
/* Set padding of in-line highlighted text. */
@@ -200,8 +200,8 @@ table.docutils:not(.mjcf-attributes) > tbody > tr.row-odd {
/* MJCF attributes table. */
table.mjcf-attributes {
border-style: none;
margin-left: 0px;
margin-right: 0px;
margin-left: 0;
margin-right: 0;
width: 100%;
box-shadow: none;
}
@@ -209,7 +209,7 @@ table.mjcf-attributes {
table.mjcf-attributes tbody tr td,
table.mjcf-attributes tbody tr:nth-child(2n-1) td {
border-style: none;
padding: 0px 0px 0px 0px;
padding: 0 0 0 0;
width: 25%;
}
@@ -217,13 +217,13 @@ table.mjcf-attributes tbody tr td p {
margin: 0;
}
div.table-wrapper.mjcf-attributes {
.table-wrapper.mjcf-attributes {
margin: 0.5em;
padding: 0;
}
table td > div.wy-table-responsive {
margin-bottom: 0px;
margin-bottom: 0;
}
/* Remove vertical spacing before/after code blocks. */
+1 -1
View File
@@ -98,7 +98,7 @@ def read(lines: List[str]) -> Dict[str, ApiDefinition]:
if section is not None:
if 'MJAPI FUNCTIONS' in section:
# Stripped functions do not begin with MJAPI, and must be under the
# predefiend section 'MJAPI FUNCTIONS'. This is because the docs don't
# predefined section 'MJAPI FUNCTIONS'. This is because the docs don't
# include this prefix, and so we need to read such functions from the
# reference header.
stripped_functions = True
Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

+118 -66
View File
@@ -169,7 +169,8 @@ struct mjData_ {
int nf; // number of friction constraints
int nl; // number of limit constraints
int nefc; // number of constraints
int nnzJ; // number of non-zeros in constraint Jacobian
int nJ; // number of non-zeros in constraint Jacobian
int nA; // number of non-zeros in constraint inverse inertia matrix
int nisland; // number of detected constraint islands
// global properties
@@ -263,8 +264,8 @@ struct mjData_ {
mjtNum* actuator_length; // actuator lengths (nu x 1)
int* moment_rownnz; // number of non-zeros in actuator_moment row (nu x 1)
int* moment_rowadr; // row start address in colind array (nu x 1)
int* moment_colind; // column indices in sparse Jacobian (nu x nv)
mjtNum* actuator_moment; // actuator moments (nu x nv)
int* moment_colind; // column indices in sparse Jacobian (nJmom x 1)
mjtNum* actuator_moment; // actuator moments (nJmom x 1)
// computed by mj_fwdPosition/mj_crb
mjtNum* crb; // com-based composite inertia and mass (nbody x 10)
@@ -273,7 +274,6 @@ struct mjData_ {
// computed by mj_fwdPosition/mj_factorM
mjtNum* qLD; // L'*D*L factorization of M (sparse) (nM x 1)
mjtNum* qLDiagInv; // 1/diag(D) (nv x 1)
mjtNum* qLDiagSqrtInv; // 1/sqrt(diag(D)) (nv x 1)
// computed by mj_collisionTree
mjtNum* bvh_aabb_dyn; // global bounding box (center, size) (nbvhdynamic x 6)
@@ -312,12 +312,17 @@ struct mjData_ {
int* B_rownnz; // body-dof: non-zeros in each row (nbody x 1)
int* B_rowadr; // body-dof: address of each row in B_colind (nbody x 1)
int* B_colind; // body-dof: column indices of non-zeros (nB x 1)
int* M_rownnz; // inertia: non-zeros in each row (nv x 1)
int* M_rowadr; // inertia: address of each row in M_colind (nv x 1)
int* M_colind; // inertia: column indices of non-zeros (nM x 1)
int* mapM2M; // index mapping from M (legacy) to M (CSR) (nM x 1)
int* C_rownnz; // reduced dof-dof: non-zeros in each row (nv x 1)
int* C_rowadr; // reduced dof-dof: address of each row in C_colind (nv x 1)
int* C_colind; // reduced dof-dof: column indices of non-zeros (nC x 1)
int* mapM2C; // index mapping from M to C (nC x 1)
int* D_rownnz; // dof-dof: non-zeros in each row (nv x 1)
int* D_rowadr; // dof-dof: address of each row in D_colind (nv x 1)
int* D_diag; // dof-dof: index of diagonal element (nv x 1)
int* D_colind; // dof-dof: column indices of non-zeros (nD x 1)
int* mapM2D; // index mapping from M to D (nD x 1)
int* mapD2M; // index mapping from D to M (nM x 1)
@@ -361,13 +366,13 @@ struct mjData_ {
int* efc_J_rownnz; // number of non-zeros in constraint Jacobian row (nefc x 1)
int* efc_J_rowadr; // row start address in colind array (nefc x 1)
int* efc_J_rowsuper; // number of subsequent rows in supernode (nefc x 1)
int* efc_J_colind; // column indices in constraint Jacobian (nnzJ x 1)
int* efc_J_colind; // column indices in constraint Jacobian (nJ x 1)
int* efc_JT_rownnz; // number of non-zeros in constraint Jacobian row T (nv x 1)
int* efc_JT_rowadr; // row start address in colind array T (nv x 1)
int* efc_JT_rowsuper; // number of subsequent rows in supernode T (nv x 1)
int* efc_JT_colind; // column indices in constraint Jacobian T (nnzJ x 1)
mjtNum* efc_J; // constraint Jacobian (nnzJ x 1)
mjtNum* efc_JT; // constraint Jacobian transposed (nnzJ x 1)
int* efc_JT_colind; // column indices in constraint Jacobian T (nJ x 1)
mjtNum* efc_J; // constraint Jacobian (nJ x 1)
mjtNum* efc_JT; // constraint Jacobian transposed (nJ x 1)
mjtNum* efc_pos; // constraint position (equality, contact) (nefc x 1)
mjtNum* efc_margin; // inclusion margin (contact) (nefc x 1)
mjtNum* efc_frictionloss; // frictionloss (friction) (nefc x 1)
@@ -391,8 +396,8 @@ struct mjData_ {
// computed by mj_projectConstraint (PGS solver)
int* efc_AR_rownnz; // number of non-zeros in AR (nefc x 1)
int* efc_AR_rowadr; // row start address in colind array (nefc x 1)
int* efc_AR_colind; // column indices in sparse AR (nefc x nefc)
mjtNum* efc_AR; // J*inv(M)*J' + R (nefc x nefc)
int* efc_AR_colind; // column indices in sparse AR (nA x 1)
mjtNum* efc_AR; // J*inv(M)*J' + R (nA x 1)
//-------------------- arena-allocated: POSITION, VELOCITY dependent
@@ -428,8 +433,9 @@ typedef enum mjtDisableBit_ { // disable default feature bitflags
mjDSBL_MIDPHASE = 1<<13, // mid-phase collision filtering
mjDSBL_EULERDAMP = 1<<14, // implicit integration of joint damping in Euler integrator
mjDSBL_AUTORESET = 1<<15, // automatic reset when numerical issues are detected
mjDSBL_NATIVECCD = 1<<16, // native convex collision detection
mjNDISABLE = 16 // number of disable flags
mjNDISABLE = 17 // number of disable flags
} mjtDisableBit;
typedef enum mjtEnableBit_ { // enable optional feature bitflags
mjENBL_OVERRIDE = 1<<0, // override contact parameters
@@ -439,9 +445,8 @@ typedef enum mjtEnableBit_ { // enable optional feature bitflags
// experimental features:
mjENBL_MULTICCD = 1<<4, // multi-point convex collision detection
mjENBL_ISLAND = 1<<5, // constraint island discovery
mjENBL_NATIVECCD = 1<<6, // native convex collision detection
mjNENABLE = 7 // number of enable flags
mjNENABLE = 6 // number of enable flags
} mjtEnableBit;
typedef enum mjtJoint_ { // type of degree of freedom
mjJNT_FREE = 0, // global position and orientation (quat) (7)
@@ -598,7 +603,9 @@ typedef enum mjtObj_ { // type of MujoCo object
mjNOBJECT, // number of object types
// meta elements, do not appear in mjModel
mjOBJ_FRAME = 100 // frame
mjOBJ_FRAME = 100, // frame
mjOBJ_DEFAULT // default
} mjtObj;
typedef enum mjtConstraint_ { // type of constraint
mjCNSTR_EQUALITY = 0, // equality constraint
@@ -673,6 +680,8 @@ typedef enum mjtSensor_ { // type of sensor
mjSENS_GEOMFROMTO, // segment between two geoms
// global sensors
mjSENS_E_POTENTIAL, // potential energy
mjSENS_E_KINETIC, // kinetic energy
mjSENS_CLOCK, // simulation time
// plugin-controlled sensors
@@ -898,6 +907,7 @@ struct mjModel_ {
int ncam; // number of cameras
int nlight; // number of lights
int nflex; // number of flexes
int nflexnode; // number of dofs in all flexes
int nflexvert; // number of vertices in all flexes
int nflexedge; // number of edges in all flexes
int nflexelem; // number of elements in all flexes
@@ -912,6 +922,9 @@ struct mjModel_ {
int nmeshtexcoord; // number of texcoords in all meshes
int nmeshface; // number of triangular faces in all meshes
int nmeshgraph; // number of ints in mesh auxiliary data
int nmeshpoly; // number of polygons in all meshes
int nmeshpolyvert; // number of vertices in all polygons
int nmeshpolymap; // number of polygons in vertex map
int nskin; // number of skins
int nskinvert; // number of vertices in all skins
int nskintexvert; // number of vertiex with texcoords in all skins
@@ -948,19 +961,20 @@ struct mjModel_ {
int nuser_actuator; // number of mjtNums in actuator_user
int nuser_sensor; // number of mjtNums in sensor_user
int nnames; // number of chars in all names
int nnames_map; // number of slots in the names hash map
int npaths; // number of chars in all paths
// sizes set after mjModel construction (only affect mjData)
// sizes set after mjModel construction
int nnames_map; // number of slots in the names hash map
int nM; // number of non-zeros in sparse inertia matrix
int nB; // number of non-zeros in sparse body-dof matrix
int nC; // number of non-zeros in sparse reduced dof-dof matrix
int nD; // number of non-zeros in sparse dof-dof matrix
int nJmom; // number of non-zeros in sparse actuator_moment matrix
int ntree; // number of kinematic trees under world body
int ngravcomp; // number of bodies with nonzero gravcomp
int nemax; // number of potential equality-constraint rows
int njmax; // number of available rows in constraint Jacobian
int nconmax; // number of potential contacts in contact list
int njmax; // number of available rows in constraint Jacobian (legacy)
int nconmax; // number of potential contacts in contact list (legacy)
int nuserdata; // number of mjtNums reserved for the user
int nsensordata; // number of mjtNums in sensor data vector
int npluginstate; // number of mjtNums in plugin state vector
@@ -1149,6 +1163,9 @@ struct mjModel_ {
int* flex_dim; // 1: lines, 2: triangles, 3: tetrahedra (nflex x 1)
int* flex_matid; // material id for rendering (nflex x 1)
int* flex_group; // group for visibility (nflex x 1)
int* flex_interp; // interpolation (0: vertex, 1: nodes) (nflex x 1)
int* flex_nodeadr; // first node address (nflex x 1)
int* flex_nodenum; // number of nodes (nflex x 1)
int* flex_vertadr; // first vertex address (nflex x 1)
int* flex_vertnum; // number of vertices (nflex x 1)
int* flex_edgeadr; // first edge address (nflex x 1)
@@ -1162,15 +1179,19 @@ struct mjModel_ {
int* flex_evpairadr; // first evpair address (nflex x 1)
int* flex_evpairnum; // number of evpairs (nflex x 1)
int* flex_texcoordadr; // address in flex_texcoord; -1: none (nflex x 1)
int* flex_nodebodyid; // node body ids (nflexnode x 1)
int* flex_vertbodyid; // vertex body ids (nflexvert x 1)
int* flex_edge; // edge vertex ids (2 per edge) (nflexedge x 2)
int* flex_elem; // element vertex ids (dim+1 per elem) (nflexelemdata x 1)
int* flex_elemtexcoord; // element texture coordinates (dim+1) (nflexelemdata x 1)
int* flex_elemedge; // element edge ids (nflexelemedge x 1)
int* flex_elemlayer; // element distance from surface, 3D only (nflexelem x 1)
int* flex_shell; // shell fragment vertex ids (dim per frag) (nflexshelldata x 1)
int* flex_evpair; // (element, vertex) collision pairs (nflexevpair x 2)
mjtNum* flex_vert; // vertex positions in local body frames (nflexvert x 3)
mjtNum* flex_xvert0; // Cartesian vertex positions in qpos0 (nflexvert x 3)
mjtNum* flex_vert0; // vertex positions in qpos0 on [0, 1]^d (nflexvert x 3)
mjtNum* flex_node; // node positions in local body frames (nflexnode x 3)
mjtNum* flex_node0; // Cartesian node positions in qpos0 (nflexnode x 3)
mjtNum* flexedge_length0; // edge lengths in qpos0 (nflexedge x 1)
mjtNum* flexedge_invweight0; // edge inv. weight in qpos0 (nflexedge x 1)
mjtNum* flex_radius; // radius around primitive element (nflex x 1)
@@ -1211,6 +1232,15 @@ struct mjModel_ {
mjtNum* mesh_pos; // translation applied to asset vertices (nmesh x 3)
mjtNum* mesh_quat; // rotation applied to asset vertices (nmesh x 4)
int* mesh_pathadr; // address of asset path for mesh; -1: none (nmesh x 1)
int* mesh_polynum; // number of polygons per mesh (nmesh x 1)
int* mesh_polyadr; // first polygon address per mesh (nmesh x 1)
mjtNum* mesh_polynormal; // all polygon normals (nmeshpoly x 3)
int* mesh_polyvertadr; // polygon vertex start address (nmeshpoly x 1)
int* mesh_polyvertnum; // number of vertices per polygon (nmeshpoly x 1)
int* mesh_polyvert; // all polygon vertices (nmeshpolyvert x 1)
int* mesh_polymapadr; // first polygon address per vertex (nmeshvert x 1)
int* mesh_polymapnum; // number of polygons per vertex (nmeshvert x 1)
int* mesh_polymap; // vertex to polygon map (nmeshpolymap x 1)
// skins
int* skin_matid; // skin material id; -1: none (nskin x 1)
@@ -1582,14 +1612,14 @@ struct mjrContext_ { // custom OpenGL context
unsigned int auxColor_r[mjNAUX]; // auxiliary color buffer for resolving
// materials with textures
int mat_texid[mjMAXMATERIAL*mjNTEXROLE]; // material texture ids (-1: no texture)
int mat_texuniform[mjMAXMATERIAL]; // texture repetition for 2d mapping
int mat_texrepeat[mjMAXMATERIAL*2]; // texture repetition for 2d mapping
int mat_texid[mjMAXMATERIAL*mjNTEXROLE]; // material texture ids (-1: no texture)
int mat_texuniform[mjMAXMATERIAL]; // uniform cube mapping
float mat_texrepeat[mjMAXMATERIAL*2]; // texture repetition for 2d mapping
// texture objects and info
int ntexture; // number of allocated textures
int textureType[mjMAXTEXTURE]; // type of texture (mjtTexture) (ntexture)
unsigned int texture[mjMAXTEXTURE]; // texture names
int ntexture; // number of allocated textures
int textureType[mjMAXTEXTURE]; // type of texture (mjtTexture) (ntexture)
unsigned int texture[mjMAXTEXTURE]; // texture names
// displaylist starting positions
unsigned int basePlane; // all planes from model
@@ -1628,23 +1658,24 @@ struct mjrContext_ { // custom OpenGL context
int windowDoublebuffer; // is default/window framebuffer double buffered
// framebuffer
int currentBuffer; // currently active framebuffer: mjFB_WINDOW or mjFB_OFFSCREEN
int currentBuffer; // currently active framebuffer: mjFB_WINDOW or mjFB_OFFSCREEN
// pixel output format
int readPixelFormat; // default color pixel format for mjr_readPixels
int readPixelFormat; // default color pixel format for mjr_readPixels
// depth output format
int readDepthMap; // depth mapping: mjDEPTH_ZERONEAR or mjDEPTH_ZEROFAR
int readDepthMap; // depth mapping: mjDEPTH_ZERONEAR or mjDEPTH_ZEROFAR
};
typedef struct mjrContext_ mjrContext;
typedef enum mjtGeomInertia_ { // type of inertia inference
mjINERTIA_VOLUME = 0, // mass distributed in the volume
mjINERTIA_SHELL, // mass distributed on the surface
} mjtGeomInertia;
typedef enum mjtMeshInertia_ { // type of mesh inertia
mjINERTIA_CONVEX = 0, // convex mesh inertia
mjINERTIA_EXACT, // exact mesh inertia
mjINERTIA_LEGACY, // legacy mesh inertia
typedef enum mjtMeshInertia_ { // type of mesh inertia
mjMESH_INERTIA_CONVEX = 0, // convex mesh inertia
mjMESH_INERTIA_EXACT, // exact mesh inertia
mjMESH_INERTIA_LEGACY, // legacy mesh inertia
mjMESH_INERTIA_SHELL // shell mesh inertia
} mjtMeshInertia;
typedef enum mjtBuiltin_ { // type of built-in procedural texture
mjBUILTIN_NONE = 0, // no built-in texture
@@ -1683,30 +1714,32 @@ typedef enum mjtOrientation_ { // type of orientation specifier
typedef struct mjsElement_ { // element type, do not modify
mjtObj elemtype; // element type
} mjsElement;
typedef struct mjSpec_ { // model specification
mjsElement* element; // element type
mjString* modelname; // model name
// compiler settings
typedef struct mjsCompiler_ { // compiler options
mjtByte autolimits; // infer "limited" attribute based on range
double boundmass; // enforce minimum body mass
double boundinertia; // enforce minimum body diagonal inertia
double settotalmass; // rescale masses and inertias; <=0: ignore
mjtByte balanceinertia; // automatically impose A + B >= C rule
mjtByte strippath; // automatically strip paths from mesh files
mjtByte fitaabb; // meshfit to aabb instead of inertia box
mjtByte degree; // angles in radians or degrees
char eulerseq[3]; // sequence for euler rotations
mjString* meshdir; // mesh and hfield directory
mjString* texturedir; // texture directory
mjtByte discardvisual; // discard visual geoms in parser
mjtByte convexhull; // compute mesh convex hulls
mjtByte usethread; // use multiple threads to speed up compiler
mjtByte fusestatic; // fuse static bodies with parent
int inertiafromgeom; // use geom inertias (mjtInertiaFromGeom)
int inertiagrouprange[2]; // range of geom groups used to compute inertia
int alignfree; // align free joints with inertial frame
mjLROpt LRopt; // options for lengthrange computation
} mjsCompiler;
typedef struct mjSpec_ { // model specification
mjsElement* element; // element type
mjString* modelname; // model name
// compiler data
mjsCompiler compiler; // compiler options
mjtByte strippath; // automatically strip paths from mesh files
mjString* meshdir; // mesh and hfield directory
mjString* texturedir; // texture directory
// engine data
mjOption option; // physics options
@@ -1977,10 +2010,13 @@ typedef struct mjsFlex_ { // flex specification
double thickness; // thickness (2D only)
// mesh properties
mjStringVec* nodebody; // node body names
mjStringVec* vertbody; // vertex body names
mjDoubleVec* node; // node positions
mjDoubleVec* vert; // vertex positions
mjIntVec* elem; // element vertex ids
mjFloatVec* texcoord; // vertex texture coordinates
mjIntVec* elemtexcoord; // element texture coordinates
// other
mjString* info; // message appended to compiler errors
@@ -1993,14 +2029,13 @@ typedef struct mjsMesh_ { // mesh specification
double refpos[3]; // reference position
double refquat[4]; // reference orientation
double scale[3]; // rescale mesh
mjtMeshInertia inertia; // inertia type (convex, legacy, exact)
mjtMeshInertia inertia; // inertia type (convex, legacy, exact, shell)
mjtByte smoothnormal; // do not exclude large-angle faces from normals
int maxhullvert; // maximum vertex count for the convex hull
mjFloatVec* uservert; // user vertex data
mjFloatVec* usernormal; // user normal data
mjFloatVec* usertexcoord; // user texcoord data
mjIntVec* userface; // user vertex indices
mjIntVec* userfacenormal; // user normal indices
mjIntVec* userfacetexcoord; // user texcoord indices
mjsPlugin plugin; // sdf plugin
mjString* info; // message appended to compiler errors
@@ -2496,7 +2531,7 @@ struct mjUI_ { // entire UI
// UI sizes (framebuffer units)
int width; // width
int height; // current heigth
int height; // current height
int maxheight; // height when all sections open
int scroll; // scroll from top of UI
@@ -2963,9 +2998,14 @@ struct mjvSceneState_ {
int* flex_dim;
int* flex_matid;
int* flex_group;
int* flex_interp;
int* flex_nodeadr;
int* flex_nodenum;
int* flex_nodebodyid;
int* flex_vertadr;
int* flex_vertnum;
int* flex_elem;
int* flex_elemtexcoord;
int* flex_elemlayer;
int* flex_elemadr;
int* flex_elemnum;
@@ -2976,8 +3016,11 @@ struct mjvSceneState_ {
int* flex_texcoordadr;
int* flex_bvhadr;
int* flex_bvhnum;
mjtByte* flex_centered;
mjtNum* flex_node;
mjtNum* flex_radius;
float* flex_rgba;
float* flex_texcoord;
int* hfield_pathadr;
@@ -3178,11 +3221,12 @@ mjSpec* mj_makeSpec(void);
mjSpec* mj_copySpec(const mjSpec* s);
void mj_deleteSpec(mjSpec* s);
int mjs_activatePlugin(mjSpec* s, const char* name);
int mjs_setDeepCopy(mjSpec* s, int deepcopy);
void mj_printFormattedModel(const mjModel* m, const char* filename, const char* float_format);
void mj_printModel(const mjModel* m, const char* filename);
void mj_printFormattedData(const mjModel* m, mjData* d, const char* filename,
void mj_printFormattedData(const mjModel* m, const mjData* d, const char* filename,
const char* float_format);
void mj_printData(const mjModel* m, mjData* d, const char* filename);
void mj_printData(const mjModel* m, const mjData* d, const char* filename);
void mju_printMat(const mjtNum* mat, int nr, int nc);
void mju_printMatSparse(const mjtNum* mat, int nr,
const int* rownnz, const int* rowadr, const int* colind);
@@ -3217,7 +3261,8 @@ void mj_transmission(const mjModel* m, mjData* d);
void mj_crb(const mjModel* m, mjData* d);
void mj_factorM(const mjModel* m, mjData* d);
void mj_solveM(const mjModel* m, mjData* d, mjtNum* x, const mjtNum* y, int n);
void mj_solveM2(const mjModel* m, mjData* d, mjtNum* x, const mjtNum* y, int n);
void mj_solveM2(const mjModel* m, mjData* d, mjtNum* x, const mjtNum* y,
const mjtNum* sqrtInvD, int n);
void mj_comVel(const mjModel* m, mjData* d);
void mj_passive(const mjModel* m, mjData* d);
void mj_subtreeVel(const mjModel* m, mjData* d);
@@ -3335,9 +3380,6 @@ void mjv_defaultOption(mjvOption* opt);
void mjv_defaultFigure(mjvFigure* fig);
void mjv_initGeom(mjvGeom* geom, int type, const mjtNum size[3],
const mjtNum pos[3], const mjtNum mat[9], const float rgba[4]);
void mjv_makeConnector(mjvGeom* geom, int type, mjtNum width,
mjtNum a0, mjtNum a1, mjtNum a2,
mjtNum b0, mjtNum b1, mjtNum b2);
void mjv_connector(mjvGeom* geom, int type, mjtNum width,
const mjtNum from[3], const mjtNum to[3]);
void mjv_defaultScene(mjvScene* scn);
@@ -3348,6 +3390,7 @@ void mjv_updateScene(const mjModel* m, mjData* d, const mjvOption* opt,
int mjv_updateSceneFromState(const mjvSceneState* scnstate, const mjvOption* opt,
const mjvPerturb* pert, mjvCamera* cam, int catmask,
mjvScene* scn);
void mjv_copyModel(mjModel* dest, const mjModel* src);
void mjv_defaultSceneState(mjvSceneState* scnstate);
void mjv_makeSceneState(const mjModel* m, const mjData* d,
mjvSceneState* scnstate, int maxgeom);
@@ -3429,8 +3472,6 @@ mjtNum mju_dot3(const mjtNum vec1[3], const mjtNum vec2[3]);
mjtNum mju_dist3(const mjtNum pos1[3], const mjtNum pos2[3]);
void mju_mulMatVec3(mjtNum res[3], const mjtNum mat[9], const mjtNum vec[3]);
void mju_mulMatTVec3(mjtNum res[3], const mjtNum mat[9], const mjtNum vec[3]);
void mju_rotVecMat(mjtNum res[3], const mjtNum vec[3], const mjtNum mat[9]);
void mju_rotVecMatT(mjtNum res[3], const mjtNum vec[3], const mjtNum mat[9]);
void mju_cross(mjtNum res[3], const mjtNum a[3], const mjtNum b[3]);
void mju_zero4(mjtNum res[4]);
void mju_unit4(mjtNum res[4]);
@@ -3467,6 +3508,8 @@ void mju_sqrMatTD(mjtNum* res, const mjtNum* mat, const mjtNum* diag, int nr, in
void mju_transformSpatial(mjtNum res[6], const mjtNum vec[6], int flg_force,
const mjtNum newpos[3], const mjtNum oldpos[3],
const mjtNum rotnew2old[9]);
int mju_dense2sparse(mjtNum* res, const mjtNum* mat, int nr, int nc,
int* rownnz, int* rowadr, int* colind, int nnz);
void mju_sparse2dense(mjtNum* res, const mjtNum* mat, int nr, int nc,
const int* rownnz, const int* rowadr, const int* colind);
void mju_rotVecQuat(mjtNum res[3], const mjtNum vec[3], const mjtNum quat[4]);
@@ -3481,6 +3524,7 @@ void mju_mat2Quat(mjtNum quat[4], const mjtNum mat[9]);
void mju_derivQuat(mjtNum res[4], const mjtNum quat[4], const mjtNum vel[3]);
void mju_quatIntegrate(mjtNum quat[4], const mjtNum vel[3], mjtNum scale);
void mju_quatZ2Vec(mjtNum quat[4], const mjtNum vec[3]);
int mju_mat2Rot(mjtNum quat[4], const mjtNum mat[9]);
void mju_euler2Quat(mjtNum quat[4], const mjtNum euler[3], const char* seq);
void mju_mulPose(mjtNum posres[3], mjtNum quatres[4],
const mjtNum pos1[3], const mjtNum quat1[4],
@@ -3567,23 +3611,26 @@ mjsFrame* mjs_attachFrame(mjsBody* parent, const mjsFrame* child,
const char* prefix, const char* suffix);
mjsBody* mjs_attachToSite(mjsSite* parent, const mjsBody* child,
const char* prefix, const char* suffix);
mjsFrame* mjs_attachFrameToSite(mjsSite* parent, const mjsFrame* child,
const char* prefix, const char* suffix);
int mjs_detachBody(mjSpec* s, mjsBody* b);
mjsBody* mjs_addBody(mjsBody* body, mjsDefault* def);
mjsSite* mjs_addSite(mjsBody* body, mjsDefault* def);
mjsJoint* mjs_addJoint(mjsBody* body, mjsDefault* def);
int mjs_detachDefault(mjSpec* s, mjsDefault* d);
mjsBody* mjs_addBody(mjsBody* body, const mjsDefault* def);
mjsSite* mjs_addSite(mjsBody* body, const mjsDefault* def);
mjsJoint* mjs_addJoint(mjsBody* body, const mjsDefault* def);
mjsJoint* mjs_addFreeJoint(mjsBody* body);
mjsGeom* mjs_addGeom(mjsBody* body, mjsDefault* def);
mjsCamera* mjs_addCamera(mjsBody* body, mjsDefault* def);
mjsLight* mjs_addLight(mjsBody* body, mjsDefault* def);
mjsGeom* mjs_addGeom(mjsBody* body, const mjsDefault* def);
mjsCamera* mjs_addCamera(mjsBody* body, const mjsDefault* def);
mjsLight* mjs_addLight(mjsBody* body, const mjsDefault* def);
mjsFrame* mjs_addFrame(mjsBody* body, mjsFrame* parentframe);
void mjs_delete(mjsElement* element);
mjsActuator* mjs_addActuator(mjSpec* s, mjsDefault* def);
int mjs_delete(mjsElement* element);
mjsActuator* mjs_addActuator(mjSpec* s, const mjsDefault* def);
mjsSensor* mjs_addSensor(mjSpec* s);
mjsFlex* mjs_addFlex(mjSpec* s);
mjsPair* mjs_addPair(mjSpec* s, mjsDefault* def);
mjsPair* mjs_addPair(mjSpec* s, const mjsDefault* def);
mjsExclude* mjs_addExclude(mjSpec* s);
mjsEquality* mjs_addEquality(mjSpec* s, mjsDefault* def);
mjsTendon* mjs_addTendon(mjSpec* s, mjsDefault* def);
mjsEquality* mjs_addEquality(mjSpec* s, const mjsDefault* def);
mjsTendon* mjs_addTendon(mjSpec* s, const mjsDefault* def);
mjsWrap* mjs_wrapSite(mjsTendon* tendon, const char* name);
mjsWrap* mjs_wrapGeom(mjsTendon* tendon, const char* name, const char* sidesite);
mjsWrap* mjs_wrapJoint(mjsTendon* tendon, const char* name, double coef);
@@ -3594,16 +3641,18 @@ mjsTuple* mjs_addTuple(mjSpec* s);
mjsKey* mjs_addKey(mjSpec* s);
mjsPlugin* mjs_addPlugin(mjSpec* s);
mjsDefault* mjs_addDefault(mjSpec* s, const char* classname, const mjsDefault* parent);
mjsMesh* mjs_addMesh(mjSpec* s, mjsDefault* def);
mjsMesh* mjs_addMesh(mjSpec* s, const mjsDefault* def);
mjsHField* mjs_addHField(mjSpec* s);
mjsSkin* mjs_addSkin(mjSpec* s);
mjsTexture* mjs_addTexture(mjSpec* s);
mjsMaterial* mjs_addMaterial(mjSpec* s, mjsDefault* def);
mjsMaterial* mjs_addMaterial(mjSpec* s, const mjsDefault* def);
mjSpec* mjs_getSpec(mjsElement* element);
mjSpec* mjs_findSpec(mjSpec* spec, const char* name);
mjsBody* mjs_findBody(mjSpec* s, const char* name);
mjsElement* mjs_findElement(mjSpec* s, mjtObj type, const char* name);
mjsBody* mjs_findChild(mjsBody* body, const char* name);
mjsBody* mjs_getParent(mjsElement* element);
mjsFrame* mjs_getFrame(mjsElement* element);
mjsFrame* mjs_findFrame(mjSpec* s, const char* name);
mjsDefault* mjs_getDefault(mjsElement* element);
mjsDefault* mjs_findDefault(mjSpec* s, const char* classname);
@@ -3626,11 +3675,14 @@ void mjs_setDouble(mjDoubleVec* dest, const double* array, int size);
void mjs_setPluginAttributes(mjsPlugin* plugin, void* attributes);
const char* mjs_getString(const mjString* source);
const double* mjs_getDouble(const mjDoubleVec* source, int* size);
void mjs_setDefault(mjsElement* element, mjsDefault* def);
void mjs_setDefault(mjsElement* element, const mjsDefault* def);
void mjs_setFrame(mjsElement* dest, mjsFrame* frame);
const char* mjs_resolveOrientation(double quat[4], mjtByte degree, const char* sequence,
const mjsOrientation* orientation);
mjsFrame* mjs_bodyToFrame(mjsBody** body);
void mjs_setUserValue(mjsElement* element, const char* key, const void* data);
const void* mjs_getUserValue(mjsElement* element, const char* key);
void mjs_deleteUserValue(mjsElement* element, const char* key);
void mjs_defaultSpec(mjSpec* spec);
void mjs_defaultOrientation(mjsOrientation* orient);
void mjs_defaultBody(mjsBody* body);
+128
View File
@@ -0,0 +1,128 @@
const SRCS = [
'xml/xml_api.cc',
'user/user_api.cc',
'user/user_init.c',
'user/user_vfs.cc',
'thread/thread_task.cc',
'thread/thread_pool.cc',
'render/render_context.c',
'render/render_gl2.c',
'render/render_gl3.c',
'render/render_util.c',
'engine/engine_derivative.c',
'engine/engine_io.c',
'engine/engine_sensor.c',
'engine/engine_callback.c',
'engine/engine_collision_driver.c',
'engine/engine_core_constraint.c',
'engine/engine_core_smooth.c',
'engine/engine_derivative_fd.c',
'engine/engine_forward.c',
'engine/engine_inverse.c',
'engine/engine_island.c',
'engine/engine_name.c',
'engine/engine_passive.c',
'engine/engine_plugin.cc',
'engine/engine_print.c',
'engine/engine_ray.c',
'engine/engine_setconst.c',
'engine/engine_solver.c',
'engine/engine_support.c',
'engine/engine_util_blas.c',
'engine/engine_util_container.c',
'engine/engine_util_errmem.c',
'engine/engine_util_misc.c',
'engine/engine_util_solve.c',
'engine/engine_util_spatial.c',
'engine/engine_util_sparse.c',
'engine/engine_vis_init.c',
'engine/engine_vis_interact.c',
'engine/engine_vis_state.c',
'engine/engine_vis_visualize.c',
'ui/ui_main.c',
];
class LineNumbers {
constructor() {
this.map = new Map();
}
static fetch(src) {
const url = `https://raw.githubusercontent.com/google-deepmind/mujoco/refs/heads/main/src/${src}`;
const request = new XMLHttpRequest();
return new Promise((resolve, reject) => {
request.onreadystatechange = () => {
if (request.readyState === 4) {
if (request.status === 200) {
resolve(request.responseText);
} else {
reject(request.status);
}
}
};
request.open('GET', url, true);
request.send();
});
}
fetchAll() {
let requests = [];
for (const src of SRCS) {
requests.push(LineNumbers.fetch(src).then(contents => {
this.processSrc(src, contents);
}, reason => {/* swallow error */}));
}
Promise.all(requests).then(() => {
const anchors = document.querySelectorAll('h3 a.reference.external');
for (const anchor of anchors) {
const url = anchor.getAttribute('href');
if (url.startsWith('#')) {
const key = url.substring(1);
if (this.map.has(key)) {
anchor.href = this.map.get(key);
} else {
console.log(`No line number found for ${key}`);
}
}
}
});
}
processSrc(src, contents) {
const lines = contents.split('\n');
const re = /^(const )?[a-zA-Z0-9_*]+\s(.+)\(.+[{,]$/;
for (let i = 0; i < lines.length; i++) {
if (lines[i].match(re)) {
const key = lines[i].match(re)[2];
this.map.set(key, `https://github.com/google-deepmind/mujoco/blob/main/src/${src}#L${i+1}`);
}
}
// edge cases
if (src == 'user/user_api.cc') {
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith('[[nodiscard]] int mj_recompile(')) {
const key = 'mj_recompile';
this.map.set(key, `https://github.com/google-deepmind/mujoco/blob/main/src/${src}#L${i+1}`);
}
}
} else if (src == 'engine/engine_io.c') {
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith('void mj_freeStack(')) {
const key = 'mj_freeStack';
this.map.set(key, `https://github.com/google-deepmind/mujoco/blob/main/src/${src}#L${i+1}`);
} else if (lines[i].startsWith('void mj_markStack(')) {
const key = 'mj_markStack';
this.map.set(key, `https://github.com/google-deepmind/mujoco/blob/main/src/${src}#L${i+1}`);
}
}
}
}
}
window.onload = () => {
if (document.getElementById('fetchlines')) {
let lines = new LineNumbers();
lines.fetchAll();
}
};
+65 -55
View File
@@ -7,7 +7,7 @@ MuJoCo XLA (MJX)
Starting with version 3.0.0, MuJoCo includes MuJoCo XLA (MJX) under the
`mjx <https://github.com/google-deepmind/mujoco/tree/main/mjx>`__ directory. MJX allows MuJoCo to run on compute
hardware supported by the `XLA <https://www.tensorflow.org/xla>`__ compiler via the
`JAX <https://github.com/google/jax#readme>`__ framework. MJX runs on a
`JAX <https://github.com/jax-ml/jax#readme>`__ framework. MJX runs on a
`all platforms supported by JAX <https://jax.readthedocs.io/en/latest/installation.html#supported-platforms>`__: Nvidia
and AMD GPUs, Apple Silicon, and `Google Cloud TPUs <https://cloud.google.com/tpu>`__.
@@ -50,7 +50,7 @@ The recommended way to install this package is via `PyPI <https://pypi.org/proje
pip install mujoco-mjx
A copy of the MuJoCo library is provided as part of this package's depdendencies and does **not** need to be downloaded
A copy of the MuJoCo library is provided as part of this package's dependencies and does **not** need to be downloaded
or installed separately.
.. _MjxUsage:
@@ -66,9 +66,9 @@ directly from the top-level ``mjx`` module.
Structs
-------
Before running MJX functions on an accelerator device, structs must be copied onto the device via the ``mjx.put_model`` and ``mjx.put_data``
functions. Placing an :ref:`mjModel` on device yields an ``mjx.Model``. Placing an :ref:`mjData` on device yields
an ``mjx.Data``:
Before running MJX functions on an accelerator device, structs must be copied onto the device via the ``mjx.put_model``
and ``mjx.put_data`` functions. Placing an :ref:`mjModel` on device yields an ``mjx.Model``. Placing an :ref:`mjData` on
device yields an ``mjx.Data``:
.. code-block:: python
@@ -86,12 +86,10 @@ These MJX variants mirror their MuJoCo counterparts but have a few key differenc
express domain randomization (in the case of ``mjx.Model``) or high-throughput simulation for reinforcement learning
(in the case of ``mjx.Data``).
#. Numpy arrays in ``mjx.Model`` and ``mjx.Data`` are structural fields that control the output of JIT compilation.
Modifying these arrays will force JAX to recompile MJX functions. As an example,
``jnt_limited`` is a numpy array passed by reference from :ref:`mjModel`, which determines if joint limit
constraints should be applied. If ``jnt_limited`` is modified, JAX will
re-compile MJX functions.
On the other hand, ``jnt_range`` is a JAX array that can be modified at runtime, and will only apply to joints with limits
as specified by the ``jnt_limited`` field.
Modifying these arrays will force JAX to recompile MJX functions. As an example, ``jnt_limited`` is a numpy array
passed by reference from :ref:`mjModel`, which determines if joint limit constraints should be applied. If
``jnt_limited`` is modified, JAX will re-compile MJX functions. On the other hand, ``jnt_range`` is a JAX array that
can be modified at runtime, and will only apply to joints with limits as specified by the ``jnt_limited`` field.
Neither ``mjx.Model`` nor ``mjx.Data`` are meant to be constructed manually. An ``mjx.Data`` may be created by calling
@@ -110,9 +108,9 @@ Using ``mjx.make_data`` may be preferable when constructing batched ``mjx.Data``
Functions
---------
MuJoCo functions are exposed as MJX functions of the same name, but following
`PEP 8 <https://peps.python.org/pep-0008/>`__-compliant names. Most of the :ref:`main simulation <Mainsimulation>` and
some of the :ref:`sub-components <Subcomponents>` for forward simulation are available from the top-level ``mjx`` module.
MuJoCo functions are exposed as MJX functions of the same name, but following `PEP 8
<https://peps.python.org/pep-0008/>`__-compliant names. Most of the :ref:`main simulation <Mainsimulation>` and some of
the :ref:`sub-components <Subcomponents>` for forward simulation are available from the top-level ``mjx`` module.
MJX functions are not `JIT compiled <https://jax.readthedocs.io/en/latest/jax-101/02-jitting.html>`__ by default -- we
leave it to the user to JIT MJX functions, or JIT their own functions that reference MJX functions. See the
@@ -165,6 +163,30 @@ Minimal example
pos = jax.jit(batched_step)(vel)
print(pos)
.. _MjxCli:
Helpful Command Line Scripts
----------------------------
We provide two command line scripts with the ``mujoco-mjx`` package:
.. code-block:: shell
mjx-testspeed --mjcf=/PATH/TO/MJCF/ --base_path=.
This command takes in a path to an MJCF file along with optional arguments (use ``--help`` for more information)
and computes helpful metrics for performance tuning. The command will output, among other things, the total
simulation time, the total steps per second and the total realtime factor (here total is across all available
devices).
.. code-block:: shell
mjx-viewer --help
This command launches the MJX model in the simulate viewer, allowing you to visualize and interact with the model.
Note this steps the simulation using MJX physics (not C MuJoCo) so it can be helpful for example for debugging
solver parameters.
.. _MjxFeatureParity:
Feature Parity
@@ -190,18 +212,19 @@ The following features are **fully supported** in MJX:
* - :ref:`Transmission <mjtTrn>`
- ``JOINT``, ``JOINTINPARENT``, ``SITE``, ``TENDON``
* - :ref:`Actuator Dynamics <mjtDyn>`
- ``NONE``, ``INTEGRATOR``, ``FILTER``, ``FILTEREXACT``
- ``NONE``, ``INTEGRATOR``, ``FILTER``, ``FILTEREXACT``, ``MUSCLE``
* - :ref:`Actuator Gain <mjtGain>`
- ``FIXED``, ``AFFINE``
- ``FIXED``, ``AFFINE``, ``MUSCLE``
* - :ref:`Actuator Bias <mjtBias>`
- ``NONE``, ``AFFINE``
- ``NONE``, ``AFFINE``, ``MUSCLE``
* - :ref:`Tendon Wrapping <mjtWrap>`
- ``JOINT``, ``SITE``, ``PULLEY``
- ``JOINT``, ``SITE``, ``PULLEY``, ``SPHERE``, ``CYLINDER``
* - :ref:`Geom <mjtGeom>`
- ``PLANE``, ``HFIELD``, ``SPHERE``, ``CAPSULE``, ``BOX``, ``MESH`` are fully implemented. ``ELLIPSOID`` and
``CYLINDER`` are implemented but only collide with other primitives, note that ``BOX`` is implemented as a mesh.
* - :ref:`Constraint <mjtConstraint>`
- ``EQUALITY``, ``LIMIT_JOINT``, ``CONTACT_FRICTIONLESS``, ``CONTACT_PYRAMIDAL``, ``CONTACT_ELLIPTIC``, ``FRICTION_DOF``, ``FRICTION_TENDON``
- ``EQUALITY``, ``LIMIT_JOINT``, ``CONTACT_FRICTIONLESS``, ``CONTACT_PYRAMIDAL``, ``CONTACT_ELLIPTIC``,
``FRICTION_DOF``, ``FRICTION_TENDON``
* - :ref:`Equality <mjtEq>`
- ``CONNECT``, ``WELD``, ``JOINT``, ``TENDON``
* - :ref:`Integrator <mjtIntegrator>`
@@ -209,19 +232,20 @@ The following features are **fully supported** in MJX:
* - :ref:`Cone <mjtCone>`
- ``PYRAMIDAL``, ``ELLIPTIC``
* - :ref:`Condim <coContact>`
- 1, 3, 4, 6
- 1, 3, 4, 6 (1 is not supported with ``ELLIPTIC``)
* - :ref:`Solver <mjtSolver>`
- ``CG``, ``NEWTON``
* - Fluid Model
- :ref:`flInertia`
* - :ref:`Tendons <tendon>`
- :ref:`Fixed <tendon-fixed>`
- :ref:`Fixed <tendon-fixed>`, :ref:`Spatial <tendon-spatial>`
* - :ref:`Sensors <mjtSensor>`
- ``MAGNETOMETER``, ``CAMPROJECTION``, ``RANGEFINDER``, ``JOINTPOS``, ``TENDONPOS``, ``ACTUATORPOS``, ``BALLQUAT``,
``FRAMEPOS``, ``FRAMEXAXIS``, ``FRAMEYAXIS``, ``FRAMEZAXIS``, ``FRAMEQUAT``, ``SUBTREECOM``, ``CLOCK``,
``VELOCIMETER``, ``GYRO``, ``JOINTVEL``, ``TENDONVEL``, ``ACTUATORVEL``, ``BALLANGVEL``, ``FRAMELINVEL``,
``FRAMEANGVEL``, ``SUBTREELINVEL``, ``SUBTREEANGMOM``, ``TOUCH``, ``ACCELEROMETER``, ``FORCE``, ``TORQUE``,
``ACTUATORFRC``, ``JOINTACTFRC``, ``FRAMELINACC``, ``FRAMEANGACC``.
``ACTUATORFRC``, ``JOINTACTFRC``, ``FRAMELINACC``, ``FRAMEANGACC``
(``ACCELEROMETER``, ``FORCE``, ``TORQUE`` not supported with connect or weld equality constraints)
The following features are **in development** and coming soon:
@@ -240,18 +264,8 @@ The following features are **in development** and coming soon:
- ``IMPLICIT``
* - Dynamics
- :ref:`Inverse <mj_inverse>`
* - :ref:`Actuator Dynamics <mjtDyn>`
- ``MUSCLE``
* - :ref:`Actuator Gain <mjtGain>`
- ``MUSCLE``
* - :ref:`Actuator Bias <mjtBias>`
- ``MUSCLE``
* - :ref:`Tendon Wrapping <mjtWrap>`
- ``SPHERE``, ``CYLINDER``
* - Fluid Model
- :ref:`flEllipsoid`
* - :ref:`Tendons <tendon>`
- :ref:`Spatial <tendon-spatial>`
* - :ref:`Sensors <mjtSensor>`
- All except ``PLUGIN``, ``USER``
* - Lights
@@ -300,22 +314,19 @@ Single scene simulation
carefully optimized for CPU. MJX works best when simulating thousands or tens of thousands of scenes in parallel.
Collisions between large meshes
MJX supports collisions between convex mesh geometries. However the convex collision algorithms
in MJX are implemented differently than in MuJoCo. MJX uses a branchless version of the
`Separating Axis Test <https://ubm-twvideo01.s3.amazonaws.com/o1/vault/gdc2013/slides/822403Gregorius_Dirk_TheSeparatingAxisTest.pdf>`__
(SAT) to determine if geometries are colliding with convex meshes, while MuJoCo uses the Minkowski Portal Refinement (MPR)
algorithm as implemented in `libccd <https://github.com/danfis/libccd>`__.
SAT works well for smaller meshes but suffers in both runtime and memory for larger meshes.
MJX supports collisions between convex mesh geometries. However the convex collision algorithms in MJX are implemented
differently than in MuJoCo. MJX uses a branchless version of the `Separating Axis Test
<https://ubm-twvideo01.s3.amazonaws.com/o1/vault/gdc2013/slides/822403Gregorius_Dirk_TheSeparatingAxisTest.pdf>`__
(SAT) to determine if geometries are colliding with convex meshes, while MuJoCo uses either MPR or GJK/EPA, see
:ref:`Collision Detection<coChecking>` for more details. SAT works well for smaller meshes but suffers in both runtime
and memory for larger meshes.
For
collisions with convex meshes and primitives, the convex decompositon of the mesh should have
roughly **200 vertices or less** for reasonable performance. For convex-convex collisions,
the convex mesh should have roughly **fewer than 32 vertices**. We recommend using
:ref:`maxhullvert<asset-mesh-maxhullvert>` in the MuJoCo compiler to achieve desired convex mesh properties.
With careful
tuning, MJX can simulate scenes with mesh collisions -- see the MJX
`shadow hand <https://github.com/google-deepmind/mujoco/tree/main/mjx/mujoco/mjx/test_data/shadow_hand>`__
config for an example. Speeding up mesh collision detection is an active area of development for MJX.
For collisions with convex meshes and primitives, the convex decompositon of the mesh should have roughly **200
vertices or less** for reasonable performance. For convex-convex collisions, the convex mesh should have roughly
**fewer than 32 vertices**. We recommend using :ref:`maxhullvert<asset-mesh-maxhullvert>` in the MuJoCo compiler to
achieve desired convex mesh properties. With careful tuning, MJX can simulate scenes with mesh collisions -- see the
MJX `shadow hand <https://github.com/google-deepmind/mujoco/tree/main/mjx/mujoco/mjx/test_data/shadow_hand>`__ config
for an example. Speeding up mesh collision detection is an active area of development for MJX.
Large, complex scenes with many contacts
Accelerators exhibit poor performance for
@@ -377,13 +388,12 @@ For MJX to perform well, some configuration parameters should be adjusted from t
of 10% to 20%, as long as the dense matrices can fit on the device.
Broadphase
While MuJoCo handles broadphase culling out of the box, MJX requires additional parameters. For an approximate version of
broadphase, use the experimental custom numeric parameters
``max_contact_points`` and ``max_geom_pairs``. ``max_contact_points`` caps the number of contact points
sent to the solver for each condim type. ``max_geom_pairs`` caps the total number of geom-pairs sent to
respective collision functions for each geom-type pair. As an example, the
`shadow hand <https://github.com/google-deepmind/mujoco/tree/main/mjx/mujoco/mjx/test_data/shadow_hand>`__
environment makes use of these parameters.
While MuJoCo handles broadphase culling out of the box, MJX requires additional parameters. For an approximate version
of broadphase, use the experimental custom numeric parameters ``max_contact_points`` and ``max_geom_pairs``.
``max_contact_points`` caps the number of contact points sent to the solver for each condim type. ``max_geom_pairs``
caps the total number of geom-pairs sent to respective collision functions for each geom-type pair. As an example, the
`shadow hand <https://github.com/google-deepmind/mujoco/tree/main/mjx/mujoco/mjx/test_data/shadow_hand>`__ environment
makes use of these parameters.
GPU performance
---------------
@@ -393,4 +403,4 @@ The following environment variables should be set:
``XLA_FLAGS=--xla_gpu_triton_gemm_any=true``
This enables the Triton-based GEMM (matmul) emitter for any GEMM that it supports. This can yield a 30% speedup on
NVIDIA GPUs. If you have multiple GPUs, you may also benefit from enabling flags related to
`communciation between GPUs <https://jax.readthedocs.io/en/latest/gpu_performance_tips.html>`__.
`communication between GPUs <https://jax.readthedocs.io/en/latest/gpu_performance_tips.html>`__.
+75 -205
View File
@@ -136,21 +136,21 @@ the model. We start with an example.
.. code-block:: xml
<mujoco>
<default class="main">
<geom rgba="1 0 0 1"/>
<default class="sub">
<geom rgba="0 1 0 1"/>
</default>
<default class="main">
<geom rgba="1 0 0 1"/>
<default class="sub">
<geom rgba="0 1 0 1"/>
</default>
</default>
<worldbody>
<geom type="box"/>
<body childclass="sub">
<geom type="ellipsoid"/>
<geom type="sphere" rgba="0 0 1 1"/>
<geom type="cylinder" class="main"/>
</body>
</worldbody>
<worldbody>
<geom type="box"/>
<body childclass="sub">
<geom type="ellipsoid"/>
<geom type="sphere" rgba="0 0 1 1"/>
<geom type="cylinder" class="main"/>
</body>
</worldbody>
</mujoco>
This example will not actually compile because some required information is missing, but here we are only interested
@@ -629,11 +629,12 @@ Group disable
The :ref:`actuatorgroupdisable<option-actuatorgroupdisable>` attribute, which can be changed at runtime by setting the
:ref:`mjOption.disableactuator<mjOption>` integer bitfield, allows the user to disable sets of actuators according to
their :ref:`group<actuator-general-group>`. This feature is convenient when one would like to use multiple types of
actuators for the same kinematic tree. For example consider a robot with firmware that supports mutiple control modes
actuators for the same kinematic tree. For example consider a robot with firmware that supports multiple control modes
e.g., torque-control and position-control. In this case, one can define both types of actuators in the same MJCF
model, assigning one type of actuator to group 0 and the other to group 1.
.. youtube:: H9qG9Zf2W44
:aspect: 2:1
:align: right
:width: 40%
@@ -1120,122 +1121,40 @@ Intrinsics
Composite objects
~~~~~~~~~~~~~~~~~
Composite objects are not new model elements. Instead, they are (large) collections of existing elements designed to
simulate particle systems, ropes, cloth, and soft bodies. These collections are generated by the model compiler
automatically. The user configures the automatic generator on a high level, using the new XML element
:ref:`composite <body-composite>` and its attributes and sub-elements, as described in the XML reference
chapter. If the compiled model is then saved, :el:`composite` is no longer present and is replaced with the collection
of regular model elements that were automatically generated. So think of it as a macro that gets expanded by the model
compiler.
Composite objects are not new model elements. Instead, they are collections of existing element originally designed to
simulate particle systems, ropes, cloth, and soft bodies. Over time, most of these types have been replaced by
:ref:`replicate<replicate>` (for repeated objects) and :ref:`flexcomp<body-flexcomp>` (for soft objects). Therefore, the
only supported composite type is now ``cable``, which produces an inextensible chain of bodies connected with ball
joints.
Composite objects are made up of regular MuJoCo bodies, which we call "element bodies" in this context. The element
bodies are created as children of the body within which :el:`composite` appears; thus a composite object appears in the
same place in the XML where a regular child body may have been defined. Each automatically-generated element body has a
single geom attached to it, usually a sphere but could also be a capsule or an ellipsoid. Thus the composite object is
essentially a particle system, however the particles can be constrained to move together in ways that simulate various
flexible objects. The initial positions of the element bodies form a regular grid in 1D, 2D or 3D. They could all be
children of the parent body (which can be the world or another regular body; composite objects cannot be nested) and
have joints allowing motion relative to the parent, or they could form a kinematic tree with joints between the element
bodies. They can also be connected with tendons with soft equality constraints on the tendon length, creating the
necessary coupling. Joint equality constraints are also used in some cases. The :at:`solref` and :at:`solimp` attributes
of these equality constraints can be adjusted by the user, thereby adjusting the softness and flexibility of the
composite objects.
Composite objects are made up of regular MuJoCo bodies, which we call "element bodies" in this context. The collection
of element bodies is generated by the model compiler automatically. The user configures the automatic generator on a
high level, using the new XML element :ref:`composite <body-composite>` and its attributes and sub-elements, as
described in the XML reference chapter. If the compiled model is then saved, :el:`composite` is no longer present and is
replaced with the collection of regular model elements that were automatically generated. So think of it as a macro that
gets expanded by the model compiler. The element bodies are created as children of the body within which :el:`composite`
appears; thus a composite object appears in the same place in the XML where a regular child body may have been defined.
Each automatically-generated element body has a single geom attached to it. We have designed the composite object
generator to have intuitive high-level controls as much as possible, but at the same time it exposes a large number of
options that interact with each other and can profoundly affect the resulting physics. So at some point users should
read the :ref:`reference documentation <body-composite>` carefully.
In addition to setting up the physics, the composite object generator creates suitable rendering. 2D and 3D objects
can be rendered as :ref:`skins <asset-skin>`. The skin is generated
automatically, and can be textured as well as subdivided using bi-cubic interpolation. The actual physics and in
particular the collision detection are based on the element bodies and their geoms, while the skin is purely a
visualization object. Yet in most situations we prefer to look at the skin representation. To facilitate this, the
generator places all geoms, sites and tendons in group 3 whose visualization is disabled by default. So when you load
a 2D grid for example, you will see a continuous flexible surface and not a collection of spheres connected with
tendons. However when fine-tuning the model and trying to understand the physics behind it, it is useful to be able to
render the spheres and tendons. To switch the rendering style, disable the rendering of skins and enable group 3 for
geoms and tendons.
We have designed the composite object generator to have intuitive high-level controls as much as possible, but at the
same time it exposes a large number of options that interact with each other and can profoundly affect the resulting
physics. So at some point users should read the :ref:`reference documentation <body-composite>` carefully.
As a quick start though, MuJoCo comes with an example of each composite object type. Below we go over these
examples and explain the less obvious aspects. In all examples we have a static scene which is included in the model,
followed by a single composite object. The static scene has a mocap body (large capsule) that can be moved around with
the mouse to probe the behavior of the system. The XML snippets below are just the definition of the composite object;
see the XML model files in the distribution for the complete examples.
**Particle**.
|image4| |image5|
.. code-block:: xml
<worldbody>
<composite type="particle" count="10 10 10" spacing="0.07" offset="0 0 1">
<geom size=".02" rgba=".8 .2 .1 1"/>
</composite>
</worldbody>
The above XML is all it takes to create a system with 1000 particles with initial positions on a 10-10-10 grid, and
set the size, color, spacing and offset of the particles. The resulting element bodies become children of the world
body. One could adjust many other properties including the softness of the contacts and the joint attributes. The plot
on the right shows the joints. Each element body has 3 orthogonal slider joints, allowing it to translate but not
rotate. The idea is that particles should have position but no orientation. MuJoCo bodies always have orientation,
however by using only slider joints we do not allow the orientation to change. The geom defaults are adjusted
automatically so that they make frictionless contacts with each other and with the rest of the model. So this system
has 1000 bodies (each with a geom), 3000 degrees of freedom and around 1000 active contacts. Evaluating the dynamics
takes around 1 ms on a single core of a modern processor. As with most other MuJoCo models, the soft constraints allow
simulation at much larger timesteps (this model is stable at 30 ms timestep and even higher).
Particles are also compatible with the passive forces 2D and 3D plugins, discussed in the :ref:`deformable
<CDeformable>` section. However, collisions are limited to the particle themselves and not to the whole boundary of the
skin that encloses them. This makes contacts very fast but does not guarantee that all penetrations can be avoided. For
a more complete treatment, see again the :ref:`deformable <CDeformable>` section, which outlines how to use
:ref:`flexcomp<body-flexcomp>` to create such an object. It is easy to port models create with composite particles to
flex, see the folder `elasticity/ <https://github.com/google-deepmind/mujoco/tree/main/model/plugin/elasticity>`__ for
several examples.
**1D grid**.
|image6| |image7|
.. code-block:: xml
<composite type="grid" count="20 1 1" spacing="0.045" offset="0 0 1">
<joint kind="main" damping="0.001"/>
<tendon kind="main" width="0.01"/>
<geom size=".02" rgba=".8 .2 .1 1"/>
<pin coord="1"/>
<pin coord="13"/>
</composite>
The grid type can create 1D or 2D grids, depending on the :at:`count` attribute. Here we illustrate 1D grids. These
are strings of spheres connected with tendons whose length is soft-equality-constrained. The softness can be adjusted.
Similar to particles, the element bodies here have slider joints but no rotational joints. The plot on the right
illustrates pinning. The :el:`pin` sub-element is used to specify the grid coordinates of the pinned bodies, and the
model compiler does not generate joints for these bodies, thereby fixing them rigidly to the parent body (in this case
the world). This makes the string in the right plot hang in space. The same mechanism can be used to model a whip for
example; in that case the parent body would be moving, and the first element body would be pinned to the parent.
**2D grid**.
|image8| |image9|
.. code-block:: xml
<composite type="grid" count="9 9 1" spacing="0.05" offset="0 0 1">
<skin material="matcarpet" inflate="0.001" subgrid="3" texcoord="true"/>
<geom size=".02"/>
<pin coord="0 0"/>
<pin coord="8 0"/>
</composite>
A 2D grid can be used to simulate cloth. What it really simulates is a 2D grid of spheres connected with
equality-constrained tendons (not shown). The model compiler can also generate skin, enabled with the :el:`skin`
sub-element in the above XML. Some of the element bodies can also be pinned, similar to 1D grids but using two grid
coordinates. The plot on the right shows a cloth pinned to the world body at the two corners, and draping over our
capsule probe. The skin on the right is subdivided using bi-cubic interpolation, which increases visual quality in the
absence of textures. When textures are present (left) the benefits of subdivision are less visible.
In addition to setting up the physics, the composite object generator creates suitable rendering. Objects can be
rendered as :ref:`skins <asset-skin>`. The skin is generated automatically, and can be textured as well as subdivided
using bi-cubic interpolation. The actual physics and in particular the collision detection are based on the element
bodies and their geoms, while the skin is purely a visualization object. Yet in some situations we prefer to look at the
skin representation, as in `this model
<https://github.com/google-deepmind/mujoco/blob/main/model/plugin/elasticity/belt.xml>`__, whose skin is a continuous
flexible surface and not a collection of discontinuous thin boxes. However when fine-tuning the model and trying to
understand the physics behind it, it is useful to be able to render the geoms. To switch the rendering style, disable
the rendering of skins and enable group 3 for geoms and tendons.
**Cable**.
As a quick start, MuJoCo comes with an example of composite cables. In all examples we have a static scene which is
included in the model, followed by a single composite object. The XML snippets below are just the definition of the
composite object; see the XML model files in the distribution for the complete examples.
|coil|
.. code-block:: xml
@@ -1269,6 +1188,16 @@ stiffnesses can be set independently. Moreover, it is possible to specify if the
curve, such as in the case of coil springs. The cable requires using a first-party :ref:`engine plugin<exPlugin>`, which
may be integrated directly into the engine in the future.
**Particle**.
The particle type is deprecated. It is recommended to use the more generic :ref:`replicate<replicate>` instead, for
example `this model <https://github.com/google-deepmind/mujoco/blob/main/model/replicate/particle.xml>`__.
**Grid**.
The grid composite type has been removed. It is recommended to use 2D flex :ref:`deformable objects <CDeformable>` for
simulating thin elastic structures.
**Rope and loop**.
The rope and loop are deprecated. It is recommended to use the cable for simulating inextensible elastic rods that are
@@ -1280,62 +1209,11 @@ scenario (e.g. a stretched rubber band).
The cloth is deprecated. It is recommended to use 2D flex :ref:`deformable objects <CDeformable>` for simulating thin
elastic structures.
**Box**.
**Box, cylinder and ellipsoid**.
|image14| |image15|
.. code-block:: xml
<body pos="0 0 1">
<freejoint/>
<composite type="box" count="7 7 7" spacing="0.04">
<skin texcoord="true" material="matsponge" rgba=".7 .7 .7 1"/>
<geom type="capsule" size=".015 0.05" rgba=".8 .2 .1 1"/>
</composite>
</body>
The box type, as well as the cylinder and ellipsoid types below, are used to model soft 3D objects. The element bodies
form a grid along the outer shell, thus the number of element bodies scales with the square of the linear dimension.
This is much more efficient than simulating a 3D grid. The parent body within which :el:`composite` appears is at the
center of the soft object. All element bodies are children of the parent. Each element body has a single sliding joint
pointing away from the parent. These joints allow the surface of the soft object to compress and expand at any point.
The joints are equality-constrained to their initial position, so as to maintain the shape. In addition each joint is
equality-constrained to its neighbor joints, so that when the soft objects deforms, the deformation is smooth.
Finally, there is a tendon equality constraint specifying that the sum of all joints should remain constant. This
attempts to preserve the volume of the soft object approximately. If the object is squeezed from all sides it will
compress and the volume will decrease, but otherwise some element bodies will stick out to compensate for squeezing
elsewhere. The plot on the left shows this effect; we are using the capsule probe to compress one corner, and the
opposite sides of the cube expand a bit, while the deformations remain smooth. The :at:`count` attribute determines
the number of element bodies in each dimension, so if the counts are different the resulting object will be a
rectangular box and not a cube. The geoms attached to the element bodies can be spheres, capsules or ellipsoids.
Spheres are faster for collision detection, but they result in a thin shell, allowing other bodies to "get under the
skin" of the soft object. When capsules or ellipsoids are used, they are automatically oriented so that the long axis
points to the outside, thus creating a thicker shell which is harder to penetrate.
**Cylinder and ellipsoid**.
|image16| |image17|
.. code-block:: xml
<body pos="0 0 1">
<freejoint/>
<composite type="ellipsoid" count="5 7 9" spacing="0.05">
<skin texcoord="true" material="matsponge" rgba=".7 .7 .7 1"/>
<geom type="capsule" size=".015 0.05" rgba=".8 .2 .1 1"/>
</composite>
</body>
Cylinders and ellipsoids are created in the same way as boxes. The only difference is that the reference positions of
the element bodies (relative to the parent) are projected on a cylinder or ellipsoid, with size implied by the
:at:`count` attribute. The automatic skin generator is aware of the smooth surfaces, and adjusts the skin normals
accordingly. In the plots we have used the capsule probe to press on each body, then paused the simulation and moved the
probe away (which is possible because the probe is a mocap body which can move independent of the physics). In this way
we can see the indentation made by the probe, and the resulting deformation in the rest of the body. By changing the
solref and solimp attributes of the equality constraints that hold the soft object together, one can adjust the behavior
of the system making it softer or harder, damped or springy, etc. Note that box, cylinder and ellipsoid objects do not
involve long kinematic chains, and can be simulated at large timesteps -- similar to particle and grid, and unlike rope
and cloth.
The box type, as well as the cylinder and ellipsoid types, are now deprecated in favor of 3D flex :ref:`deformable
objects <CDeformable>`. element.
.. _CDeformable:
@@ -1386,9 +1264,7 @@ improved realism and accuracy. The edge-based model could be seen as a "lumped"
coupling of deformation modes (e.g. shear and volumetric) is averaged in a single quantity. The continuum model enables
instead to specify shear and volumetic stiffnesses separately using the `Poisson's ratio
<https://en.wikipedia.org/wiki/Poisson%27s_ratio>`__ of the material. For more details, see the `Saint Venant-Kirchhoff
<https://en.wikipedia.org/wiki/Hyperelastic_material#Saint_Venant%E2%80%93Kirchhoff_model>`__ hyperelastic model. This
functionality is currently based on first-party :ref:`engine plugins<exPlugin>` as of MuJoCo 3.0 but may be integrated
into the engine in future releases.
<https://en.wikipedia.org/wiki/Hyperelastic_material#Saint_Venant%E2%80%93Kirchhoff_model>`__ hyperelastic model.
**Creation and visualization**.
@@ -1396,20 +1272,12 @@ into the engine in future releases.
<option timestep=".001"/>
<extension>
<plugin plugin="mujoco.elasticity.solid"/>
</extension>
<worldbody>
<flexcomp type="grid" count="24 4 4" spacing=".1 .1 .1" pos=".1 0 1.5"
radius=".0" rgba="0 .7 .7 1" name="softbody" dim="3" mass="7">
<contact condim="3" solref="0.01 1" solimp=".95 .99 .0001" selfcollide="none"/>
<edge damping="1"/>
<plugin plugin="mujoco.elasticity.solid">
<config key="poisson" value="0.2"/>
<!--Units are in Pa (SI)-->
<config key="young" value="5e4"/>
</plugin>
<elasticity poisson="0.2" young="5e4">
</flexcomp>
</worldbody>
@@ -1503,7 +1371,7 @@ elements available in MuJoCo. In addition to standard URDF files, MuJoCo can loa
viewpoint of URDF) :el:`mujoco` element as a child of the top-level element :el:`robot`. This custom element can have
sub-elements :ref:`compiler <compiler>`, :ref:`option <option>`,
:ref:`size <size>` with the same functionality as in MJCF, except that the default compiler settings
are modified so as to accomodate the URDF modeling convention. The :ref:`compiler <compiler>` extension
are modified so as to accommodate the URDF modeling convention. The :ref:`compiler <compiler>` extension
in particular has proven very useful, and indeed several of its attributes were introduced because a number of
existing URDF models have non-physical dynamics parameters which MuJoCo's built-in compiler will reject if left
unmodified. This extension is also needed to specify mesh directories. Also note that the compiler attributes
@@ -1554,15 +1422,15 @@ mocap bodies around:
|particle|
The key thing to understand about mocap bodies is that the simulator treats them as being fixed. We are causing them
to move from one simulation time step to the next by updating their position and orientation directly, but as far as
the physics model is concerned their position and orientation are constant. So what happens if we make contact with a
regular dynamic body, as in the composite object examples provided with the MuJoCo distribution (recall that in
those example we have a capsule probe which is a mocap body that we move with the mouse). A contact between two
regular bodies will experience penetration as well as relative velocity, while contact with a mocap body is missing
the relative velocity component because the simulator does not know that the mocap body itself is moving. So the
resulting contact force is smaller and it takes longer for the contact to push the dynamic object away. Also, in more
complex simulations the fact that we are doing something inconsistent with the physics can cause instabilities.
The key thing to understand about mocap bodies is that the simulator treats them as being fixed. We are causing them to
move from one simulation time step to the next by updating their position and orientation directly, but as far as the
physics model is concerned their position and orientation are constant. So what happens if we make contact with a
regular dynamic body, as in the particle examples provided with the MuJoCo distribution (recall that in those example we
have a capsule probe which is a mocap body that we move with the mouse). A contact between two regular bodies will
experience penetration as well as relative velocity, while contact with a mocap body is missing the relative velocity
component because the simulator does not know that the mocap body itself is moving. So the resulting contact force is
smaller and it takes longer for the contact to push the dynamic object away. Also, in more complex simulations the fact
that we are doing something inconsistent with the physics can cause instabilities.
There is however a better-behaved alternative. In addition to the mocap body, we include a second regular body and
connect it to the mocap body with a weld equality constraint. In the plots below, the pink box is the mocap body and
@@ -1658,8 +1526,8 @@ dedicated section :ref:`therein<MjxPerformance>`.
:ref:`Numerical Integration<geIntegration>` section. The default recommended choice is the ``implicitfast``
integrator.
3. :ref:`Constraint Jacobians<option-jacobian>`: Try switching the Jacobian setting between "dense" and "sparse". These
two options use seperate code paths using dense or sparse algebra, but are otherwise compationally identical, so the
faster one is always preferred. The default "auto" heuristic does not always make the right choice.
two options use separate code paths using dense or sparse algebra, but are otherwise computationally identical, so
the faster one is always preferred. The default "auto" heuristic does not always make the right choice.
4. **Constraint solver:** If the profiler reports that a large chunk of time is spent in the solver, consider the
following:
@@ -1668,8 +1536,8 @@ dedicated section :ref:`therein<MjxPerformance>`.
freedom than constraints, the PGS solver will be fastest, though this situation is not common.
- :ref:`iterations<option-iterations>` and :ref:`tolerance<option-tolerance>`: Try reducing the number of iterations
or, equivalently, increasing the solver's termination tolerance. In particular for the Newton solver, which
typically acheives numerical convergence in 2-3 (expensive) iterations, the last iteration increases the precision
to a level that has no noticable effect, and can be skipped.
typically achieves numerical convergence in 2-3 (expensive) iterations, the last iteration increases the precision
to a level that has no noticeable effect, and can be skipped.
5. **Collisions:** If the profiler reports that collision detection takes up a large chunk of the computation
time, consider the following steps:
@@ -1723,6 +1591,8 @@ better visualize and understand the contact configuration and resulting forces.
geometry (e.g., bumps), so slippage is prevented by the normal force and not only frictional components.
b. If contacts are between flat surfaces, try enabling the :ref:`multiccd<option-flag-multiccd>` flag, which allows
the detector to find more contacts than the single contact returned by the convex-convex collider.
c. Try enabling the native collision detection pipeline by setting the :ref:`nativeccd<option-flag-nativeccd>` flag,
which uses a more accurate and efficient convex collision detection algorithm.
**High-frequency vibration**
High-frequency, low-amplitude vibrations are also a real-world problem in many industrial settings, but unlike in
+2 -2
View File
@@ -591,7 +591,7 @@ Flex
Flexes were added in MuJoCo 3.0. They represent deformable meshes that can be 1, 2 or 3 dimensional (thus their elements
are capsules, triangles or tetrahedra). Unlike geoms which are static shapes attached rigidly to a single body, the
elements of a flex are deformable: they are constructed by connecting multiple bodies, thus the body positions and
orientations determine the shape of the flex elements at runtime. These deformable elements suport collisions and
orientations determine the shape of the flex elements at runtime. These deformable elements support collisions and
contact forces, as well as generate passive and constraint forces which softly preserve the shape of the deformable
entity. Automation is provided to load a mesh from a file, construct bodies corresponding to the mesh vertices,
construct flex elements corresponding to the mesh faces (or lines or tetrahedra, depending on dimensionality), and
@@ -939,7 +939,7 @@ positions and orientations as well as the linear and angular velocities of such
The semantics of free joints are as follows. The position data is 7 numbers (3D position followed
by unit quaternion) while the velocity data is 6 numbers (3D linear velocity followed by 3D angular velocity).
The linear postions of free joints are in the global frame, as are
The linear positions of free joints are in the global frame, as are
linear velocities. The orientation of a free joint (the quaternion) is also in the global frame. However, the rotational
velocities of a free joint are in the local body frame. This is not so much a design decision but rather correct
use of the topology of quaternions. Angular velocities live in the quaternion tangent space, which is defined locally
+57 -42
View File
@@ -201,22 +201,22 @@ the ``copy`` callback from :ref:`mjpPlugin` for each plugin instance present.
.. _exActuatorAct:
Actuator activations
""""""""""""""""""""
Actuator states
"""""""""""""""
When writing stateful actuator plugins, there are two choices for where to save the actuator state. One option is using
``plugin_state`` as described above, and the other is to use ``mjData.act`` by implementing the ``actuator_actdim`` and
``actuator_act_dot`` callbacks on :ref:`mjpPlugin`.
``plugin_state`` as described above, and the other is to use ``mjData.act`` by implementing the callback on
:ref:`mjpPlugin`.
When using the latter option, the actuator plugin's state will be added to ``mjData.act``, and MuJoCo will
automatically integrate ``mjData.act_dot`` values between timesteps. One advantage of this approach is that
finite-differencing functions like :ref:`mjd_transitionFD` will work as they do for native actuators. The
``mjpPlugin.advance`` callback will be called after ``act_dot`` is integrated, and actuator plugins may overwrite
the ``act`` values at that point, if Euler integration isn't appropriate.
the ``act`` values at that point, if the built-in integrator is not appropriate.
Users may specify the :ref:`dyntype<actuator-plugin-dyntype>` attribute on actuator plugins, to introduce a filter or
an integrator between user inputs and actuator activations. When they do, the activation variable introduced by
``dyntype`` will be placed *after* the plugin's activation variables in the ``act`` array.
an integrator between user inputs and actuator states. When they do, the state variable introduced by
``dyntype`` will be placed *after* the plugin's state variables in the ``act`` array.
.. _exRegistration:
@@ -269,43 +269,58 @@ A future version of this section will include:
There are several first-party plugin directories:
* **actuator:** The plugins in the `actuator/ <https://github.com/google-deepmind/mujoco/tree/main/plugin/actuator>`__
directory implement custom actuators, so far only a PID controller. See the
`README <https://github.com/google-deepmind/mujoco/blob/main/plugin/actuator/README.md>`__ for details.
* **elasticity:** The plugins in the `elasticity/
<https://github.com/google-deepmind/mujoco/tree/main/plugin/elasticity>`__ directory are passive forces based on
continuum mechanics for 1-dimensional and 2-dimensional bodies. The 1D model is invariant under rotations and captures
the large deformation of elastic cables, decoupling twisting and bending strains. The 2D model is a suitable for
computing the bending stiffness of thin elastic plates (i.e. shells having a flat stress-free configuration). In this
case, the elastic energy is quadratic and therefore the stiffness matrix is constant. For more information, please see
the `README <https://github.com/google-deepmind/mujoco/blob/main/plugin/elasticity/README.md>`__.
* **sensor:** The plugins in the `sensor/ <https://github.com/google-deepmind/mujoco/tree/main/plugin/sensor>`__
directory implement custom sensors. Currently the sole sensor plugin is the touch grid sensor, see the
`README <https://github.com/google-deepmind/mujoco/blob/main/plugin/sensor/README.md>`__ for details.
* **sdf:** The plugins in the `sdf/ <https://github.com/google-deepmind/mujoco/tree/main/plugin/sdf>`__ directory
specify custom shapes in a mesh-free manner, by defining methods computing a signed distance field and its gradient at
query points. This shape then acts as a new geom type in the collision table at the top of `engine_collision_driver.c
<https://github.com/google-deepmind/mujoco/blob/main/src/engine/engine_collision_driver.c>`__. For more information
concerning the available SDFs and how to write your own implicit geometry, please see the `README
<https://github.com/google-deepmind/mujoco/blob/main/plugin/sdf/README.md>`__. The rest of this section will give more
detail concerning the collision algorithm and the plugin engine interface.
actuator
""""""""
The plugins in the `actuator/ <https://github.com/google-deepmind/mujoco/tree/main/plugin/actuator>`__ directory
implement custom actuators, so far only a PID controller. See the `README
<https://github.com/google-deepmind/mujoco/blob/main/plugin/actuator/README.md>`__ for details.
Collision points are found by minimizing the function A + B + abs(max(A, B)), where A and B are the two colliding
SDFs, via gradient descent. Because SDFs are non-convex, multiple starting points are required in order to converge to
multiple local minima. The number of starting points is set using :ref:`sdf_initpoints<option-sdf_initpoints>`, and
are initialized using the Halton sequence inside the intersection of the axis-aligned bounding boxes. The number of
gradient descent iterations is set using :ref:`sdf_iterations<option-sdf_iterations>`.
While *exact* SDFs---encoding the precise signed distance to the surface---are preferred, collisions are possible with
any function whose value vanishes at the surface and grows monotonically away from it, with a negative sign in the
interior. For such functions, it is still possible to find collisons, albeit with a possibly
increased number of starting points.
elasticity
""""""""""
The plugins in the `elasticity/ <https://github.com/google-deepmind/mujoco/tree/main/plugin/elasticity>`__ directory are
passive forces based on continuum mechanics for 1-dimensional and 2-dimensional bodies. The 1D model is invariant under
rotations and captures the large deformation of elastic cables, decoupling twisting and bending strains. The 2D model is
a suitable for computing the bending stiffness of thin elastic plates (i.e. shells having a flat stress-free
configuration). In this case, the elastic energy is quadratic and therefore the stiffness matrix is constant. For more
information, please see the `README
<https://github.com/google-deepmind/mujoco/blob/main/plugin/elasticity/README.md>`__.
The ``sdf_distance`` method is called by the compiler to produce a visual mesh for rendering using the marching cubes
algorithm implemented by `MarchingCubeCpp <https://github.com/aparis69/MarchingCubeCpp>`__.
Future improvement to the gradient descent algorithm, such as a line search which takes advantage of the properties of
SDFs, might reduce the number of iterations and/or starting points.
sensor
""""""
The plugins in the `sensor/ <https://github.com/google-deepmind/mujoco/tree/main/plugin/sensor>`__ directory implement
custom sensors. Currently the sole sensor plugin is the touch grid sensor, see the `README
<https://github.com/google-deepmind/mujoco/blob/main/plugin/sensor/README.md>`__ for details.
.. _exSDF:
sdf
"""
The plugins in the `sdf/ <https://github.com/google-deepmind/mujoco/tree/main/plugin/sdf>`__ directory
specify custom shapes in a mesh-free manner, by defining methods computing a signed distance field and its gradient at
query points. This shape then acts as a new geom type in the collision table at the top of `engine_collision_driver.c
<https://github.com/google-deepmind/mujoco/blob/main/src/engine/engine_collision_driver.c>`__. For more information
concerning the available SDFs and how to write your own implicit geometry, please see the `README
<https://github.com/google-deepmind/mujoco/blob/main/plugin/sdf/README.md>`__. The rest of this section will give more
detail concerning the collision algorithm and the plugin engine interface.
Collision points are found by minimizing the function A + B + abs(max(A, B)), where A and B are the two colliding
SDFs, via gradient descent. Because SDFs are non-convex, multiple starting points are required in order to converge to
multiple local minima. The number of starting points is set using :ref:`sdf_initpoints<option-sdf_initpoints>`, and
are initialized using the Halton sequence inside the intersection of the axis-aligned bounding boxes. The number of
gradient descent iterations is set using :ref:`sdf_iterations<option-sdf_iterations>`.
While *exact* SDFs---encoding the precise signed distance to the surface---are preferred, collisions are possible with
any function whose value vanishes at the surface and grows monotonically away from it, with a negative sign in the
interior. For such functions, it is still possible to find collisions, albeit with a possibly
increased number of starting points.
The ``sdf_distance`` method is called by the compiler to produce a visual mesh for rendering using the marching cubes
algorithm implemented by `MarchingCubeCpp <https://github.com/aparis69/MarchingCubeCpp>`__.
Future improvement to the gradient descent algorithm, such as a line search which takes advantage of the properties of
SDFs, might reduce the number of iterations and/or starting points.
For the sdf plugin, the following methods need to be specified
@@ -317,7 +332,7 @@ For the sdf plugin, the following methods need to be specified
required because mesh creation occurs during model compilation before the plugin object has been instantiated.
``sdf_gradient``:
Computes the gradient in local coodinates of the SDF at the query point.
Computes the gradient in local coordinates of the SDF at the query point.
``sdf_aabb``:
Computes the axis-aligned bounding box in local coordinates. This volume is voxelized uniformly before the call to
@@ -378,7 +393,7 @@ Resource providers work via callbacks:
resource name. For example, the resource name ``http://www.example.com/myasset.obj`` would have
``http://www.example.com/`` as its directory.
- :ref:`mjfResourceModified<mjfResourceModified>`: This callback is optional and is used to check if an existing
opened resource has been modifed from its orginal source.
opened resource has been modified from its original source.
.. _exProviderUsage:
+16 -3
View File
@@ -94,8 +94,8 @@ Building from source
To build MuJoCo from source, you will need CMake and a working C++17 compiler installed. The steps are:
#. Clone the ``mujoco`` repository from GitHub.
#. Create a new build directory somewhere, and ``cd`` into it.
#. Clone the ``mujoco`` repository: ``git clone https://github.com/deepmind/mujoco.git``
#. Create a new build directory and ``cd`` into it.
#. Run :shell:`cmake $PATH_TO_CLONED_REPO` to configure the build.
#. Run ``cmake --build .`` to build.
@@ -120,6 +120,19 @@ installed (see :github:issue:`862` for more details).
`continuous integration setup <https://github.com/google-deepmind/mujoco/blob/main/.github/workflows/build.yml>`_ on
GitHub.
.. _inBuildDocs:
Building the docs
~~~~~~~~~~~~~~~~~
If you wish to build the documentation locally, for example to test pull-requests that improve it, do:
1. Clone the ``mujoco`` repository: ``git clone https://github.com/deepmind/mujoco.git``
2. Go to the ``doc/`` directory: ``cd mujoco/doc``
3. Install the dependencies: ``pip install -r requirements.txt``
4. Build the HTML: ``make html``
5. Open ``_build/html/index.html`` in your browser of choice.
.. _inHeader:
Header files
@@ -130,7 +143,7 @@ links below, to make this documentation self-contained.
`mujoco.h <https://github.com/google-deepmind/mujoco/blob/main/include/mujoco/mujoco.h>`__
This is the main header file and must be included in all programs using MuJoCo. It defines all API functions and
global variables, and includes the all other header files except mjxmacro.h.
global variables, and includes all other header files except mjxmacro.h.
`mjmodel.h <https://github.com/google-deepmind/mujoco/blob/main/include/mujoco/mjmodel.h>`__
Defines the C structure :ref:`mjModel` which is the runtime representation of the
model being simulated. It also defines a number of primitive types and other structures needed to define mjModel.
+130 -47
View File
@@ -1,13 +1,13 @@
Model Editing
-------------
.. admonition:: Unstable API
:class: attention
.. admonition:: New API
:class: note
The API described below is new and unstable. There may be latent bugs and function signatures may change. Early
adopters are welcome (indeed, encouraged) to try it out and report any issues on GitHub.
The API described below is new but feature complete. It is recommended for general use, but latent bugs are still
possible. Please report any issues on GitHub.
As of MuJoCo 3.2, it is possible to create and modify models using the :ref:`mjSpec` struct and related API.
As of MuJoCo 3.2.0, it is possible to create and modify models using the :ref:`mjSpec` struct and related API.
This datastructure is in one-to-one correspondence with MJCF and indeed, MuJoCo's own XML parsers (both MJCF and URDF)
use this API when loading a model.
@@ -18,35 +18,38 @@ Overview
~~~~~~~~
The new API augments the traditional workflow of creating and editing models using XML files, breaking up the *parse* and
*compile* steps. As summarized in the the :ref:`Overview chapter<Instance>`, the traditional workflow is:
*compile* steps. As summarized in the :ref:`Overview chapter<Instance>`, the traditional workflow is:
1. Create an XML model description file (MJCF or URDF) and associated assets. |br|
2. Call :ref:`mj_loadXML`, obtain an :ref:`mjModel` instance.
2. Call :ref:`mj_loadXML`, obtain an mjModel instance.
The new workflow is:
The new workflow using :ref:`mjSpec` is:
1. :ref:`Create<mj_makeSpec>` an empty :ref:`mjSpec` or :ref:`parse<mj_parseXML>` an existing XML file to an
:ref:`mjSpec`.
2. Edit the mutable :ref:`mjSpec` datastructure adding, changing and removing elements.
3. Compile the :ref:`mjSpec` at any point, obtaining an updated :ref:`mjModel` instance. After compilation, the
:ref:`mjSpec` remains editable, so steps 2 and 3 are interchangable.
1. :ref:`Create<mj_makeSpec>` an empty mjSpec or :ref:`parse<mj_parseXML>` an existing XML file.
2. Programmatically edit the mjSpec datastructure by adding, modifying and removing elements.
3. :ref:`Compile<mj_compile>` the mjSpec to an mjModel instance.
After compilation, the mjSpec remains editable, so steps 2 and 3 are interchangeable.
.. _meUsage:
Usage
~~~~~
Here we describe the C API for procedural model editing, but it is also exposed in the
:ref:`Python bindings<PyModelEdit>`.
After creating a new :ref:`mjSpec` or parsing an existing XML file to an :ref:`mjSpec`, procedural editing corresponds
to setting attributes. For example, in order to change the timestep, one can do:
Here we describe the C API for procedural model editing, but it is also exposed in the :ref:`Python
bindings<PyModelEdit>`. Advanced users can refer to `user_api_test.cc
<https://github.com/google-deepmind/mujoco/blob/main/test/user/user_api_test.cc>`__ and the MJCF parser in
`xml_native_reader.cc <https://github.com/google-deepmind/mujoco/blob/main/src/xml/xml_native_reader.cc>`__ for more
usage examples. After creating a new :ref:`mjSpec` or parsing an existing XML file to an :ref:`mjSpec`, procedural
editing corresponds to setting attributes. For example, in order to change the timestep, one can do:
.. code-block:: C
mjSpec* spec = mj_makeSpec();
spec->opt.timestep = 0.01;
...
mjModel* model = mj_compile(spec);
mjModel* model = mj_compile(spec, NULL);
Attributes which have variable length are C++ vectors and strings, :ref:`exposed to C as opaque types<ArrayHandles>`.
In C one uses the provided :ref:`getters<AttributeGetters>` and :ref:`setters<AttributeSetters>`:
@@ -55,61 +58,141 @@ In C one uses the provided :ref:`getters<AttributeGetters>` and :ref:`setters<At
mjs_setString(model->modelname, "my_model");
In C++ one can use these directly:
In C++, one can use vectors and strings directly:
.. code-block:: C++
std::string modelname = "my_model";
*spec->modelname = modelname;
Loading a spec from XML can be done as follows:
.. code-block:: C
std::array<char, 1000> error;
mjSpec* s = mj_parseXML(filename, vfs, error.data(), error.size());
.. _meMjsElements:
Model elements
^^^^^^^^^^^^^^
Model elements corresponding to MJCF are exposed to the user as C structs with the ``mjs`` prefix, the definitions are
listed under the :ref:`Model Editing<tySpecStructure>` section of the struct reference. For example, an MJCF
:ref:`geom<body-geom>` corresponds to an :ref:`mjsGeom`.
Model elements corresponding to MJCF are added to the spec using the corresponding functions. For example, to add a box
geom to the world body, one would do
Global defaults for all elements are set by :ref:`initializers<ElementInitialization>` like :ref:`mjs_defaultGeom`.
These functions are defined in `user_init.c
<https://github.com/google-deepmind/mujoco/blob/main/src/user/user_init.c>`__ and are the source of truth for all
default values.
Elements cannot be created directly; they are returned to the user by the corresponding constructor function, e.g.
:ref:`mjs_addGeom`. For example, to add a box geom to the world body, one would do
.. code-block:: C
mjSpec* spec = mj_makeSpec();
mjsBody* world = mjs_findBody(spec, "world");
mjsGeom* my_geom = mjs_addGeom(world, NULL);
my_geom->type = mjGEOM_BOX;
my_geom->size[0] = my_geom->size[1] = my_geom->size[2] = 0.5;
mjModel* model = mj_compile(spec);
mjSpec* spec = mj_makeSpec(); // make an empty spec
mjsBody* world = mjs_findBody(spec, "world"); // find the world body
mjsGeom* my_geom = mjs_addGeom(world, NULL); // add a geom to the world
my_geom->type = mjGEOM_BOX; // set geom type
my_geom->size[0] = my_geom->size[1] = my_geom->size[2] = 0.5; // set box size
mjModel* model = mj_compile(spec, NULL); // compile to mjModel
The ``NULL`` second argument to :ref:`mjs_addGeom` is the optional default class pointer. When using defaults
procedurally, default classes are passed in explicitly to element constructors. The global defaults of all elements
(used when no default class is passed in) can be inspected in
`user_init.c <https://github.com/google-deepmind/mujoco/blob/main/src/user/user_init.c>`__.
.. _meAttachment:
Attachment
^^^^^^^^^^
The new framework introduces a powerful new feature: attaching and detaching model subtrees. Attachment allows the user
copy a subtree from one model into another, while also copying related referenced assets and referencing elements from
outside the kinematic tree (e.g., actuators and sensors). Similarly, detaching a subtree will remove all associated
elements from the model.
This feature is incomplete and will be described in detail once it is fully implemented, but it is already used to power
the :ref:`attach<body-attach>` and :ref:`replicate<replicate>` meta-elements in MJCF.
This framework introduces a powerful new feature: attaching and detaching model subtrees. This feature is already used
to power the :ref:`attach<body-attach>` an :ref:`replicate<replicate>` meta-elements in MJCF. Attachment allows the user
to move or copy a subtree from one model into another, while also copying or moving related referenced assets and
referencing elements from outside the kinematic tree (e.g., actuators and sensors). Similarly, detaching a subtree will
remove all associated elements from the model. The default behavior is to move during attach. The user can select to
instead copy by passing the corresponding flag to ``mjs_setDeepCopy``. This flag is temporary set to true while parsing
XMLs. It is possible to :ref:`attach a body to a frame<mjs_attachBody>`:
.. code-block:: C
.. _meKnownIssues:
mjSpec* parent = mj_makeSpec();
mjSpec* child = mj_makeSpec();
parent->compiler.degree = 0;
child->compiler.degree = 1;
mjsFrame* frame = mjs_addFrame(mjs_findBody(parent, "world"), NULL);
mjsBody* body = mjs_addBody(mjs_findBody(child, "world"), NULL);
mjsBody* attached_body_1 = mjs_attachBody(frame, body, "attached-", "-1");
Known issues
~~~~~~~~~~~~
or :ref:`attach a body to a site<mjs_attachToSite>`:
- Better documentation is still missing and will be added in the future. In the meantime, advanced users can refer
to `user_api_test.cc <https://github.com/google-deepmind/mujoco/blob/main/test/user/user_api_test.cc>`__ and the MJCF
parser in `xml_native_reader.cc <https://github.com/google-deepmind/mujoco/blob/main/src/xml/xml_native_reader.cc>`__,
which is already using this API.
- One of the central design considerations of the new API is incremental compilation, meaning that after making small
changes to a spec that has already been compiled, subsequent re-compilation will be very fast. While the code is
written to support incremental compilation, this functionality is not fully implemented and will be added in the
future, resulting in faster re-compilation times.
- Since the main test for the new API is the MJCF parser, which always constructs a model from scratch, there
might be latent bugs related to model editing. Please report such bugs if you encounter them.
.. code-block:: C
mjSpec* parent = mj_makeSpec();
mjSpec* child = mj_makeSpec();
mjsSite* site = mjs_addSite(mjs_findBody(parent, "world"), NULL);
mjsBody* body = mjs_addBody(mjs_findBody(child, "world"), NULL);
mjsBody* attached_body_2 = mjs_attachToSite(site, body, "attached-", "-2");
or :ref:`attach a frame to a body<mjs_attachFrame>`:
.. code-block:: C
mjSpec* parent = mj_makeSpec();
mjSpec* child = mj_makeSpec();
mjsBody* body = mjs_addBody(mjs_findBody(parent, "world"), NULL);
mjsFrame* frame = mjs_addFrame(mjs_findBody(child, "world"), NULL);
mjsFrame* attached_frame = mjs_attachFrame(body, frame, "attached-", "-1");
Note that in the above examples, the parent and child models have different values for ``compiler.degree``,
corresponding to the :ref:`compiler/angle<compiler-angle>` attribute, specifying the units in which angles are
interperted. Compiler flags are carried over during attachment, so the child model will be compiled using the child
flags, while the parent will be compiled using the parent flags.
Note also that once a child is attached by reference to a parent, the child cannot be compiled on its own.
.. _meDefault:
Default classes
^^^^^^^^^^^^^^^
Default classes are fully supported in the new API, however using them requires an understanding of how defaults
are implemented. As explained in the :ref:`Default settings <CDefault>` section, default classes are first loaded as a
tree of dummy elements, which are then used to initialize elements which reference them. When editing models with
defaults, this initialization is explicit:
.. code-block:: C
mjSpec* spec = mj_makeSpec();
mjsDefault* main = mjs_getSpecDefault(spec);
main->geom.type = mjGEOM_BOX;
mjsGeom* geom = mjs_addGeom(mjs_findBody(spec, "world"), main);
Importantly, changing a default class after it has been used to initialize elements will not change the properties of
already initialized elements.
.. admonition:: Possible future change
:class: note
The behaviour described above, where defaults are only applied at initialization, is a remnant of the old, XML-only
loading pipeline. A future API change could allow defaults to be changed and applied after initialization. If you
think this feature is important to you, please let us know on GitHub.
.. _meSaving:
XML saving
^^^^^^^^^^
Specs can be saved to an XML file or string using :ref:`mj_saveXML` or :ref:`mj_saveXMLString`, respectively.
Saving requires that the spec first be compiled.
Importantly, the saved XML will take into account any defined defaults. This is useful when a model has many repeated
values, for example if loaded from URDF, which does not support defaults. In such a case one can add default classes,
set the class of the relevant elements, and save; the resulting XML will use the defaults and be more human-readable.
.. _meRecompilation:
In-place recompilation
^^^^^^^^^^^^^^^^^^^^^^
Compilation with :ref:`mj_compile` can be called at any point to obtain a new mjModel instance. In contrast,
:ref:`mj_recompile` updates an existing mjModel and mjData pair in-place, while preserving the simulation state. This
allows model editing to occur **during simulation**, for example adding or removing bodies.
+4 -3
View File
@@ -61,8 +61,8 @@ Where the command line arguments are
- The ``ctrlnoise`` argument prevents models from settling into a static state where, due to warmstarts, one can
measure artificially faster simulation.
- When ``npoolthread > 1`` is specified, an engine-internal :ref:`mjThreadPool` is created with the specified number of
threads, to speed up simulation of large scenes. Note that while it is possible to to use both ``nthread`` and
``npoolthread``, the scenarios for which one would want these different type of multithreading are usually mutually
threads, to speed up simulation of large scenes. Note that while it is possible to use both ``nthread`` and
``npoolthread``, the scenarios for which one would want these different types of multithreading are usually mutually
exclusive.
- For more repeatable performance statistics, run the tool with the ``performance``
`governor <https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt>`__ on Linux, or the
@@ -85,6 +85,7 @@ functionality. This code sample uses the native UI to render various controls, a
illustration of how the new UI framework is intended to be used. Below is a screen-capture of ``simulate`` in action:
.. youtube:: P83tKA1iz2Y
:aspect: 16:9
:width: 95%
:align: center
@@ -203,7 +204,7 @@ data file into a playable movie file:
ffmpeg -f rawvideo -pixel_format rgb24 -video_size 2560x1440
-framerate 60 -i rgb.out -vf "vflip,format=yuv420p" video.mp4
Note that the offscreen rendering resolution of the model and ffmpeg's video_size must be the identical.
Note that the offscreen rendering resolution of the model and ffmpeg's video_size must be identical.
This sample can be compiled in three ways which differ in how the OpenGL context is created: using GLFW with an
invisible window, using OSMesa, or using EGL. The latter two options are only available on Linux and are envoked by
+21 -11
View File
@@ -267,7 +267,7 @@ control laws obtained from trajectory optimizers would normally be indexed by ``
The reason for the "official" caveat above is because user callbacks may store additional state variables that change
over time and affect the callback outputs; indeed the field ``mjData.userdata`` exists mostly for that purpose. Other
state-like quantities that are part of mjData and are treated as inputs by forward dynamics are ``mjData.mocap_pos`` and
mjData.mocap_quat. These quantities are unusual in that they are meant to change at each time step (normally driven by a
``mjData.mocap_quat``. These quantities are unusual in that they are meant to change at each time step (normally driven by a
motion capture device), however this change is implemented by the user, while the simulator treats them as constants. In
that sense they are no different from all the constants in mjModel, or the function callback pointers set by the user:
such constants affect the computation, but are not part of the state vector of a dynamical system.
@@ -286,7 +286,7 @@ Next we turn to the controls and applied forces. The control vector in MuJoCo is
u = (mjData.ctrl, mjData.qfrc_applied, mjData.xfrc_applied)
These quantities specify control signals (``mjData.ctrl``) for the actuators defined in the model, or directly apply
forces and torques specified in joint space (``mjData.qfrc_applied``) or in Cartesian space (mjData.xfrc_applied).
forces and torques specified in joint space (``mjData.qfrc_applied``) or in Cartesian space (``mjData.xfrc_applied``).
Finally, calling mj_forward which corresponds to the abstract dynamics function ``f(t,x,u)`` computes the
time-derivative of the state vector. The corresponding fields of mjData are
@@ -556,6 +556,13 @@ or termination of the iterative solver.
Model changes
~~~~~~~~~~~~~
.. admonition:: Model editing framework
:class: tip
The discussion below regarding mjModel changes at runtime was written before the 3.2.0 introduction of the
:doc:`Model Editing<modeledit>` framework. It is still valid, but the new framework is the safe and recommended way
to modify models.
The MuJoCo model contained in mjModel is supposed to represent constant physical properties of the system, and in
theory should not change after compilation. Of course in practice things are not that simple. It is often desirable to
change the physics options in ``mjModel.opt``, so as to experiment with different aspects of the physics or to create
@@ -590,8 +597,8 @@ asking a "decompiler" to make corresponding changes to the C code -- it is just
.. _siLayout:
Data layout and buffer allocation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Data layout
~~~~~~~~~~~
All matrices in MuJoCo are in **row-major** format. For example, the linear memory array (a0, a1, ... a5) represents the
2-by-3 matrix
@@ -607,12 +614,15 @@ essential to keep it in mind at all times. All MuJoCo utility functions that ope
:ref:`mju_mulMatMat`, :ref:`mju_mulMatVec` etc. assume this matrix layout. For vectors there is of course no
difference between row-major and column-major formats.
.. TODO(tassa): update this section when qM is migrated to CSR.
When possible, MuJoCo exploits sparsity. This can make all the difference between O(N) and O(N^3) scaling. The inertia
matrix ``mjData.qM`` and its LTDL factorization ``mjData.qLD`` are always represented as sparse, using a custom
indexing format designed for matrices that correspond to tree topology. The functions :ref:`mj_factorM`,
:ref:`mj_solveM`, :ref:`mj_solveM2` and :ref:`mj_mulM` are used for sparse factorization, substitution and
matrix-vector multiplication. The user can also convert these matrices to dense format with the function
:ref:`mj_fullM` although MuJoCo never does that internally.
matrix ``mjData.qM`` and its LTDL factorization ``mjData.qLD`` are always represented as sparse. ``qM`` uses a custom
indexing format designed for matrices that correspond to tree topology, while ``qLD`` uses the standard CSR format.
``qM`` will be migrated to CSR in and upcoming change. The functions :ref:`mj_factorM`, :ref:`mj_solveM`,
:ref:`mj_solveM2` and :ref:`mj_mulM` are used for sparse factorization, substitution and matrix-vector multiplication.
The user can also convert these matrices to dense format with the function :ref:`mj_fullM` although MuJoCo never does
that internally.
The constraint Jacobian matrix ``mjData.efc_J`` is represented as sparse whenever the sparse Jacobian option is
enabled. The function :ref:`mj_isSparse` can be used to determine if sparse format is currently in use. In that case
@@ -712,8 +722,8 @@ and :ref:`mj_stackAllocByte` is provided for allocation of arbitrary number of b
.. _siError:
Errors, warnings, memory allocation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Errors and warnings
~~~~~~~~~~~~~~~~~~~
When a terminal error occurs, MuJoCo calls the function :ref:`mju_error` internally. Here is what mju_error does:
+1 -1
View File
@@ -151,7 +151,7 @@ The low-level mjvGLCamera is what determines the actual rendering. There are two
for each eye. Each has position, forward and up directions. Forward corresponds to the negative Z axis of the camera
frame, while up corresponds to the positive Y axis. There is also a frustum in the sense of OpenGL, except we store the
average of the left and right frustum edges and then during rendering compute the actual edges from the viewport aspect
ratio assuming 1:1 pixel aspect ratio. The distance between the two camera positions corresponds to the inter-pupilary
ratio assuming 1:1 pixel aspect ratio. The distance between the two camera positions corresponds to the inter-pupillary
distance (ipd). When the low-level camera parameters are computed automatically from an abstract camera, the ipd as well
as vertical field of view (fovy) are taken from ``mjModel.vis.global.ipd``/``fovy`` for free and tracking cameras, and
from the camera-specific ``mjModel.cam_ipd/fovy`` for cameras defined in the model. When stereoscopic mode is not
+222 -34
View File
@@ -126,7 +126,7 @@ attributes:
- ``user_scn``: an :ref:`mjvScene` object that allows users to add change rendering flags and add custom
visualization geoms to the rendered scene. This is separate from the ``mjvScene`` that the viewer uses internally to
render the final scene, and is entirely under the user's control. User scripts can call e.g. :ref:`mjv_initGeom` or
:ref:`mjv_makeConnector` to add visualization geoms to ``user_scn``, and upon the next call to ``sync()``, the viewer
:ref:`mjv_connector` to add visualization geoms to ``user_scn``, and upon the next call to ``sync()``, the viewer
will incorporate these geoms to future rendered images. Similarly, user scripts can make changes to ``user_scn.flags``
which would be picked up at the next call to ``sync()``. The ``sync()`` call also copies changes to rendering flags
made via the GUI back into ``user_scn`` to preserve consistency. For example:
@@ -243,7 +243,9 @@ access to the raw memory used by MuJoCo without copying or buffering. This means
:ref:`mj_step`) change the content of fields *in place*. The user is therefore advised to create copies where required.
For example, when logging the position of a body, one could write
``positions.append(data.body('my_body').xpos.copy())``. Without the ``.copy()``, the list would contain identical
elements, all pointing to the most recent value.
elements, all pointing to the most recent value. The same applies to NumPy slices. For example if a local
variable ``qpos_slice = data.qpos[3:8]`` is created and then :ref:`mj_step` is called, the values in ``qpos_slice``
will have been changed.
In order to conform to `PEP 8 <https://peps.python.org/pep-0008/>`__
naming guidelines, struct names begin with a capital letter, for example ``mjData`` becomes ``mujoco.MjData`` in Python.
@@ -469,19 +471,17 @@ the raw callback pointer, and the GIL will **not** be acquired each time the cal
Model editing
=============
The :doc:`Model Editing<programming/modeledit>` framework which allows for procedural model manipulation is exposed
via Python. In many ways this API is conceptually similar to ``dm_control``'s
`PyMJCF module <https://github.com/google-deepmind/dm_control/tree/main/dm_control/mjcf#readme>`__, where ``MjSpec``
plays the role of ``mjcf_model``. The largest difference between these two APIs is speed. Native model manipulation via
``MjSpec`` is around ~100x faster than PyMJCF.
The C API for model editing is documented in the :doc:`Programming<../programming/modeledit>` chapter.
This functionality is mirrored in the Python API, with the addition of several convenience methods.
Below is a minimal usage example, more examples can be found in the Model Editing
`colab notebook <https://colab.research.google.com/github/google-deepmind/mujoco/blob/main/python/mjspec.ipynb>`__.
Below is a simple example of how to use the model editing API. For more examples, please refer to
`specs_test.py <https://github.com/google-deepmind/mujoco/blob/main/python/mujoco/specs_test.py>`__.
.. code-block:: python
import mujoco
spec = mujoco.MjSpec()
spec.modelname = "my model"
body = spec.worldbody.add_body(
pos=[1, 2, 3],
quat=[0, 1, 0, 0],
@@ -495,18 +495,163 @@ Below is a simple example of how to use the model editing API. For more examples
...
model = spec.compile()
.. admonition:: Missing features
:class: attention
Construction
------------
We are aware of multiple missing features in the Python API, including:
The ``MjSpec`` object wraps the :ref:`mjSpec` struct and can be constructed in three ways:
- Better tree traversal utilities like :python:`children = body.children()` etc.
- PyMJCF's notion of "binding", allowing access to :ref:`mjModel` and :ref:`mjData` values via the associated ``mjs``
elements.
1. Create an empty spec: ``spec = mujoco.MjSpec()``
2. Load the spec from XML string: ``spec = mujoco.MjSpec.from_string(xml_string)``
3. Load the spec from XML file: ``spec = mujoco.MjSpec.from_file(file_path)``
There are certainly other missing features that we are not aware of. Please contact us on GitHub with feature
requests or bug reports and we will prioritize accordingly.
Note the ``from_string()`` and ``from_file()`` methods can only be called at construction time.
Assets
^^^^^^
All three methods take in an optional argument called ``assets`` which is used to resolve asset references in the XML.
This argument is a dictionary that maps asset name (string) to asset data (bytes), as demonstrated below:
.. code-block:: python
assets = {'image.png': b'image_data'}
spec = mujoco.MjSpec.from_string(xml_referencing_image_png, assets=assets)
model = spec.compile()
Save to XML
-----------
Compiled ``MjSpec`` objects can be saved to XML string with the ``to_xml()`` method:
.. code-block:: python
print(spec.to_xml())
.. code-block:: XML
<mujoco model="my model">
<compiler angle="radian"/>
<worldbody>
<body pos="1 2 3" quat="0 1 0 0">
<geom name="my_geom" size="1" rgba="1 0 0 1"/>
</body>
</worldbody>
</mujoco>
Attachment
----------
It is possible to combine multiple specs by using attachments. The following options are possible:
- Attach a body from the child spec to a frame in the parent spec: ``body.attach_body(body, prefix, suffix)``, returns
the reference to the attached body, which should be identical to the body used as input.
- Attach a frame from the child spec to a body in the parent spec: ``body.attach_frame(frame, prefix, suffix)``,
returns the reference to the attached frame, which should be identical to the frame used as input.
- Attach a child spec to a site in the parent spec: ``parent_spec.attach(child_spec, site=site_name_or_obj)``, returns
the reference to a frame, which is the attached worldbody transformed into a frame. The site must belong to the
child spec. Prefix and suffix can also be specified as keyword arguments.
- Attach a child spec to a frame in the parent spec: ``parent_spec.attach(child_spec, frame=frame_name_or_obj)``,
returns the reference to a frame, which is the attached worldbody transformed into a frame. The frame must belong to
the child spec. Prefix and suffix can also be specified as keyword arguments.
The default behavior of attaching is to not copy, so all the child references (except for the worldbody) are still valid
in the parent and therefore modifying the child will modify the parent. This is not true for the attach
:ref:`attach<body-attach>` and :ref:`replicate<replicate>` meta-elements in MJCF, which create deep copies while
attaching. However, it is possible to override the default behavior by setting ``spec.copy_during_attach`` to
``True``. In this case, the child spec is copied and the references to the child will not point to the parent.
.. code-block:: python
import mujoco
# Create the parent spec.
parent = mujoco.MjSpec()
body = parent.worldbody.add_body()
frame = parent.worldbody.add_frame()
site = parent.worldbody.add_site()
# Create the child spec.
child = mujoco.MjSpec()
child_body = child.worldbody.add_body()
child_frame = child.worldbody.add_frame()
# Attach the child to the parent in different ways.
body_in_frame = frame.attach_body(child_body, 'child-', '')
frame_in_body = body.attach_frame(child_frame, 'child-', '')
worldframe_in_site = parent.attach(child, site=site, prefix='child-')
worldframe_in_frame = parent.attach(child, frame=frame, prefix='child-')
Convenience methods
-------------------
The Python bindings provide a number of convenience methods and attributes not directly available in the C API in order
to make model editing easier:
Named access
^^^^^^^^^^^^
The ``MjSpec`` object has methods like ``.body(), .joint(), .site(), ...`` for named access of elements.
``spec.geom('my_geom')`` will return the :ref:`mjsGeom` called "my_geom", or ``None`` if it does not exist.
Element lists
^^^^^^^^^^^^^
Lists of all elements in a spec can be accessed using named properties, using the plural form. For example,
``spec.meshes`` returns a list of all meshes in the spec. The following properties are implemented: ``sites``,
``geoms``, ``joints``, ``lights``, ``cameras``, ``bodies``, ``frames``, ``materials``, ``meshes``, ``pairs``,
``equalities``, ``tendons``, ``actuators``, ``skins``, ``textures``, ``texts``, ``tuples``, ``flexes``, ``hfields``,
``keys``, ``numerics``, ``excludes``, ``sensors``, ``plugins``.
Tree traversal
^^^^^^^^^^^^^^
Traversal of the kinematic tree is aided by the following methods which return tree-related lists of elements:
Direct children:
Like the spec-level element lists described above, bodies have properties which return lists of all direct children.
For example, ``body.geoms`` returns a list of all geoms that are direct children of the body. This works for all
in tree elements namely ``bodies``, ``joints``, ``geoms``, ``sites``, ``cameras``, ``lights`` and ``frames``.
Recursive search:
``body.find_all()`` returns a list of all elements of the given type which are in the subtree of the given body.
Element types can be specified with the :ref:`mjtObj` enum, or with the corresponding string. For example either
``body.find_all(mujoco.mjtObj.mjOBJ_SITE)`` or ``body.find_all('site')`` will return a list of all sites under the
body.
Parent:
The parent body of a given element -- including bodies and frames -- can be accessed via the ``parent`` property.
For example, the parent of a site can be accessed via ``site.parent``.
Relationship to ``PyMJCF``
--------------------------
`dm_control <https://github.com/google-deepmind/dm_control/tree/main>`__'s
`PyMJCF <https://github.com/google-deepmind/dm_control/blob/main/dm_control/mjcf/README.md>`__ module provides similar
functionality to the native model editing API described here, but is roughly two orders of magnitude slower due to its
reliance on Python manipulation of strings.
For users familiar with ``PyMJCF``, the ``MjSpec`` object is conceptually similar to ``dm_control``'s
``mjcf_model``. A more detailed migration guide could be added here in the future; in the meantime, note that the
Model Editing
`colab notebook <https://colab.research.google.com/github/google-deepmind/mujoco/blob/main/python/mjspec.ipynb>`__
includes a reimplementation of the ``PyMJCF`` example in the ``dm_control``
`tutorial notebook <https://github.com/google-deepmind/dm_control/blob/main/dm_control/mjcf/tutorial.ipynb>`__.
``PyMJCF`` provides a notion of "binding", giving access to :ref:`mjModel` and :ref:`mjData` values via a helper class.
In the native API, the helper class is not needed, so it is possible to directly bind an ``mjs`` object to
:ref:`mjModel` and :ref:`mjData`. This requires the objects to have a non-empty name. For example, say we have multiple
geoms containing the string "torso" in their name. We want to get their Cartesian positions in the XY plane from
``mjData``. This can be done as follows:
.. code-block:: python
torsos = [data.bind(geom) for geom in spec.geoms if 'torso' in geom.name]
pos_x = [torso.xpos[0] for torso in torsos]
pos_y = [torso.xpos[1] for torso in torsos]
Notes
-----
- :ref:`mj_recompile` works differently than in the C API. In the C API, it modifies the model and the data in place,
while in the Python API it returns new :ref:`MjModel` and :ref:`MjData` objects. This is to avoid dangling references.
.. _PyBuild:
@@ -593,34 +738,77 @@ The ``mujoco`` package contains two sub-modules: ``mujoco.rollout`` and ``mujoco
rollout
-------
``mujoco.rollout`` and ``mujoco.rollout.Rollout`` shows how to add additional C/C++ functionality, exposed as a Python
module via pybind11. It is implemented in `rollout.cc
<https://github.com/google-deepmind/mujoco/blob/main/python/mujoco/rollout.cc>`__ and wrapped in `rollout.py
<https://github.com/google-deepmind/mujoco/blob/main/python/mujoco/rollout.py>`__. The module addresses a common
use-case where tight loops implemented outside of Python are beneficial: rolling out a trajectory (i.e., calling
:ref:`mj_step` in a loop), given an initial state and sequence of controls, and returning subsequent states and sensor
values. The rollouts are run in parallel with an internally managed thread pool if multiple MjData instances (one per
thread) are passed as an argument. This notebook shows how to use ``rollout`` |rollout_colab|, along with some
benchmarks e.g., the figure below.
``mujoco.rollout`` shows how to add additional C/C++ functionality, exposed as a Python module via pybind11. It is
implemented in `rollout.cc <https://github.com/google-deepmind/mujoco/blob/main/python/mujoco/rollout.cc>`__
and wrapped in `rollout.py <https://github.com/google-deepmind/mujoco/blob/main/python/mujoco/rollout.py>`__. The module
performs a common functionality where tight loops implemented outside of Python are beneficial: rolling out a trajectory
(i.e., calling :ref:`mj_step` in a loop), given an intial state and sequence of controls, and returning subsequent
states and sensor values. The basic usage form is
.. |rollout_colab| image:: https://colab.research.google.com/assets/colab-badge.svg
:target: https://colab.research.google.com/github/google-deepmind/mujoco/blob/main/python/rollout.ipynb
.. image:: images/python/rollout.png
:align: right
:width: 97%
The basic usage form is
.. code-block:: python
state, sensordata = rollout.rollout(model, data, initial_state, control)
``initial_state`` is an ``nroll x nstate`` array, with ``nroll`` initial states of size ``nstate``, where
``nstate = mj_stateSize(model, mjtState.mjSTATE_FULLPHYSICS)`` is the size of the
:ref:`full physics state<geFullPhysics>`. ``control`` is a ``nroll x nstep x ncontrol`` array of controls. Controls are
by default the ``mjModel.nu`` standard actuators, but any combination of :ref:`user input<geInput>` arrays can be
specified by passing an optional ``control_spec`` bitflag.
- ``model`` is either a single instance of MjModel or a sequence of homogeneous MjModels of length ``nbatch``.
Homogeneous models have the same integer sizes, but floating point values can differ.
- ``data`` is either a single instance of MjData or a sequence of compatible MjDatas of length ``nthread``.
- ``initial_state`` is an ``nbatch x nstate`` array, with ``nbatch`` initial states of size ``nstate``, where
``nstate = mj_stateSize(model, mjtState.mjSTATE_FULLPHYSICS)`` is the size of the
:ref:`full physics state<geFullPhysics>`.
- ``control`` is a ``nbatch x nstep x ncontrol`` array of controls. Controls are by default the ``mjModel.nu`` standard
actuators, but any combination of :ref:`user input<geInput>` arrays can be specified by passing an optional
``control_spec`` bitflag.
If a rollout diverges, the current state and sensor values are used to fill the remainder of the trajectory.
Therefore, non-increasing time values can be used to detect diverged rollouts.
The ``rollout`` function is designed to be completely stateless, so all inputs of the stepping pipeline are set and any
values already present in the given ``MjData`` instance will have no effect on the output.
The ``rollout`` function is designed to be computationally stateless, so all inputs of the stepping pipeline are set and
any values already present in the given ``MjData`` instance will have no effect on the output.
Since the Global Interpreter Lock can be released, this function can be efficiently threaded using Python threads. See
the ``test_threading`` function in
By default ``rollout.rollout`` creates a new thread pool every call if ``len(data) > 1``. To reuse the thread pool
over multiple calls use the ``persistent_pool`` argument. ``rollout.rollout`` is not thread safe when using
a persistent pool. The basic usage form is
.. code-block:: python
state, sensordata = rollout.rollout(model, data, initial_state, persistent_pool=True)
The pool is shutdown on interpreter shutdown or by a call to ``rollout.shutdown_persistent_pool``.
To use multiple thread pools from multiple threads, use ``Rollout`` objects. The basic usage form is
.. code-block:: python
# Pool shutdown upon exiting block.
with rollout.Rollout(nthread=nthread) as rollout_:
rollout_.rollout(model, data, initial_state)
or
.. code-block:: python
# Pool shutdown on object deletion or call to rollout_.close().
# To ensure clean shutdown of threads, call close() before interpreter exit.
rollout_ = rollout.Rollout(nthread=nthread)
rollout_.rollout(model, data, initial_state)
rollout_.close()
Since the Global Interpreter Lock is released, this function can also be threaded using Python threads. However, this
is less efficient than using native threads. See the ``test_threading`` function in
`rollout_test.py <https://github.com/google-deepmind/mujoco/blob/main/python/mujoco/rollout_test.py>`__ for an example
of threaded operation (and more generally for usage examples).
of threaded operation (and for more general usage examples).
.. _PyMinimize:
@@ -769,7 +957,7 @@ USD Export API
- ``add_camera(self, pos, rotation_xyz, obj_name)``: adds a camera to the USD scene with the given properties post hoc.
- ``save_scene(self, filetype)``: exports the USD scene using one of the usd filetype extensions ``.usd``, ``.usda``,
- ``save_scene(self, filetype)``: exports the USD scene using one of the USD filetype extensions ``.usd``, ``.usda``,
or ``.usdc``.
.. _PyUSDTodos:
+1 -1
View File
@@ -6,7 +6,7 @@ sphinxcontrib-youtube==1.2.0
sphinx-copybutton==0.5.2
sphinx-favicon==1.0.1
sphinx-reredirects==0.1.1
sphinx-toolbox==3.4.0
sphinx-toolbox==3.8.2
nbsphinx==0.9.1
pandoc==1.1.0
pygments==2.15.0
+3 -3
View File
@@ -11,10 +11,10 @@
#}
<script>
document.addEventListener('DOMContentLoaded', (event) => {
let home_link = document.querySelector('a.icon');
let homeLink = document.querySelector('a.icon');
{# Be extra safe and don't break the page if theme changes and querySelector can't match. #}
if (home_link) {
home_link.href="https://mujoco.org";
if (homeLink) {
homeLink.href="https://mujoco.org";
}
});
</script>
+34 -16
View File
@@ -5,20 +5,27 @@ Unity Plug-in
Introduction
------------
The MuJoCo `Unity plug-in <https://github.com/google-deepmind/mujoco/tree/main/unity>`_ allows the Unity Editor and
The MuJoCo `Unity plug-in <https://github.com/google-deepmind/mujoco/tree/main/unity>`__ allows the Unity Editor and
runtime to use the MuJoCo physics engine. Users can import MJCF files and edit the models in the Editor. The plug-in
relies on Unity for most aspects -- assets, game logic, simulation time -- but uses MuJoCo to determine how objects
move, giving the designer access to MuJoCo's full API.
An example project using MuJoCo's Unity plugin in a set of introductory tutorials are also available as a `standalone
repository <https://github.com/Balint-H/mj-unity-tutorial>`__.
.. _UInstallation:
Installation instructions
-------------------------
The plug-in directory (available at https://github.com/google-deepmind/mujoco/tree/main/unity) includes a
The `plug-in directory <https://github.com/google-deepmind/mujoco/tree/main/unity>`__ includes a
``package.json`` file. Unity's package manager recognizes this file and will import the plug-in's C# codebase to your
project. In addition, Unity also needs the native MuJoCo library, which can be found in the specific platform archive at
https://github.com/google-deepmind/mujoco/releases.
project. In addition, Unity also needs the native MuJoCo library, which can be found in the corresponding `platform
archive <https://github.com/google-deepmind/mujoco/releases>`__. If you wish to simply use the plug-in and not
develop it, you should use one of the version-specific stable commits of the repository, identified by git tags. Check
out the relevant version of the cloned repository with git (``git checkout 3.X.Y`` where X and Y specify the engine
version). Simply using the ``main`` branch of the repository may not be compatible with the most recent release binary
of MuJoCo.
On Unity version 2020.2 and later, the Package Manager will look for the native library file and copy it to the package
directory when the package is imported. Alternatively, you can manually copy the native library to the package directory
@@ -30,14 +37,14 @@ _____
The MuJoCo app needs to be run at least once before the native library can be used, in order to register the library as
a trusted binary. Then, copy the dynamic library file from
``/Applications/MuJoCo.app/Contents/Frameworks/mujoco.framework/Versions/Current/libmujoco.3.2.5.dylib`` (it can be
``/Applications/MuJoCo.app/Contents/Frameworks/mujoco.framework/Versions/Current/libmujoco.3.3.1.dylib`` (it can be
found by browsing the contents of ``MuJoCo.app``) and rename it as ``mujoco.dylib``.
Linux
_____
Expand the ``tar.gz`` archive to ``~/.mujoco``. Then copy the dynamic library from
``~/.mujoco/mujoco-3.2.5/lib/libmujoco.so.3.2.5`` and rename it as ``libmujoco.so``.
``~/.mujoco/mujoco-3.3.1/lib/libmujoco.so.3.3.1`` and rename it as ``libmujoco.so``.
Windows
_______
@@ -115,7 +122,7 @@ This design principle has several implications:
- The layout of MuJoCo components in the GameObject hierarchy determines the layout of the resulting MuJoCo model.
Therefore, we adopt a design rule that **every game object must have at most one MuJoCo component**.
- We rely on Unity for spatial configuration, which requires vector components to be `swizzled
<https://en.wikipedia.org/wiki/Swizzling_(computer_graphics)>`_ since Unity uses left-handed frames with Y as the
<https://en.wikipedia.org/wiki/Swizzling_(computer_graphics)>`__ since Unity uses left-handed frames with Y as the
vertical axis, while MuJoCo uses right-handed frames with Z as the vertical axis.
- Unity transform scaling affects positions, orientations, and scale of the entire game object subtree. However, MuJoCo
doesn’t support collision of skewed cylinders and capsules (skewed spheres are supported via the ellipsoid primitive).
@@ -145,10 +152,6 @@ effects:
material assets for geom RGBA specification.
- It allows the importer to handle :ref:`\<include\> <include>` elements without replicating MuJoCo’s file-system
workflow.
- The current version of MuJoCo generates MJCF files with explicit :ref:`\<inertial\> <body-inertial>` elements, even when
the original model uses geoms for implicit definition of the body inertia. If you plan to change geom properties of
an imported model, remove these auto-generated ``MjInertial`` components manually. We plan to address this in a
future release of MuJoCo.
In Unity, there is no equivalent to MJCF’s “cascading” :ref:`\<default\> <default>` clauses. Therefore, components in
Unity reflect the corresponding elements’ state after applying all the relevant default classes, and the class structure
@@ -177,9 +180,10 @@ Scene recreation maintains continuity of physics and state in the following way:
persisted.
4. The MuJoCo state (for the joints that persisted) is set from the cache, and Unity transforms are synchronized.
Because the MuJoCo library doesn’t (yet) expose an API for scene editing, adding and removing MuJoCo components causes
complete scene recreation. This can be expensive for large models or if it happens frequently. We expect this
performance limitation to be lifted in future versions of MuJoCo.
MuJoCo has functionality for dynamic scene editing (through :ref:`mjSpec`), however, this is not yet
supported in the Unity plugin. Therefore, adding and removing MuJoCo components causes complete scene recreation. This
can be expensive for large models or if it happens frequently. We intend to lift this performance limitation to be in a
future versions of the plugin.
Global Settings
_______________
@@ -323,12 +327,26 @@ The plug-in allows using arbitrary Unity meshes for MuJoCo collision. At model
<http://www.qhull.org/>`__ to create a convex hull of the mesh, and uses that for collisions. Currently the computed
convex hull is not visible in Unity, but we intend to expose it in future versions.
Height fields
_____________
MuJoCo hfields are represented in Unity through terrain gameobjects. This allows the use of the terrain editing tools
available in Unity to generate shapes for collisions with MuJoCo. When selecting hfield type in the Unity geom
component, the right click context menu provides utility to add the corresponding Unity terrain to the scene. The data
from the terrain is dynamically kept in sync with the simulation.
MuJoCo plugins
______________
The current version of the Unity package does not support loading MJCF scenes that use :ref:`MuJoCo plugins<exPlugin>` such as
`elasticity <https://github.com/google-deepmind/mujoco/tree/main/plugin/elasticity#readme>`__ . Adding basic functionality to do this will be part of an upcoming release.
Interaction with External Processes
___________________________________
Roboti’s `MuJoCo plug-in for Unity <https://roboti.us/download.html>`_ steps the simulation in an external Python
Roboti’s `MuJoCo plug-in for Unity <https://roboti.us/download.html>`__ steps the simulation in an external Python
process, and uses Unity only for rendering. In contrast, our plug-in relies on Unity to step the simulation. It should
be possible to use our plug-in while an external process "drives" the simulation, for example by setting ``qpos``,
calling ``mj_kinematics``, synchronizing the transforms, and then using Unity to render or compute game logic. In order
to establish communication with an external process, you can use Unity's `ML-Agents
<https://github.com/Unity-Technologies/ml-agents>`_ package.
<https://github.com/Unity-Technologies/ml-agents>`__ package.
+15 -10
View File
@@ -197,7 +197,8 @@ struct mjData_ {
int nf; // number of friction constraints
int nl; // number of limit constraints
int nefc; // number of constraints
int nnzJ; // number of non-zeros in constraint Jacobian
int nJ; // number of non-zeros in constraint Jacobian
int nA; // number of non-zeros in constraint inverse inertia matrix
int nisland; // number of detected constraint islands
// global properties
@@ -291,8 +292,8 @@ struct mjData_ {
mjtNum* actuator_length; // actuator lengths (nu x 1)
int* moment_rownnz; // number of non-zeros in actuator_moment row (nu x 1)
int* moment_rowadr; // row start address in colind array (nu x 1)
int* moment_colind; // column indices in sparse Jacobian (nu x nv)
mjtNum* actuator_moment; // actuator moments (nu x nv)
int* moment_colind; // column indices in sparse Jacobian (nJmom x 1)
mjtNum* actuator_moment; // actuator moments (nJmom x 1)
// computed by mj_fwdPosition/mj_crb
mjtNum* crb; // com-based composite inertia and mass (nbody x 10)
@@ -301,7 +302,6 @@ struct mjData_ {
// computed by mj_fwdPosition/mj_factorM
mjtNum* qLD; // L'*D*L factorization of M (sparse) (nM x 1)
mjtNum* qLDiagInv; // 1/diag(D) (nv x 1)
mjtNum* qLDiagSqrtInv; // 1/sqrt(diag(D)) (nv x 1)
// computed by mj_collisionTree
mjtNum* bvh_aabb_dyn; // global bounding box (center, size) (nbvhdynamic x 6)
@@ -340,12 +340,17 @@ struct mjData_ {
int* B_rownnz; // body-dof: non-zeros in each row (nbody x 1)
int* B_rowadr; // body-dof: address of each row in B_colind (nbody x 1)
int* B_colind; // body-dof: column indices of non-zeros (nB x 1)
int* M_rownnz; // inertia: non-zeros in each row (nv x 1)
int* M_rowadr; // inertia: address of each row in M_colind (nv x 1)
int* M_colind; // inertia: column indices of non-zeros (nM x 1)
int* mapM2M; // index mapping from M (legacy) to M (CSR) (nM x 1)
int* C_rownnz; // reduced dof-dof: non-zeros in each row (nv x 1)
int* C_rowadr; // reduced dof-dof: address of each row in C_colind (nv x 1)
int* C_colind; // reduced dof-dof: column indices of non-zeros (nC x 1)
int* mapM2C; // index mapping from M to C (nC x 1)
int* D_rownnz; // dof-dof: non-zeros in each row (nv x 1)
int* D_rowadr; // dof-dof: address of each row in D_colind (nv x 1)
int* D_diag; // dof-dof: index of diagonal element (nv x 1)
int* D_colind; // dof-dof: column indices of non-zeros (nD x 1)
int* mapM2D; // index mapping from M to D (nD x 1)
int* mapD2M; // index mapping from D to M (nM x 1)
@@ -389,13 +394,13 @@ struct mjData_ {
int* efc_J_rownnz; // number of non-zeros in constraint Jacobian row (nefc x 1)
int* efc_J_rowadr; // row start address in colind array (nefc x 1)
int* efc_J_rowsuper; // number of subsequent rows in supernode (nefc x 1)
int* efc_J_colind; // column indices in constraint Jacobian (nnzJ x 1)
int* efc_J_colind; // column indices in constraint Jacobian (nJ x 1)
int* efc_JT_rownnz; // number of non-zeros in constraint Jacobian row T (nv x 1)
int* efc_JT_rowadr; // row start address in colind array T (nv x 1)
int* efc_JT_rowsuper; // number of subsequent rows in supernode T (nv x 1)
int* efc_JT_colind; // column indices in constraint Jacobian T (nnzJ x 1)
mjtNum* efc_J; // constraint Jacobian (nnzJ x 1)
mjtNum* efc_JT; // constraint Jacobian transposed (nnzJ x 1)
int* efc_JT_colind; // column indices in constraint Jacobian T (nJ x 1)
mjtNum* efc_J; // constraint Jacobian (nJ x 1)
mjtNum* efc_JT; // constraint Jacobian transposed (nJ x 1)
mjtNum* efc_pos; // constraint position (equality, contact) (nefc x 1)
mjtNum* efc_margin; // inclusion margin (contact) (nefc x 1)
mjtNum* efc_frictionloss; // frictionloss (friction) (nefc x 1)
@@ -419,8 +424,8 @@ struct mjData_ {
// computed by mj_projectConstraint (PGS solver)
int* efc_AR_rownnz; // number of non-zeros in AR (nefc x 1)
int* efc_AR_rowadr; // row start address in colind array (nefc x 1)
int* efc_AR_colind; // column indices in sparse AR (nefc x nefc)
mjtNum* efc_AR; // J*inv(M)*J' + R (nefc x nefc)
int* efc_AR_colind; // column indices in sparse AR (nA x 1)
mjtNum* efc_AR; // J*inv(M)*J' + R (nA x 1)
//-------------------- arena-allocated: POSITION, VELOCITY dependent
+36 -9
View File
@@ -29,6 +29,7 @@
#define mjMAXIMP 0.9999 // maximum constraint impedance
#define mjMAXCONPAIR 50 // maximum number of contacts per geom pair
#define mjMAXTREEDEPTH 50 // maximum bounding volume hierarchy depth
#define mjMAXFLEXNODES 27 // maximum number of flex nodes
//---------------------------------- sizes ---------------------------------------------------------
@@ -43,6 +44,7 @@
#define mjNSOLVER 200 // size of one mjData.solver array
#define mjNISLAND 20 // number of mjData.solver arrays
//---------------------------------- enum types (mjt) ----------------------------------------------
typedef enum mjtDisableBit_ { // disable default feature bitflags
@@ -62,8 +64,9 @@ typedef enum mjtDisableBit_ { // disable default feature bitflags
mjDSBL_MIDPHASE = 1<<13, // mid-phase collision filtering
mjDSBL_EULERDAMP = 1<<14, // implicit integration of joint damping in Euler integrator
mjDSBL_AUTORESET = 1<<15, // automatic reset when numerical issues are detected
mjDSBL_NATIVECCD = 1<<16, // native convex collision detection
mjNDISABLE = 16 // number of disable flags
mjNDISABLE = 17 // number of disable flags
} mjtDisableBit;
@@ -75,9 +78,8 @@ typedef enum mjtEnableBit_ { // enable optional feature bitflags
// experimental features:
mjENBL_MULTICCD = 1<<4, // multi-point convex collision detection
mjENBL_ISLAND = 1<<5, // constraint island discovery
mjENBL_NATIVECCD = 1<<6, // native convex collision detection
mjNENABLE = 7 // number of enable flags
mjNENABLE = 6 // number of enable flags
} mjtEnableBit;
@@ -266,7 +268,9 @@ typedef enum mjtObj_ { // type of MujoCo object
mjNOBJECT, // number of object types
// meta elements, do not appear in mjModel
mjOBJ_FRAME = 100 // frame
mjOBJ_FRAME = 100, // frame
mjOBJ_DEFAULT // default
} mjtObj;
@@ -347,6 +351,8 @@ typedef enum mjtSensor_ { // type of sensor
mjSENS_GEOMFROMTO, // segment between two geoms
// global sensors
mjSENS_E_POTENTIAL, // potential energy
mjSENS_E_KINETIC, // kinetic energy
mjSENS_CLOCK, // simulation time
// plugin-controlled sensors
@@ -605,6 +611,7 @@ struct mjModel_ {
int ncam; // number of cameras
int nlight; // number of lights
int nflex; // number of flexes
int nflexnode; // number of dofs in all flexes
int nflexvert; // number of vertices in all flexes
int nflexedge; // number of edges in all flexes
int nflexelem; // number of elements in all flexes
@@ -619,6 +626,9 @@ struct mjModel_ {
int nmeshtexcoord; // number of texcoords in all meshes
int nmeshface; // number of triangular faces in all meshes
int nmeshgraph; // number of ints in mesh auxiliary data
int nmeshpoly; // number of polygons in all meshes
int nmeshpolyvert; // number of vertices in all polygons
int nmeshpolymap; // number of polygons in vertex map
int nskin; // number of skins
int nskinvert; // number of vertices in all skins
int nskintexvert; // number of vertiex with texcoords in all skins
@@ -655,19 +665,20 @@ struct mjModel_ {
int nuser_actuator; // number of mjtNums in actuator_user
int nuser_sensor; // number of mjtNums in sensor_user
int nnames; // number of chars in all names
int nnames_map; // number of slots in the names hash map
int npaths; // number of chars in all paths
// sizes set after mjModel construction (only affect mjData)
// sizes set after mjModel construction
int nnames_map; // number of slots in the names hash map
int nM; // number of non-zeros in sparse inertia matrix
int nB; // number of non-zeros in sparse body-dof matrix
int nC; // number of non-zeros in sparse reduced dof-dof matrix
int nD; // number of non-zeros in sparse dof-dof matrix
int nJmom; // number of non-zeros in sparse actuator_moment matrix
int ntree; // number of kinematic trees under world body
int ngravcomp; // number of bodies with nonzero gravcomp
int nemax; // number of potential equality-constraint rows
int njmax; // number of available rows in constraint Jacobian
int nconmax; // number of potential contacts in contact list
int njmax; // number of available rows in constraint Jacobian (legacy)
int nconmax; // number of potential contacts in contact list (legacy)
int nuserdata; // number of mjtNums reserved for the user
int nsensordata; // number of mjtNums in sensor data vector
int npluginstate; // number of mjtNums in plugin state vector
@@ -856,6 +867,9 @@ struct mjModel_ {
int* flex_dim; // 1: lines, 2: triangles, 3: tetrahedra (nflex x 1)
int* flex_matid; // material id for rendering (nflex x 1)
int* flex_group; // group for visibility (nflex x 1)
int* flex_interp; // interpolation (0: vertex, 1: nodes) (nflex x 1)
int* flex_nodeadr; // first node address (nflex x 1)
int* flex_nodenum; // number of nodes (nflex x 1)
int* flex_vertadr; // first vertex address (nflex x 1)
int* flex_vertnum; // number of vertices (nflex x 1)
int* flex_edgeadr; // first edge address (nflex x 1)
@@ -869,15 +883,19 @@ struct mjModel_ {
int* flex_evpairadr; // first evpair address (nflex x 1)
int* flex_evpairnum; // number of evpairs (nflex x 1)
int* flex_texcoordadr; // address in flex_texcoord; -1: none (nflex x 1)
int* flex_nodebodyid; // node body ids (nflexnode x 1)
int* flex_vertbodyid; // vertex body ids (nflexvert x 1)
int* flex_edge; // edge vertex ids (2 per edge) (nflexedge x 2)
int* flex_elem; // element vertex ids (dim+1 per elem) (nflexelemdata x 1)
int* flex_elemtexcoord; // element texture coordinates (dim+1) (nflexelemdata x 1)
int* flex_elemedge; // element edge ids (nflexelemedge x 1)
int* flex_elemlayer; // element distance from surface, 3D only (nflexelem x 1)
int* flex_shell; // shell fragment vertex ids (dim per frag) (nflexshelldata x 1)
int* flex_evpair; // (element, vertex) collision pairs (nflexevpair x 2)
mjtNum* flex_vert; // vertex positions in local body frames (nflexvert x 3)
mjtNum* flex_xvert0; // Cartesian vertex positions in qpos0 (nflexvert x 3)
mjtNum* flex_vert0; // vertex positions in qpos0 on [0, 1]^d (nflexvert x 3)
mjtNum* flex_node; // node positions in local body frames (nflexnode x 3)
mjtNum* flex_node0; // Cartesian node positions in qpos0 (nflexnode x 3)
mjtNum* flexedge_length0; // edge lengths in qpos0 (nflexedge x 1)
mjtNum* flexedge_invweight0; // edge inv. weight in qpos0 (nflexedge x 1)
mjtNum* flex_radius; // radius around primitive element (nflex x 1)
@@ -918,6 +936,15 @@ struct mjModel_ {
mjtNum* mesh_pos; // translation applied to asset vertices (nmesh x 3)
mjtNum* mesh_quat; // rotation applied to asset vertices (nmesh x 4)
int* mesh_pathadr; // address of asset path for mesh; -1: none (nmesh x 1)
int* mesh_polynum; // number of polygons per mesh (nmesh x 1)
int* mesh_polyadr; // first polygon address per mesh (nmesh x 1)
mjtNum* mesh_polynormal; // all polygon normals (nmeshpoly x 3)
int* mesh_polyvertadr; // polygon vertex start address (nmeshpoly x 1)
int* mesh_polyvertnum; // number of vertices per polygon (nmeshpoly x 1)
int* mesh_polyvert; // all polygon vertices (nmeshpolyvert x 1)
int* mesh_polymapadr; // first polygon address per vertex (nmeshvert x 1)
int* mesh_polymapnum; // number of polygons per vertex (nmeshvert x 1)
int* mesh_polymap; // vertex to polygon map (nmeshpolymap x 1)
// skins
int* skin_matid; // skin material id; -1: none (nskin x 1)
+1 -1
View File
@@ -48,7 +48,7 @@ typedef void (*mjfGetResourceDir)(mjResource* resource, const char** dir, int* n
// callback for checking if the current resource was modified from the time
// specified by the timestamp
// returns 0 if the resource's timestamp matches the provided timestamp
// returns > 0 if the the resource is younger than the given timestamp
// returns > 0 if the resource is younger than the given timestamp
// returns < 0 if the resource is older than the given timestamp
typedef int (*mjfResourceModified)(const mjResource* resource, const char* timestamp);
+9 -9
View File
@@ -115,14 +115,14 @@ struct mjrContext_ { // custom OpenGL context
unsigned int auxColor_r[mjNAUX]; // auxiliary color buffer for resolving
// materials with textures
int mat_texid[mjMAXMATERIAL*mjNTEXROLE]; // material texture ids (-1: no texture)
int mat_texuniform[mjMAXMATERIAL]; // texture repetition for 2d mapping
int mat_texrepeat[mjMAXMATERIAL*2]; // texture repetition for 2d mapping
int mat_texid[mjMAXMATERIAL*mjNTEXROLE]; // material texture ids (-1: no texture)
int mat_texuniform[mjMAXMATERIAL]; // uniform cube mapping
float mat_texrepeat[mjMAXMATERIAL*2]; // texture repetition for 2d mapping
// texture objects and info
int ntexture; // number of allocated textures
int textureType[mjMAXTEXTURE]; // type of texture (mjtTexture) (ntexture)
unsigned int texture[mjMAXTEXTURE]; // texture names
int ntexture; // number of allocated textures
int textureType[mjMAXTEXTURE]; // type of texture (mjtTexture) (ntexture)
unsigned int texture[mjMAXTEXTURE]; // texture names
// displaylist starting positions
unsigned int basePlane; // all planes from model
@@ -161,13 +161,13 @@ struct mjrContext_ { // custom OpenGL context
int windowDoublebuffer; // is default/window framebuffer double buffered
// framebuffer
int currentBuffer; // currently active framebuffer: mjFB_WINDOW or mjFB_OFFSCREEN
int currentBuffer; // currently active framebuffer: mjFB_WINDOW or mjFB_OFFSCREEN
// pixel output format
int readPixelFormat; // default color pixel format for mjr_readPixels
int readPixelFormat; // default color pixel format for mjr_readPixels
// depth output format
int readDepthMap; // depth mapping: mjDEPTH_ZERONEAR or mjDEPTH_ZEROFAR
int readDepthMap; // depth mapping: mjDEPTH_ZERONEAR or mjDEPTH_ZEROFAR
};
typedef struct mjrContext_ mjrContext;
+22 -15
View File
@@ -62,10 +62,11 @@ typedef enum mjtGeomInertia_ { // type of inertia inference
} mjtGeomInertia;
typedef enum mjtMeshInertia_ { // type of mesh inertia
mjINERTIA_CONVEX = 0, // convex mesh inertia
mjINERTIA_EXACT, // exact mesh inertia
mjINERTIA_LEGACY, // legacy mesh inertia
typedef enum mjtMeshInertia_ { // type of mesh inertia
mjMESH_INERTIA_CONVEX = 0, // convex mesh inertia
mjMESH_INERTIA_EXACT, // exact mesh inertia
mjMESH_INERTIA_LEGACY, // legacy mesh inertia
mjMESH_INERTIA_SHELL // shell mesh inertia
} mjtMeshInertia;
@@ -121,30 +122,34 @@ typedef struct mjsElement_ { // element type, do not modify
} mjsElement;
typedef struct mjSpec_ { // model specification
mjsElement* element; // element type
mjString* modelname; // model name
// compiler settings
typedef struct mjsCompiler_ { // compiler options
mjtByte autolimits; // infer "limited" attribute based on range
double boundmass; // enforce minimum body mass
double boundinertia; // enforce minimum body diagonal inertia
double settotalmass; // rescale masses and inertias; <=0: ignore
mjtByte balanceinertia; // automatically impose A + B >= C rule
mjtByte strippath; // automatically strip paths from mesh files
mjtByte fitaabb; // meshfit to aabb instead of inertia box
mjtByte degree; // angles in radians or degrees
char eulerseq[3]; // sequence for euler rotations
mjString* meshdir; // mesh and hfield directory
mjString* texturedir; // texture directory
mjtByte discardvisual; // discard visual geoms in parser
mjtByte convexhull; // compute mesh convex hulls
mjtByte usethread; // use multiple threads to speed up compiler
mjtByte fusestatic; // fuse static bodies with parent
int inertiafromgeom; // use geom inertias (mjtInertiaFromGeom)
int inertiagrouprange[2]; // range of geom groups used to compute inertia
int alignfree; // align free joints with inertial frame
mjLROpt LRopt; // options for lengthrange computation
} mjsCompiler;
typedef struct mjSpec_ { // model specification
mjsElement* element; // element type
mjString* modelname; // model name
// compiler data
mjsCompiler compiler; // compiler options
mjtByte strippath; // automatically strip paths from mesh files
mjString* meshdir; // mesh and hfield directory
mjString* texturedir; // texture directory
// engine data
mjOption option; // physics options
@@ -435,10 +440,13 @@ typedef struct mjsFlex_ { // flex specification
double thickness; // thickness (2D only)
// mesh properties
mjStringVec* nodebody; // node body names
mjStringVec* vertbody; // vertex body names
mjDoubleVec* node; // node positions
mjDoubleVec* vert; // vertex positions
mjIntVec* elem; // element vertex ids
mjFloatVec* texcoord; // vertex texture coordinates
mjIntVec* elemtexcoord; // element texture coordinates
// other
mjString* info; // message appended to compiler errors
@@ -453,14 +461,13 @@ typedef struct mjsMesh_ { // mesh specification
double refpos[3]; // reference position
double refquat[4]; // reference orientation
double scale[3]; // rescale mesh
mjtMeshInertia inertia; // inertia type (convex, legacy, exact)
mjtMeshInertia inertia; // inertia type (convex, legacy, exact, shell)
mjtByte smoothnormal; // do not exclude large-angle faces from normals
int maxhullvert; // maximum vertex count for the convex hull
mjFloatVec* uservert; // user vertex data
mjFloatVec* usernormal; // user normal data
mjFloatVec* usertexcoord; // user texcoord data
mjIntVec* userface; // user vertex indices
mjIntVec* userfacenormal; // user normal indices
mjIntVec* userfacetexcoord; // user texcoord indices
mjsPlugin plugin; // sdf plugin
mjString* info; // message appended to compiler errors
+1 -1
View File
@@ -304,7 +304,7 @@ struct mjUI_ { // entire UI
// UI sizes (framebuffer units)
int width; // width
int height; // current heigth
int height; // current height
int maxheight; // height when all sections open
int scroll; // scroll from top of UI
+8
View File
@@ -516,9 +516,14 @@ struct mjvSceneState_ {
int* flex_dim;
int* flex_matid;
int* flex_group;
int* flex_interp;
int* flex_nodeadr;
int* flex_nodenum;
int* flex_nodebodyid;
int* flex_vertadr;
int* flex_vertnum;
int* flex_elem;
int* flex_elemtexcoord;
int* flex_elemlayer;
int* flex_elemadr;
int* flex_elemnum;
@@ -529,8 +534,11 @@ struct mjvSceneState_ {
int* flex_texcoordadr;
int* flex_bvhadr;
int* flex_bvhnum;
mjtByte* flex_centered;
mjtNum* flex_node;
mjtNum* flex_radius;
float* flex_rgba;
float* flex_texcoord;
int* hfield_pathadr;
+56 -25
View File
@@ -81,6 +81,7 @@
XMJV( ncam ) \
XMJV( nlight ) \
XMJV( nflex ) \
X ( nflexnode ) \
XMJV( nflexvert ) \
X ( nflexedge ) \
X ( nflexelem ) \
@@ -95,6 +96,9 @@
X ( nmeshtexcoord ) \
X ( nmeshface ) \
X ( nmeshgraph ) \
X ( nmeshpoly ) \
X ( nmeshpolyvert ) \
X ( nmeshpolymap ) \
XMJV( nskin ) \
XMJV( nskinvert ) \
X ( nskintexvert ) \
@@ -137,11 +141,12 @@
X ( nB ) \
X ( nC ) \
X ( nD ) \
X ( nJmom ) \
XMJV( ntree ) \
X ( ngravcomp ) \
X ( nemax ) \
X ( njmax ) \
X ( nconmax ) \
XMJV( ntree ) \
X ( ngravcomp ) \
X ( nuserdata ) \
XMJV( nsensordata ) \
X ( npluginstate ) \
@@ -325,6 +330,9 @@
XMJV( int, flex_dim, nflex, 1 ) \
XMJV( int, flex_matid, nflex, 1 ) \
XMJV( int, flex_group, nflex, 1 ) \
XMJV( int, flex_interp, nflex, 1 ) \
XMJV( int, flex_nodeadr, nflex, 1 ) \
XMJV( int, flex_nodenum, nflex, 1 ) \
XMJV( int, flex_vertadr, nflex, 1 ) \
XMJV( int, flex_vertnum, nflex, 1 ) \
X ( int, flex_edgeadr, nflex, 1 ) \
@@ -338,15 +346,19 @@
X ( int, flex_evpairadr, nflex, 1 ) \
X ( int, flex_evpairnum, nflex, 1 ) \
XMJV( int, flex_texcoordadr, nflex, 1 ) \
XMJV( int, flex_nodebodyid, nflexnode, 1 ) \
X ( int, flex_vertbodyid, nflexvert, 1 ) \
X ( int, flex_edge, nflexedge, 2 ) \
XMJV( int, flex_elem, nflexelemdata, 1 ) \
XMJV( int, flex_elemtexcoord, nflexelemdata, 1 ) \
X ( int, flex_elemedge, nflexelemedge, 1 ) \
XMJV( int, flex_elemlayer, nflexelem, 1 ) \
XMJV( int, flex_shell, nflexshelldata,1 ) \
X ( int, flex_evpair, nflexevpair, 2 ) \
X ( mjtNum, flex_vert, nflexvert, 3 ) \
X ( mjtNum, flex_xvert0, nflexvert, 3 ) \
X ( mjtNum, flex_vert0, nflexvert, 3 ) \
XMJV( mjtNum, flex_node, nflexnode, 3 ) \
X ( mjtNum, flex_node0, nflexnode, 3 ) \
X ( mjtNum, flexedge_length0, nflexedge, 1 ) \
X ( mjtNum, flexedge_invweight0, nflexedge, 1 ) \
XMJV( mjtNum, flex_radius, nflex, 1 ) \
@@ -357,12 +369,12 @@
X ( mjtByte, flex_edgeequality, nflex, 1 ) \
X ( mjtByte, flex_rigid, nflex, 1 ) \
X ( mjtByte, flexedge_rigid, nflexedge, 1 ) \
X ( mjtByte, flex_centered, nflex, 1 ) \
XMJV( mjtByte, flex_centered, nflex, 1 ) \
XMJV( mjtByte, flex_flatskin, nflex, 1 ) \
XMJV( int, flex_bvhadr, nflex, 1 ) \
XMJV( int, flex_bvhnum, nflex, 1 ) \
XMJV( float, flex_rgba, nflex, 4 ) \
X ( float, flex_texcoord, nflextexcoord, 2 ) \
XMJV( float, flex_texcoord, nflextexcoord, 2 ) \
X ( int, mesh_vertadr, nmesh, 1 ) \
X ( int, mesh_vertnum, nmesh, 1 ) \
X ( int, mesh_normaladr, nmesh, 1 ) \
@@ -377,14 +389,23 @@
X ( mjtNum, mesh_scale, nmesh, 3 ) \
X ( mjtNum, mesh_pos, nmesh, 3 ) \
X ( mjtNum, mesh_quat, nmesh, 4 ) \
X ( float, mesh_vert, nmeshvert, 3 ) \
X ( float, mesh_normal, nmeshnormal, 3 ) \
X ( float, mesh_texcoord, nmeshtexcoord, 2 ) \
X ( int, mesh_face, nmeshface, 3 ) \
X ( int, mesh_facenormal, nmeshface, 3 ) \
X ( int, mesh_facetexcoord, nmeshface, 3 ) \
X ( int, mesh_graph, nmeshgraph, 1 ) \
XNV ( float, mesh_vert, nmeshvert, 3 ) \
XNV ( float, mesh_normal, nmeshnormal, 3 ) \
XNV ( float, mesh_texcoord, nmeshtexcoord, 2 ) \
XNV ( int, mesh_face, nmeshface, 3 ) \
XNV ( int, mesh_facenormal, nmeshface, 3 ) \
XNV ( int, mesh_facetexcoord, nmeshface, 3 ) \
XNV ( int, mesh_graph, nmeshgraph, 1 ) \
XMJV( int, mesh_pathadr, nmesh, 1 ) \
X ( int, mesh_polynum, nmesh, 1 ) \
X ( int, mesh_polyadr, nmesh, 1 ) \
X ( mjtNum, mesh_polynormal, nmeshpoly, 3 ) \
X ( int, mesh_polyvertadr, nmeshpoly, 1 ) \
X ( int, mesh_polyvertnum, nmeshpoly, 1 ) \
X ( int, mesh_polyvert, nmeshpolyvert, 1 ) \
X ( int, mesh_polymapadr, nmeshvert, 1 ) \
X ( int, mesh_polymapnum, nmeshvert, 1 ) \
X ( int, mesh_polymap, nmeshpolymap, 1 ) \
XMJV( int, skin_matid, nskin, 1 ) \
XMJV( int, skin_group, nskin, 1 ) \
XMJV( float, skin_rgba, nskin, 4 ) \
@@ -411,14 +432,14 @@
X ( int, hfield_nrow, nhfield, 1 ) \
X ( int, hfield_ncol, nhfield, 1 ) \
X ( int, hfield_adr, nhfield, 1 ) \
X ( float, hfield_data, nhfielddata, 1 ) \
XNV ( float, hfield_data, nhfielddata, 1 ) \
XMJV( int, hfield_pathadr, nhfield, 1 ) \
X ( int, tex_type, ntex, 1 ) \
X ( int, tex_height, ntex, 1 ) \
X ( int, tex_width, ntex, 1 ) \
X ( int, tex_nchannel, ntex, 1 ) \
X ( int, tex_adr, ntex, 1 ) \
X ( mjtByte, tex_data, ntexdata, 1 ) \
XNV ( mjtByte, tex_data, ntexdata, 1 ) \
XMJV( int, tex_pathadr, ntex, 1 ) \
XMJV( int, mat_texid, nmat, mjNTEXROLE ) \
XMJV( mjtByte, mat_texuniform, nmat, 1 ) \
@@ -625,13 +646,12 @@
X ( mjtNum, actuator_length, nu, 1 ) \
X ( int, moment_rownnz, nu, 1 ) \
X ( int, moment_rowadr, nu, 1 ) \
X ( int, moment_colind, nu, MJ_M(nv) ) \
X ( mjtNum, actuator_moment, nu, MJ_M(nv) ) \
X ( int, moment_colind, nJmom, 1 ) \
X ( mjtNum, actuator_moment, nJmom, 1 ) \
X ( mjtNum, crb, nbody, 10 ) \
X ( mjtNum, qM, nM, 1 ) \
X ( mjtNum, qLD, nM, 1 ) \
X ( mjtNum, qLDiagInv, nv, 1 ) \
X ( mjtNum, qLDiagSqrtInv, nv, 1 ) \
XMJV( mjtNum, bvh_aabb_dyn, nbvhdynamic, 6 ) \
XMJV( mjtByte, bvh_active, nbvh, 1 ) \
X ( mjtNum, flexedge_velocity, nflexedge, 1 ) \
@@ -652,12 +672,17 @@
X ( int, B_rownnz, nbody, 1 ) \
X ( int, B_rowadr, nbody, 1 ) \
X ( int, B_colind, nB, 1 ) \
X ( int, M_rownnz, nv, 1 ) \
X ( int, M_rowadr, nv, 1 ) \
X ( int, M_colind, nM, 1 ) \
X ( int, mapM2M, nM, 1 ) \
X ( int, C_rownnz, nv, 1 ) \
X ( int, C_rowadr, nv, 1 ) \
X ( int, C_colind, nC, 1 ) \
X ( int, mapM2C, nC, 1 ) \
X ( int, D_rownnz, nv, 1 ) \
X ( int, D_rowadr, nv, 1 ) \
X ( int, D_diag, nv, 1 ) \
X ( int, D_colind, nD, 1 ) \
X ( int, mapM2D, nD, 1 ) \
X ( int, mapD2M, nM, 1 ) \
@@ -689,13 +714,13 @@
X( int, efc_J_rownnz, MJ_D(nefc), 1 ) \
X( int, efc_J_rowadr, MJ_D(nefc), 1 ) \
X( int, efc_J_rowsuper, MJ_D(nefc), 1 ) \
X( int, efc_J_colind, MJ_D(nnzJ), 1 ) \
X( int, efc_J_colind, MJ_D(nJ), 1 ) \
X( int, efc_JT_rownnz, MJ_M(nv), 1 ) \
X( int, efc_JT_rowadr, MJ_M(nv), 1 ) \
X( int, efc_JT_rowsuper, MJ_M(nv), 1 ) \
X( int, efc_JT_colind, MJ_D(nnzJ), 1 ) \
X( mjtNum, efc_J, MJ_D(nnzJ), 1 ) \
X( mjtNum, efc_JT, MJ_D(nnzJ), 1 ) \
X( int, efc_JT_colind, MJ_D(nJ), 1 ) \
X( mjtNum, efc_J, MJ_D(nJ), 1 ) \
X( mjtNum, efc_JT, MJ_D(nJ), 1 ) \
X( mjtNum, efc_pos, MJ_D(nefc), 1 ) \
X( mjtNum, efc_margin, MJ_D(nefc), 1 ) \
X( mjtNum, efc_frictionloss, MJ_D(nefc), 1 ) \
@@ -714,8 +739,8 @@
#define MJDATA_ARENA_POINTERS_DUAL \
X( int, efc_AR_rownnz, MJ_D(nefc), 1 ) \
X( int, efc_AR_rowadr, MJ_D(nefc), 1 ) \
X( int, efc_AR_colind, MJ_D(nefc), MJ_D(nefc) ) \
X( mjtNum, efc_AR, MJ_D(nefc), MJ_D(nefc) )
X( int, efc_AR_colind, MJ_D(nA), 1 ) \
X( mjtNum, efc_AR, MJ_D(nA), 1 )
// array fields of mjData that are used for constraint islands
#define MJDATA_ARENA_POINTERS_ISLAND \
@@ -755,7 +780,8 @@
X( int, nf ) \
X( int, nl ) \
X( int, nefc ) \
X( int, nnzJ ) \
X( int, nJ ) \
X( int, nA ) \
X( int, nisland ) \
X( mjtNum, time ) \
X( uintptr_t, threadpool )
@@ -766,7 +792,7 @@
X( size_t, maxuse_threadstack, mjMAXTHREAD, 1 ) \
X( mjWarningStat, warning, mjNWARNING, 1 ) \
X( mjTimerStat, timer, mjNTIMER, 1 ) \
X( mjSolverStat, solver, mjNILSAND, mjNSOLVER ) \
X( mjSolverStat, solver, mjNISLAND, mjNSOLVER ) \
X( int, solver_niter, mjNISLAND, 1 ) \
X( int, solver_nnz, mjNISLAND, 1 ) \
X( mjtNum, solver_fwdinv, 2, 1 ) \
@@ -778,4 +804,9 @@
// redefine X to expand to nothing, and XMJV to do what's required
#define XMJV X
// alias XNV to be the same as X
// to obtain only X macros for fields that are relevant for mjvScene creation,
// redefine XNV to expand to nothing
#define XNV X
#endif // MUJOCO_MJXMACRO_H_
+62 -38
View File
@@ -16,7 +16,7 @@
#define MUJOCO_MUJOCO_H_
// header version; should match the library version as returned by mj_version()
#define mjVERSION_HEADER 325
#define mjVERSION_HEADER 331
// needed to define size_t, fabs and log10
#include <stdlib.h>
@@ -110,17 +110,18 @@ MJAPI mjModel* mj_compile(mjSpec* s, const mjVFS* vfs);
// Recompile spec to model, preserving the state, return 0 on success.
MJAPI int mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d);
// Update XML data structures with info from low-level model, save as MJCF.
// Update XML data structures with info from low-level model created with mj_loadXML, save as MJCF.
// If error is not NULL, it must have size error_sz.
MJAPI int mj_saveLastXML(const char* filename, const mjModel* m, char* error, int error_sz);
// Free last XML model if loaded. Called internally at each load.
MJAPI void mj_freeLastXML(void);
// Save spec to XML string, return 1 on success, 0 otherwise.
// Save spec to XML string, return 0 on success, -1 on failure.
// If length of the output buffer is too small, returns the required size.
MJAPI int mj_saveXMLString(const mjSpec* s, char* xml, int xml_sz, char* error, int error_sz);
// Save spec to XML file, return 1 on success, 0 otherwise.
// Save spec to XML file, return 0 on success, -1 otherwise.
MJAPI int mj_saveXML(const mjSpec* s, const char* filename, char* error, int error_sz);
@@ -241,6 +242,9 @@ MJAPI void mj_deleteSpec(mjSpec* s);
// Activate plugin. Returns 0 on success.
MJAPI int mjs_activatePlugin(mjSpec* s, const char* name);
// Turn deep copy on or off attach. Returns 0 on success.
MJAPI int mjs_setDeepCopy(mjSpec* s, int deepcopy);
//---------------------------------- Printing ------------------------------------------------------
@@ -253,11 +257,11 @@ MJAPI void mj_printModel(const mjModel* m, const char* filename);
// Print mjData to text file, specifying format.
// float_format must be a valid printf-style format string for a single float value
MJAPI void mj_printFormattedData(const mjModel* m, mjData* d, const char* filename,
MJAPI void mj_printFormattedData(const mjModel* m, const mjData* d, const char* filename,
const char* float_format);
// Print data to text file.
MJAPI void mj_printData(const mjModel* m, mjData* d, const char* filename);
MJAPI void mj_printData(const mjModel* m, const mjData* d, const char* filename);
// Print matrix to screen.
MJAPI void mju_printMat(const mjtNum* mat, int nr, int nc);
@@ -364,7 +368,8 @@ MJAPI void mj_factorM(const mjModel* m, mjData* d);
MJAPI void mj_solveM(const mjModel* m, mjData* d, mjtNum* x, const mjtNum* y, int n);
// Half of linear solve: x = sqrt(inv(D))*inv(L')*y
MJAPI void mj_solveM2(const mjModel* m, mjData* d, mjtNum* x, const mjtNum* y, int n);
MJAPI void mj_solveM2(const mjModel* m, mjData* d, mjtNum* x, const mjtNum* y,
const mjtNum* sqrtInvD, int n);
// Compute cvel, cdof_dot.
MJAPI void mj_comVel(const mjModel* m, mjData* d);
@@ -668,14 +673,6 @@ MJAPI void mjv_defaultFigure(mjvFigure* fig);
MJAPI void mjv_initGeom(mjvGeom* geom, int type, const mjtNum size[3],
const mjtNum pos[3], const mjtNum mat[9], const float rgba[4]);
// Set (type, size, pos, mat) for connector-type geom between given points.
// Assume that mjv_initGeom was already called to set all other properties.
// Width of mjGEOM_LINE is denominated in pixels.
// Deprecated: use mjv_connector.
MJAPI void mjv_makeConnector(mjvGeom* geom, int type, mjtNum width,
mjtNum a0, mjtNum a1, mjtNum a2,
mjtNum b0, mjtNum b1, mjtNum b2);
// Set (type, size, pos, mat) for connector-type geom between given points.
// Assume that mjv_initGeom was already called to set all other properties.
// Width of mjGEOM_LINE is denominated in pixels.
@@ -700,6 +697,9 @@ MJAPI int mjv_updateSceneFromState(const mjvSceneState* scnstate, const mjvOptio
const mjvPerturb* pert, mjvCamera* cam, int catmask,
mjvScene* scn);
// Copy mjModel, skip large arrays not required for abstract visualization.
MJAPI void mjv_copyModel(mjModel* dest, const mjModel* src);
// Set default scene state.
MJAPI void mjv_defaultSceneState(mjvSceneState* scnstate);
@@ -975,12 +975,6 @@ MJAPI void mju_mulMatVec3(mjtNum res[3], const mjtNum mat[9], const mjtNum vec[3
// Multiply transposed 3-by-3 matrix by vector: res = mat' * vec.
MJAPI void mju_mulMatTVec3(mjtNum res[3], const mjtNum mat[9], const mjtNum vec[3]);
// Deprecated, use mju_mulMatVec3(res, mat, vec).
MJAPI void mju_rotVecMat(mjtNum res[3], const mjtNum vec[3], const mjtNum mat[9]);
// Deprecated, use mju_mulMatTVec3(res, mat, vec).
MJAPI void mju_rotVecMatT(mjtNum res[3], const mjtNum vec[3], const mjtNum mat[9]);
// Compute cross-product: res = cross(a, b).
MJAPI void mju_cross(mjtNum res[3], const mjtNum a[3], const mjtNum b[3]);
@@ -1083,6 +1077,11 @@ MJAPI void mju_transformSpatial(mjtNum res[6], const mjtNum vec[6], int flg_forc
//---------------------------------- Sparse math ---------------------------------------------------
// Convert matrix from dense to sparse.
// nnz is size of res and colind, return 1 if too small, 0 otherwise.
MJAPI int mju_dense2sparse(mjtNum* res, const mjtNum* mat, int nr, int nc,
int* rownnz, int* rowadr, int* colind, int nnz);
// Convert matrix from sparse to dense.
MJAPI void mju_sparse2dense(mjtNum* res, const mjtNum* mat, int nr, int nc,
const int* rownnz, const int* rowadr, const int* colind);
@@ -1126,6 +1125,10 @@ MJAPI void mju_quatIntegrate(mjtNum quat[4], const mjtNum vel[3], mjtNum scale);
// Construct quaternion performing rotation from z-axis to given vector.
MJAPI void mju_quatZ2Vec(mjtNum quat[4], const mjtNum vec[3]);
// Extract 3D rotation from an arbitrary 3x3 matrix by refining the input quaternion.
// Returns the number of iterations required to converge
MJAPI int mju_mat2Rot(mjtNum quat[4], const mjtNum mat[9]);
// Convert sequence of Euler angles (radians) to quaternion.
// seq[0,1,2] must be in 'xyzXYZ', lower/upper-case mean intrinsic/extrinsic rotations.
MJAPI void mju_euler2Quat(mjtNum quat[4], const mjtNum euler[3], const char* seq);
@@ -1423,44 +1426,50 @@ MJAPI mjsFrame* mjs_attachFrame(mjsBody* parent, const mjsFrame* child,
MJAPI mjsBody* mjs_attachToSite(mjsSite* parent, const mjsBody* child,
const char* prefix, const char* suffix);
// Detach body from mjSpec, remove all references and delete the body, return 0 on success.
// Attach child frame to a parent site, return the attached frame if success or NULL otherwise.
MJAPI mjsFrame* mjs_attachFrameToSite(mjsSite* parent, const mjsFrame* child,
const char* prefix, const char* suffix);
// Delete body and descendants from mjSpec, remove all references, return 0 on success.
MJAPI int mjs_detachBody(mjSpec* s, mjsBody* b);
// Delete default class and descendants from mjSpec, remove all references, return 0 on success.
MJAPI int mjs_detachDefault(mjSpec* s, mjsDefault* d);
//---------------------------------- Tree elements -------------------------------------------------
// Add child body to body, return child.
MJAPI mjsBody* mjs_addBody(mjsBody* body, mjsDefault* def);
MJAPI mjsBody* mjs_addBody(mjsBody* body, const mjsDefault* def);
// Add site to body, return site spec.
MJAPI mjsSite* mjs_addSite(mjsBody* body, mjsDefault* def);
MJAPI mjsSite* mjs_addSite(mjsBody* body, const mjsDefault* def);
// Add joint to body.
MJAPI mjsJoint* mjs_addJoint(mjsBody* body, mjsDefault* def);
MJAPI mjsJoint* mjs_addJoint(mjsBody* body, const mjsDefault* def);
// Add freejoint to body.
MJAPI mjsJoint* mjs_addFreeJoint(mjsBody* body);
// Add geom to body.
MJAPI mjsGeom* mjs_addGeom(mjsBody* body, mjsDefault* def);
MJAPI mjsGeom* mjs_addGeom(mjsBody* body, const mjsDefault* def);
// Add camera to body.
MJAPI mjsCamera* mjs_addCamera(mjsBody* body, mjsDefault* def);
MJAPI mjsCamera* mjs_addCamera(mjsBody* body, const mjsDefault* def);
// Add light to body.
MJAPI mjsLight* mjs_addLight(mjsBody* body, mjsDefault* def);
MJAPI mjsLight* mjs_addLight(mjsBody* body, const mjsDefault* def);
// Add frame to body.
MJAPI mjsFrame* mjs_addFrame(mjsBody* body, mjsFrame* parentframe);
// Delete object corresponding to the given element.
MJAPI void mjs_delete(mjsElement* element);
// Delete object corresponding to the given element, return 0 on success.
MJAPI int mjs_delete(mjsElement* element);
//---------------------------------- Non-tree elements ---------------------------------------------
// Add actuator.
MJAPI mjsActuator* mjs_addActuator(mjSpec* s, mjsDefault* def);
MJAPI mjsActuator* mjs_addActuator(mjSpec* s, const mjsDefault* def);
// Add sensor.
MJAPI mjsSensor* mjs_addSensor(mjSpec* s);
@@ -1469,16 +1478,16 @@ MJAPI mjsSensor* mjs_addSensor(mjSpec* s);
MJAPI mjsFlex* mjs_addFlex(mjSpec* s);
// Add contact pair.
MJAPI mjsPair* mjs_addPair(mjSpec* s, mjsDefault* def);
MJAPI mjsPair* mjs_addPair(mjSpec* s, const mjsDefault* def);
// Add excluded body pair.
MJAPI mjsExclude* mjs_addExclude(mjSpec* s);
// Add equality.
MJAPI mjsEquality* mjs_addEquality(mjSpec* s, mjsDefault* def);
MJAPI mjsEquality* mjs_addEquality(mjSpec* s, const mjsDefault* def);
// Add tendon.
MJAPI mjsTendon* mjs_addTendon(mjSpec* s, mjsDefault* def);
MJAPI mjsTendon* mjs_addTendon(mjSpec* s, const mjsDefault* def);
// Wrap site using tendon.
MJAPI mjsWrap* mjs_wrapSite(mjsTendon* tendon, const char* name);
@@ -1514,7 +1523,7 @@ MJAPI mjsDefault* mjs_addDefault(mjSpec* s, const char* classname, const mjsDefa
//---------------------------------- Assets --------------------------------------------------------
// Add mesh.
MJAPI mjsMesh* mjs_addMesh(mjSpec* s, mjsDefault* def);
MJAPI mjsMesh* mjs_addMesh(mjSpec* s, const mjsDefault* def);
// Add height field.
MJAPI mjsHField* mjs_addHField(mjSpec* s);
@@ -1526,7 +1535,7 @@ MJAPI mjsSkin* mjs_addSkin(mjSpec* s);
MJAPI mjsTexture* mjs_addTexture(mjSpec* s);
// Add material.
MJAPI mjsMaterial* mjs_addMaterial(mjSpec* s, mjsDefault* def);
MJAPI mjsMaterial* mjs_addMaterial(mjSpec* s, const mjsDefault* def);
//---------------------------------- Find and get utilities ----------------------------------------
@@ -1546,6 +1555,12 @@ MJAPI mjsElement* mjs_findElement(mjSpec* s, mjtObj type, const char* name);
// Find child body by name.
MJAPI mjsBody* mjs_findChild(mjsBody* body, const char* name);
// Get parent body.
MJAPI mjsBody* mjs_getParent(mjsElement* element);
// Get parent frame.
MJAPI mjsFrame* mjs_getFrame(mjsElement* element);
// Find frame by name.
MJAPI mjsFrame* mjs_findFrame(mjSpec* s, const char* name);
@@ -1623,9 +1638,9 @@ MJAPI const double* mjs_getDouble(const mjDoubleVec* source, int* size);
//---------------------------------- Spec utilities ------------------------------------------------
// Set element's default.
MJAPI void mjs_setDefault(mjsElement* element, mjsDefault* def);
MJAPI void mjs_setDefault(mjsElement* element, const mjsDefault* def);
// Set element's enlcosing frame.
// Set element's enclosing frame.
MJAPI void mjs_setFrame(mjsElement* dest, mjsFrame* frame);
// Resolve alternative orientations to quat, return error if any.
@@ -1635,6 +1650,15 @@ MJAPI const char* mjs_resolveOrientation(double quat[4], mjtByte degree, const c
// Transform body into a frame.
MJAPI mjsFrame* mjs_bodyToFrame(mjsBody** body);
// Set user payload, overriding the existing value for the specified key if present.
MJAPI void mjs_setUserValue(mjsElement* element, const char* key, const void* data);
// Return user payload or NULL if none found.
MJAPI const void* mjs_getUserValue(mjsElement* element, const char* key);
// Delete user payload.
MJAPI void mjs_deleteUserValue(mjsElement* element, const char* key);
//---------------------------------- Element initialization ---------------------------------------
// Default spec attributes.
+1 -1
View File
@@ -9,7 +9,7 @@
This package is a re-implementation of the
[MuJoCo physics engine](https://github.com/google-deepmind/mujoco) in
[JAX](https://github.com/google/jax). This library is developed and maintained
[JAX](https://github.com/jax-ml/jax). This library is developed and maintained
by Google DeepMind, and is kept up-to-date with the latest developments in
MuJoCo itself.
+15 -13
View File
@@ -1,13 +1,15 @@
-f https://storage.googleapis.com/jax-releases/jax_cuda_releases.html
jax[cuda12_local]==0.4.18; python_version >= '3.9' \
--hash=sha256:c3ab72ea2f1c5d8ccf2561e79f6562fb2964629f3e55b3ac1c11c48b64c20336
jaxlib==0.4.18+cuda12.cudnn89; python_version >= '3.9' \
--hash=sha256:14f74ff081882ea091c121e355051b35932e39cb7ff7242b88a87f3690f3ca90 \
--hash=sha256:7c87dc2d68257b02e83c04be88a3c447373ee7077d65f43545bcbda5bfe2231d \
--hash=sha256:4d16e9c7592e1aaca0b3d28d2c8beba415a2721bb7001f2947728247951a250d \
--hash=sha256:759c08c69f4a5b1e6b39c3e4eff908a04ce3b2b483bb594ed624407c7d12d110 \
--hash=sha256:a7a04dbe1851cd50d07691282116aee49a2f0be7838e55b76d7ada86db06be62 \
--hash=sha256:2bf842db3d58c8c6c52fbc8ed3fabefd7b91a21746cd59d3eaf3522eea229b53 \
--hash=sha256:35d265ef9bb3835a14580cbaa9402060f117e46056f80e0996405fff3964667a \
--hash=sha256:0e4352f24d629e912965e6435e140c1b06086243a098651f2d01b75f3738b51c
jax[cuda12_local]==0.4.34; python_version >= '3.10' \
--hash=sha256:b957ca1fc91f7343f91a186af9f19c7f342c946f95a8c11c7f1e5cdfe2e58d9e
jax[cuda12_local]==0.4.30; python_version == '3.9' \
--hash=sha256:289b30ae03b52f7f4baf6ef082a9f4e3e29c1080e22d13512c5ecf02d5f1a55b
jax-cuda12-plugin==0.4.34; python_version >= '3.10' \
--hash=sha256:d035ea72bd9b8a65a6ea621bca1affdd33127fa3a52e7bded7692670d360adab \
--hash=sha256:db988b7ba5063483a936ddbf162f04d1b4412e0d64340f11788c7bbc877e8a43 \
--hash=sha256:e23721d1654b311b47cd6b35768520284bd036f8c7e6b11600143258b4a0409a \
--hash=sha256:b2099a4407225122ff76f6dcdc8dbdae47e6f29343bdfd21460ad337dc34a209
jax-cuda12-plugin==0.4.30; python_version == '3.9' \
--hash=sha256:d8d196241b9253ecb1144a4409b5deacbb9771624f097b2bbf025da3c7d8f4f8
jax-cuda12-pjrt==0.4.34; python_version >= '3.10' \
--hash=sha256:0c7cc98f962cc7fc8e0a5ea6331b42a0cee516f202f1c3019f6aa5cd9530cca0
jax-cuda12-pjrt==0.4.30; python_version == '3.9' \
--hash=sha256:895d0198ad99638fcaf976c47592e2a543eef79ea15fabd24a402d055390c328
+56 -64
View File
@@ -678,7 +678,7 @@ def _create_contact_manifold(
return dist, pos, normal
def _sat_bruteforce(
def _box_box_impl(
faces_a: jax.Array,
faces_b: jax.Array,
vertices_a: jax.Array,
@@ -688,17 +688,7 @@ def _sat_bruteforce(
unique_edges_a: jax.Array,
unique_edges_b: jax.Array,
) -> Tuple[jax.Array, jax.Array, jax.Array]:
"""Runs the Separating Axis Test for a pair of hulls.
Given two convex hulls, the Separating Axis Test finds a separating axis
between all edge pairs and face pairs. Edge pairs create a single contact
point and face pairs create a contact manifold (up to four contact points).
We return both the edge and face contacts. Valid contacts can be checked with
dist < 0. Resulting edge contacts should be preferred over face contacts.
This method checks all separating axes via a brute force support function, and
is thus costly to run over large meshes, but is more performant for smaller
meshes (boxes, tetrahedra, etc.).
"""Runs the Separating Axis Test for two boxes.
Args:
faces_a: Faces for hull A.
@@ -713,18 +703,15 @@ def _sat_bruteforce(
Returns:
tuple of dist, pos, and normal
"""
# get the separating axes
v_norm = jax.vmap(math.normalize)
edge_dir_a = v_norm(unique_edges_a[:, 0] - unique_edges_a[:, 1])
edge_dir_b = v_norm(unique_edges_b[:, 0] - unique_edges_b[:, 1])
edge_dir_a_r = jp.tile(edge_dir_a, reps=(unique_edges_b.shape[0], 1))
edge_dir_b_r = jp.repeat(edge_dir_b, repeats=unique_edges_a.shape[0], axis=0)
edge_dir_a, edge_dir_b = unique_edges_a, unique_edges_b
edge_dir_a_r = jp.tile(edge_dir_a, reps=(edge_dir_b.shape[0], 1))
edge_dir_b_r = jp.repeat(edge_dir_b, repeats=edge_dir_a.shape[0], axis=0)
edge_axes = jax.vmap(jp.cross)(edge_dir_a_r, edge_dir_b_r)
degenerate_edge_axes = (edge_axes**2).sum(axis=1) < 1e-6
edge_axes = jax.vmap(lambda x: math.normalize(x, axis=0))(edge_axes)
n_norm = normals_a.shape[0] + normals_b.shape[0]
n_face_axes = normals_a.shape[0] + normals_b.shape[0]
degenerate_axes = jp.concatenate(
[jp.array([False] * n_norm), degenerate_edge_axes]
[jp.array([False] * n_face_axes), degenerate_edge_axes]
)
axes = jp.concatenate([normals_a, normals_b, edge_axes])
@@ -745,11 +732,16 @@ def _sat_bruteforce(
support, sign = get_support(axes, degenerate_axes)
# get the best face axis
best_face_idx = jp.argmin(support[:n_face_axes])
best_face_axis = axes[best_face_idx]
# choose the best separating axis
best_idx = jp.argmin(support)
best_sign = sign[best_idx]
best_axis = axes[best_idx]
is_edge_contact = best_idx >= (normals_a.shape[0] + normals_b.shape[0])
is_edge_contact = best_idx >= n_face_axes
is_edge_contact &= jp.abs(best_face_axis.dot(best_axis)) < 0.99 # prefer face
# get the (reference) face most aligned with the separating axis
dist_a = normals_a @ best_axis
@@ -788,6 +780,40 @@ def _sat_bruteforce(
return dist, pos, normal
def _box_box(b1: ConvexInfo, b2: ConvexInfo) -> Collision:
"""Calculates contacts between two boxes."""
faces1 = b1.face
faces2 = b2.face
to_local_pos = b2.mat.T @ (b1.pos - b2.pos)
to_local_mat = b2.mat.T @ b1.mat
faces1 = to_local_pos + faces1 @ to_local_mat.T
normals1 = b1.face_normal @ to_local_mat.T
normals2 = b2.face_normal
vertices1 = to_local_pos + b1.vert @ to_local_mat.T
vertices2 = b2.vert
dist, pos, normal = _box_box_impl(
faces1,
faces2,
vertices1,
vertices2,
normals1,
normals2,
to_local_mat.T,
jp.eye(3, dtype=float),
)
# Go back to world frame.
pos = b2.pos + pos @ b2.mat.T
n = normal @ b2.mat.T
dist = jp.where(jp.isinf(dist), jp.finfo(float).max, dist)
return dist, pos, n
def _arcs_intersect(
a: jax.Array, b: jax.Array, c: jax.Array, d: jax.Array
) -> jax.Array:
@@ -906,7 +932,8 @@ def _sat_gaussmap(
return edge_axis * sign, degenerate_edge_axis
edge_axes, degenerate_edge_axes = jax.vmap(get_normals)(
edge_a_dir, edge_a_pt, edge_b_dir)
edge_a_dir, edge_a_pt, edge_b_dir
)
edge_dist = jax.vmap(jp.dot)(edge_axes, edge_b_pt - edge_a_pt)
# handle degenerate axis
edge_dist = jp.where(degenerate_edge_axes, -jp.inf, edge_dist)
@@ -928,51 +955,16 @@ def _sat_gaussmap(
dist,
)
a_closest, b_closest = math.closest_segment_to_segment_points(
edge_a_pt[best_edge_idx], edge_a_pt_2[best_edge_idx],
edge_b_pt[best_edge_idx], edge_b_pt_2[best_edge_idx])
edge_a_pt[best_edge_idx],
edge_a_pt_2[best_edge_idx],
edge_b_pt[best_edge_idx],
edge_b_pt_2[best_edge_idx],
)
pos = jp.where(
is_edge_contact,
jp.tile(0.5 * (a_closest + b_closest), (4, 1)), pos)
return dist, pos, normal
def _box_box(b1: ConvexInfo, b2: ConvexInfo) -> Collision:
"""Calculates contacts between two boxes."""
faces1 = b1.face
faces2 = b2.face
to_local_pos = b2.mat.T @ (b1.pos - b2.pos)
to_local_mat = b2.mat.T @ b1.mat
faces1 = to_local_pos + faces1 @ to_local_mat.T
normals1 = b1.face_normal @ to_local_mat.T
normals2 = b2.face_normal
vertices1 = to_local_pos + b1.vert @ to_local_mat.T
vertices2 = b2.vert
unique_edges1 = jp.take(vertices1, b1.edge_dir, axis=0)
unique_edges2 = jp.take(vertices2, b2.edge_dir, axis=0)
# brute-force SAT is more performant for box-box
dist, pos, normal = _sat_bruteforce(
faces1,
faces2,
vertices1,
vertices2,
normals1,
normals2,
unique_edges1,
unique_edges2,
is_edge_contact, jp.tile(0.5 * (a_closest + b_closest), (4, 1)), pos
)
# Go back to world frame.
pos = b2.pos + pos @ b2.mat.T
n = normal @ b2.mat.T
dist = jp.where(jp.isinf(dist), jp.finfo(float).max, dist)
return dist, pos, n
return dist, pos, normal
def _convex_convex(c1: ConvexInfo, c2: ConvexInfo) -> Collision:
+4 -4
View File
@@ -146,13 +146,13 @@ def geom_pairs(
b_end = b_start + m.body_geomnum
for b1 in range(m.nbody):
if not geom_con[b_start[b1]:b_end[b1]].any():
if not geom_con[b_start[b1] : b_end[b1]].any():
continue
w1 = m.body_weldid[b1]
w1_p = m.body_weldid[m.body_parentid[w1]]
for b2 in range(b1, m.nbody):
if not geom_con[b_start[b2]:b_end[b2]].any():
if not geom_con[b_start[b2] : b_end[b2]].any():
continue
signature = (b1 << 16) + (b2)
if signature in exclude_signature:
@@ -272,7 +272,7 @@ def _contact_groups(m: Model, d: Data) -> Dict[FunctionKey, Contact]:
jp.clip(m.pair_friction[ip], a_min=eps),
m.pair_solref[ip],
m.pair_solreffriction[ip],
m.pair_solimp[ip]
m.pair_solimp[ip],
))
if geom1.size > 0 and geom2.size > 0:
# other contacts get their params from geom fields
@@ -377,11 +377,11 @@ def collision(m: Model, d: Data) -> Data:
if d.ncon == 0:
return d
groups = _contact_groups(m, d)
max_geom_pairs = _numeric(m, 'max_geom_pairs')
max_contact_points = _numeric(m, 'max_contact_points')
# run collision functions on groups
groups = _contact_groups(m, d)
for key, contact in groups.items():
# determine which contacts we'll use for collision testing by running a
# broad phase cull if requested
+37 -21
View File
@@ -49,11 +49,15 @@ def _assert_attr_eq(mjx_d, mj_d, attr, name, atol):
def _collide(
mjcf: str, assets: Optional[Dict[str, str]] = None
mjcf: str,
assets: Optional[Dict[str, str]] = None,
keyframe: Optional[int] = None,
) -> Tuple[mujoco.MjModel, mujoco.MjData, Model, Data]:
m = mujoco.MjModel.from_xml_string(mjcf, assets or {})
mx = mjx.put_model(m)
d = mujoco.MjData(m)
if keyframe is not None:
mujoco.mj_resetDataKeyframe(m, d, keyframe)
dx = mjx.put_data(m, d)
mujoco.mj_step(m, d)
@@ -218,7 +222,8 @@ class EllipsoidCollisionTest(parameterized.TestCase):
self.assertLess(dx.contact.dist[0], 0)
for field in dataclasses.fields(Contact):
_assert_attr_eq(
dx.contact, d.contact, field.name, 'ellipsoid-plane', 1e-5)
dx.contact, d.contact, field.name, 'ellipsoid-plane', 1e-5
)
_ELLIPSOID_ELLIPSOID = """
<mujoco>
@@ -240,7 +245,8 @@ class EllipsoidCollisionTest(parameterized.TestCase):
self.assertLess(dx.contact.dist[0], 0)
for field in dataclasses.fields(Contact):
_assert_attr_eq(
dx.contact, d.contact, field.name, 'ellipsoid-ellipsoid', 1e-5)
dx.contact, d.contact, field.name, 'ellipsoid-ellipsoid', 1e-2
)
_ELLIPSOID_SPHERE = """
<mujoco>
@@ -263,7 +269,8 @@ class EllipsoidCollisionTest(parameterized.TestCase):
self.assertLess(dx.contact.dist[0], 0)
for field in dataclasses.fields(Contact):
_assert_attr_eq(
dx.contact, d.contact, field.name, 'ellipsoid-sphere', 1e-4)
dx.contact, d.contact, field.name, 'ellipsoid-sphere', 1e-4
)
_ELLIPSOID_CAPSULE = """
<mujoco>
@@ -285,7 +292,8 @@ class EllipsoidCollisionTest(parameterized.TestCase):
self.assertLess(dx.contact.dist[0], 0)
for field in dataclasses.fields(Contact):
_assert_attr_eq(
dx.contact, d.contact, field.name, 'ellipsoid-capsule', 1e-4)
dx.contact, d.contact, field.name, 'ellipsoid-capsule', 1e-5
)
_ELLIPSOID_CYLINDER = """
<mujoco>
@@ -308,7 +316,8 @@ class EllipsoidCollisionTest(parameterized.TestCase):
self.assertLess(dx.contact.dist[0], 0)
for field in dataclasses.fields(Contact):
_assert_attr_eq(
dx.contact, d.contact, field.name, 'ellipsoid-cylinder', 1e-4)
dx.contact, d.contact, field.name, 'ellipsoid-cylinder', 1e-4
)
class CapsuleCollisionTest(parameterized.TestCase):
@@ -550,7 +559,8 @@ class CylinderTest(absltest.TestCase):
# cylinder is vertical
xml = self._CYLINDER_PLANE.replace(
'<geom fromto="-0.1 0 0 0.1 0 0"', '<geom fromto="0 0 -0.1 0 0 0.1"')
'<geom fromto="-0.1 0 0 0.1 0 0"', '<geom fromto="0 0 -0.1 0 0 0.1"'
)
xml = xml.replace('pos="0 0 0.04"', 'pos="0 0 0.095"')
d, dx = _collide(xml)
@@ -579,7 +589,8 @@ class CylinderTest(absltest.TestCase):
self.assertLess(dx.contact.dist[0], 0)
for field in dataclasses.fields(Contact):
_assert_attr_eq(
dx.contact, d.contact, field.name, 'sphere-cylinder', 1e-4)
dx.contact, d.contact, field.name, 'sphere-cylinder', 1e-4
)
class ConvexTest(absltest.TestCase):
@@ -641,28 +652,30 @@ class ConvexTest(absltest.TestCase):
_BOX_BOX = """
<mujoco>
<worldbody>
<body pos="0.0 1.0 0.2">
<joint axis="1 0 0" type="free"/>
<geom size="0.2 0.2 0.2" type="box"/>
</body>
<body pos="0.1 1.0 0.495" euler="0.1 -0.1 0">
<joint axis="1 0 0" type="free"/>
<geom size="0.1 0.1 0.1" type="box"/>
</body>
<light name="top" pos="0 0 1"/>
<geom type="box" size="0.025 0.025 0.025" pos="0 0 0.025"/>
<body name="peg" pos="0 0 0.06">
<freejoint/>
<geom name="peg" size="0.048 0.01 0.01" type="box"/>
</body>
</worldbody>
<keyframe>
<!-- Boxes are penetrating with a slightly off-axis face contact -->
<key qpos='-0.00234853 0.0112999 0.0533649 0.474162 0.472141 0.524886 0.526069'/>
</keyframe>
</mujoco>
"""
def test_box_box(self):
"""Tests a face contact for a box-box collision."""
d, dx = _collide(self._BOX_BOX)
d, dx = _collide(self._BOX_BOX, keyframe=0)
c = dx.contact
self.assertEqual(c.pos.shape[0], 4)
np.testing.assert_array_less(c.dist, 0)
np.testing.assert_array_almost_equal(c.pos[:, 2], np.array([0.39] * 4), 2)
np.testing.assert_array_almost_equal(c.pos[:, 2], np.array([0.05] * 4), 2)
np.testing.assert_array_almost_equal(
c.frame[:, 0, :], np.array([[0.0, 0.0, 1.0]] * 4)
c.frame[:, 0, :], np.array([[0.0, 0.0, 1.0]] * 4), decimal=2
)
np.testing.assert_array_almost_equal(
c.frame.reshape((-1, 9)), d.contact.frame[:4, :]
@@ -760,11 +773,14 @@ class ConvexTest(absltest.TestCase):
np.testing.assert_array_less(0, c.dist[1:])
np.testing.assert_array_almost_equal(c.frame[0, 0], np.array([0, 0, 1]))
np.testing.assert_array_almost_equal(
c.pos[0], np.array([0, 2, 1.3155]), decimal=5)
c.pos[0], np.array([0, 2, 1.3155]), decimal=5
)
_, dx = _collide(
self._CONVEX_CONVEX_THIN.replace(
'pos="0.0 2.0 0.35"', 'pos="0.0 2.0 0"'))
'pos="0.0 2.0 0.35"', 'pos="0.0 2.0 0"'
)
)
c = dx.contact
self.assertTrue((c.dist > 0).all())
+11 -6
View File
@@ -29,6 +29,7 @@ from mujoco.mjx._src.types import Model
def collider(ncon: int):
"""Wraps collision functions for use by collision_driver."""
def wrapper(func):
def collide(m: Model, d: Data, _, geom: jax.Array) -> Collision:
g1, g2 = geom.T
@@ -119,7 +120,7 @@ def plane_cylinder(plane: GeomInfo, cylinder: GeomInfo) -> Collision:
# disk parallel to plane: pick x-axis of cylinder, scale by radius
cylinder.mat[:, 0] * cylinder.size[0],
# general configuration: normalize vector, scale by radius
vec / len_ * cylinder.size[0]
vec / len_ * cylinder.size[0],
)
# project vector on normal
@@ -138,11 +139,15 @@ def plane_cylinder(plane: GeomInfo, cylinder: GeomInfo) -> Collision:
d1 = dist0 + prjaxis + prjvec
d2 = dist0 + prjaxis + prjvec1
dist = jp.array([d1, d2, d2])
pos = cylinder.pos + axis + jp.array([
vec - n * d1 * 0.5,
vec1 + vec * -0.5 - n * d2 * 0.5,
-vec1 + vec * -0.5 - n * d2 * 0.5,
])
pos = (
cylinder.pos
+ axis
+ jp.array([
vec - n * d1 * 0.5,
vec1 + vec * -0.5 - n * d2 * 0.5,
-vec1 + vec * -0.5 - n * d2 * 0.5,
])
)
# cylinder parallel to plane
cond = jp.abs(prjaxis) < 1e-3
+17 -12
View File
@@ -41,6 +41,7 @@ SDFFn = Callable[[jax.Array], jax.Array]
def collider(ncon: int):
"""Wraps collision functions for use by collision_driver."""
def wrapper(func):
def collide(m: Model, d: Data, _, geom: jax.Array) -> Collision:
g1, g2 = geom.T
@@ -81,7 +82,7 @@ def _capsule(pos: jax.Array, size: jax.Array):
def _ellipsoid(pos: jax.Array, size: jax.Array) -> jax.Array:
k0 = math.norm(pos / size)
k1 = math.norm(pos / (size*size))
k1 = math.norm(pos / (size * size))
return k0 * (k0 - 1.0) / (k1 + (k1 == 0.0) * 1e-12)
@@ -96,12 +97,12 @@ def _cylinder(pos: jax.Array, size: jax.Array) -> jax.Array:
def _cylinder_grad(x: jax.Array, size: jax.Array) -> jax.Array:
"""Gradient of the cylinder SDF wrt query point and singularities removed."""
c = jp.sqrt(x[0]*x[0]+x[1]*x[1])
c = jp.sqrt(x[0] * x[0] + x[1] * x[1])
e = jp.abs(x[2])
a = jp.array([c - size[0], e - size[1]])
b = jp.array([jp.maximum(a[0], 0), jp.maximum(a[1], 0)])
j = jp.argmax(a)
bnorm = jp.sqrt(b[0]*b[0] + b[1]*b[1])
bnorm = jp.sqrt(b[0] * b[0] + b[1] * b[1])
bnorm += jp.allclose(bnorm, 0) * 1e-12
grada = jp.array([
x[0] / (c + jp.allclose(c, 0) * 1e-12),
@@ -151,7 +152,7 @@ def _gradient_step(objective: SDFFn, state: GradientState) -> GradientState:
"""Performs a step of gradient descent."""
# TODO: find better parameters
amin = 1e-4 # minimum value for line search factor scaling the gradient
amax = 2. # maximum value for line search factor scaling the gradient
amax = 2.0 # maximum value for line search factor scaling the gradient
nlinesearch = 10 # line search points
grad = jax.grad(objective)(state.x)
alpha = jp.geomspace(amin, amax, nlinesearch).reshape(nlinesearch, -1)
@@ -179,7 +180,11 @@ def _gradient_descent(
def _optim(
d1, d2, info1: GeomInfo, info2: GeomInfo, x0: jax.Array,
d1,
d2,
info1: GeomInfo,
info2: GeomInfo,
x0: jax.Array,
) -> Collision:
"""Optimizes the clearance function."""
d1 = functools.partial(d1, size=info1.size)
@@ -198,14 +203,14 @@ def _optim(
@collider(ncon=1)
def sphere_ellipsoid(s: GeomInfo, e: GeomInfo) -> Collision:
""""Calculates contact between a sphere and an ellipsoid."""
"""Calculates contact between a sphere and an ellipsoid."""
x0 = 0.5 * (s.pos + e.pos)
return _optim(_sphere, _ellipsoid, s, e, x0)
@collider(ncon=1)
def sphere_cylinder(s: GeomInfo, c: GeomInfo) -> Collision:
""""Calculates contact between a sphere and a cylinder."""
"""Calculates contact between a sphere and a cylinder."""
# TODO: implement analytical version.
x0 = 0.5 * (s.pos + c.pos)
return _optim(_sphere, _cylinder, s, c, x0)
@@ -213,14 +218,14 @@ def sphere_cylinder(s: GeomInfo, c: GeomInfo) -> Collision:
@collider(ncon=1)
def capsule_ellipsoid(c: GeomInfo, e: GeomInfo) -> Collision:
""""Calculates contact between a capsule and an ellipsoid."""
""" "Calculates contact between a capsule and an ellipsoid."""
x0 = 0.5 * (c.pos + e.pos)
return _optim(_capsule, _ellipsoid, c, e, x0)
@collider(ncon=2)
def capsule_cylinder(ca: GeomInfo, cy: GeomInfo) -> Collision:
""""Calculates contact between a capsule and a cylinder."""
"""Calculates contact between a capsule and a cylinder."""
# TODO: improve robustness
# Near sharp corners, the SDF might give the penetration depth with respect
# to a surface that is not in collision. Possible solutions is to find the
@@ -235,21 +240,21 @@ def capsule_cylinder(ca: GeomInfo, cy: GeomInfo) -> Collision:
@collider(ncon=1)
def ellipsoid_ellipsoid(e1: GeomInfo, e2: GeomInfo) -> Collision:
""""Calculates contact between two ellipsoids."""
"""Calculates contact between two ellipsoids."""
x0 = 0.5 * (e1.pos + e2.pos)
return _optim(_ellipsoid, _ellipsoid, e1, e2, x0)
@collider(ncon=1)
def ellipsoid_cylinder(e: GeomInfo, c: GeomInfo) -> Collision:
""""Calculates contact between and ellipsoid and a cylinder."""
"""Calculates contact between and ellipsoid and a cylinder."""
x0 = 0.5 * (e.pos + c.pos)
return _optim(_ellipsoid, _cylinder, e, c, x0)
@collider(ncon=4)
def cylinder_cylinder(c1: GeomInfo, c2: GeomInfo) -> Collision:
""""Calculates contact between a cylinder and a cylinder."""
"""Calculates contact between a cylinder and a cylinder."""
# TODO: improve robustness
# Near sharp corners, the SDF might give the penetration depth with respect
# to a surface that is not in collision. Possible solutions is to find the
+2 -2
View File
@@ -15,7 +15,7 @@
"""Collision base types."""
import dataclasses
from typing import Optional, Tuple
from typing import Tuple
import jax
from mujoco.mjx._src.dataclasses import PyTreeNode # pylint: disable=g-importing-member
import numpy as np
@@ -46,7 +46,6 @@ class ConvexInfo(PyTreeNode):
face_normal: jax.Array
edge: jax.Array
edge_face_normal: jax.Array
edge_dir: Optional[jax.Array] = None
class HFieldInfo(PyTreeNode):
@@ -73,6 +72,7 @@ class FunctionKey:
resulting constraint jacobian is determined at compile time.
subgrid_size: the size determines the hfield subgrid to collide with
"""
types: Tuple[int, int]
data_ids: Tuple[int, int]
condim: int
+29 -10
View File
@@ -39,6 +39,7 @@ import numpy as np
class _Efc(PyTreeNode):
"""Support data for creating constraint matrices."""
J: jax.Array
pos_aref: jax.Array
pos_imp: jax.Array
@@ -107,7 +108,9 @@ def _efc_equality_connect(m: Model, d: Data) -> Optional[_Efc]:
return None
@jax.vmap
def rows(is_site, obj1id, obj2id, body1id, body2id, data, solref, solimp):
def rows(
is_site, obj1id, obj2id, body1id, body2id, data, solref, solimp, active
):
anchor1, anchor2 = data[0:3], data[3:6]
pos1 = d.xmat[body1id] @ anchor1 + d.xpos[body1id]
@@ -128,7 +131,8 @@ def _efc_equality_connect(m: Model, d: Data) -> Optional[_Efc]:
invweight = m.body_invweight0[body1id, 0] + m.body_invweight0[body2id, 0]
zero = jp.zeros_like(pos)
return _row(j, pos, pos_imp, invweight, solref, solimp, zero, zero)
efc = _row(j, pos, pos_imp, invweight, solref, solimp, zero, zero)
return jax.tree_util.tree_map(lambda x: x * active, efc)
is_site = m.eq_objtype == ObjType.SITE
body1id = np.copy(m.eq_obj1id)
@@ -147,6 +151,7 @@ def _efc_equality_connect(m: Model, d: Data) -> Optional[_Efc]:
m.eq_data,
m.eq_solref,
m.eq_solimp,
d.eq_active,
)
args = jax.tree_util.tree_map(lambda x: x[eq_id], args)
# concatenate to drop row grouping
@@ -161,7 +166,9 @@ def _efc_equality_weld(m: Model, d: Data) -> Optional[_Efc]:
return None
@jax.vmap
def rows(is_site, obj1id, obj2id, body1id, body2id, data, solref, solimp):
def rows(
is_site, obj1id, obj2id, body1id, body2id, data, solref, solimp, active
):
anchor1, anchor2 = data[0:3], data[3:6]
relpose, torquescale = data[6:10], data[10]
@@ -208,7 +215,8 @@ def _efc_equality_weld(m: Model, d: Data) -> Optional[_Efc]:
invweight = jp.repeat(invweight, 3, axis=0)
zero = jp.zeros_like(pos)
return _row(j, pos, pos_imp, invweight, solref, solimp, zero, zero)
efc = _row(j, pos, pos_imp, invweight, solref, solimp, zero, zero)
return jax.tree_util.tree_map(lambda x: x * active, efc)
is_site = m.eq_objtype == ObjType.SITE
body1id = np.copy(m.eq_obj1id)
@@ -227,6 +235,7 @@ def _efc_equality_weld(m: Model, d: Data) -> Optional[_Efc]:
m.eq_data,
m.eq_solref,
m.eq_solimp,
d.eq_active,
)
args = jax.tree_util.tree_map(lambda x: x[eq_id], args)
# concatenate to drop row grouping
@@ -242,7 +251,9 @@ def _efc_equality_joint(m: Model, d: Data) -> Optional[_Efc]:
return None
@jax.vmap
def rows(obj2id, data, solref, solimp, dofadr1, dofadr2, qposadr1, qposadr2):
def rows(
obj2id, data, solref, solimp, active, dofadr1, dofadr2, qposadr1, qposadr2
):
pos1, pos2 = d.qpos[qposadr1], d.qpos[qposadr2]
ref1, ref2 = m.qpos0[qposadr1], m.qpos0[qposadr2]
dif = (pos2 - ref2) * (obj2id > -1)
@@ -255,9 +266,11 @@ def _efc_equality_joint(m: Model, d: Data) -> Optional[_Efc]:
invweight += m.dof_invweight0[dofadr2] * (obj2id > -1)
zero = jp.zeros_like(pos)
return _row(j, pos, pos, invweight, solref, solimp, zero, zero)
efc = _row(j, pos, pos, invweight, solref, solimp, zero, zero)
return jax.tree_util.tree_map(lambda x: x * active, efc)
args = (m.eq_obj1id, m.eq_obj2id, m.eq_data, m.eq_solref, m.eq_solimp)
args += (d.eq_active,)
args = jax.tree_util.tree_map(lambda x: x[eq_id], args)
dofadr1, dofadr2 = m.jnt_dofadr[args[0]], m.jnt_dofadr[args[1]]
qposadr1, qposadr2 = m.jnt_qposadr[args[0]], m.jnt_qposadr[args[1]]
@@ -274,7 +287,7 @@ def _efc_equality_tendon(m: Model, d: Data) -> Optional[_Efc]:
if (m.opt.disableflags & DisableBit.EQUALITY) or eq_id.size == 0:
return None
obj1id, obj2id, data, solref, solimp = jax.tree_util.tree_map(
obj1id, obj2id, data, solref, solimp, active = jax.tree_util.tree_map(
lambda x: x[eq_id],
(
m.eq_obj1id,
@@ -282,11 +295,14 @@ def _efc_equality_tendon(m: Model, d: Data) -> Optional[_Efc]:
m.eq_data,
m.eq_solref,
m.eq_solimp,
d.eq_active,
),
)
@jax.vmap
def rows(obj2id, data, solref, solimp, invweight, jac1, jac2, pos1, pos2):
def rows(
obj2id, data, solref, solimp, invweight, jac1, jac2, pos1, pos2, active
):
dif = pos2 * (obj2id > -1)
dif_power = jp.power(dif, jp.arange(0, 5))
pos = pos1 - jp.dot(data[:5], dif_power)
@@ -294,7 +310,8 @@ def _efc_equality_tendon(m: Model, d: Data) -> Optional[_Efc]:
j = jac1 + jac2 * -deriv
zero = jp.zeros_like(pos)
return _row(j, pos, pos, invweight, solref, solimp, zero, zero)
efc = _row(j, pos, pos, invweight, solref, solimp, zero, zero)
return jax.tree_util.tree_map(lambda x: x * active, efc)
inv1, inv2 = m.tendon_invweight0[obj1id], m.tendon_invweight0[obj2id]
jac1, jac2 = d.ten_J[obj1id], d.ten_J[obj2id]
@@ -302,7 +319,9 @@ def _efc_equality_tendon(m: Model, d: Data) -> Optional[_Efc]:
pos2 = d.ten_length[obj2id] - m.tendon_length0[obj2id]
invweight = inv1 + inv2 * (obj2id > -1)
return rows(obj2id, data, solref, solimp, invweight, jac1, jac2, pos1, pos2)
return rows(
obj2id, data, solref, solimp, invweight, jac1, jac2, pos1, pos2, active
)
def _efc_friction(m: Model, d: Data) -> Optional[_Efc]:
+13 -3
View File
@@ -30,7 +30,7 @@ _TOLERANCE = 5e-5
def _assert_eq(a, b, name):
tol = _TOLERANCE * 10 # avoid test noise
tol = _TOLERANCE * 10 # avoid test noise
err_msg = f'mismatch: {name}'
np.testing.assert_allclose(a, b, err_msg=err_msg, atol=tol, rtol=tol)
@@ -41,10 +41,17 @@ def _assert_attr_eq(a, b, attr):
class ConstraintTest(parameterized.TestCase):
def setUp(self):
super().setUp()
np.random.seed(42)
@parameterized.parameters(
mujoco.mjtCone.mjCONE_PYRAMIDAL, mujoco.mjtCone.mjCONE_ELLIPTIC
{'cone': mujoco.mjtCone.mjCONE_PYRAMIDAL, 'rand_eq_active': False},
{'cone': mujoco.mjtCone.mjCONE_ELLIPTIC, 'rand_eq_active': False},
{'cone': mujoco.mjtCone.mjCONE_PYRAMIDAL, 'rand_eq_active': True},
{'cone': mujoco.mjtCone.mjCONE_ELLIPTIC, 'rand_eq_active': True},
)
def test_constraints(self, cone):
def test_constraints(self, cone, rand_eq_active):
"""Test constraints."""
m = test_util.load_test_file('constraints.xml')
m.opt.cone = cone
@@ -53,6 +60,8 @@ class ConstraintTest(parameterized.TestCase):
# sample a mix of active/inactive constraints at different timesteps
for key in range(3):
mujoco.mj_resetDataKeyframe(m, d, key)
if rand_eq_active:
d.eq_active[:] = np.random.randint(0, 2, size=m.neq)
mujoco.mj_forward(m, d)
mx = mjx.put_model(m)
dx = mjx.put_data(m, d)
@@ -66,6 +75,7 @@ class ConstraintTest(parameterized.TestCase):
_assert_eq(0, dx.efc_aref[order][d.nefc :], 'efc_aref')
_assert_eq(d.efc_D, dx.efc_D[order][: d.nefc], 'efc_D')
_assert_eq(d.efc_pos, dx.efc_pos[order][: d.nefc], 'efc_pos')
_assert_eq(dx.efc_pos[order][d.nefc :], 0, 'efc_pos')
_assert_eq(
d.efc_frictionloss,
dx.efc_frictionloss[order][: d.nefc],
+39 -42
View File
@@ -16,7 +16,6 @@
import copy
import dataclasses
import typing
from typing import Dict, Optional, Sequence, Tuple, TypeVar, Union
import jax
@@ -35,7 +34,7 @@ def _jax_in_args(typ) -> bool:
return False
def dataclass(clz: _T) -> _T:
def dataclass(clz: _T, register_as_pytree: bool) -> _T:
"""Wraps a dataclass with metadata for which fields are pytrees.
This is based off flax.struct.dataclass, but instead of using field
@@ -49,54 +48,51 @@ def dataclass(clz: _T) -> _T:
the resulting dataclass, registered with Jax
"""
data_clz = dataclasses.dataclass(frozen=True)(clz)
meta_fields, data_fields = [], []
for field in dataclasses.fields(data_clz):
if _jax_in_args(field.type):
data_fields.append(field)
else:
meta_fields.append(field)
data_clz.replace = dataclasses.replace
def replace(self, **updates):
""""Returns a new object replacing the specified fields with new values."""
return dataclasses.replace(self, **updates)
data_clz.replace = replace
def iterate_clz_with_keys(x):
def to_meta(field, obj):
val = getattr(obj, field.name)
# numpy arrays are not hashable so return raw bytes instead
if isinstance(val, np.ndarray):
return (val.tobytes(), val.dtype, val.shape)
if register_as_pytree:
meta_fields, data_fields = [], []
for field in dataclasses.fields(data_clz):
if _jax_in_args(field.type):
data_fields.append(field)
else:
return val
meta_fields.append(field)
def to_data(field, obj):
return (jax.tree_util.GetAttrKey(field.name), getattr(obj, field.name))
def iterate_clz_with_keys(x):
def to_meta(field, obj):
val = getattr(obj, field.name)
# numpy arrays are not hashable so return raw bytes instead
if isinstance(val, np.ndarray):
return (val.tobytes(), val.dtype, val.shape)
else:
return val
data = tuple(to_data(f, x) for f in data_fields)
meta = tuple(to_meta(f, x) for f in meta_fields)
return data, meta
def to_data(field, obj):
return (jax.tree_util.GetAttrKey(field.name), getattr(obj, field.name))
def clz_from_iterable(meta, data):
data = tuple(to_data(f, x) for f in data_fields)
meta = tuple(to_meta(f, x) for f in meta_fields)
return data, meta
def from_meta(field, meta):
if field.type is np.ndarray:
arr = np.frombuffer(meta[0], dtype=meta[1]).reshape(meta[2])
return (field.name, arr)
else:
return (field.name, meta)
def clz_from_iterable(meta, data):
from_data = lambda field, meta: (field.name, meta)
def from_meta(field, meta):
if field.type is np.ndarray:
arr = np.frombuffer(meta[0], dtype=meta[1]).reshape(meta[2])
return (field.name, arr)
else:
return (field.name, meta)
meta_args = tuple(from_meta(f, m) for f, m in zip(meta_fields, meta))
data_args = tuple(from_data(f, m) for f, m in zip(data_fields, data))
from_data = lambda field, meta: (field.name, meta)
return data_clz(**dict(meta_args + data_args))
meta_args = tuple(from_meta(f, m) for f, m in zip(meta_fields, meta))
data_args = tuple(from_data(f, m) for f, m in zip(data_fields, data))
jax.tree_util.register_pytree_with_keys(
data_clz, iterate_clz_with_keys, clz_from_iterable
)
return data_clz(**dict(meta_args + data_args))
jax.tree_util.register_pytree_with_keys(
data_clz, iterate_clz_with_keys, clz_from_iterable
)
return data_clz
@@ -110,8 +106,9 @@ class PyTreeNode:
This base class additionally avoids type checking errors when using PyType.
"""
def __init_subclass__(cls):
dataclass(cls)
def __init_subclass__(cls, register_as_pytree: bool = True, **kwargs):
super().__init_subclass__(**kwargs)
dataclass(cls, register_as_pytree=register_as_pytree)
def __init__(self, *args, **kwargs):
# stub for pytype
+10 -2
View File
@@ -115,6 +115,8 @@ def fwd_actuation(m: Model, d: Data) -> Data:
act_dot = ctrl
elif dyn_typ in (DynType.FILTER, DynType.FILTEREXACT):
act_dot = (ctrl - act) / jp.clip(dyn_prm[0], mujoco.mjMINVAL)
elif dyn_typ == DynType.MUSCLE:
act_dot = support.muscle_dynamics(ctrl, act, dyn_prm)
else:
raise NotImplementedError(f'dyntype {dyn_typ.name} not implemented.')
return act_dot
@@ -139,13 +141,15 @@ def fwd_actuation(m: Model, d: Data) -> Data:
ctrl_act = jp.where(m.actuator_actadr == -1, ctrl, act_last_dim)
def get_force(*args):
gain_t, gain_p, bias_t, bias_p, len_, vel, ctrl_act = args
gain_t, gain_p, bias_t, bias_p, len_, vel, ctrl_act, len_range, acc0 = args
typ, prm = GainType(gain_t), gain_p
if typ == GainType.FIXED:
gain = prm[0]
elif typ == GainType.AFFINE:
gain = prm[0] + prm[1] * len_ + prm[2] * vel
elif typ == GainType.MUSCLE:
gain = support.muscle_gain(len_, vel, len_range, acc0, prm)
else:
raise RuntimeError(f'unrecognized gaintype {typ.name}.')
@@ -153,13 +157,15 @@ def fwd_actuation(m: Model, d: Data) -> Data:
bias = jp.array(0.0)
if typ == BiasType.AFFINE:
bias = prm[0] + prm[1] * len_ + prm[2] * vel
elif typ == BiasType.MUSCLE:
bias = support.muscle_bias(len_, len_range, acc0, prm)
return gain * ctrl_act + bias
force = scan.flat(
m,
get_force,
'uuuuuuu',
'uuuuuuuuu',
'u',
m.actuator_gaintype,
m.actuator_gainprm,
@@ -168,6 +174,8 @@ def fwd_actuation(m: Model, d: Data) -> Data:
d.actuator_length,
d.actuator_velocity,
ctrl_act,
jp.array(m.actuator_lengthrange),
jp.array(m.actuator_acc0),
group_by='u',
)
forcerange = jp.where(
+15 -26
View File
@@ -15,6 +15,7 @@
"""Tests for forward functions."""
from absl.testing import absltest
from absl.testing import parameterized
import jax
import mujoco
from mujoco import mjx
@@ -167,40 +168,28 @@ class ForwardTest(absltest.TestCase):
np.testing.assert_allclose(dx.qvel, 1 + m.opt.timestep)
class ActuatorTest(absltest.TestCase):
_DYN_XML = """
<mujoco>
<compiler autolimits="true"/>
<worldbody>
<body name="box">
<joint name="slide1" type="slide" axis="1 0 0" />
<joint name="slide2" type="slide" axis="0 1 0" />
<joint name="slide3" type="slide" axis="0 0 1" />
<joint name="slide4" type="slide" axis="1 1 0" />
<geom type="box" size=".05 .05 .05" mass="1"/>
</body>
</worldbody>
<actuator>
<general joint="slide1" dynprm="0.1" gainprm="1.1" />
<general joint="slide2" dyntype="integrator" dynprm="0.1" gainprm="1.1" />
<general joint="slide3" dyntype="filter" dynprm="0.1" gainprm="1.1" />
<general joint="slide4" dyntype="filterexact" dynprm="0.1" gainprm="1.1" />
</actuator>
</mujoco>
"""
class ActuatorTest(parameterized.TestCase):
def test_dyntype(self):
m = mujoco.MjModel.from_xml_string(self._DYN_XML)
@parameterized.parameters(
'actuator/arm21.xml',
'actuator/arm26.xml',
'actuator/general_dyntype.xml',
)
def test_actuator(self, fname):
m = test_util.load_test_file(fname)
d = mujoco.MjData(m)
d.ctrl = np.array([1.5, 1.5, 1.5, 1.5])
d.act = np.array([0.5, 0.5, 0.5])
mujoco.mj_step(m, d)
d.ctrl = 1.5 * np.random.random(m.nu)
d.act = 0.5 * np.random.random(m.na)
mx = mjx.put_model(m)
dx = mjx.put_data(m, d)
mujoco.mj_fwdActuation(m, d)
dx = jax.jit(mjx.fwd_actuation)(mx, dx)
_assert_attr_eq(d, dx, 'act_dot')
_assert_attr_eq(d, dx, 'qfrc_actuator')
_assert_attr_eq(d, dx, 'actuator_force')
mujoco.mj_Euler(m, d)
dx = jax.jit(mjx.euler)(mx, dx)
+179 -83
View File
@@ -34,31 +34,36 @@ def _strip_weak_type(tree):
if isinstance(leaf, jax.Array):
return leaf.astype(jax.dtypes.canonicalize_dtype(leaf.dtype))
return leaf
return jax.tree_util.tree_map(f, tree)
def _make_option(o: mujoco.MjOption) -> types.Option:
def _make_option(
o: mujoco.MjOption, _full_compat: bool = False # pylint: disable=invalid-name
) -> types.Option:
"""Returns mjx.Option given mujoco.MjOption."""
if o.integrator not in set(types.IntegratorType):
raise NotImplementedError(f'{mujoco.mjtIntegrator(o.integrator)}')
if not _full_compat:
if o.integrator not in set(types.IntegratorType):
raise NotImplementedError(f'{mujoco.mjtIntegrator(o.integrator)}')
if o.cone not in set(types.ConeType):
raise NotImplementedError(f'{mujoco.mjtCone(o.cone)}')
if o.cone not in set(types.ConeType):
raise NotImplementedError(f'{mujoco.mjtCone(o.cone)}')
if o.jacobian not in set(types.JacobianType):
raise NotImplementedError(f'{mujoco.mjtJacobian(o.jacobian)}')
if o.jacobian not in set(types.JacobianType):
raise NotImplementedError(f'{mujoco.mjtJacobian(o.jacobian)}')
if o.solver not in set(types.SolverType):
raise NotImplementedError(f'{mujoco.mjtSolver(o.solver)}')
if o.solver not in set(types.SolverType):
raise NotImplementedError(f'{mujoco.mjtSolver(o.solver)}')
for i in range(mujoco.mjtEnableBit.mjNENABLE):
if o.enableflags & 2**i:
raise NotImplementedError(f'{mujoco.mjtEnableBit(2 ** i)}')
for i in range(mujoco.mjtEnableBit.mjNENABLE):
if o.enableflags & 2**i:
raise NotImplementedError(f'{mujoco.mjtEnableBit(2 ** i)}')
has_fluid_params = o.density > 0 or o.viscosity > 0 or o.wind.any()
implicitfast = o.integrator == mujoco.mjtIntegrator.mjINT_IMPLICITFAST
if implicitfast and has_fluid_params:
raise NotImplementedError('implicitfast not implemented for fluid drag.')
if not _full_compat:
if implicitfast and has_fluid_params:
raise NotImplementedError('implicitfast not implemented for fluid drag.')
fields = {f.name: getattr(o, f.name, None) for f in types.Option.fields()}
fields['integrator'] = types.IntegratorType(o.integrator)
@@ -91,7 +96,7 @@ def put_model(
m: the model to put onto device
device: which device to use - if unspecified picks the default device
_full_compat: put all MjModel fields onto device irrespective of MJX support
This is an experimental feature. Avoid using it for now.
This is an experimental feature. Avoid using it for now.
Returns:
an mjx.Model placed on device
@@ -118,29 +123,20 @@ def put_model(
if t == mujoco.mjtGeom.mjGEOM_MESH:
mesh_geomid.add(g)
# check for spatial tendon internal geom wrapping
if m.ntendon:
# find sphere or cylinder geoms (if any exist)
(wrap_id_geom,) = np.nonzero(
(m.wrap_type == mujoco.mjtWrap.mjWRAP_SPHERE)
| (m.wrap_type == mujoco.mjtWrap.mjWRAP_CYLINDER)
# check for unsupported sensor and equality constraint combinations
sensor_rne_postconstraint = (
np.any(m.sensor_type == types.SensorType.ACCELEROMETER)
| np.any(m.sensor_type == types.SensorType.FORCE)
| np.any(m.sensor_type == types.SensorType.TORQUE)
)
eq_connect_weld = np.any(m.eq_type == types.EqType.CONNECT) | np.any(
m.eq_type == types.EqType.WELD
)
if sensor_rne_postconstraint and eq_connect_weld:
raise NotImplementedError(
'rne_postconstraint not implemented with equality constraints:'
' connect, weld.'
)
wrap_objid_geom = m.wrap_objid[wrap_id_geom]
geom_pos = m.geom_pos[wrap_objid_geom]
geom_size = m.geom_size[wrap_objid_geom, 0]
# find sidesites (if any exist)
side_id = np.round(m.wrap_prm[wrap_id_geom]).astype(int)
side = m.site_pos[side_id]
# check for sidesite inside geom
if np.any(
(np.linalg.norm(side - geom_pos, axis=1) < geom_size) & (side_id >= 0)
):
raise NotImplementedError(
'Internal wrapping with sphere and cylinder geoms is not'
' implemented for spatial tendons.'
)
for enum_field, enum_type, mj_type in (
(m.actuator_biastype, types.BiasType, mujoco.mjtBias),
@@ -163,20 +159,54 @@ def put_model(
if f.metadata.get('restricted_to') != 'mjx'
}
fields = {f: getattr(m, f) for f in mj_field_names}
# zero out fields restricted to MuJoCo
if not _full_compat:
for f in types.Model.fields():
if f.metadata.get('restricted_to') == 'mujoco' and isinstance(
fields[f.name], np.ndarray
):
fields[f.name] = np.zeros((0,), dtype=fields[f.name].dtype)
fields['dof_hasfrictionloss'] = fields['dof_frictionloss'] > 0
fields['tendon_hasfrictionloss'] = fields['tendon_frictionloss'] > 0
fields['geom_rbound_hfield'] = fields['geom_rbound']
fields['cam_mat0'] = fields['cam_mat0'].reshape((-1, 3, 3))
fields['opt'] = _make_option(m.opt)
fields['opt'] = _make_option(m.opt, _full_compat=_full_compat)
fields['stat'] = _make_statistic(m.stat)
# spatial tendon wrap inside
fields['wrap_inside_maxiter'] = 5
fields['wrap_inside_tolerance'] = 1.0e-4
fields['wrap_inside_z_init'] = 1.0 - 1.0e-5
fields['is_wrap_inside'] = np.zeros(0, dtype=bool)
if m.nsite:
# find sphere or cylinder geoms (if any exist)
(wrap_id_geom,) = np.nonzero(
(m.wrap_type == mujoco.mjtWrap.mjWRAP_SPHERE)
| (m.wrap_type == mujoco.mjtWrap.mjWRAP_CYLINDER)
)
wrap_objid_geom = m.wrap_objid[wrap_id_geom]
geom_pos = m.geom_pos[wrap_objid_geom]
geom_size = m.geom_size[wrap_objid_geom, 0]
# find sidesites (if any exist)
side_id = np.round(m.wrap_prm[wrap_id_geom]).astype(int)
side = m.site_pos[side_id]
# wrap inside flag
fields['is_wrap_inside'] = np.array(
(np.linalg.norm(side - geom_pos, axis=1) < geom_size) & (side_id >= 0)
)
# Pre-compile meshes for MJX collisions.
fields['mesh_convex'] = [None] * m.nmesh
for i in mesh_geomid:
dataid = m.geom_dataid[i]
if fields['mesh_convex'][dataid] is None:
fields['mesh_convex'][dataid] = mesh.convex(m, dataid) # pytype: disable=unsupported-operands
fields['mesh_convex'] = tuple(fields['mesh_convex'])
if not _full_compat:
for i in mesh_geomid:
dataid = m.geom_dataid[i]
if fields['mesh_convex'][dataid] is None:
fields['mesh_convex'][dataid] = mesh.convex(m, dataid) # pytype: disable=unsupported-operands
fields['mesh_convex'] = tuple(fields['mesh_convex'])
model = types.Model(**{k: copy.copy(v) for k, v in fields.items()})
@@ -195,8 +225,8 @@ def make_data(
m: the model to use
device: which device to use - if unspecified picks the default device
_full_compat: create all MjData fields on device irrespective of MJX support
This is an experimental feature. Avoid using it for now.
If using this flag, also use _full_compat for put_model.
This is an experimental feature. Avoid using it for now. If using this
flag, also use _full_compat for put_model.
Returns:
an initialized mjx.Data placed on device
@@ -218,12 +248,19 @@ def make_data(
solreffriction=jp.zeros((ncon, mujoco.mjNREF), dtype=float),
solimp=jp.zeros((ncon, mujoco.mjNIMP), dtype=float),
dim=dim,
geom1=jp.full((ncon,), -1, dtype=jp.int32),
geom2=jp.full((ncon,), -1, dtype=jp.int32),
geom=jp.full((ncon, 2), -1, dtype=jp.int32),
# let jax pick contact.geom int precision, for interop with
# jax_enable_x64
geom1=jp.full((ncon,), -1, dtype=int),
geom2=jp.full((ncon,), -1, dtype=int),
geom=jp.full((ncon, 2), -1, dtype=int),
efc_address=efc_address,
)
if m.opt.cone == types.ConeType.ELLIPTIC and np.any(contact.dim == 1):
raise NotImplementedError(
'condim=1 with ConeType.ELLIPTIC not implemented.'
)
zero_fields = {
'solver_niter': (int,),
'time': (float,),
@@ -233,7 +270,6 @@ def make_data(
'ctrl': (m.nu, float),
'qfrc_applied': (m.nv, float),
'xfrc_applied': (m.nbody, 6, float),
'eq_active': (m.neq, jp.uint8),
'mocap_pos': (m.nmocap, 3, float),
'mocap_quat': (m.nmocap, 4, float),
'qacc': (m.nv, float),
@@ -275,12 +311,14 @@ def make_data(
'wrap_obj': (m.nwrap, 2, jp.int32),
'wrap_xpos': (m.nwrap, 6, float),
'actuator_length': (m.nu, float),
'moment_rownnz': (m.nu, jp.int32),
'moment_rowadr': (m.nu, jp.int32),
'moment_colind': (m.nJmom, jp.int32),
'actuator_moment': (m.nu, m.nv, float),
'crb': (m.nbody, 10, float),
'qM': (m.nM, float) if support.is_sparse(m) else (m.nv, m.nv, float),
'qLD': (m.nM, float) if support.is_sparse(m) else (m.nv, m.nv, float),
'qLDiagInv': (m.nv, float) if support.is_sparse(m) else (0, float),
'qLDiagSqrtInv': (m.nv, float),
'bvh_aabb_dyn': (m.nbvhdynamic, 6, float),
'bvh_active': (m.nbvh, jp.uint8),
'flexedge_velocity': (m.nflexedge, float),
@@ -298,12 +336,23 @@ def make_data(
'subtree_angmom': (m.nbody, 3, float),
'qH': (m.nM, float) if support.is_sparse(m) else (m.nv, m.nv, float),
'qHDiagInv': (m.nv, float),
'D_rownnz': (m.nv, jp.int32),
'D_rowadr': (m.nv, jp.int32),
'D_colind': (m.nD, jp.int32),
'B_rownnz': (m.nbody, jp.int32),
'B_rowadr': (m.nbody, jp.int32),
'B_colind': (m.nB, jp.int32),
'M_rownnz': (m.nv, jp.int32),
'M_rowadr': (m.nv, jp.int32),
'M_colind': (m.nM, jp.int32),
'mapM2M': (m.nM, jp.int32),
'C_rownnz': (m.nv, jp.int32),
'C_rowadr': (m.nv, jp.int32),
'C_colind': (m.nC, jp.int32),
'mapM2C': (m.nC, jp.int32),
'D_rownnz': (m.nv, jp.int32),
'D_rowadr': (m.nv, jp.int32),
'D_diag': (m.nv, jp.int32),
'D_colind': (m.nD, jp.int32),
'mapM2D': (m.nD, jp.int32),
'mapD2M': (m.nM, jp.int32),
'qDeriv': (m.nD, float),
'qLU': (m.nD, float),
'actuator_force': (m.nu, float),
@@ -345,7 +394,8 @@ def make_data(
qpos=jp.array(m.qpos0),
contact=contact,
efc_type=efc_type,
**zero_fields
eq_active=m.eq_active0,
**zero_fields,
)
return d
@@ -408,7 +458,7 @@ def get_data_into(
ncon = (d_i.contact.dist <= 0).sum()
efc_active = (d_i.efc_J != 0).any(axis=1)
nefc = int(efc_active.sum())
result_i.nnzJ = nefc * m.nv
result_i.nJ = nefc * m.nv
if ncon != result_i.ncon or nefc != result_i.nefc:
mujoco._functions._realloc_con_efc(result_i, ncon=ncon, nefc=nefc) # pylint: disable=protected-access
result_i.efc_J_rownnz[:] = np.repeat(m.nv, nefc)
@@ -427,6 +477,26 @@ def get_data_into(
result_i.contact.efc_address[:] = efc_map[result_i.contact.efc_address]
continue
# MuJoCo actuator_moment is sparse, MJX uses a dense representation.
if field.name == 'actuator_moment':
moment_rownnz = np.zeros(m.nu, dtype=np.int32)
moment_rowadr = np.zeros(m.nu, dtype=np.int32)
moment_colind = np.zeros(m.nJmom, dtype=np.int32)
actuator_moment = np.zeros(m.nJmom)
if m.nu:
mujoco.mju_dense2sparse(
actuator_moment,
d_i.actuator_moment,
moment_rownnz,
moment_rowadr,
moment_colind,
)
result_i.moment_rownnz[:] = moment_rownnz
result_i.moment_rowadr[:] = moment_rowadr
result_i.moment_colind[:] = moment_colind
result_i.actuator_moment[:] = actuator_moment
continue
value = getattr(d_i, field.name)
if field.name in ('nefc', 'ncon'):
@@ -440,14 +510,22 @@ def get_data_into(
elif field.name == 'qM' and not support.is_sparse(m):
value = value[dof_i, dof_j]
elif field.name == 'qLD' and not support.is_sparse(m):
value = value[dof_i, dof_j]
# TODO(erikfrey): provide correct qLDs
value = np.zeros(m.nM)
elif field.name == 'qLDiagInv' and not support.is_sparse(m):
value = np.ones(m.nv)
if isinstance(value, np.ndarray) and value.shape:
if restricted_to in ('mujoco', 'mjx') and value.shape == (0,):
if restricted_to in ('mujoco', 'mjx'):
continue # don't copy fields that are mujoco-only or MJX-only
getattr(result_i, field.name)[:] = value
else:
result_field = getattr(result_i, field.name)
if result_field.shape != value.shape:
raise ValueError(
f'Input field {field.name} has shape {value.shape}, but output'
f' has shape {result_field.shape}'
)
result_field[:] = value
else:
setattr(result_i, field.name, value)
@@ -464,7 +542,7 @@ def _make_contact(
# if we have fewer Contacts for a condim range, pad the range with zeros
# build a map for where to find a dim-matching contact, or -1 if none
contact_map = np.zeros_like(dim) - 1
contact_map = -np.ones_like(dim)
for i, di in enumerate(fields['dim']):
space = [j for j, dj in enumerate(dim) if di == dj and contact_map[j] == -1]
if not space:
@@ -497,8 +575,8 @@ def put_data(
d: the data to put on device
device: which device to use - if unspecified picks the default device
_full_compat: put all MjModel fields onto device irrespective of MJX support
This is an experimental feature. Avoid using it for now.
If using this flag, also use _full_compat for put_model.
This is an experimental feature. Avoid using it for now. If using this
flag, also use _full_compat for put_model.
Returns:
an mjx.Data placed on device
@@ -532,6 +610,17 @@ def put_data(
# MJX does not support islanding, so only transfer the first solver_niter
fields['solver_niter'] = fields['solver_niter'][0]
# convert sparse representation of actuator_moment to dense matrix
moment = np.zeros((m.nu, m.nv))
mujoco.mju_sparse2dense(
moment,
d.actuator_moment,
d.moment_rownnz,
d.moment_rowadr,
d.moment_colind,
)
fields['actuator_moment'] = moment
contact, contact_map = _make_contact(d.contact, dim, efc_address)
# pad efc fields: MuJoCo efc arrays are sparse for inactive constraints.
@@ -539,12 +628,14 @@ def put_data(
# neither: it contains zeros for inactive constraints, and efc_J is always
# (nefc, nv). this may change in the future.
if mujoco.mj_isSparse(m):
nr = d.efc_J_rownnz.shape[0]
efc_j = np.zeros((nr, m.nv))
for i in range(nr):
rowadr = d.efc_J_rowadr[i]
for j in range(d.efc_J_rownnz[i]):
efc_j[i, d.efc_J_colind[rowadr + j]] = fields['efc_J'][rowadr + j]
efc_j = np.zeros((d.efc_J_rownnz.shape[0], m.nv))
mujoco.mju_sparse2dense(
efc_j,
fields['efc_J'],
d.efc_J_rownnz,
d.efc_J_rowadr,
d.efc_J_colind,
)
fields['efc_J'] = efc_j
else:
fields['efc_J'] = fields['efc_J'].reshape((-1 if m.nv else 0, m.nv))
@@ -574,10 +665,28 @@ def put_data(
if num_rows > 1 and m.opt.cone == mujoco.mjtCone.mjCONE_PYRAMIDAL:
num_rows = (num_rows - 1) * 2
efc_i, efc_o = d.contact.efc_address[id_from], efc_address[id_to]
value[efc_o:efc_o + num_rows] = fields[fname][efc_i:efc_i + num_rows]
if efc_i == -1:
continue
value[efc_o : efc_o + num_rows] = fields[fname][efc_i : efc_i + num_rows]
fields[fname] = value
if _full_compat:
# full compatibility mode, we store sparse qM regardless of jacobian setting
fields['_qM_sparse'] = fields['qM']
fields['_qLD_sparse'] = fields['qLD']
fields['_qLDiagInv_sparse'] = fields['qLDiagInv']
else:
fields['_qM_sparse'] = jp.zeros(0, dtype=float)
fields['_qLD_sparse'] = jp.zeros(0, dtype=float)
fields['_qLDiagInv_sparse'] = jp.zeros(0, dtype=float)
# otherwise clear out unused arrays
for f in types.Data.fields():
if f.metadata.get('restricted_to') == 'mujoco' and isinstance(
fields[f.name], np.ndarray
):
fields[f.name] = np.zeros(0, dtype=fields[f.name].dtype)
# convert qM and qLD if jacobian is dense
if not support.is_sparse(m):
fields['qM'] = np.zeros((m.nv, m.nv))
@@ -590,24 +699,11 @@ def put_data(
fields['qLD'] = np.zeros((m.nv, m.nv))
fields['qLDiagInv'] = np.zeros(0)
if _full_compat:
# full compatibility mode, we store sparse qM regardless of jacobian setting
fields['_qM_sparse'] = fields['qM']
fields['_qLD_sparse'] = fields['qLD']
fields['_qLDiagInv_sparse'] = fields['qLDiagInv']
else:
fields['_qM_sparse'] = jp.zeros(0, dtype=float)
fields['_qLD_sparse'] = jp.zeros(0, dtype=float)
fields['_qLDiagInv_sparse'] = jp.zeros(0, dtype=float)
# otherwise clear out unused arrays
for f in types.Data.fields():
if f.metadata.get('restricted_to') == 'mujoco':
fields[f.name] = np.zeros(0, dtype=fields[f.name].dtype)
fields['contact'] = contact
fields.update(ne=ne, nf=nf, nl=nl, nefc=nefc, ncon=ncon, efc_type=efc_type)
# copy because device_put is async:
data = types.Data(**{k: copy.copy(v) for k, v in fields.items()})
return jax.device_put(data, device=device)
data = jax.device_put(data, device=device)
return _strip_weak_type(data)
+114 -30
View File
@@ -20,6 +20,10 @@ import jax
from jax import numpy as jp
import mujoco
from mujoco import mjx
from mujoco.mjx._src import test_util
# pylint: disable=g-importing-member
from mujoco.mjx._src.types import ConeType
# pylint: enable=g-importing-member
import numpy as np
@@ -91,12 +95,11 @@ _MULTIPLE_CONSTRAINTS = """
class ModelIOTest(parameterized.TestCase):
"""IO tests for mjx.Model."""
@parameterized.parameters(
_MULTIPLE_CONVEX_OBJECTS, _MULTIPLE_CONSTRAINTS
)
@parameterized.parameters(_MULTIPLE_CONVEX_OBJECTS, _MULTIPLE_CONSTRAINTS)
def test_put_model(self, xml):
m = mujoco.MjModel.from_xml_string(xml)
mx = mjx.put_model(m)
def assert_not_weak_type(x):
if isinstance(x, jax.Array):
assert not x.weak_type
@@ -117,6 +120,9 @@ class ModelIOTest(parameterized.TestCase):
self.assertEqual(mx.nM, m.nM)
self.assertAlmostEqual(mx.opt.timestep, m.opt.timestep)
# fields restricted to MuJoCo should not be populated
self.assertEqual(mx.bvh_aabb.shape, (0,))
np.testing.assert_allclose(mx.body_parentid, m.body_parentid)
np.testing.assert_allclose(mx.geom_type, m.geom_type)
np.testing.assert_allclose(mx.geom_bodyid, m.geom_bodyid)
@@ -166,33 +172,6 @@ class ModelIOTest(parameterized.TestCase):
)
)
def test_spatial_tendon_not_implemented(self):
with self.assertRaises(NotImplementedError):
mjx.put_model(mujoco.MjModel.from_xml_string("""
<mujoco>
<worldbody>
<body name="arm">
<joint name="arm" axis="0 1 0"/>
<geom name="shoulder" type="sphere" size=".05"/>
<site name="arm" pos="-.1 0 .05"/>
<site name="sidesite" pos="0 0 0"/>
</body>
<body name="slider" pos=".05 0 -.2">
<joint name="slider" type="slide" damping="1"/>
<geom name="slider" type="box" size=".01 .01 .01"/>
<site name="slider" pos="0 0 .01"/>
</body>
</worldbody>
<tendon>
<spatial name="rope" range="0 .35">
<site site="slider"/>
<geom geom="shoulder" sidesite="sidesite"/>
<site site="arm"/>
</spatial>
</tendon>
</mujoco>"""))
def test_margin_gap_mesh_not_implemented(self):
with self.assertRaises(NotImplementedError):
mjx.put_model(mujoco.MjModel.from_xml_string("""
@@ -220,6 +199,20 @@ class ModelIOTest(parameterized.TestCase):
<worldbody/>
</mujoco>"""))
def test_wrap_inside(self):
m = test_util.load_test_file('tendon/wrap_sidesite.xml')
mx0 = mjx.put_model(m)
np.testing.assert_equal(
mx0.is_wrap_inside,
np.array([1, 0, 1, 0, 1, 1, 0]),
)
m.site_pos[2] = m.site_pos[1]
mx1 = mjx.put_model(m)
np.testing.assert_equal(
mx1.is_wrap_inside,
np.array([0, 0, 1, 0, 1, 0, 0]),
)
class DataIOTest(parameterized.TestCase):
"""IO tests for mjx.Data."""
@@ -310,6 +303,15 @@ class DataIOTest(parameterized.TestCase):
np.testing.assert_allclose(dx.cvel, d.cvel)
np.testing.assert_allclose(dx.cdof_dot, d.cdof_dot)
# check that there are no weak types
self.assertFalse(
any(
jax.tree_util.tree_flatten(
jax.tree_util.tree_map(lambda x: x.weak_type, dx)
)[0]
)
)
# check that qM is transformed properly
qm = np.zeros((m.nv, m.nv), dtype=np.float64)
mujoco.mj_fullM(m, qm, d.qM)
@@ -422,6 +424,26 @@ class DataIOTest(parameterized.TestCase):
np.testing.assert_allclose(d_2.efc_aref, d.efc_aref)
np.testing.assert_allclose(d_2.contact.efc_address, d.contact.efc_address)
def test_get_data_runs(self):
xml = """
<mujoco>
<compiler autolimits="true"/>
<worldbody>
<body name="box">
<joint name="slide1" type="slide" axis="1 0 0" />
<geom type="box" size=".05 .05 .05" mass="1"/>
</body>
</worldbody>
<actuator>
<motor joint="slide1"/>
</actuator>
</mujoco>
"""
m = mujoco.MjModel.from_xml_string(xml)
d = mujoco.MjData(m)
dx = mjx.put_data(m, d)
mjx.get_data(m, dx)
def test_get_data_batched(self):
"""Test that get_data makes correct List[MjData] for batched Data."""
@@ -460,6 +482,18 @@ class DataIOTest(parameterized.TestCase):
self.assertEqual(d_2.contact.frame.shape, (1, 9))
np.testing.assert_allclose(d_2.contact.frame, d.contact.frame)
def test_get_data_into_wrong_shape(self):
"""Tests that get_data_into throwsif input and output shapes don't match."""
m = mujoco.MjModel.from_xml_string(_MULTIPLE_CONSTRAINTS)
d = mujoco.MjData(m)
mujoco.mj_step(m, d, 2)
dx = mjx.put_data(m, d)
m_2 = mujoco.MjModel.from_xml_string(_MULTIPLE_CONVEX_OBJECTS)
d_2 = mujoco.MjData(m_2)
with self.assertRaisesRegex(ValueError, r'Input field.*has shape.*'):
mjx.get_data_into(d_2, m, dx)
def test_make_matches_put(self):
"""Test that make_data produces a pytree that matches put_data."""
@@ -475,5 +509,55 @@ class DataIOTest(parameterized.TestCase):
# calling make_data. they should be interchangeable for jax functions:
step_fn_jit(mjx.make_data(m))
def test_contact_elliptic_condim1(self):
"""Test that condim=1 with ConeType.ELLIPTIC is not implemented."""
m = mujoco.MjModel.from_xml_string("""
<mujoco>
<worldbody>
<geom size="0 0 1e-5" type="plane" condim="1"/>
<body>
<freejoint/>
<geom size="0.1" condim="1"/>
</body>
</worldbody>
</mujoco>
""")
m.opt.cone = ConeType.ELLIPTIC
with self.assertRaises(NotImplementedError):
mjx.make_data(m)
@parameterized.product(
sensor=['accelerometer', 'force', 'torque'], equality=['connect', 'weld']
)
def test_sensor_constraint_compatibility(self, sensor, equality):
"""Test unsupported sensor and equality constraint combinations."""
equality_constraint = f'{equality} body1="body1" body2="body2"'
if equality == 'connect':
equality_constraint += ' anchor="0 0 0"'
m = mujoco.MjModel.from_xml_string(f"""
<mujoco>
<worldbody>
<body name="body1">
<freejoint/>
<geom size="0.1"/>
<site name="site1"/>
</body>
<body name="body2">
<freejoint/>
<geom size="0.1"/>
</body>
</worldbody>
<equality>
<{equality_constraint}/>
</equality>
<sensor>
<{sensor} site="site1"/>
</sensor>
</mujoco>
""")
with self.assertRaises(NotImplementedError):
mjx.put_model(m)
if __name__ == '__main__':
absltest.main()
+2 -1
View File
@@ -28,6 +28,7 @@ def matmul_unroll(a: jax.Array, b: jax.Array) -> jax.Array:
Args:
a: left hand of matmul operand
b: right hand of matmul operand
Returns:
the matrix product of the inputs.
"""
@@ -50,7 +51,7 @@ def norm(
"""Calculates a linalg.norm(x) that's safe for gradients at x=0.
Avoids a poorly defined gradient for jnp.linal.norm(0) see
https://github.com/google/jax/issues/3058 for details
https://github.com/jax-ml/jax/issues/3058 for details
Args:
x: A jnp.array
axis: The axis along which to compute the norm
+2 -41
View File
@@ -53,42 +53,6 @@ def _get_face_norm(vert: np.ndarray, face: np.ndarray) -> np.ndarray:
return face_norm
def _get_unique_edge_dir(vert: np.ndarray, face: np.ndarray) -> np.ndarray:
"""Returns unique edge directions.
Args:
vert: (n_vert, 3) vertices
face: (n_face, n_vert) face index array
Returns:
edges: tuples of vertex indexes for each edge
"""
r_face = np.roll(face, 1, axis=1)
edges = np.concatenate(np.array([face, r_face]).T)
# do a first pass to remove duplicates
edges.sort(axis=1)
edges = np.unique(edges, axis=0)
edges = edges[edges[:, 0] != edges[:, 1]] # get rid of edges from padded face
# get normalized edge directions
edge_vert = vert.take(edges, axis=0)
edge_dir = edge_vert[:, 0] - edge_vert[:, 1]
norms = np.sqrt(np.sum(edge_dir**2, axis=1))
edge_dir = edge_dir / norms.reshape((-1, 1))
# get the first unique edge for all pairwise comparisons
diff1 = edge_dir[:, None, :] - edge_dir[None, :, :]
diff2 = edge_dir[:, None, :] + edge_dir[None, :, :]
matches = (np.linalg.norm(diff1, axis=-1) < 1e-6) | (
np.linalg.norm(diff2, axis=-1) < 1e-6
)
matches = np.tril(matches).sum(axis=-1)
unique_edge_idx = np.where(matches == 1)[0]
return edges[unique_edge_idx]
def _get_edge_normals(
face: np.ndarray, face_norm: np.ndarray
) -> Tuple[np.ndarray, np.ndarray]:
@@ -172,8 +136,8 @@ def _merge_coplanar(
# resize faces that exceed max polygon vertices
if face.shape[0] > _MAX_HULL_FACE_VERTICES:
name = m.names[m.name_meshadr[meshid]:]
name = name[:name.find(b'\x00')].decode('utf-8')
name = m.names[m.name_meshadr[meshid] :]
name = name[: name.find(b'\x00')].decode('utf-8')
warnings.warn(
f'Mesh "{name}" has a coplanar face with more than '
f'{_MAX_HULL_FACE_VERTICES} vertices. This may lead to performance '
@@ -221,7 +185,6 @@ def box(info: GeomInfo) -> ConvexInfo:
# pyformat: enable
face_normal = _get_face_norm(vert, face)
edge, edge_face_normal = _get_edge_normals(face, face_normal)
edge_dir = _get_unique_edge_dir(vert, face)
face = vert[face] # materialize full nface x nvert matrix
c = ConvexInfo(
@@ -233,7 +196,6 @@ def box(info: GeomInfo) -> ConvexInfo:
face_normal,
edge,
edge_face_normal,
edge_dir,
)
c = jax.tree_util.tree_map(jp.array, c)
vert = jax.vmap(jp.multiply, in_axes=(None, 0))(c.vert, info.size)
@@ -356,7 +318,6 @@ def hfield_prism(vert: jax.Array) -> ConvexInfo:
face_norm,
edges,
face_norm[edge_face_norm],
None,
)
return jax.tree_util.tree_map(jp.array, c)
+3 -25
View File
@@ -53,22 +53,14 @@ class MeshTest(absltest.TestCase):
map_ = {v: k for k, v in enumerate(vidx)}
h_face = np.vectorize(map_.get)(convex_face)
face_verts = sorted([tuple(sorted(set(s))) for s in h_face.tolist()])
expected_face_verts = sorted([
(0, 3, 4), (1, 3, 4), (0, 2, 4), (0, 1, 2, 3), (1, 2, 4)])
expected_face_verts = sorted(
[(0, 3, 4), (1, 3, 4), (0, 2, 4), (0, 1, 2, 3), (1, 2, 4)]
)
self.assertSequenceEqual(
face_verts,
expected_face_verts,
)
# check edges
edge_dir = mesh._get_unique_edge_dir(convex_vert, convex_face)
unique_edge = np.vectorize(map_.get)(edge_dir)
unique_edge = np.array(sorted(unique_edge.tolist()))
np.testing.assert_array_equal(
unique_edge,
np.array([[0, 2], [0, 3], [0, 4], [1, 4], [2, 4], [3, 4]]),
)
# face normals
face_normal = mesh._get_face_norm(convex_vert, convex_face)
self.assertEqual(face_normal.shape, (5, 3))
@@ -142,19 +134,5 @@ class ConvexHull2DTest(absltest.TestCase):
np.testing.assert_array_almost_equal(normal, expected)
class UniqueEdgesTest(absltest.TestCase):
def test_tetrahedron_edges(self):
"""Tests unique edges for a tetrahedron."""
vert = np.array(
[[-0.1, 0.0, -0.1], [0.0, 0.1, 0.1], [0.1, 0.0, -0.1], [0.0, -0.1, 0.1]]
)
face = np.array([[0, 1, 2], [0, 2, 3], [0, 3, 1], [2, 1, 3]])
idx = mesh._get_unique_edge_dir(vert, face)
np.testing.assert_array_equal(
idx, np.array([[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]])
)
if __name__ == '__main__':
absltest.main()
+1
View File
@@ -31,6 +31,7 @@ from mujoco.mjx._src.types import Model
def _spring_damper(m: Model, d: Data) -> jax.Array:
"""Applies joint level spring and damping forces."""
def fn(jnt_typs, stiffness, qpos_spring, qpos):
qpos_i = 0
qfrcs = []
+28 -3
View File
@@ -108,6 +108,29 @@ def _ray_capsule(
return x
def _ray_ellipsoid(
size: jax.Array,
pnt: jax.Array,
vec: jax.Array,
) -> jax.Array:
"""Returns the distance at which a ray intersects with an ellipsoid."""
# invert size^2
s = 1 / jp.square(size)
# (x*lvec+lpnt)' * diag(1/size^2) * (x*lvec+lpnt) = 1
svec = s * vec
a = svec @ vec
b = svec @ pnt
c = (s * pnt) @ pnt - 1
# solve a*x^2 + 2*b*x + c = 0
x0, x1 = _ray_quad(a, b, c)
x = jp.where(jp.isinf(x0), x1, x0)
return x
def _ray_box(
size: jax.Array,
pnt: jax.Array,
@@ -201,6 +224,7 @@ _RAY_FUNC = {
GeomType.PLANE: _ray_plane,
GeomType.SPHERE: _ray_sphere,
GeomType.CAPSULE: _ray_capsule,
GeomType.ELLIPSOID: _ray_ellipsoid,
GeomType.BOX: _ray_box,
GeomType.MESH: _ray_mesh,
}
@@ -233,8 +257,6 @@ def ray(
dists, ids = [], []
geom_filter = m.geom_bodyid != bodyexclude
geom_filter &= (m.geom_matid != -1) | (m.geom_rgba[:, 3] != 0)
geom_filter &= (m.geom_matid == -1) | (m.mat_rgba[m.geom_matid, 3] != 0)
geom_filter &= flg_static | (m.body_weldid[m.geom_bodyid] != 0)
if geomgroup:
geomgroup = np.array(geomgroup, dtype=bool)
@@ -244,8 +266,10 @@ def ray(
geom_pnts = jax.vmap(lambda x, y: x.T @ (pnt - y))(d.geom_xmat, d.geom_xpos)
geom_vecs = jax.vmap(lambda x: x.T @ vec)(d.geom_xmat)
geom_filter_dyn = (m.geom_matid != -1) | (m.geom_rgba[:, 3] != 0)
geom_filter_dyn &= (m.geom_matid == -1) | (m.mat_rgba[m.geom_matid, 3] != 0)
for geom_type, fn in _RAY_FUNC.items():
id_, = np.nonzero(geom_filter & (m.geom_type == geom_type))
(id_,) = np.nonzero(geom_filter & (m.geom_type == geom_type))
if id_.size == 0:
continue
@@ -257,6 +281,7 @@ def ray(
else:
dist = jax.vmap(fn)(*args)
dist = jp.where(geom_filter_dyn[id_], dist, jp.inf)
dists, ids = dists + [dist], ids + [id_]
if not ids:
+11 -15
View File
@@ -144,13 +144,11 @@ def _check_input(m: Model, args: Any, in_types: str) -> None:
}
for idx, (arg, typ) in enumerate(zip(args, in_types)):
if len(arg) != size[typ]:
raise IndexError(
(
f'f argument "{idx}" with type "{typ}" has length "{len(arg)}"'
f' which does not match the in_types[{idx}] expected length of '
f'"{size[typ]}".'
)
)
raise IndexError((
f'f argument "{idx}" with type "{typ}" has length "{len(arg)}"'
f' which does not match the in_types[{idx}] expected length of '
f'"{size[typ]}".'
))
def _check_output(
@@ -158,13 +156,11 @@ def _check_output(
) -> None:
"""Checks that scan output has the right shape."""
if y.shape[0] != take_ids.shape[0]:
raise IndexError(
(
f'f output "{idx}" with type "{typ}" has shape "{y.shape[0]}" '
f'which does not match the out_types[{idx}] expected size of'
f' "{take_ids.shape[0]}".'
)
)
raise IndexError((
f'f output "{idx}" with type "{typ}" has shape "{y.shape[0]}" '
f'which does not match the out_types[{idx}] expected size of'
f' "{take_ids.shape[0]}".'
))
def flat(
@@ -400,7 +396,7 @@ def body_tree(
if t == 'b':
continue
elif t == 'j':
key += (tuple(m.jnt_type[np.nonzero(m.jnt_bodyid == id_)[0]]))
key += tuple(m.jnt_type[np.nonzero(m.jnt_bodyid == id_)[0]])
elif t == 'v':
key += (len(np.nonzero(m.dof_bodyid == id_)[0]),)
elif t == 'q':
+3
View File
@@ -90,6 +90,7 @@ class ScanTest(absltest.TestCase):
if tuple(jnt_types) == (JointType.FREE,):
return None
return val + sum(jnt_types)
b_expect = jp.array([[0, 0], [3, 3], [8, 8]])
b_out = scan.flat(m, no_free, 'jb', 'b', m.jnt_type, b_in)
np.testing.assert_equal(np.array(b_out), np.array(b_expect))
@@ -99,6 +100,7 @@ class ScanTest(absltest.TestCase):
if jnt_types.size == 0:
self.fail('world has no dofs, should not be called')
return val + sum(jnt_types)
v_in = jp.ones((m.nv, 1))
scan.flat(m, no_world, 'jv', 'v', m.jnt_type, v_in)
@@ -141,6 +143,7 @@ class ScanTest(absltest.TestCase):
return None
carry = jp.zeros_like(val) if carry is None else carry
return carry + val + sum(jnt_types)
b_expect = jp.array([[0, 0], [3, 3], [8, 8]])
b_out = scan.body_tree(m, no_free, 'jb', 'b', m.jnt_type, b_in)
np.testing.assert_equal(np.array(b_out), np.array(b_expect))
+10 -11
View File
@@ -461,7 +461,7 @@ def sensor_acc(m: Model, d: Data) -> Data:
force, condim_id = support.contact_force_dim(m, d, dim)
forces.append(force)
condim_ids.append(condim_id)
forces = jp.concatenate(forces)[jp.concatenate(condim_ids)]
forces = jp.concatenate(forces)[np.argsort(np.concatenate(condim_ids))]
# get bodies of contact geoms
conbody = jp.array(m.geom_bodyid)[d.contact.geom]
@@ -483,14 +483,14 @@ def sensor_acc(m: Model, d: Data) -> Data:
conray = jp.where(conbody1[..., None], -conray, conray)
# compute distance, mapping over sites and contacts
def _distance(
site_size, site_xpos, site_xmat, site_type, contact_pos, conray
):
return jax.vmap(
lambda site_size, site_xpos, site_xmat, conray: jax.vmap(
lambda pnt, vec: ray.ray_geom(site_size, pnt, vec, site_type)
)((contact_pos - site_xpos) @ site_xmat, conray @ site_xmat)
)(site_size, site_xpos, site_xmat, conray)
def _distance(site_size, site_xpos, site_xmat, site_type, pos, conray):
def dist(size, xpos, xmat, conray):
pnt = (pos - xpos) @ xmat
vec = conray @ xmat
ray_geom_ = lambda pnt, vec: ray.ray_geom(size, pnt, vec, site_type)
return jax.vmap(ray_geom_)(pnt, vec)
return jax.vmap(dist)(site_size, site_xpos, site_xmat, conray)
dist = []
dist_id = []
@@ -506,8 +506,7 @@ def sensor_acc(m: Model, d: Data) -> Data:
)
dist.append(jp.where(jp.isinf(dist_site), 0, dist_site))
dist_id.append(dist_id_site)
dist = jp.vstack(dist)[np.concatenate(dist_id)]
dist = jp.vstack(dist)[np.argsort(np.concatenate(dist_id))]
# accumulate normal forces for each site
sensor = jp.dot((dist > 0) & contacts, forces[:, 0])
+7 -6
View File
@@ -17,11 +17,11 @@
from absl.testing import absltest
from absl.testing import parameterized
import jax
from jax import numpy as jp
import mujoco
from mujoco import mjx
from mujoco.mjx._src import test_util
from mujoco.mjx._src.types import ConeType
import numpy as np
# tolerance for difference between MuJoCo and MJX smooth calculations - mostly
@@ -41,10 +41,14 @@ def _assert_attr_eq(a, b, attr):
class SensorTest(parameterized.TestCase):
@parameterized.parameters('sensor/model.xml', 'sensor/sensor.xml')
def test_sensor(self, filename):
@parameterized.product(
filename=['sensor/model.xml', 'sensor/sensor.xml'],
cone_type=list(ConeType),
)
def test_sensor(self, filename, cone_type):
"""Tests MJX sensor functions match MuJoCo sensor functions."""
m = test_util.load_test_file(filename)
m.opt.cone = cone_type
d = mujoco.MjData(m)
# give the system a little kick to ensure we have non-identity rotations
d.qvel = 0.1 * np.random.random(m.nv)
@@ -101,17 +105,14 @@ class SensorTest(parameterized.TestCase):
<body>
<joint type="hinge"/>
<geom name="geom0" size="0.1"/>
<site name="site0"/>
<body>
<joint type="hinge"/>
<geom name="geom1" size="0.25"/>
<site name="site1"/>
</body>
</body>
</worldbody>
<sensor>
<distance name="distance" geom1="geom0" geom2="geom1"/>
<touch name="touch" site="site0"/>
</sensor>
</mujoco>
""")
+61 -15
View File
@@ -24,6 +24,7 @@ from mujoco.mjx._src import support
from mujoco.mjx._src.types import CamLightType
from mujoco.mjx._src.types import Data
from mujoco.mjx._src.types import DisableBit
from mujoco.mjx._src.types import EqType
from mujoco.mjx._src.types import JointType
from mujoco.mjx._src.types import Model
from mujoco.mjx._src.types import TrnType
@@ -291,7 +292,7 @@ def crb(m: Model, d: Data) -> Data:
crb_cdof = jax.vmap(math.inert_mul)(crb_dof, d.cdof)
qm = support.make_m(m, crb_cdof, d.cdof, m.dof_armature)
d = d.replace(qM=qm)
if support.is_sparse(m):
if support.is_sparse(m) and d._qM_sparse.size > 0: # pylint: disable=protected-access
d = d.replace(_qM_sparse=qm)
return d
@@ -333,7 +334,7 @@ def factor_m(m: Model, d: Data) -> Data:
pivots = []
out = []
for (b, e, madr_d, madr_ij) in updates:
for b, e, madr_d, madr_ij in updates:
width = e - b
rows.append(np.arange(madr_ij, madr_ij + width))
madr_ijs.append(np.full((width,), madr_ij))
@@ -353,7 +354,10 @@ def factor_m(m: Model, d: Data) -> Data:
qld = (qld / qld[jp.array(madr_ds)]).at[m.dof_Madr].set(qld_diag)
d = d.replace(qLD=qld, qLDiagInv=1 / qld_diag)
d = d.replace(_qLD_sparse=d.qLD, _qLDiagInv_sparse=d.qLDiagInv)
if d._qLD_sparse.size > 0: # pylint: disable=protected-access
d = d.replace(_qLD_sparse=d.qLD)
if d._qLDiagInv_sparse.size > 0: # pylint: disable=protected-access
d = d.replace(_qLDiagInv_sparse=d.qLDiagInv)
return d
@@ -507,7 +511,6 @@ def subtree_vel(m: Model, d: Data) -> Data:
angmom_child, mom_parent_child = carry
return angmom + mom + angmom_child + mom_parent_child, mom_parent
subtree_angmom, _ = scan.body_tree(
m,
_subtree_angmom,
@@ -531,6 +534,7 @@ def subtree_vel(m: Model, d: Data) -> Data:
def rne(m: Model, d: Data) -> Data:
"""Computes inverse dynamics using the recursive Newton-Euler algorithm."""
# forward scan over tree: accumulate link center of mass acceleration
def cacc_fn(cacc, cdof_dot, qvel):
if cacc is None:
@@ -632,6 +636,10 @@ def rne_postconstraint(m: Model, d: Data) -> Data:
)
# TODO(taylorhowell): connect and weld constraints
if np.any(m.eq_type == EqType.CONNECT):
raise NotImplementedError('Connect constraints are not implemented.')
if np.any(m.eq_type == EqType.WELD):
raise NotImplementedError('Weld constraints are not implemented.')
# forward pass over bodies: compute cacc, cfrc_int
def _forward(carry, cfrc_ext, cinert, cvel, body_dofadr, body_dofnum):
@@ -726,7 +734,9 @@ def tendon(m: Model, d: Data) -> Data:
for adr, num in zip(m.tendon_adr, m.tendon_num):
for id_pulley in wrap_id_pulley:
if adr <= id_pulley < adr + num:
divisor[id_pulley : adr + num] = m.wrap_prm[id_pulley]
divisor[id_pulley : adr + num] = np.maximum(
mujoco.mjMINVAL, m.wrap_prm[id_pulley]
)
# process spatial tendon sites
(wrap_id_site,) = np.nonzero(m.wrap_type == WrapType.SITE)
@@ -802,6 +812,7 @@ def tendon(m: Model, d: Data) -> Data:
geom_xmat = d.geom_xmat[wrap_objid_geom]
geom_size = m.geom_size[wrap_objid_geom, 0]
geom_type = m.wrap_type[wrap_id_geom]
is_sphere = geom_type == WrapType.SPHERE
# get body ids for site-geom-site instances
body_id_site0 = m.site_bodyid[wrap_objid_site0]
@@ -813,17 +824,52 @@ def tendon(m: Model, d: Data) -> Data:
side = d.site_xpos[side_id]
has_sidesite = np.expand_dims(np.array(side_id >= 0), -1)
# wrap inside
# TODO(taylorhowell): check that is_wrap_inside is consistent with
# site and geom relative positions
(wrap_inside_id,) = np.nonzero(m.is_wrap_inside)
(wrap_outside_id,) = np.nonzero(~m.is_wrap_inside)
# compute geom wrap length and connect points (if wrap occurs)
lengths_geomgeom, geom_pnt0, geom_pnt1 = jax.vmap(support.wrap)(
site_pnt0,
site_pnt1,
geom_xpos,
geom_xmat,
geom_size,
side,
has_sidesite,
geom_type == WrapType.SPHERE,
v_wrap = jax.vmap(
support.wrap, in_axes=(0, 0, 0, 0, 0, 0, 0, 0, None, None, None, None)
)
lengths_inside, pnt0_inside, pnt1_inside = v_wrap(
site_pnt0[wrap_inside_id],
site_pnt1[wrap_inside_id],
geom_xpos[wrap_inside_id],
geom_xmat[wrap_inside_id],
geom_size[wrap_inside_id],
side[wrap_inside_id],
has_sidesite[wrap_inside_id],
is_sphere[wrap_inside_id],
True,
m.wrap_inside_maxiter,
m.wrap_inside_tolerance,
m.wrap_inside_z_init,
)
lengths_outside, pnt0_outside, pnt1_outside = v_wrap(
site_pnt0[wrap_outside_id],
site_pnt1[wrap_outside_id],
geom_xpos[wrap_outside_id],
geom_xmat[wrap_outside_id],
geom_size[wrap_outside_id],
side[wrap_outside_id],
has_sidesite[wrap_outside_id],
is_sphere[wrap_outside_id],
False,
m.wrap_inside_maxiter,
m.wrap_inside_tolerance,
m.wrap_inside_z_init,
)
wrap_id = np.argsort(np.concatenate([wrap_inside_id, wrap_outside_id]))
vstack_ = lambda x, y: jp.vstack([x, y])[wrap_id]
lengths_geomgeom = vstack_(lengths_inside, lengths_outside)
geom_pnt0 = vstack_(pnt0_inside, pnt0_outside)
geom_pnt1 = vstack_(pnt1_inside, pnt1_outside)
lengths_geomgeom = lengths_geomgeom.reshape(-1)
# identify geoms where wrap does not occur
@@ -980,7 +1026,7 @@ def _site_dof_mask(m: Model) -> np.ndarray:
mask = np.ones((m.nu, m.nv))
for i in np.nonzero(m.actuator_trnid[:, 1] != -1)[0]:
id_, refid = m.actuator_trnid[i]
# intialize last dof address for each body
# initialize last dof address for each body
b0 = m.body_weldid[m.site_bodyid[id_]]
b1 = m.body_weldid[m.site_bodyid[refid]]
dofadr0 = m.body_dofadr[b0] + m.body_dofnum[b0] - 1
+50 -8
View File
@@ -20,6 +20,7 @@ import jax
import mujoco
from mujoco import mjx
from mujoco.mjx._src import test_util
from mujoco.mjx._src.types import ConeType
import numpy as np
# tolerance for difference between MuJoCo and MJX smooth calculations - mostly
@@ -87,10 +88,16 @@ class SmoothTest(absltest.TestCase):
dx = jax.jit(mjx.crb)(mx, mjx.put_data(m, d))
_assert_attr_eq(d, dx, 'crb')
_assert_attr_eq(d, dx, 'qM')
_assert_eq(dx._qM_sparse, np.zeros(0), '_qM_sparse')
# factor_m
dx = jax.jit(mjx.factor_m)(mx, mjx.put_data(m, d))
_assert_attr_eq(d, dx, 'qLD')
qLDLegacy = np.zeros(mx.nM) # pylint:disable=invalid-name
for i in range(m.nM):
qLDLegacy[d.mapM2M[i]] = d.qLD[i]
_assert_eq(qLDLegacy, dx.qLD, 'qLD')
_assert_attr_eq(d, dx, 'qLDiagInv')
_assert_eq(dx._qLD_sparse, np.zeros(0), '_qLD_sparse')
_assert_eq(dx._qLDiagInv_sparse, np.zeros(0), '_qLDiagInv_sparse')
# com_vel
dx = jax.jit(mjx.com_vel)(mx, mjx.put_data(m, d))
_assert_attr_eq(d, dx, 'cvel')
@@ -113,7 +120,17 @@ class SmoothTest(absltest.TestCase):
# transmission
dx = jax.jit(mjx.transmission)(mx, dx)
_assert_attr_eq(d, dx, 'actuator_length')
_assert_attr_eq(d, dx, 'actuator_moment')
# convert sparse actuator_moment to dense representation
moment = np.zeros((m.nu, m.nv))
mujoco.mju_sparse2dense(
moment,
d.actuator_moment,
d.moment_rownnz,
d.moment_rowadr,
d.moment_colind,
)
_assert_eq(moment, dx.actuator_moment, 'actuator_moment')
def test_disable_gravity(self):
m = mujoco.MjModel.from_xml_string("""
@@ -174,7 +191,17 @@ class SmoothTest(absltest.TestCase):
mujoco.mj_transmission(m, d)
dx = jax.jit(mjx.transmission)(mx, dx)
_assert_attr_eq(d, dx, 'actuator_length')
_assert_attr_eq(d, dx, 'actuator_moment')
# convert sparse actuator_moment to dense representation
moment = np.zeros((m.nu, m.nv))
mujoco.mju_sparse2dense(
moment,
d.actuator_moment,
d.moment_rownnz,
d.moment_rowadr,
d.moment_colind,
)
_assert_eq(moment, dx.actuator_moment, 'actuator_moment')
def test_subtree_vel(self):
"""Tests MJX subtree_vel function matches MuJoCo mj_subtreeVel."""
@@ -195,27 +222,42 @@ class SmoothTest(absltest.TestCase):
_assert_attr_eq(d, dx, 'subtree_linvel')
_assert_attr_eq(d, dx, 'subtree_angmom')
def test_rnepostconstraint(self):
class RnePostConstraintTest(parameterized.TestCase):
@parameterized.parameters(ConeType)
def test_rnepostconstraint(self, cone_type):
"""Tests MJX rne_postconstraint function to match MuJoCo mj_rnePostConstraint."""
m = mujoco.MjModel.from_xml_string("""
<mujoco>
<worldbody>
<geom name="floor" size="0 0 .05" type="plane"/>
<geom name="floor" size="10 10 .05" type="plane"/>
<body pos="0 0 1">
<joint type="ball" damping="1"/>
<geom type="capsule" size="0.1 0.5" fromto="0 0 0 0.5 0 0"/>
<geom type="capsule" size="0.1 0.5" fromto="0 0 0 0.5 0 0" condim="1"/>
<body pos="0.5 0 0">
<joint type="ball" damping="1"/>
<geom type="capsule" size="0.1 0.5" fromto="0 0 0 0.5 0 0"/>
<geom type="capsule" size="0.1 0.5" fromto="0 0 0 0.5 0 0" condim="3"/>
</body>
</body>
<body pos="0 1 1">
<joint type="ball" damping="1"/>
<geom type="capsule" size="0.1 0.5" fromto="0 0 0 0.5 0 0" condim="6"/>
<body pos="0.5 0 0">
<joint type="ball" damping="1"/>
<geom type="capsule" size="0.1 0.5" fromto="0 0 0 0.5 0 0" condim="3"/>
</body>
</body>
</worldbody>
<keyframe>
<key qpos='0.424577 0.450592 0.451703 -0.642391 0.729379 0.545151 0.407756 0.0674697'/>
<key qpos='0.424577 0.450592 0.451703 -0.642391 0.729379 0.545151 0.407756 0.0674697 0.424577 1.450592 0.451703 -0.642391 0.729379 0.545151 0.407756 0.0674697'/>
</keyframe>
</mujoco>
""")
# set cone type
m.opt.cone = cone_type
# create data and set to keyframe
d = mujoco.MjData(m)
mujoco.mj_resetDataKeyframe(m, d, 0)
# apply external forces
+15 -3
View File
@@ -52,6 +52,7 @@ class _Context(PyTreeNode):
u: friction cone (normal and tangents) (num(con.dim > 1), 6)
h: cone hessian (num(con.dim > 1), 6, 6)
"""
qacc: jax.Array
qfrc_constraint: jax.Array
Jaref: jax.Array # pylint: disable=invalid-name
@@ -225,6 +226,7 @@ class _LSContext(PyTreeNode):
def _while_loop_scan(cond_fun, body_fun, init_val, max_iter):
"""Scan-based implementation (jit ok, reverse-mode autodiff ok)."""
def _iter(val):
next_val = body_fun(val)
next_cond = cond_fun(next_val)
@@ -279,7 +281,12 @@ def _update_constraint(m: Model, d: Data, ctx: _Context) -> _Context:
friction = d.contact.friction[d.contact.dim > 1]
efc_address = d.contact.efc_address[d.contact.dim > 1]
dim = d.contact.dim[d.contact.dim > 1]
slice_fn = jax.vmap(lambda x: jax.lax.dynamic_slice(ctx.Jaref, (x,), (6,)))
# to prevent out of range append zeros to ctx.Jaref
slice_fn = jax.vmap(
lambda x: jax.lax.dynamic_slice(
jp.concatenate((ctx.Jaref, jp.zeros((3)))), (x,), (6,)
)
)
u = slice_fn(efc_address) * ctx.fri
mu, n, t = ctx.fri[:, 0], u[:, 0], jax.vmap(math.norm)(u[:, 1:])
@@ -377,7 +384,7 @@ def _update_gradient(m: Model, d: Data, ctx: _Context) -> _Context:
# set efc of cone H along diagonal
for i, (condim, addr) in enumerate(zip(dim, efc_address)):
h_cone = ctx.h[i, :condim, :condim]
cm = cm.at[addr:addr+condim, addr:addr+condim].add(h_cone)
cm = cm.at[addr : addr + condim, addr : addr + condim].add(h_cone)
h = d.efc_J.T @ cm @ d.efc_J
else:
h = (d.efc_J.T * d.efc_D * ctx.active) @ d.efc_J
@@ -433,7 +440,12 @@ def _linesearch(m: Model, d: Data, ctx: _Context) -> _Context:
quad = quad.at[jp.array(efc_con)].add(quad[jp.array(efc_fri)])
# rescale to make primal cone circular
jv_fn = jax.vmap(lambda x: jax.lax.dynamic_slice(jv, (x,), (6,)))
# to prevent out of range append zeros to jv
jv_fn = jax.vmap(
lambda x: jax.lax.dynamic_slice(
jp.concatenate((jv, jp.zeros(3))), (x,), (6,)
)
)
efc_elliptic = d.contact.efc_address[mask]
v = jv_fn(efc_elliptic) * ctx.fri
uu = jp.sum(ctx.u[:, 1:] * ctx.u[:, 1:], axis=1)
+19
View File
@@ -21,6 +21,7 @@ import mujoco
from mujoco import mjx
from mujoco.mjx._src import solver
from mujoco.mjx._src import test_util
from mujoco.mjx._src.types import ConeType
import numpy as np
@@ -146,6 +147,24 @@ class SolverTest(parameterized.TestCase):
nnz = dx.efc_J.any(axis=1)
_assert_eq(d.efc_force, dx.efc_force[nnz], 'efc_force')
# TODO(taylorhowell): condim=1 with ConeType.ELLIPTIC
@parameterized.product(condim=(3, 4, 6), cone=tuple(ConeType))
def test_condim(self, condim, cone):
"""Test contact dimension."""
m = mujoco.MjModel.from_xml_string(f"""
<mujoco>
<worldbody>
<geom size="0 0 1e-5" type="plane" condim="1"/>
<body pos="0 0 0.09">
<freejoint/>
<geom size="0.1" condim="{condim}"/>
</body>
</worldbody>
</mujoco>
""")
m.opt.cone = cone
solver.solve(mjx.put_model(m), mjx.put_data(m, mujoco.MjData(m)))
if __name__ == '__main__':
absltest.main()
+593 -21
View File
@@ -13,16 +13,20 @@
# limitations under the License.
# ==============================================================================
"""Engine support functions."""
from collections.abc import Iterable, Sequence
from typing import Optional, Tuple, Union
import jax
from jax import numpy as jp
import mujoco
from mujoco.introspect import mjxmacro
from mujoco.mjx._src import math
from mujoco.mjx._src import scan
# pylint: disable=g-importing-member
from mujoco.mjx._src.types import ConeType
from mujoco.mjx._src.types import Data
from mujoco.mjx._src.types import JacobianType
from mujoco.mjx._src.types import JointType
from mujoco.mjx._src.types import Model
# pylint: enable=g-importing-member
import numpy as np
@@ -235,7 +239,7 @@ def _getadr(
def id2name(
m: Union[Model, mujoco.MjModel], typ: mujoco._enums.mjtObj, i: int
) -> Optional[str]:
"""Gets the name of an object with the specified mjtObj type and id.
"""Gets the name of an object with the specified mjtObj type and ids.
See mujoco.id2name for more info.
@@ -283,6 +287,268 @@ def name2id(
return names_map.get(name, -1)
class BindModel(object):
"""Class holding the requested MJX Model and spec id for binding a spec to Model."""
def __init__(self, model: Model, specs: Sequence[mujoco.MjStruct]):
self.model = model
self.prefix = ''
ids = []
for spec in specs:
if not spec.name:
raise KeyError(f'cannot bind spec with empty name')
elif isinstance(spec, mujoco.MjsBody):
self.prefix = 'body_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_BODY, spec.name)
elif isinstance(spec, mujoco.MjsJoint):
self.prefix = 'jnt_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_JOINT, spec.name)
elif isinstance(spec, mujoco.MjsGeom):
self.prefix = 'geom_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_GEOM, spec.name)
elif isinstance(spec, mujoco.MjsSite):
self.prefix = 'site_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_SITE, spec.name)
elif isinstance(spec, mujoco.MjsLight):
self.prefix = 'light_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_LIGHT, spec.name)
elif isinstance(spec, mujoco.MjsCamera):
self.prefix = 'cam_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_CAMERA, spec.name)
elif isinstance(spec, mujoco.MjsMesh):
self.prefix = 'mesh_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_MESH, spec.name)
elif isinstance(spec, mujoco.MjsHField):
self.prefix = 'hfield_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_HFIELD, spec.name)
elif isinstance(spec, mujoco.MjsPair):
self.prefix = 'pair_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_PAIR, spec.name)
elif isinstance(spec, mujoco.MjsTendon):
self.prefix = 'tendon_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_TENDON, spec.name)
elif isinstance(spec, mujoco.MjsActuator):
self.prefix = 'actuator_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_ACTUATOR, spec.name)
elif isinstance(spec, mujoco.MjsSensor):
self.prefix = 'sensor_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_SENSOR, spec.name)
elif isinstance(spec, mujoco.MjsNumeric):
self.prefix = 'numeric_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_NUMERIC, spec.name)
elif isinstance(spec, mujoco.MjsText):
self.prefix = 'text_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_TEXT, spec.name)
elif isinstance(spec, mujoco.MjsTuple):
self.prefix = 'tuple_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_TUPLE, spec.name)
elif isinstance(spec, mujoco.MjsKey):
self.prefix = 'key_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_KEY, spec.name)
elif isinstance(spec, mujoco.MjsEquality):
self.prefix = 'eq_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_EQUALITY, spec.name)
elif isinstance(spec, mujoco.MjsExclude):
self.prefix = 'exclude_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_EXCLUDE, spec.name)
elif isinstance(spec, mujoco.MjsSkin):
self.prefix = 'skin_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_SKIN, spec.name)
elif isinstance(spec, mujoco.MjsMaterial):
self.prefix = 'material_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_MATERIAL, spec.name)
else:
raise ValueError('invalid spec type')
if idx < 0:
raise KeyError(f'invalid name: {spec.name}') # pytype: disable=attribute-error
ids.append(idx)
if len(ids) == 1:
self.id = ids[0]
else:
self.id = ids
def _slice(self, name: str, idx: Union[int, slice, Sequence[int]]):
_, expected_dim = mjxmacro.MJMODEL[name]
var = getattr(self.model, name)
if expected_dim == '1':
return var[..., idx]
elif expected_dim == '9':
return var[..., idx, :, :]
return var[..., idx, :]
def __getattr__(self, name: str):
return self._slice(self.prefix + name, self.id)
def _bind_model(
self: Model, obj: mujoco.MjStruct | Iterable[mujoco.MjStruct]
) -> BindModel:
"""Bind a Mujoco spec to an MJX Model."""
if isinstance(obj, mujoco.MjStruct):
obj = (obj,)
else:
obj = tuple(obj)
return BindModel(self, obj)
class BindData(object):
"""Class holding the requested MJX Data and spec id for binding a spec to Data."""
def __init__(
self, data: Data, model: Model, specs: Sequence[mujoco.MjStruct]
):
self.data = data
self.model = model
self.prefix = ''
ids = []
for spec in specs:
if not spec.name:
raise KeyError(f'cannot bind spec with empty name')
elif isinstance(spec, mujoco.MjsBody):
idx = name2id(model, mujoco.mjtObj.mjOBJ_BODY, spec.name)
elif isinstance(spec, mujoco.MjsJoint):
self.prefix = 'jnt_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_JOINT, spec.name)
elif isinstance(spec, mujoco.MjsGeom):
self.prefix = 'geom_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_GEOM, spec.name)
elif isinstance(spec, mujoco.MjsSite):
self.prefix = 'site_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_SITE, spec.name)
elif isinstance(spec, mujoco.MjsLight):
self.prefix = 'light_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_LIGHT, spec.name)
elif isinstance(spec, mujoco.MjsCamera):
self.prefix = 'cam_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_CAMERA, spec.name)
elif isinstance(spec, mujoco.MjsTendon):
self.prefix = 'ten_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_TENDON, spec.name)
elif isinstance(spec, mujoco.MjsActuator):
self.prefix = 'actuator_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_ACTUATOR, spec.name)
elif isinstance(spec, mujoco.MjsSensor):
self.prefix = 'sensor_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_SENSOR, spec.name)
elif isinstance(spec, mujoco.MjsEquality):
self.prefix = 'eq_'
idx = name2id(model, mujoco.mjtObj.mjOBJ_EQUALITY, spec.name)
else:
raise ValueError('invalid spec type')
if idx < 0:
raise KeyError(f'invalid name: {spec.name}') # pytype: disable=attribute-error
ids.append(idx)
if len(ids) == 1:
self.id = ids[0]
else:
self.id = ids
def __getname(self, name: str):
"""Get the name of the attribute and check if the type is correct."""
if name == 'sensordata':
if self.prefix == 'sensor_':
return name
else:
raise AttributeError('sensordata is not available for this type')
if name == 'ctrl':
if self.prefix == 'actuator_':
return name
else:
raise AttributeError('ctrl is not available for this type')
if name == 'qpos' or name == 'qvel' or name == 'qacc':
if self.prefix == 'jnt_':
return name
else:
raise AttributeError('qpos, qvel, qacc are not available for this type')
else:
return self.prefix + name
def _slice(self, name: str, idx: Union[int, slice, Sequence[int]]):
_, expected_dim = mjxmacro.MJDATA[name]
var = getattr(self.data, name)
if expected_dim == '1':
return var[..., idx]
elif expected_dim == '9':
return var[..., idx, :, :]
return var[..., idx, :]
def __getattr__(self, name: str):
if name in ('sensordata', 'qpos', 'qvel', 'qacc'):
adr = num = 0
if name == 'sensordata':
adr = self.model.sensor_adr[self.id]
num = self.model.sensor_dim[self.id]
elif name == 'qpos':
adr = self.model.jnt_qposadr[self.id]
typ = self.model.jnt_type[self.id]
num = sum((typ == jt) * jt.qpos_width() for jt in JointType)
elif name == 'qvel' or name == 'qacc':
adr = self.model.jnt_dofadr[self.id]
typ = self.model.jnt_type[self.id]
num = sum((typ == jt) * jt.dof_width() for jt in JointType)
if isinstance(self.id, list):
idx = []
for a, n in zip(adr, num):
idx.extend(a + j for j in range(n))
return self._slice(self.__getname(name), idx)
elif num > 1:
return self._slice(self.__getname(name), slice(adr, adr + num))
else:
return self._slice(self.__getname(name), adr)
return self._slice(self.__getname(name), self.id)
def set(self, name: str, value: jax.Array) -> Data:
"""Set the value of an array in an MJX Data."""
if name == 'sensordata':
raise AttributeError('sensordata is readonly')
array = getattr(self.data, self.__getname(name))
dim = 1 if len(array.shape) == 1 else array.shape[-1]
try:
iter(value)
except TypeError:
value = [value]
if name in ('qpos', 'qvel', 'qacc'):
adr = num = 0
if name == 'qpos':
adr = self.model.jnt_qposadr[self.id]
typ = self.model.jnt_type[self.id]
num = sum((typ == jt) * jt.qpos_width() for jt in JointType)
elif name == 'qvel' or name == 'qacc':
adr = self.model.jnt_dofadr[self.id]
typ = self.model.jnt_type[self.id]
num = sum((typ == jt) * jt.dof_width() for jt in JointType)
if not isinstance(self.id, list):
adr = [adr]
num = [num]
elif isinstance(self.id, list):
adr = self.id * dim
num = [dim for _ in range(len(self.id))]
else:
adr = [self.id * dim]
num = [dim]
i = 0
for a, n in zip(adr, num):
shape = array.shape
array = array.flatten().at[a : a + n].set(value[i : i + n]).reshape(shape)
i += n
return self.data.replace(**{self.__getname(name): array})
def _bind_data(
self: Data, model: Model, obj: mujoco.MjStruct | Iterable[mujoco.MjStruct]
) -> BindData:
"""Bind a Mujoco spec to an MJX Data."""
if isinstance(obj, mujoco.MjStruct):
obj = (obj,)
else:
obj = tuple(obj)
return BindData(self, model, obj)
Model.bind = _bind_model
Data.bind = _bind_data
def _decode_pyramid(
pyramid: jax.Array, mu: jax.Array, condim: int
) -> jax.Array:
@@ -307,12 +573,13 @@ def contact_force(
"""Extract 6D force:torque for one contact, in contact frame by default."""
efc_address = d.contact.efc_address[contact_id]
condim = d.contact.dim[contact_id]
if m.opt.cone == mujoco.mjtCone.mjCONE_PYRAMIDAL:
if m.opt.cone == ConeType.PYRAMIDAL:
force = _decode_pyramid(
d.efc_force[efc_address:], d.contact.friction[contact_id], condim
)
elif m.opt.cone == mujoco.mjtCone.mjCONE_ELLIPTIC:
raise NotImplementedError('Elliptic cone force is not implemented yet.')
elif m.opt.cone == ConeType.ELLIPTIC:
force = d.efc_force[efc_address : efc_address + condim]
force = jp.concatenate([force, jp.zeros((6 - condim))])
else:
raise ValueError(f'Unknown cone type: {m.opt.cone}')
@@ -331,7 +598,7 @@ def contact_force_dim(
idx_dim = (d.contact.efc_address >= 0) & (d.contact.dim == dim)
# contact force from efc
if m.opt.cone == mujoco.mjtCone.mjCONE_PYRAMIDAL:
if m.opt.cone == ConeType.PYRAMIDAL:
efc_address = (
d.contact.efc_address[idx_dim, None]
+ np.arange(np.where(dim == 1, 1, 2 * (dim - 1)))[None]
@@ -340,12 +607,13 @@ def contact_force_dim(
force = jax.vmap(_decode_pyramid, in_axes=(0, 0, None))(
efc_force, d.contact.friction[idx_dim], dim
)
return force, np.where(idx_dim)[0]
elif m.opt.cone == mujoco.mjtCone.mjCONE_ELLIPTIC:
# TODO(taylorhowell): add support for elliptic cone
raise NotImplementedError('Elliptic cone force is not implemented yet.')
elif m.opt.cone == ConeType.ELLIPTIC:
efc_address = d.contact.efc_address[idx_dim, None] + np.arange(dim)[None]
force = d.efc_force[efc_address]
force = jp.hstack([force, jp.zeros((force.shape[0], 6 - dim))])
else:
raise ValueError(f'Unknown cone type: {m.opt.cone}.')
return force, np.where(idx_dim)[0]
def _length_circle(
@@ -356,7 +624,9 @@ def _length_circle(
p0n = math.normalize(p0).reshape(-1)
p1n = math.normalize(p1).reshape(-1)
angle = jp.arccos(jp.dot(p0n, p1n))
# clip input to closed interval for jp.arccos to prevent potential nan
# TODO(taylorhowell): add test for case where clip is necessary
angle = jp.arccos(jp.clip(jp.dot(p0n, p1n), -1, 1))
# flip if necessary
cross = p0[1] * p1[0] - p0[0] * p1[1]
@@ -381,7 +651,11 @@ def _is_intersect(
(p2[0] - p1[0]) * (p1[1] - p3[1]) - (p2[1] - p1[1]) * (p1[0] - p3[0])
) / det
return (a >= 0) & (a <= 1) & (b >= 0) & (b <= 1)
return jp.where(
jp.abs(det) < mujoco.mjMINVAL,
0,
(a >= 0) & (a <= 1) & (b >= 0) & (b <= 1),
)
def wrap_circle(
@@ -394,7 +668,9 @@ def wrap_circle(
sqrad = rad * rad
dif = jp.array([d[2] - d[0], d[3] - d[1]])
dd = dif[0] ** 2 + dif[1] ** 2
a = jp.clip(-(dif[0] * d[0] + dif[1] * d[1]) / dd, 0, 1)
a = jp.clip(
-(dif[0] * d[0] + dif[1] * d[1]) / jp.maximum(mujoco.mjMINVAL, dd), 0, 1
)
seg = jp.array([a * dif[0] + d[0], a * dif[1] + d[1]])
point_inside0 = sqlen0 < sqrad
@@ -408,13 +684,21 @@ def wrap_circle(
# construct the two solutions, compute goodness
def _sol(sgn):
sqrt0 = jp.sqrt(sqlen0 - sqrad)
sqrt1 = jp.sqrt(sqlen1 - sqrad)
sqrt0 = jp.sqrt(jp.maximum(mujoco.mjMINVAL, sqlen0 - sqrad))
sqrt1 = jp.sqrt(jp.maximum(mujoco.mjMINVAL, sqlen1 - sqrad))
d00 = (d[0] * sqrad + sgn * rad * d[1] * sqrt0) / sqlen0
d01 = (d[1] * sqrad - sgn * rad * d[0] * sqrt0) / sqlen0
d10 = (d[2] * sqrad - sgn * rad * d[3] * sqrt1) / sqlen1
d11 = (d[3] * sqrad + sgn * rad * d[2] * sqrt1) / sqlen1
d00 = (d[0] * sqrad + sgn * rad * d[1] * sqrt0) / jp.maximum(
mujoco.mjMINVAL, sqlen0
)
d01 = (d[1] * sqrad - sgn * rad * d[0] * sqrt0) / jp.maximum(
mujoco.mjMINVAL, sqlen0
)
d10 = (d[2] * sqrad - sgn * rad * d[3] * sqrt1) / jp.maximum(
mujoco.mjMINVAL, sqlen1
)
d11 = (d[3] * sqrad + sgn * rad * d[2] * sqrt1) / jp.maximum(
mujoco.mjMINVAL, sqlen1
)
sol = jp.array([[d00, d01], [d10, d11]])
@@ -463,6 +747,136 @@ def wrap_circle(
return wlen, pnt
def wrap_inside(
end: jax.Array,
radius: jax.Array,
maxiter: int,
tolerance: float,
z_init: float,
) -> Tuple[jax.Array, jax.Array]:
"""Compute 2D inside wrap point.
Args:
end: 2D points
radius: radius of circle
Returns:
status: 0 if wrap, else -1
concatentated 2D wrap points: jax.Array
"""
mjMINVAL = mujoco.mjMINVAL # pylint: disable=invalid-name
# constants
len0 = math.norm(end[:2])
len1 = math.norm(end[2:])
dif = jp.array([end[2] - end[0], end[3] - end[1]])
dd = dif[0] * dif[0] + dif[1] * dif[1]
# either point inside circle or circle too small: no wrap
no_wrap0 = (
(len0 <= radius)
| (len1 <= radius)
| (radius < mjMINVAL)
| (len0 < mjMINVAL)
| (len1 < mjMINVAL)
)
# find nearest point on line segment to origin: d0 + a*dif
a = -1 * (dif[0] * end[0] + dif[1] * end[1]) / jp.maximum(mjMINVAL, dd)
tmp = end[:2] + a * dif
# segment-circle intersection: no wrap
no_wrap1 = (dd > mjMINVAL) & (a > 0) & (a < 1) & (math.norm(tmp) <= radius)
# prepare default in case of numerical failure: average
pnt_avg = 0.5 * jp.array([end[0] + end[2], end[1] + end[3]])
pnt_avg = radius * math.normalize(pnt_avg)
# compute function parameters: asin(A*z) + asin(B*z) - 2*asin(z) + G = 0
A = radius / jp.maximum(mjMINVAL, len0) # pylint: disable=invalid-name
B = radius / jp.maximum(mjMINVAL, len1) # pylint: disable=invalid-name
cosG = (len0 * len0 + len1 * len1 - dd) / jp.maximum(mjMINVAL, 2 * len0 * len1) # pylint: disable=invalid-name
no_wrap2 = cosG < -1 + mjMINVAL
early_return0 = cosG > 1 - mjMINVAL
G = jp.arccos(cosG) # pylint: disable=invalid-name
# initialize solver
z = jp.array([z_init])
f = jp.arcsin(A * z) + jp.arcsin(B * z) - 2 * jp.arcsin(z) + G
# make sure initialization is not on the other side
early_return1 = f > 0
# iteratively solve with Newton's method
def _newton(carry, _):
# unpack
z, f, status_prev = carry
# check current solution
converged = jp.abs(f) <= tolerance
# compute derivative
df = (
A / jp.maximum(mjMINVAL, jp.sqrt(1 - z * z * A * A))
+ B / jp.maximum(mjMINVAL, jp.sqrt(1 - z * z * B * B))
- 2 / jp.maximum(mjMINVAL, jp.sqrt(1 - z * z))
)
# check sign; SHOULD NOT OCCUR
status0 = df > -mjMINVAL
# new point
z_next = z - (1 - converged) * f / jp.where(
jp.abs(df) < mjMINVAL, mjMINVAL, df
)
# make sure we are moving to the left; SHOULD NOT OCCUR
status1 = z_next > z
# evaluate solution
f_next = (
jp.arcsin(A * z_next)
+ jp.arcsin(B * z_next)
- 2 * jp.arcsin(z_next)
+ G
)
# exit if positive; SHOULD NOT OCCUR
status2 = f_next > tolerance
return (
z_next,
f_next,
status_prev | status0 | status1 | status2,
), None
# TODO(taylorhowell): compare performance of jax.lax.scan and jax.lax.while_loop
z, _, early_return2 = jax.lax.scan(
_newton, (z, f, jp.array([False])), None, maxiter
)[0]
# finalize: rotation by ang from vec = a or b, depending on cross(a,b) sign
sign = end[0] * end[3] - end[1] * end[2] > 0
vec = jp.where(sign, end[:2], end[2:])
vec = math.normalize(vec)
ang = jp.arcsin(z) - jp.where(sign, jp.arcsin(A * z), jp.arcsin(B * z))
pnt_sol = radius * jp.array([
jp.cos(ang) * vec[0] - jp.sin(ang) * vec[1],
jp.sin(ang) * vec[0] + jp.cos(ang) * vec[1],
]).reshape(-1)
no_wrap = no_wrap0 | no_wrap1 | no_wrap2
early_return = early_return0 | early_return1 | early_return2
status = -1 * no_wrap * jp.ones(1)
pnt = jp.where(early_return, pnt_avg, pnt_sol)
pnt = jp.where(no_wrap, jp.zeros(2), pnt)
return status, jp.concatenate([pnt, pnt])
def wrap(
x0: jax.Array,
x1: jax.Array,
@@ -472,7 +886,11 @@ def wrap(
side: jax.Array,
sidesite: jax.Array,
is_sphere: jax.Array,
):
is_wrap_inside: bool,
wrap_inside_maxiter: int,
wrap_inside_tolerance: float,
wrap_inside_z_init: float,
) -> Tuple[jax.Array, jax.Array, jax.Array]:
"""Wrap tendon around sphere or cylinder."""
# map sites to wrap object's local frame
p0 = xmat.T @ (x0 - xpos)
@@ -520,8 +938,13 @@ def wrap(
sd = jp.array([jp.dot(s, axis0), jp.dot(s, axis1)])
sd = math.normalize(sd) * size
# TODO(taylorhowell): implement wrap_inside for internal wrapping case
wlen, pnt = wrap_circle(d, sd, sidesite, size)
if is_wrap_inside:
wlen, pnt = wrap_inside(
d, size, wrap_inside_maxiter, wrap_inside_tolerance, wrap_inside_z_init
)
else:
wlen, pnt = wrap_circle(d, sd, sidesite, size)
no_wrap = wlen < 0
# reconstruct 3D points in local frame: res
@@ -557,3 +980,152 @@ def wrap(
wpnt1 = jp.where(invalid, jp.zeros(3), wpnt1)
return wlen, wpnt0, wpnt1
def muscle_gain_length(
length: jax.Array, lmin: jax.Array, lmax: jax.Array
) -> jax.Array:
"""Normalized muscle length-gain curve."""
# mid-ranges (maximum is at 1.0)
a = 0.5 * (lmin + 1)
b = 0.5 * (1 + lmax)
out0 = 0.5 * jp.square(
(length - lmin) / jp.maximum(mujoco.mjMINVAL, a - lmin)
)
out1 = 1 - 0.5 * jp.square((1 - length) / jp.maximum(mujoco.mjMINVAL, 1 - a))
out2 = 1 - 0.5 * jp.square((length - 1) / jp.maximum(mujoco.mjMINVAL, b - 1))
out3 = 0.5 * jp.square(
(lmax - length) / jp.maximum(mujoco.mjMINVAL, lmax - b)
)
out = jp.where(length <= b, out2, out3)
out = jp.where(length <= 1, out1, out)
out = jp.where(length <= a, out0, out)
out = jp.where((lmin <= length) & (length <= lmax), out, 0.0)
return out
def muscle_gain(
length: jax.Array,
vel: jax.Array,
lengthrange: jax.Array,
acc0: jax.Array,
prm: jax.Array,
) -> jax.Array:
"""Muscle active force."""
# unpack parameters
lrange = prm[:2]
force, scale, lmin, lmax, vmax, _, fvmax = prm[2:9]
force = jp.where(force < 0, scale / jp.maximum(mujoco.mjMINVAL, acc0), force)
# optimum length
L0 = (lengthrange[1] - lengthrange[0]) / jp.maximum( # pylint:disable=invalid-name
mujoco.mjMINVAL, lrange[1] - lrange[0]
)
# normalized length and velocity
L = lrange[0] + (length - lengthrange[0]) / jp.maximum(mujoco.mjMINVAL, L0) # pylint:disable=invalid-name
V = vel / jp.maximum(mujoco.mjMINVAL, L0 * vmax) # pylint:disable=invalid-name
# length curve
FL = muscle_gain_length(L, lmin, lmax) # pylint:disable=invalid-name
# velocity curve
y = fvmax - 1
FV = jp.where( # pylint:disable=invalid-name
V <= y, fvmax - jp.square(y - V) / jp.maximum(mujoco.mjMINVAL, y), fvmax
)
FV = jp.where(V <= 0, jp.square(V + 1), FV) # pylint:disable=invalid-name
FV = jp.where(V <= -1, 0, FV) # pylint:disable=invalid-name
# compute FVL and scale, make it negative
return -force * FL * FV
def muscle_bias(
length: jax.Array, lengthrange: jax.Array, acc0: jax.Array, prm: jax.Array
) -> jax.Array:
"""Muscle passive force."""
# unpack parameters
lrange = prm[:2]
force, scale, _, lmax, _, fpmax = prm[2:8]
force = jp.where(force < 0, scale / jp.maximum(mujoco.mjMINVAL, acc0), force)
# optimum length
L0 = (lengthrange[1] - lengthrange[0]) / jp.maximum( # pylint:disable=invalid-name
mujoco.mjMINVAL, lrange[1] - lrange[0]
)
# normalized length
L = lrange[0] + (length - lengthrange[0]) / jp.maximum(mujoco.mjMINVAL, L0) # pylint:disable=invalid-name
# half-quadratic to (L0 + lmax) / 2, linear beyond
b = 0.5 * (1 + lmax)
out1 = (
-force
* fpmax
* 0.5
* jp.square((L - 1) / jp.maximum(mujoco.mjMINVAL, b - 1))
)
out2 = -force * fpmax * (0.5 + (L - b) / jp.maximum(mujoco.mjMINVAL, b - 1))
out = jp.where(L <= b, out1, out2)
out = jp.where(L <= 1, 0.0, out)
return out
def muscle_dynamics_timescale(
dctrl: jax.Array,
tau_act: jax.Array,
tau_deact: jax.Array,
smoothing_width: jax.Array,
) -> jax.Array:
"""Muscle time constant with optional smoothing."""
# hard switching
tau_hard = jp.where(dctrl > 0, tau_act, tau_deact)
def _sigmoid(x):
# sigmoid function over 0 <= x <= 1 using quintic polynomial
# sigmoid: f(x) = 6 * x^5 - 15 * x^4 + 10 * x^3
# solution of f(0) = f'(0) = f''(0) = 0, f(1) = 1, f'(1) = f''(1) = 0
sol = x * x * x * (3 * x * (2 * x - 5) + 10)
sol = jp.where(x <= 0, 0, sol)
sol = jp.where(x >= 1, 1, sol)
return sol
# smooth switching
# scale by width, center around 0.5 midpoint, rescale to bounds
tau_smooth = tau_deact + (tau_act - tau_deact) * _sigmoid(
dctrl / smoothing_width + 0.5
)
return jp.where(smoothing_width < mujoco.mjMINVAL, tau_hard, tau_smooth)
def muscle_dynamics(
ctrl: jax.Array, act: jax.Array, prm: jax.Array
) -> jax.Array:
"""Muscle activation dynamics."""
# clamp control
ctrlclamp = jp.clip(ctrl, 0, 1)
# clamp activation
actclamp = jp.clip(act, 0, 1)
# compute timescales as in Millard et at. (2013)
# https://doi.org/10.1115/1.4023390
tau_act = prm[0] * (0.5 + 1.5 * actclamp) # activation timescale
tau_deact = prm[1] / (0.5 + 1.5 * actclamp) # deactivation timescale
smoothing_width = prm[2] # width of smoothing sigmoid
dctrl = ctrlclamp - act # excess excitation
tau = muscle_dynamics_timescale(dctrl, tau_act, tau_deact, smoothing_width)
# filter output
return dctrl / jp.maximum(mujoco.mjMINVAL, tau)
+519
View File
@@ -157,6 +157,199 @@ class SupportTest(parameterized.TestCase):
i = i if n is not None else -1
self.assertEqual(support.name2id(mx, obj, n), i)
def test_bind(self):
xml = """
<mujoco model="test_bind_model">
<worldbody>
<body pos="10 20 30" name="body1">
<joint axis="1 0 0" type="ball" name="joint1"/>
<geom size="1 2 3" type="box" name="geom1" pos="0 1 0"/>
</body>
<body pos="40 50 60" name="body2">
<joint axis="0 1 0" type="slide" name="joint2"/>
<geom size="4 5 6" type="box" name="geom2"/>
</body>
<body pos="70 80 90" name="body3">
<joint axis="0 0 1" type="slide" name="joint3"/>
<geom size="7 8 9" type="box" name="geom3"/>
</body>
</worldbody>
<actuator>
<motor name="actuator1" joint="joint1"/>
<motor name="actuator2" joint="joint2"/>
<motor name="actuator3" joint="joint3"/>
</actuator>
<sensor>
<framepos name="sensor1" objtype="body" objname="body1"/>
<framepos name="sensor2" objtype="body" objname="body2"/>
<framepos name="sensor3" objtype="body" objname="body3"/>
</sensor>
</mujoco>
"""
s = mujoco.MjSpec.from_string(xml)
m = s.compile()
d = mujoco.MjData(m)
mx = mjx.put_model(m)
dx = mjx.put_data(m, d)
mujoco.mj_step(m, d)
dx = mjx.step(mx, dx)
# test getting
np.testing.assert_array_equal(mx.bind(s.bodies).pos, m.body_pos)
np.testing.assert_array_equal(dx.bind(mx, s.bodies).xpos, d.xpos)
np.testing.assert_array_equal(m.bind(s.bodies[0]).mass, m.body_mass[0])
np.testing.assert_array_equal(m.bind(s.bodies[0:1]).mass, [m.body_mass[0]])
np.testing.assert_array_equal(mx.bind(s.bodies[0]).mass, m.body_mass[0])
np.testing.assert_array_equal(mx.bind(s.bodies[0:1]).mass, [m.body_mass[0]])
for i in range(m.nbody):
np.testing.assert_array_equal(m.bind(s.bodies[i]).pos, m.body_pos[i, :])
np.testing.assert_array_equal(mx.bind(s.bodies[i]).pos, m.body_pos[i, :])
np.testing.assert_array_equal(d.bind(s.bodies[i]).xpos, d.xpos[i, :])
np.testing.assert_array_equal(
dx.bind(mx, s.bodies[i]).xpos, d.xpos[i, :]
)
np.testing.assert_array_equal(
dx.bind(mx, s.bodies[i]).xfrc_applied, d.xfrc_applied[i, :]
)
np.testing.assert_array_equal(mx.bind(s.geoms).size, m.geom_size)
np.testing.assert_array_equal(dx.bind(mx, s.geoms).xpos, d.geom_xpos)
for i in range(m.ngeom):
np.testing.assert_array_equal(m.bind(s.geoms[i]).size, m.geom_size[i, :])
np.testing.assert_array_equal(mx.bind(s.geoms[i]).size, m.geom_size[i, :])
np.testing.assert_array_equal(d.bind(s.geoms[i]).xpos, d.geom_xpos[i, :])
np.testing.assert_array_equal(
dx.bind(mx, s.geoms[i]).xpos, d.geom_xpos[i, :]
)
np.testing.assert_array_equal(mx.bind(s.joints).axis, m.jnt_axis)
np.testing.assert_array_equal(mx.bind(s.joints).qposadr, m.jnt_qposadr)
np.testing.assert_array_equal(mx.bind(s.joints).dofadr, m.jnt_dofadr)
np.testing.assert_array_equal(dx.bind(mx, s.joints[1]).id, 1)
np.testing.assert_array_equal(dx.bind(mx, s.joints[1:2]).id, [1])
qposnum = [4, 1, 1] # one ball joint (4) and two slide joints (1)
dofnum = [3, 1, 1] # one ball joint (3) and two slide joints (1)
for i in range(m.njnt):
np.testing.assert_array_equal(m.bind(s.joints[i]).axis, m.jnt_axis[i, :])
np.testing.assert_array_equal(mx.bind(s.joints[i]).axis, m.jnt_axis[i, :])
np.testing.assert_array_almost_equal(
dx.bind(mx, s.joints[i]).qpos,
d.qpos[m.jnt_qposadr[i]:m.jnt_qposadr[i] + qposnum[i]], decimal=6
)
np.testing.assert_array_almost_equal(
dx.bind(mx, s.joints[i]).qvel,
d.qvel[m.jnt_dofadr[i]:m.jnt_dofadr[i] + dofnum[i]], decimal=6
)
np.testing.assert_array_almost_equal(
dx.bind(mx, s.joints[i]).qacc,
d.qacc[m.jnt_dofadr[i]:m.jnt_dofadr[i] + dofnum[i]], decimal=6
)
np.testing.assert_array_equal(dx.bind(mx, s.actuators).ctrl, d.ctrl)
for i in range(m.nu):
np.testing.assert_array_equal(d.bind(s.actuators[i]).ctrl, d.ctrl[i])
np.testing.assert_array_equal(
dx.bind(mx, s.actuators[i]).ctrl, d.ctrl[i]
)
np.testing.assert_array_equal(
dx.bind(mx, s.sensors).sensordata, d.sensordata
)
for i in range(m.nsensor):
np.testing.assert_array_equal(
dx.bind(mx, s.sensors[i]).sensordata,
d.sensordata[m.sensor_adr[i] : m.sensor_adr[i] + m.sensor_dim[i]],
)
# test setting
np.testing.assert_array_equal(d.ctrl, [0, 0, 0])
np.testing.assert_array_equal(dx.bind(mx, s.actuators).ctrl, d.ctrl)
dx2 = dx.bind(mx, s.actuators).set('ctrl', [1, 2, 3])
np.testing.assert_array_equal(dx2.bind(mx, s.actuators).ctrl, [1, 2, 3])
np.testing.assert_array_equal(dx.bind(mx, s.actuators).ctrl, [0, 0, 0])
dx3 = dx.bind(mx, s.actuators[1:]).set('ctrl', [4, 5])
np.testing.assert_array_equal(dx3.bind(mx, s.actuators).ctrl, [0, 4, 5])
np.testing.assert_array_equal(dx.bind(mx, s.actuators).ctrl, [0, 0, 0])
dx4 = dx.bind(mx, s.actuators[1]).set('ctrl', [6])
np.testing.assert_array_equal(dx4.bind(mx, s.actuators).ctrl, [0, 6, 0])
np.testing.assert_array_equal(dx.bind(mx, s.actuators).ctrl, [0, 0, 0])
dx5 = dx.bind(mx, s.actuators[1]).set('ctrl', 7)
np.testing.assert_array_equal(dx5.bind(mx, s.actuators).ctrl, [0, 7, 0])
np.testing.assert_array_equal(dx.bind(mx, s.actuators).ctrl, [0, 0, 0])
qpos_1step = [1.00000e00, -3.67875e-06, 0, 0, 0, -3.924e-05]
qpos_desired = [1, 0, 0, 0, 0, 8]
np.testing.assert_array_almost_equal(d.qpos, qpos_1step)
np.testing.assert_array_almost_equal(dx.bind(mx, s.joints).qpos, d.qpos)
dx6 = dx.bind(mx, s.joints[::2]).set('qpos', [1, 0, 0, 0, 8])
np.testing.assert_array_equal(dx6.bind(mx, s.joints).qpos, qpos_desired)
np.testing.assert_array_almost_equal(dx.bind(mx, s.joints).qpos, d.qpos)
dx6a = dx.bind(mx, s.joints[0]).set('qpos', qpos_desired[:4])
np.testing.assert_array_equal(
dx6a.bind(mx, s.joints[0]).qpos, qpos_desired[:4]
)
dx7 = dx.bind(mx, s.joints[::2]).set('qvel', [2.0, -1.2, 0.5, 0.3])
np.testing.assert_array_almost_equal(
dx7.bind(mx, s.joints).qvel, [2.0, -1.2, 0.5, 0.0, 0.3], decimal=6
)
dx8 = dx.bind(mx, s.joints[::2]).set('qacc', [3.0, -2.1, 0.6, 0.4])
np.testing.assert_array_almost_equal(
dx8.bind(mx, s.joints).qacc, [3.0, -2.1, 0.6, 0.0, 0.4], decimal=6
)
dx9 = dx.bind(mx, s.bodies[1]).set('xfrc_applied', [1, 2, 3, 4, 5, 6])
np.testing.assert_array_equal(
dx9.bind(mx, s.bodies[1]).xfrc_applied, [1, 2, 3, 4, 5, 6]
)
for body in s.bodies[:1] + s.bodies[2:]:
np.testing.assert_array_equal(
dx7.bind(mx, body).xfrc_applied, [0, 0, 0, 0, 0, 0]
)
# test invalid name
with self.assertRaises(
AttributeError, msg='ctrl is not available for this type'
):
print(dx.bind(mx, s.geoms).ctrl)
with self.assertRaises(
KeyError, msg='actuator_actuator_ctrl'
):
print(dx.bind(mx, s.actuators).actuator_ctrl)
with self.assertRaises(
AttributeError, msg='actuator_actuator_ctrl'
):
print(dx.bind(mx, s.actuators).set('actuator_ctrl', [1, 2, 3]))
with self.assertRaises(
AttributeError, msg='qpos, qvel, qacc are not available for this type'
):
print(dx.bind(mx, s.geoms).qpos)
with self.assertRaises(KeyError, msg='invalid name: invalid_actuator_name'):
s.actuators[0].name = 'invalid_actuator_name'
print(dx.bind(mx, s.actuators).set('ctrl', [1, 2, 3]))
with self.assertRaises(KeyError, msg='invalid name: invalid_geom_name'):
s.geoms[0].name = 'invalid_geom_name'
print(mx.bind(s.geoms).pos)
# test batched data
batch_size = 16
ds = [d for _ in range(batch_size)]
vdx = jax.vmap(lambda xpos: dx.replace(xpos=xpos))(
jp.array([d.xpos for d in ds], device=jax.devices('cpu')[0]))
for i in range(m.nbody):
np.testing.assert_array_equal(
vdx.bind(mx, s.bodies[i]).xpos, [d.xpos[i, :]] * batch_size
)
# test emtpy name
s.worldbody.add_body()
m = s.compile()
mx = mjx.put_model(m)
with self.assertRaises(KeyError, msg='cannot bind spec with empty name'):
mx.bind(s.bodies)
_CONTACTS = """
<mujoco>
<worldbody>
@@ -221,6 +414,332 @@ class SupportTest(parameterized.TestCase):
force = force.at[3:].set(dx.contact.frame[j] @ force[3:])
np.testing.assert_allclose(result, force, rtol=1e-5, atol=2)
def test_wrap_inside(self):
maxiter = 5
tolerance = 1.0e-4
z_init = 1.0 - 1.0e-5
# len0 <= radius
np.testing.assert_equal(
support.wrap_inside(
jp.array([1.0, 0, 0, 0]),
jp.array([1.0]),
maxiter,
tolerance,
z_init,
)[0],
jp.array([-1]),
)
# len1 <= radius
np.testing.assert_equal(
support.wrap_inside(
jp.array([0, 0, 1.0, 0]),
jp.array([1.0]),
maxiter,
tolerance,
z_init,
)[0],
jp.array([-1]),
)
# radius < mjMINVAL
np.testing.assert_equal(
support.wrap_inside(
jp.array([1, 0, 0, 1]), jp.array([0.1 * mujoco.mjMINVAL]), maxiter,
tolerance,
z_init,
)[0],
jp.array([-1]),
)
# len0 < mjMINVAL and radius < mjMINVAL
np.testing.assert_equal(
support.wrap_inside(
jp.array([0.1 * mujoco.mjMINVAL, 0, 0, 0]),
jp.array([0.1 * mujoco.mjMINVAL]),
maxiter,
tolerance,
z_init,
)[0],
jp.array([-1]),
)
# len1 < mjMINVAL and radius < mjMINVAL
np.testing.assert_equal(
support.wrap_inside(
jp.array([0, 0, 0.1 * mujoco.mjMINVAL, 0]),
jp.array([0.1 * mujoco.mjMINVAL]),
maxiter,
tolerance,
z_init,
)[0],
jp.array([-1]),
)
# wrap: p0 = [1, 0], p1 = [0, 1]
status, pnt = support.wrap_inside(
jp.array([1, 0, 0, 1]), jp.array([0.5]), maxiter, tolerance, z_init
)
np.testing.assert_allclose(
pnt,
jp.array([0.353553, 0.353553, 0.353553, 0.353553]),
atol=1e-3,
rtol=1e-3,
)
np.testing.assert_equal(status, jp.array([0]))
# no wrap, point on circle: p0 = [1, 0], p1 = [0, 0.5]
status, pnt = support.wrap_inside(
jp.array([1, 0, 0, 0.5]), jp.array([0.5]), maxiter, tolerance, z_init
)
np.testing.assert_allclose(
pnt,
jp.zeros(4),
atol=1e-3,
rtol=1e-3,
)
np.testing.assert_equal(status, jp.array([-1]))
# no wrap, segment-circle intersection: p0 = [0.75, 0], p1 = [0, 0.51]
status, pnt = support.wrap_inside(
jp.array([0.75, 0, 0, 0.51]),
jp.array([0.5]),
maxiter,
tolerance,
z_init,
)
np.testing.assert_allclose(
pnt,
jp.zeros(4),
atol=1e-3,
rtol=1e-3,
)
np.testing.assert_equal(status, jp.array([-1]))
# wrap: p0 = [-0.5, 1], p1 = [0.5, 1]
status, pnt = support.wrap_inside(
jp.array([-0.5, 1, 0.5, 1]),
jp.array([0.5]),
maxiter,
tolerance,
z_init,
)
np.testing.assert_allclose(
pnt,
jp.array([0, 0.5, 0, 0.5]),
atol=1e-3,
rtol=1e-3,
)
np.testing.assert_equal(status, jp.array([0]))
# TODO(taylorhowell): improve wrap_inside testing with additional test cases
def test_muscle_gain_length(self):
lmin = 0.5
lmax = 1.5
np.testing.assert_allclose(
support.muscle_gain_length(0, lmin, lmax),
jp.zeros(1),
rtol=1e-5,
atol=1e-5,
)
np.testing.assert_allclose(
support.muscle_gain_length(0.5, lmin, lmax),
jp.zeros(1),
rtol=1e-5,
atol=1e-5,
)
np.testing.assert_allclose(
support.muscle_gain_length(0.6, lmin, lmax),
jp.array([0.08]),
rtol=1e-5,
atol=1e-5,
)
np.testing.assert_allclose(
support.muscle_gain_length(0.75, lmin, lmax),
jp.array([0.5]),
rtol=1e-5,
atol=1e-5,
)
np.testing.assert_allclose(
support.muscle_gain_length(1.0, lmin, lmax),
jp.ones(1),
rtol=1e-5,
atol=1e-5,
)
np.testing.assert_allclose(
support.muscle_gain_length(1.25, lmin, lmax),
jp.array([0.5]),
rtol=1e-5,
atol=1e-5,
)
np.testing.assert_allclose(
support.muscle_gain_length(1.5, lmin, lmax),
jp.zeros(1),
rtol=1e-5,
atol=1e-5,
)
np.testing.assert_allclose(
support.muscle_gain_length(2.0, lmin, lmax),
jp.zeros(1),
rtol=1e-5,
atol=1e-5,
)
def test_muscle_gain(self):
length = jp.array([1.0])
lengthrange = jp.array([0.0, 1.0])
acc0 = jp.array([1.0])
prm = jp.array([0.0, 1.0, 1.0, 200.0, 0.5, 3.0, 1.0, 0.0, 2.0, 0.0])
# V <= -1
vel = jp.array([-1.5])
np.testing.assert_allclose(
support.muscle_gain(length, vel, lengthrange, acc0, prm),
jp.array([-0.0]),
rtol=1e-5,
atol=1e-5,
)
# V <= 0
vel = jp.array([-0.5])
np.testing.assert_allclose(
support.muscle_gain(length, vel, lengthrange, acc0, prm),
jp.array([-0.25]),
rtol=1e-5,
atol=1e-5,
)
# V <= y
vel = jp.array([0.5])
np.testing.assert_allclose(
support.muscle_gain(length, vel, lengthrange, acc0, prm),
jp.array([-1.75]),
rtol=1e-5,
atol=1e-5,
)
# V > y
vel = jp.array([1.5])
np.testing.assert_allclose(
support.muscle_gain(length, vel, lengthrange, acc0, prm),
jp.array([-2.0]),
rtol=1e-5,
atol=1e-5,
)
# force < 0
prm = prm.at[2].set(-1.0)
np.testing.assert_allclose(
support.muscle_gain(length, vel, lengthrange, acc0, prm),
jp.array([-400.0]),
rtol=1e-5,
atol=1e-5,
)
def test_muscle_bias(self):
lengthrange = jp.array([0.0, 1.0])
acc0 = jp.array([1.0])
prm = jp.array([0.0, 1.0, 1.0, 200.0, 0.5, 3.0, 1.5, 1.3, 1.2, 0.0])
# L <= 1
length = jp.array([0.5])
np.testing.assert_allclose(
support.muscle_bias(length, lengthrange, acc0, prm),
jp.array([0.0]),
rtol=1e-5,
atol=1e-5,
)
# L <= b
length = jp.array([1.5])
np.testing.assert_allclose(
support.muscle_bias(length, lengthrange, acc0, prm),
jp.array([-0.1625]),
rtol=1e-5,
atol=1e-5,
)
# L > b
length = jp.array([2.5])
np.testing.assert_allclose(
support.muscle_bias(length, lengthrange, acc0, prm),
jp.array([-1.3]),
rtol=1e-5,
atol=1e-5,
)
# force < 0
prm = prm.at[2].set(-1.0)
np.testing.assert_allclose(
support.muscle_bias(length, lengthrange, acc0, prm),
jp.array([-260.0]),
rtol=1e-5,
atol=1e-5,
)
def test_smooth_muscle_dynamics(self):
# compute time constant as in Millard et al. (2013)
# https://doi.org/10.1115/1.4023390
def _muscle_dynamics_millard(ctrl, act, prm):
ctrlclamp = jp.clip(ctrl, 0, 1)
actclamp = jp.clip(act, 0, 1)
tau0 = prm[0] * (0.5 + 1.5 * actclamp)
tau1 = prm[1] / (0.5 + 1.5 * actclamp)
tau = jp.where(ctrlclamp > act, tau0, tau1)
return (ctrlclamp - act) / jp.maximum(mujoco.mjMINVAL, tau)
prm = jp.array([0.01, 0.04, 0.0])
# exact equality if tau_smooth = 0
for ctrl in [-0.1, 0.0, 0.4, 0.5, 1.0, 1.0]:
for act in [-0.1, 0.0, 0.4, 0.5, 1.0, 1.1]:
actdot_old = _muscle_dynamics_millard(ctrl, act, prm)
actdot_new = support.muscle_dynamics(ctrl, act, prm)
np.testing.assert_allclose(actdot_old, actdot_new, rtol=1e-5, atol=1e-5)
# positive tau_smooth
tau_smooth = 0.2
prm = prm.at[2].set(tau_smooth)
act = 0.5
eps = 1.0e-6
ctrl = 0.4 - eps # smaller than act by just over 0.5 * tau_smooth
np.testing.assert_allclose(
_muscle_dynamics_millard(ctrl, act, prm),
support.muscle_dynamics(ctrl, act, prm),
rtol=1e-5,
atol=1e-5,
)
ctrl = 0.6 + eps # larger than act by just over 0.5 * tau_smooth
np.testing.assert_allclose(
_muscle_dynamics_millard(ctrl, act, prm),
support.muscle_dynamics(ctrl, act, prm),
rtol=1e-5,
atol=1e-5,
)
# right in the middle should give average of time constants
tau_act = 0.2
tau_deact = 0.3
for dctrl in [0.0, 0.1, 0.2, 1.0, 1.1]:
lower = support.muscle_dynamics_timescale(
-dctrl, tau_act, tau_deact, tau_smooth
)
upper = support.muscle_dynamics_timescale(
dctrl, tau_act, tau_deact, tau_smooth
)
np.testing.assert_allclose(
0.5 * (upper + lower),
0.5 * (tau_act + tau_deact),
rtol=1e-5,
atol=1e-5,
)
if __name__ == '__main__':
absltest.main()
+2 -2
View File
@@ -53,7 +53,7 @@ def benchmark(
nstep: int = 1000,
batch_size: int = 1024,
unroll_steps: int = 1,
solver: str = 'cg',
solver: str = 'newton',
iterations: int = 1,
ls_iterations: int = 4,
) -> Tuple[float, float, int]:
@@ -116,7 +116,7 @@ def efc_order(m: mujoco.MjModel, d: mujoco.MjData, dx: Data) -> np.ndarray:
if dx.contact.dim[i] > 1 and m.opt.cone == mujoco.mjtCone.mjCONE_PYRAMIDAL:
num_rows = (dx.contact.dim[i] - 1) * 2
if dx.contact.dist[i] > 0: # move empty contacts to end
order = np.append(order, np.repeat(2 ** 16, num_rows))
order = np.append(order, np.repeat(2**16, num_rows))
continue
contact_match = (d.contact.geom == dx.contact.geom[i]).all(axis=-1)
contact_match &= (d.contact.pos == dx.contact.pos[i]).all(axis=-1)
+139 -30
View File
@@ -47,6 +47,7 @@ class DisableBit(enum.IntFlag):
REFSAFE: integrator safety: make ref[0]>=2*timestep
SENSOR: sensors
"""
CONSTRAINT = mujoco.mjtDisableBit.mjDSBL_CONSTRAINT
EQUALITY = mujoco.mjtDisableBit.mjDSBL_EQUALITY
FRICTIONLOSS = mujoco.mjtDisableBit.mjDSBL_FRICTIONLOSS
@@ -73,6 +74,7 @@ class JointType(enum.IntEnum):
SLIDE: sliding distance along body-fixed axis (1,)
HINGE: rotation angle (rad) around body-fixed axis (1,)
"""
FREE = mujoco.mjtJoint.mjJNT_FREE
BALL = mujoco.mjtJoint.mjJNT_BALL
SLIDE = mujoco.mjtJoint.mjJNT_SLIDE
@@ -93,6 +95,7 @@ class IntegratorType(enum.IntEnum):
RK4: 4th-order Runge Kutta
IMPLICITFAST: implicit in velocity, no rne derivative
"""
EULER = mujoco.mjtIntegrator.mjINT_EULER
RK4 = mujoco.mjtIntegrator.mjINT_RK4
IMPLICITFAST = mujoco.mjtIntegrator.mjINT_IMPLICITFAST
@@ -113,6 +116,7 @@ class GeomType(enum.IntEnum):
MESH: mesh
SDF: signed distance field
"""
PLANE = mujoco.mjtGeom.mjGEOM_PLANE
HFIELD = mujoco.mjtGeom.mjGEOM_HFIELD
SPHERE = mujoco.mjtGeom.mjGEOM_SPHERE
@@ -134,6 +138,7 @@ class ConvexMesh(PyTreeNode):
edge: edge indexes for all edges in the convex mesh
edge_face_normal: indexes for face normals adjacent to edges in `edge`
"""
vert: jax.Array
face: jax.Array
face_normal: jax.Array
@@ -148,6 +153,7 @@ class ConeType(enum.IntEnum):
PYRAMIDAL: pyramidal
ELLIPTIC: elliptic
"""
PYRAMIDAL = mujoco.mjtCone.mjCONE_PYRAMIDAL
ELLIPTIC = mujoco.mjtCone.mjCONE_ELLIPTIC
@@ -160,6 +166,7 @@ class JacobianType(enum.IntEnum):
SPARSE: sparse
AUTO: sparse if nv>60 and device is TPU, dense otherwise
"""
DENSE = mujoco.mjtJacobian.mjJAC_DENSE
SPARSE = mujoco.mjtJacobian.mjJAC_SPARSE
AUTO = mujoco.mjtJacobian.mjJAC_AUTO
@@ -172,6 +179,7 @@ class SolverType(enum.IntEnum):
CG: Conjugate gradient (primal)
NEWTON: Newton (primal)
"""
# unsupported: PGS
CG = mujoco.mjtSolver.mjSOL_CG
NEWTON = mujoco.mjtSolver.mjSOL_NEWTON
@@ -186,6 +194,7 @@ class EqType(enum.IntEnum):
JOINT: couple the values of two scalar joints with cubic
TENDON: couple the lengths of two tendons with cubic
"""
CONNECT = mujoco.mjtEq.mjEQ_CONNECT
WELD = mujoco.mjtEq.mjEQ_WELD
JOINT = mujoco.mjtEq.mjEQ_JOINT
@@ -203,6 +212,7 @@ class WrapType(enum.IntEnum):
SPHERE: wrap around sphere
CYLINDER: wrap around (infinite) cylinder
"""
JOINT = mujoco.mjtWrap.mjWRAP_JOINT
PULLEY = mujoco.mjtWrap.mjWRAP_PULLEY
SITE = mujoco.mjtWrap.mjWRAP_SITE
@@ -219,6 +229,7 @@ class TrnType(enum.IntEnum):
TENDON: force on tendon
SITE: force on site
"""
JOINT = mujoco.mjtTrn.mjTRN_JOINT
JOINTINPARENT = mujoco.mjtTrn.mjTRN_JOINTINPARENT
SITE = mujoco.mjtTrn.mjTRN_SITE
@@ -234,12 +245,15 @@ class DynType(enum.IntEnum):
INTEGRATOR: integrator: da/dt = u
FILTER: linear filter: da/dt = (u-a) / tau
FILTEREXACT: linear filter: da/dt = (u-a) / tau, with exact integration
MUSCLE: piece-wise linear filter with two time constants
"""
NONE = mujoco.mjtDyn.mjDYN_NONE
INTEGRATOR = mujoco.mjtDyn.mjDYN_INTEGRATOR
FILTER = mujoco.mjtDyn.mjDYN_FILTER
FILTEREXACT = mujoco.mjtDyn.mjDYN_FILTEREXACT
# unsupported: MUSCLE, USER
MUSCLE = mujoco.mjtDyn.mjDYN_MUSCLE
# unsupported: USER
class GainType(enum.IntEnum):
@@ -248,10 +262,13 @@ class GainType(enum.IntEnum):
Members:
FIXED: fixed gain
AFFINE: const + kp*length + kv*velocity
MUSCLE: muscle FLV curve computed by muscle_gain
"""
FIXED = mujoco.mjtGain.mjGAIN_FIXED
AFFINE = mujoco.mjtGain.mjGAIN_AFFINE
# unsupported: MUSCLE, USER
MUSCLE = mujoco.mjtGain.mjGAIN_MUSCLE
# unsupported: USER
class BiasType(enum.IntEnum):
@@ -260,10 +277,13 @@ class BiasType(enum.IntEnum):
Members:
NONE: no bias
AFFINE: const + kp*length + kv*velocity
MUSCLE: muscle passive force computed by muscle_bias
"""
NONE = mujoco.mjtBias.mjBIAS_NONE
AFFINE = mujoco.mjtBias.mjBIAS_AFFINE
# unsupported: MUSCLE, USER
MUSCLE = mujoco.mjtBias.mjBIAS_MUSCLE
# unsupported: USER
class ConstraintType(enum.IntEnum):
@@ -276,6 +296,7 @@ class ConstraintType(enum.IntEnum):
CONTACT_FRICTIONLESS: frictionless contact
CONTACT_PYRAMIDAL: frictional contact, pyramidal friction cone
"""
EQUALITY = mujoco.mjtConstraint.mjCNSTR_EQUALITY
FRICTION_DOF = mujoco.mjtConstraint.mjCNSTR_FRICTION_DOF
FRICTION_TENDON = mujoco.mjtConstraint.mjCNSTR_FRICTION_TENDON
@@ -296,6 +317,7 @@ class CamLightType(enum.IntEnum):
TARGETBODY: pos fixed in body, rot tracks target body
TARGETBODYCOM: pos fixed in body, rot tracks target subtree com
"""
FIXED = mujoco.mjtCamLight.mjCAMLIGHT_FIXED
TRACK = mujoco.mjtCamLight.mjCAMLIGHT_TRACK
TRACKCOM = mujoco.mjtCamLight.mjCAMLIGHT_TRACKCOM
@@ -340,6 +362,7 @@ class SensorType(enum.IntEnum):
FRAMELINACC: 3D linear acceleration
FRAMEANGACC: 3D angular acceleration
"""
MAGNETOMETER = mujoco.mjtSensor.mjSENS_MAGNETOMETER
CAMPROJECTION = mujoco.mjtSensor.mjSENS_CAMPROJECTION
RANGEFINDER = mujoco.mjtSensor.mjSENS_RANGEFINDER
@@ -385,6 +408,7 @@ class ObjType(PyTreeNode):
SITE: site
CAMERA: camera
"""
UNKNOWN = mujoco.mjtObj.mjOBJ_UNKNOWN
BODY = mujoco.mjtObj.mjOBJ_BODY
XBODY = mujoco.mjtObj.mjOBJ_XBODY
@@ -431,7 +455,7 @@ class Option(PyTreeNode):
disableactuator: bit flags for disabling actuators by group id (not used)
sdf_initpoints: number of starting points for gradient descent (not used)
sdf_iterations: max number of iterations for gradient descent (not used)
"""
""" # fmt: skip
timestep: jax.Array
apirate: jax.Array = _restricted_to('mujoco')
impratio: jax.Array
@@ -474,6 +498,7 @@ class Statistic(PyTreeNode):
extent: spatial extent (not used)
center: center of model (not used)
"""
meaninertia: jax.Array
meanmass: jax.Array
meansize: jax.Array
@@ -514,6 +539,8 @@ class Model(PyTreeNode):
nmeshgraph: number of ints in mesh auxiliary data
nhfield: number of heightfields
nhfielddata: number of data points in all heightfields
ntex: number of textures
ntexdata: number of bytes in texture rgb data
nmat: number of materials
npair: number of predefined geom pairs
nexclude: number of excluded geom pairs
@@ -528,6 +555,9 @@ class Model(PyTreeNode):
nM: number of non-zeros in sparse inertia matrix
nD: number of non-zeros in sparse dof-dof matrix
nB: number of non-zeros in sparse body-dof matrix
nC: number of non-zeros in sparse reduced dof-dof matrix
nD: number of non-zeros in sparse dof-dof matrix
nJmom: number of non-zeros in sparse actuator_moment matrix
ntree: number of kinematic trees under world body
ngravcomp: number of bodies with nonzero gravcomp
nuserdata: size of userdata array
@@ -635,11 +665,14 @@ class Model(PyTreeNode):
light_mode: light tracking mode (mjtCamLight) (nlight,)
light_bodyid: id of light's body (nlight,)
light_targetbodyid: id of targeted body; -1: none (nlight,)
light_directional: directional light (nlight,)
light_castshadow: does light cast shadows (nlight,)
light_pos: position rel. to body frame (nlight, 3)
light_dir: direction rel. to body frame (nlight, 3)
light_poscom0: global position rel. to sub-com in qpos0 (nlight, 3)
light_pos0: global position rel. to body in qpos0 (nlight, 3)
light_dir0: global direction in qpos0 (nlight, 3)
light_cutoff: OpenGL cutoff (nlight,)
flex_contype: flex contact type (nflex,)
flex_conaffinity: flex contact affinity (nflex,)
flex_condim: contact dimensionality (1, 3, 4, 6) (nflex,)
@@ -692,12 +725,22 @@ class Model(PyTreeNode):
mesh_pos: translation applied to asset vertices (nmesh, 3)
mesh_quat: rotation applied to asset vertices (nmesh, 4)
mesh_convex: pre-compiled convex mesh info for MJX (nmesh,)
mesh_texcoordadr: texcoord data address; -1: no texcoord (nmesh,)
mesh_texcoordnum: number of texcoord (nmesh,)
mesh_texcoord: vertex texcoords for all meshes (nmeshtexcoord, 2)
hfield_size: (x, y, z_top, z_bottom) (nhfield,)
hfield_nrow: number of rows in grid (nhfield,)
hfield_ncol: number of columns in grid (nhfield,)
hfield_adr: address in hfield_data (nhfield,)
hfield_data: elevation data (nhfielddata,)
tex_type: texture type (mjtTexture) (ntex,)
tex_height: number of rows in texture image (ntex,)
tex_width: number of columns in texture image (ntex,)
tex_nchannel: number of channels in texture image (ntex,)
tex_adr: start address in tex_data (ntex,)
tex_data: pixel values (ntexdata,)
mat_rgba: rgba (nmat, 4)
mat_texid: indices of textures; -1: none (nmat, mjNTEXROLE)
pair_dim: contact dimensionality (npair,)
pair_geom1: id of geom1 (npair,)
pair_geom2: id of geom2 (npair,)
@@ -736,6 +779,10 @@ class Model(PyTreeNode):
wrap_type: wrap object type (mjtWrap) (nwrap,)
wrap_objid: object id: geom, site, joint (nwrap,)
wrap_prm: divisor, joint coef, or site id (nwrap,)
wrap_inside_maxiter: maximum iterations for wrap_inside
wrap_inside_tolerance: tolerance for wrap_inside
wrap_inside_z_init: initialization for wrap_inside
is_wrap_inside: spatial tendon sidesite inside geom (nwrapinside,)
actuator_trntype: transmission type (mjtTrn) (nu,)
actuator_dyntype: dynamics type (mjtDyn) (nu,)
actuator_gaintype: gain type (mjtGain) (nu,)
@@ -775,6 +822,13 @@ class Model(PyTreeNode):
tuple_objtype: array of object types in all tuples (ntupledata,)
tuple_objid: array of object ids in all tuples (ntupledata,)
tuple_objprm: array of object params in all tuples (ntupledata,)
key_time: key time (nkey,)
key_qpos: key position (nkey, nq)
key_qvel: key velocity (nkey, nv)
key_act: key activation (nkey, na)
key_mpos: key mocap position (nkey, nmocap, 3)
key_mquat: key mocap quaternion (nkey, nmocap, 4)
key_ctrl: key control (nkey, nu)
name_bodyadr: body name pointers (nbody,)
name_jntadr: joint name pointers (njnt,)
name_geomadr: geom name pointers (ngeom,)
@@ -791,6 +845,7 @@ class Model(PyTreeNode):
name_keyadr: keyframe name pointers (nkey,)
names: names of all objects, 0-terminated (nnames,)
"""
nq: int
nv: int
nu: int
@@ -820,6 +875,8 @@ class Model(PyTreeNode):
nmeshgraph: int
nhfield: int
nhfielddata: int
ntex: int
ntexdata: int
nmat: int
npair: int
nexclude: int
@@ -832,8 +889,10 @@ class Model(PyTreeNode):
nkey: int
nmocap: int
nM: int # pylint:disable=invalid-name
nD: int # pylint:disable=invalid-name
nB: int # pylint:disable=invalid-name
nC: int # pylint:disable=invalid-name
nD: int # pylint:disable=invalid-name
nJmom: int # pylint:disable=invalid-name
ntree: int = _restricted_to('mujoco')
ngravcomp: int
nuserdata: int
@@ -910,7 +969,7 @@ class Model(PyTreeNode):
geom_sameframe: np.ndarray
geom_dataid: np.ndarray
geom_group: np.ndarray
geom_matid: np.ndarray
geom_matid: jax.Array
geom_priority: np.ndarray
geom_solmix: jax.Array
geom_solref: jax.Array
@@ -925,7 +984,7 @@ class Model(PyTreeNode):
geom_margin: jax.Array
geom_gap: jax.Array
geom_fluid: np.ndarray
geom_rgba: np.ndarray
geom_rgba: jax.Array
site_type: np.ndarray
site_bodyid: np.ndarray
site_sameframe: np.ndarray
@@ -944,14 +1003,17 @@ class Model(PyTreeNode):
cam_resolution: np.ndarray
cam_sensorsize: np.ndarray
cam_intrinsic: np.ndarray
light_mode: np.ndarray = _restricted_to('mujoco')
light_mode: np.ndarray
light_bodyid: np.ndarray = _restricted_to('mujoco')
light_targetbodyid: np.ndarray = _restricted_to('mujoco')
light_pos: np.ndarray = _restricted_to('mujoco')
light_dir: np.ndarray = _restricted_to('mujoco')
light_poscom0: np.ndarray = _restricted_to('mujoco')
light_pos0: np.ndarray = _restricted_to('mujoco')
light_dir0: np.ndarray = _restricted_to('mujoco')
light_directional: jax.Array
light_castshadow: jax.Array
light_pos: jax.Array
light_dir: jax.Array
light_poscom0: jax.Array
light_pos0: np.ndarray
light_dir0: np.ndarray
light_cutoff: jax.Array
flex_contype: np.ndarray = _restricted_to('mujoco')
flex_conaffinity: np.ndarray = _restricted_to('mujoco')
flex_condim: np.ndarray = _restricted_to('mujoco')
@@ -1004,12 +1066,22 @@ class Model(PyTreeNode):
mesh_pos: np.ndarray
mesh_quat: np.ndarray
mesh_convex: Tuple[ConvexMesh, ...] = _restricted_to('mjx')
mesh_texcoordadr: np.ndarray
mesh_texcoordnum: np.ndarray
mesh_texcoord: np.ndarray
hfield_size: np.ndarray
hfield_nrow: np.ndarray
hfield_ncol: np.ndarray
hfield_adr: np.ndarray
hfield_data: jax.Array
mat_rgba: np.ndarray
tex_type: np.ndarray
tex_height: np.ndarray
tex_width: np.ndarray
tex_nchannel: np.ndarray
tex_adr: np.ndarray
tex_data: jax.Array
mat_rgba: jax.Array
mat_texid: np.ndarray
pair_dim: np.ndarray
pair_geom1: np.ndarray
pair_geom2: np.ndarray
@@ -1048,6 +1120,10 @@ class Model(PyTreeNode):
wrap_type: np.ndarray
wrap_objid: np.ndarray
wrap_prm: np.ndarray
wrap_inside_maxiter: int = _restricted_to('mjx')
wrap_inside_tolerance: float = _restricted_to('mjx')
wrap_inside_z_init: float = _restricted_to('mjx')
is_wrap_inside: np.ndarray = _restricted_to('mjx')
actuator_trntype: np.ndarray
actuator_dyntype: np.ndarray
actuator_gaintype: np.ndarray
@@ -1068,7 +1144,7 @@ class Model(PyTreeNode):
actuator_actrange: jax.Array
actuator_gear: jax.Array
actuator_cranklength: np.ndarray
actuator_acc0: np.ndarray
actuator_acc0: jax.Array
actuator_lengthrange: np.ndarray
actuator_plugin: np.ndarray = _restricted_to('mujoco')
sensor_type: np.ndarray
@@ -1088,6 +1164,13 @@ class Model(PyTreeNode):
tuple_objtype: np.ndarray
tuple_objid: np.ndarray
tuple_objprm: np.ndarray
key_time: np.ndarray
key_qpos: np.ndarray
key_qvel: np.ndarray
key_act: np.ndarray
key_mpos: np.ndarray
key_mquat: np.ndarray
key_ctrl: np.ndarray
name_bodyadr: np.ndarray
name_jntadr: np.ndarray
name_geomadr: np.ndarray
@@ -1124,7 +1207,7 @@ class Contact(PyTreeNode):
geom2: id of geom 2; deprecated, use geom[1]
geom: geom ids (2,)
efc_address: address in efc; -1: not included
"""
""" # fmt: skip
dist: jax.Array
pos: jax.Array
frame: jax.Array
@@ -1177,7 +1260,7 @@ class Data(PyTreeNode):
geom_xpos: Cartesian geom position (ngeom, 3)
geom_xmat: Cartesian geom orientation (ngeom, 3, 3)
site_xpos: Cartesian site position (nsite, 3)
site_xmat: Cartesian site orientation (nsite, 9)
site_xmat: Cartesian site orientation (nsite, 3, 3)
cam_xpos: Cartesian camera position (ncam, 3)
cam_xmat: Cartesian camera orientation (ncam, 3, 3)
light_xpos: Cartesian light position (nlight, 3)
@@ -1202,7 +1285,10 @@ class Data(PyTreeNode):
wrap_obj: geom id; -1: site; -2: pulley (nwrap*2,)
wrap_xpos: Cartesian 3D points in all path (nwrap*2, 3)
actuator_length: actuator lengths (nu,)
actuator_moment: actuator moments (nu, nv)
moment_rownnz: number of non-zeros in actuator_moment row (nu,)
moment_rowadr: row start address in colind array (nu,)
moment_colind: column indices in sparse Jacobian (nJmom,)
actuator_moment: actuator moments (nJmom,)
crb: com-based composite inertia and mass (nbody, 10)
qM: total inertia if sparse: (nM,)
if dense: (nv, nv)
@@ -1210,7 +1296,6 @@ class Data(PyTreeNode):
if dense: (nv, nv)
qLDiagInv: 1/diag(D) if sparse: (nv,)
if dense: (0,)
qLDiagSqrtInv: 1/sqrt(diag(D)) (nv,)
bvh_aabb_dyn: global bounding box (center, size) (nbvhdynamic, 6)
bvh_active: volume has been added to collisions (nbvh,)
flexedge_velocity: flex edge velocities (nflexedge,)
@@ -1228,12 +1313,23 @@ class Data(PyTreeNode):
subtree_angmom: angular momentum about subtree com (nbody, 3)
qH: L'*D*L factorization of modified M (nM,)
qHDiagInv: 1/diag(D) of modified M (nv,)
D_rownnz: non-zeros in each row (nv,)
D_rowadr: address of each row in D_colind (nv,)
D_colind: column indices of non-zeros (nD,)
B_rownnz: non-zeros in each row (nbody,)
B_rowadr: address of each row in B_colind (nbody,)
B_colind: column indices of non-zeros (nB,)
B_rownnz: body-dof: non-zeros in each row (nbody,)
B_rowadr: body-dof: address of each row in B_colind (nbody,)
B_colind: body-dof: column indices of non-zeros (nB,)
M_rownnz: inertia: non-zeros in each row (nv,)
M_rowadr: inertia: address of each row in M_colind (nv,)
M_colind: inertia: column indices of non-zeros (nM,)
mapM2M: index mapping from M (legacy) to M (CSR) (nM,)
C_rownnz: reduced dof-dof: non-zeros in each row (nv,)
C_rowadr: reduced dof-dof: address of each row in C_colind (nv,)
C_colind: reduced dof-dof: column indices of non-zeros (nC,)
mapM2C: index mapping from M to C (nC,)
D_rownnz: dof-dof: non-zeros in each row (nv,)
D_rowadr: dof-dof: address of each row in D_colind (nv,)
D_diag: dof-dof: index of diagonal element (nv,)
D_colind: dof-dof: column indices of non-zeros (nD,)
mapM2D: index mapping from M to D (nD,)
mapD2M: index mapping from D to M (nM,)
qDeriv: d (passive + actuator - bias) / d qvel (nD,)
qLU: sparse LU of (qM - dt*qDeriv) (nD,)
actuator_force: actuator force in actuation space (nu,)
@@ -1258,7 +1354,7 @@ class Data(PyTreeNode):
_qM_sparse: qM in sparse representation (nM,)
_qLD_sparse: qLD in sparse representation (nM,)
_qLDiagInv_sparse: qLDiagInv in sparse representation (nv,)
"""
""" # fmt: skip
# constant sizes:
ne: int
nf: int
@@ -1324,12 +1420,14 @@ class Data(PyTreeNode):
wrap_obj: jax.Array
wrap_xpos: jax.Array
actuator_length: jax.Array
moment_rownnz: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
moment_rowadr: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
moment_colind: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
actuator_moment: jax.Array
crb: jax.Array
qM: jax.Array # pylint:disable=invalid-name
qLD: jax.Array # pylint:disable=invalid-name
qLDiagInv: jax.Array # pylint:disable=invalid-name
qLDiagSqrtInv: jax.Array # pylint:disable=invalid-name
bvh_aabb_dyn: jax.Array = _restricted_to('mujoco')
bvh_active: jax.Array = _restricted_to('mujoco')
# position, velocity dependent:
@@ -1348,12 +1446,23 @@ class Data(PyTreeNode):
subtree_angmom: jax.Array
qH: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
qHDiagInv: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
D_rownnz: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
D_rowadr: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
D_colind: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
B_rownnz: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
B_rowadr: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
B_colind: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
M_rownnz: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
M_rowadr: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
M_colind: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
mapM2M: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
C_rownnz: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
C_rowadr: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
C_colind: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
mapM2C: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
D_rownnz: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
D_rowadr: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
D_diag: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
D_colind: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
mapM2D: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
mapD2M: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
qDeriv: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
qLU: jax.Array = _restricted_to('mujoco') # pylint:disable=invalid-name
# position, velocity, control & acceleration dependent:
+21 -4
View File
@@ -68,10 +68,27 @@ class TransmissionIntegrationTest(parameterized.TestCase):
mujoco.mj_transmission(m, d)
dx = transmission_jit_fn(mx, dx)
for field in ['actuator_length', 'actuator_moment']:
_assert_attr_eq(
d, dx, field, seed, f'transmission{seed}', atol=1e-4
)
_assert_attr_eq(
d, dx, 'actuator_length', seed, f'transmission{seed}', atol=1e-4
)
# convert sparse actuator_moment to dense representation
moment = np.zeros((m.nu, m.nv))
mujoco.mju_sparse2dense(
moment,
d.actuator_moment,
d.moment_rownnz,
d.moment_rowadr,
d.moment_colind,
)
_assert_eq(
moment,
dx.actuator_moment,
'actuator_moment',
seed,
f'transmission{seed}',
atol=1e-4,
)
if __name__ == '__main__':
@@ -13,17 +13,25 @@
limitations under the License.
-->
<mujoco model="2D grid with pins">
<include file="scene.xml"/>
<size memory="10M"/>
<mujoco model="2-link 1-muscle arm">
<worldbody>
<composite type="grid" count="9 9 1" spacing="0.05" offset="0 0 1">
<skin rgba=".6 .1 .6 1" inflate="0.001" subgrid="3"/>
<pin coord="0 0"/>
<pin coord="8 0"/>
<geom size=".02"/>
</composite>
<body>
<geom type="capsule" size="0.01" fromto="0 0 0 1 0 0"/>
<site name="s0" pos="0 0 0.01"/>
<joint type="slide" axis="1 0 0"/>
<body pos="1 0 0">
<geom type="capsule" size="0.01" fromto="0 0 0 1 0 0"/>
<site name="s1" pos="1 0 0.01"/>
</body>
</body>
</worldbody>
<tendon>
<spatial name="tendon" width="0.01">
<site site="s0"/>
<site site="s1"/>
</spatial>
</tendon>
<actuator>
<muscle tendon="tendon" lengthrange="0 2" force="1" ctrllimited="true" ctrlrange="0 1"/>
</actuator>
</mujoco>
+118
View File
@@ -0,0 +1,118 @@
<!-- Copyright 2021 DeepMind Technologies Limited
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mujoco model="2-link 6-muscle arm">
<option timestep="0.005" iterations="50" solver="Newton" tolerance="1e-10"/>
<visual>
<rgba haze=".3 .3 .3 1"/>
</visual>
<default>
<joint type="hinge" pos="0 0 0" axis="0 0 1" limited="true" range="0 120" damping="0.1"/>
<muscle ctrllimited="true" ctrlrange="0 1"/>
</default>
<asset>
<texture type="skybox" builtin="gradient" rgb1="0.6 0.6 0.6" rgb2="0 0 0" width="512" height="512"/>
<texture name="texplane" type="2d" builtin="checker" rgb1=".25 .25 .25" rgb2=".3 .3 .3" width="512" height="512" mark="cross" markrgb=".8 .8 .8"/>
<material name="matplane" reflectance="0.3" texture="texplane" texrepeat="1 1" texuniform="true"/>
</asset>
<worldbody>
<geom name="floor" pos="0 0 -0.5" size="0 0 1" type="plane" material="matplane"/>
<light directional="true" diffuse=".8 .8 .8" specular=".2 .2 .2" pos="0 0 5" dir="0 0 -1"/>
<site name="s0" pos="-0.15 0 0" size="0.02"/>
<site name="x0" pos="0 -0.15 0" size="0.02" rgba="0 .7 0 1" group="1"/>
<body pos="0 0 0">
<geom name="upper arm" type="capsule" size="0.045" fromto="0 0 0 0.5 0 0" rgba=".5 .1 .1 1"/>
<joint name="shoulder"/>
<geom name="shoulder" type="cylinder" pos="0 0 0" size=".1 .05" rgba=".5 .1 .8 .5" mass="0" group="1"/>
<site name="s1" pos="0.15 0.06 0" size="0.02"/>
<site name="s2" pos="0.15 -0.06 0" size="0.02"/>
<site name="s3" pos="0.4 0.06 0" size="0.02"/>
<site name="s4" pos="0.4 -0.06 0" size="0.02"/>
<site name="s5" pos="0.25 0.1 0" size="0.02"/>
<site name="s6" pos="0.25 -0.1 0" size="0.02"/>
<site name="x1" pos="0.5 -0.15 0" size="0.02" rgba="0 .7 0 1" group="1"/>
<body pos="0.5 0 0">
<geom name="forearm" type="capsule" size="0.035" fromto="0 0 0 0.5 0 0" rgba=".5 .1 .1 1"/>
<joint name="elbow"/>
<geom name="elbow" type="cylinder" pos="0 0 0" size=".08 .05" rgba=".5 .1 .8 .5" mass="0" group="1"/>
<site name="s7" pos="0.11 0.05 0" size="0.02"/>
<site name="s8" pos="0.11 -0.05 0" size="0.02"/>
</body>
</body>
</worldbody>
<tendon>
<spatial name="SF" width="0.01">
<site site="s0"/>
<geom geom="shoulder"/>
<site site="s1"/>
</spatial>
<spatial name="SE" width="0.01">
<site site="s0"/>
<geom geom="shoulder" sidesite="x0"/>
<site site="s2"/>
</spatial>
<spatial name="EF" width="0.01">
<site site="s3"/>
<geom geom="elbow"/>
<site site="s7"/>
</spatial>
<spatial name="EE" width="0.01">
<site site="s4"/>
<geom geom="elbow" sidesite="x1"/>
<site site="s8"/>
</spatial>
<spatial name="BF" width="0.009" rgba=".4 .6 .4 1">
<site site="s0"/>
<geom geom="shoulder"/>
<site site="s5"/>
<geom geom="elbow"/>
<site site="s7"/>
</spatial>
<spatial name="BE" width="0.009" rgba=".4 .6 .4 1">
<site site="s0"/>
<geom geom="shoulder" sidesite="x0"/>
<site site="s6"/>
<geom geom="elbow" sidesite="x1"/>
<site site="s8"/>
</spatial>
</tendon>
<actuator>
<muscle name="SF" tendon="SF"/>
<muscle name="SE" tendon="SE"/>
<muscle name="EF" tendon="EF"/>
<muscle name="EE" tendon="EE"/>
<muscle name="BF" tendon="BF"/>
<muscle name="BE" tendon="BE"/>
</actuator>
</mujoco>
@@ -0,0 +1,18 @@
<mujoco>
<compiler autolimits="true"/>
<worldbody>
<body name="box">
<joint name="slide1" type="slide" axis="1 0 0" />
<joint name="slide2" type="slide" axis="0 1 0" />
<joint name="slide3" type="slide" axis="0 0 1" />
<joint name="slide4" type="slide" axis="1 1 0" />
<geom type="box" size=".05 .05 .05" mass="1"/>
</body>
</worldbody>
<actuator>
<general joint="slide1" dynprm="0.1" gainprm="1.1" />
<general joint="slide2" dyntype="integrator" dynprm="0.1" gainprm="1.1" />
<general joint="slide3" dyntype="filter" dynprm="0.1" gainprm="1.1" />
<general joint="slide4" dyntype="filterexact" dynprm="0.1" gainprm="1.1" />
</actuator>
</mujoco>
+25 -10
View File
@@ -102,15 +102,27 @@
<geom size="0.1"/>
</body>
<!-- plane and body for touch sensors -->
<geom type="plane" size="1 1 .1" pos="-20 -20 -20"/>
<body pos="-20 -20 -19.985">
<joint type="slide" axis="0 0 1"/>
<joint type="hinge" axis="0 1 0"/>
<geom type="capsule" fromto="-.5 0 0 .5 0 0" size="0.0125"/>
<!-- plane and bodies for touch sensors -->
<geom type="plane" size="2 2 .1" pos="-20 -20 -20"/>
<body pos="-20 -20 -20">
<freejoint/>
<geom type="capsule" fromto="-.5 0 0 .5 0 0" size="0.0125" condim="1"/>
<site name="touch_sphere" type="sphere" pos="-0.5 0 0" size="0.025"/>
<site name="touch_capsule" type="capsule" fromto="-0.1 0 0.01 0.1 0 0.01" size="0.025"/>
<site name="touch_box" pos="0.5 0 0" type="box" size="0.025 0.025 0.025"/>
</body>
<body pos="-20 -20.25 -20">
<freejoint/>
<geom type="capsule" fromto="-.5 0 0 .5 0 0" size="0.0125" condim="3"/>
<site name="touch_capsule" type="capsule" fromto="0.4 0 0.0 0.5 0 0.0" size="0.025"/>
</body>
<body pos="-20 -20.5 -20">
<freejoint/>
<geom type="capsule" fromto="-.5 0 0 .5 0 0" size="0.0125" condim="6"/>
<site name="touch_box" pos="-0.5 0 0" type="box" size="0.025 0.025 0.025"/>
</body>
<body pos="-20 -20.75 -20">
<freejoint/>
<geom type="capsule" fromto="-.5 0 0 .5 0 0" size="0.0125" condim="3"/>
<site name="touch_ellipsoid" type="ellipsoid" pos="0.5 0 0.0" size="0.05 0.01 0.02"/>
</body>
</worldbody>
@@ -141,7 +153,8 @@
<velocimeter name="velocimeter0" site="site0"/>
<velocimeter name="velocimeter0cutoff" site="site0" cutoff="3e-4"/>
<gyro name="gyro0" site="site0"/>
<touch name="touch_box" site="touch_box"/>
<touch name="touch_box1" site="touch_box"/>
<touch name="touch_box2" site="touch_box"/>
<gyro name="gyro0cutoff" site="site0" cutoff="2e-3"/>
<rangefinder name="rangefinder0" site="site_rangefinder0"/>
<accelerometer name="accelerometer1" site="site1"/>
@@ -152,7 +165,8 @@
<jointpos name="jointpos0cutoff" joint="hinge0" cutoff="1e-4"/>
<jointvel name="jointvel0" joint="hinge0"/>
<jointvel name="jointvel0cutoff" joint="hinge0" cutoff="1e-3"/>
<touch name="touch_capsule" site="touch_capsule"/>
<touch name="touch_capsule1" site="touch_capsule"/>
<touch name="touch_capsule2" site="touch_capsule"/>
<actuatorfrc name="actuatorfrc0" actuator="motor0"/>
<framelinvel name="framelinvel3" objtype="site" objname="site3"/>
<framelinvel name="framelinvel3ref" objtype="site" objname="site3" reftype="body" refname="body2"/>
@@ -169,6 +183,7 @@
<framepos name="framepos0" objtype="site" objname="site0"/>
<velocimeter name="velocimeter1" site="site1"/>
<gyro name="gyro1" site="site1"/>
<touch name="touch_ellipsoid" site="touch_ellipsoid"/>
<frameangvel name="frameangvel3" objtype="site" objname="site3"/>
<frameangvel name="frameangvel3ref" objtype="site" objname="site3" reftype="body" refname="body2"/>
<frameangvel name="frameangvel3cutoff" objtype="site" objname="site3" cutoff="1e-3"/>
@@ -10,6 +10,7 @@
<site name="site0" pos="0.25 0 0.1" size="0.025"/>
<geom name="sphere0" type="sphere" pos="0.5 0 0.125" size="0.085"/>
<site name="sidesite0" pos="0.5 0 0.25"/>
<site name="inside_sidesite0" pos="0.5 0 0.125"/>
<body pos="0.5 0 0">
<joint name="joint1" type="hinge" axis="0 1 0"/>
<geom type="capsule" size="0.05 0.5" fromto="0 0 0 0.5 0 0"/>
@@ -21,6 +22,7 @@
<site name="site2" pos="0.25 0 0.1" size="0.025"/>
<geom name="cylinder2" type="cylinder" contype="0" conaffinity="0" pos="0.5 0 0.25" euler="90 0 0" size="0.085 0.1"/>
<site name="sidesite2" pos="0.5 0 0.5"/>
<site name="inside_sidesite2" pos="0.5 0 0.25"/>
<body pos="0.5 0 0">
<joint name="joint3" type="hinge" axis="0 1 0"/>
<geom type="capsule" size="0.05 0.5" fromto="0 0 0 0.5 0 0"/>
@@ -34,16 +36,23 @@
<tendon>
<spatial width="0.0125">
<site site="site0"/>
<geom geom="sphere0" sidesite="sidesite0"/>
<geom geom="sphere0" sidesite="inside_sidesite0"/>
<site site="site1"/>
<geom geom="sphere1"/>
<site site="site2"/>
<geom geom="cylinder2" sidesite="sidesite2"/>
<geom geom="cylinder2" sidesite="inside_sidesite2"/>
<site site="site3"/>
</spatial>
<spatial width="0.0125">
<site site="site0"/>
<geom geom="sphere0"/>
<geom geom="sphere0" sidesite="sidesite0"/>
<site site="site2"/>
<geom geom="cylinder2" sidesite="inside_sidesite2"/>
<site site="site3"/>
</spatial>
<spatial width="0.0125">
<site site="site0"/>
<geom geom="sphere0" sidesite="inside_sidesite0"/>
<site site="site2"/>
<geom geom="cylinder2" sidesite="sidesite2"/>
<site site="site3"/>
+3 -1
View File
@@ -22,7 +22,9 @@ from etils import epath
import mujoco
from mujoco import mjx
_MJCF = flags.DEFINE_string('mjcf', None, 'path to model `.xml` or `.mjb`', required=True)
_MJCF = flags.DEFINE_string(
'mjcf', None, 'path to model `.xml` or `.mjb`', required=True
)
_BASE_PATH = flags.DEFINE_string(
'base_path', None, 'base path, defaults to mujoco.mjx resource path'
)
+3 -2
View File
@@ -28,8 +28,9 @@ import mujoco.viewer
_JIT = flags.DEFINE_bool('jit', True, 'To jit or not to jit.')
_MODEL_PATH = flags.DEFINE_string('mjcf', None, 'Path to a MuJoCo MJCF file.',
required=True)
_MODEL_PATH = flags.DEFINE_string(
'mjcf', None, 'Path to a MuJoCo MJCF file.', required=True
)
_VIEWER_GLOBAL_STATE = {
+25 -4
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name="mujoco-mjx"
version = "3.2.5"
version = "3.3.1"
authors = [
{name = "Google DeepMind", email = "mujoco@deepmind.com"},
]
@@ -30,7 +30,7 @@ dependencies = [
"etils[epath]",
"jax",
"jaxlib",
"mujoco>=3.2.5.dev0",
"mujoco>=3.3.1.dev0",
"scipy",
"trimesh",
]
@@ -41,6 +41,27 @@ mjx-viewer = "mujoco.mjx.viewer:main"
[project.urls]
Homepage = "https://github.com/google-deepmind/mujoco/tree/main/mjx"
Documentation = "https://mujoco.readthedocs.io/en/3.2.5"
Documentation = "https://mujoco.readthedocs.io/en/3.3.1"
Repository = "https://github.com/google-deepmind/mujoco/tree/main/mjx"
Changelog = "https://mujoco.readthedocs.io/en/3.2.5/changelog.html"
Changelog = "https://mujoco.readthedocs.io/en/3.3.1/changelog.html"
[tool.isort]
force_single_line = true
force_sort_within_sections = true
lexicographical = true
single_line_exclusions = ["typing"]
order_by_type = false
group_by_package = true
line_length = 120
use_parentheses = true
multi_line_output = 3
skip_glob = ["**/*.ipynb"]
[tool.pyink]
line-length = 80
unstable = true
pyink-indentation = 2
pyink-use-majority-quotes = true
extend-exclude = '''(
.ipynb$
)'''
+166 -129
View File
@@ -1,141 +1,178 @@
absl-py==2.0.0 \
--hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3
etils[epath]==1.5.1; python_version >= '3.9' \
--hash=sha256:2c1bfa2817eb4881cb509097f1e65ac6160126ba74ec47b3bb47ee678628d8c8
jax==0.4.18; python_version >= '3.9' \
--hash=sha256:2ded3f558b74593c3533036a90c20d41ea35f35c74b25ca0fc86f4aafc388746
jaxlib==0.4.18; python_version >= '3.9' \
--hash=sha256:f0d5414bc79bdd667b81ee3c5836641bbd52d6d9c0054043dec8e025857e1260 \
--hash=sha256:ed1ba86c1a2adea8235269f3e1f5561696068ca60c68b8e5a6f0eb3b978a305a \
--hash=sha256:dfe84a294ab3de2557c49a48c0d83c555018a5190aefa1134d5cb3219865edcd \
--hash=sha256:c429a15165b6b5ded5b0c46c5861d0b978a82aaa2200b2e517366d220f3f01ee \
--hash=sha256:85572a9fa84a17cffd05b771d528012297be4c0e227a07e7dd082c15094749b8 \
--hash=sha256:055950e663fdc101b544597c1361596ff82816575640685effd4779949c8cf06 \
--hash=sha256:4ce7b001fb070e2b7926553bedb9490b7671b3dcc176fd7b52df3932c3593cb0 \
--hash=sha256:f3a8ce7096b3eadd531773c5ef7f7c3bb7552cdf163d682ccd0b0c7f7240d109 \
--hash=sha256:e73b17ac3a6a3e034bca5e5752b1bf035a2eb50ced721a4651b287f5e2c672f7 \
--hash=sha256:02895bc15ec578d3bdbdf2c3a2195852d45870f611b26e2e7261cc6b0353a928 \
--hash=sha256:a72ee7baf663ed5b9c6a426c1919f3755d4d71d4db6abf1979e6ce1f2451fb7f \
--hash=sha256:3293689a8bef495c7837a82ca3038b92c4f21204cadbad6f497306c58aa554e1 \
--hash=sha256:43287e8ece61f69b1d2a13d7f5e4d540c6edf4ab60bc1606b2b5f9321a9e8471 \
--hash=sha256:89fff93b90d054715db0bc3d3b572b799071e63f1fb44edfb1630c5f53631cfd \
--hash=sha256:e0d78703fd1219d9875f20c6c692bba0973b744d66791e0b3e3cdb230c65a2a5 \
--hash=sha256:4771e8439c48d1c3cf65e01016da02c6592a310bf973c9609fc3be7df9b49b22 \
--hash=sha256:f7787a5531d226d6cc9ec2baa7141260bb713435e1cfc053cb9f5cefa9756ac3 \
--hash=sha256:6cb20bbbdafd90e71ad0deb9295519a0175c108c8c557b84fb9fe94f751daee4 \
--hash=sha256:116a0d6aedd3e856b52493d7e392fb1b40952b84fb72448fde1c1ab5687db667 \
--hash=sha256:9593ff69f424947567e206f3e356b2a2df55ca68e6d815d5adc6cae308e8f652
pip==23.3.1 \
--hash=sha256:55eb67bb6171d37447e82213be585b75fe2b12b359e993773aca4de9247a052b
pytest==7.4.2 \
--hash=sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002
pytest-xdist==3.3.1 \
--hash=sha256:ff9daa7793569e6a68544850fd3927cd257cc03a7ef76c95e86915355e82b5f2
scipy==1.11.3; python_version >= '3.9' \
--hash=sha256:90271dbde4be191522b3903fc97334e3956d7cfb9cce3f0718d0ab4fd7d8bfd6 \
--hash=sha256:d2f6dee6cbb0e263b8142ed587bc93e3ed5e777f1f75448d24fb923d9fd4dce6 \
--hash=sha256:bae66a2d7d5768eaa33008fa5a974389f167183c87bf39160d3fefe6664f8ddc \
--hash=sha256:0d3a136ae1ff0883fffbb1b05b0b2fea251cb1046a5077d0b435a1839b3e52b7 \
--hash=sha256:dfcc1552add7cb7c13fb70efcb2389d0624d571aaf2c80b04117e2755a0c5d15 \
--hash=sha256:e1f97cd89c0fe1a0685f8f89d85fa305deb3067d0668151571ba50913e445820 \
--hash=sha256:5f290cf561a4b4edfe8d1001ee4be6da60c1c4ea712985b58bf6bc62badee221 \
--hash=sha256:00f325434b6424952fbb636506f0567898dca7b0f7654d48f1c382ea338ce9a3 \
--hash=sha256:5664e364f90be8219283eeb844323ff8cd79d7acbd64e15eb9c46b9bc7f6a42a \
--hash=sha256:925c6f09d0053b1c0f90b2d92d03b261e889b20d1c9b08a3a51f61afc5f58165 \
--hash=sha256:033c3fd95d55012dd1148b201b72ae854d5086d25e7c316ec9850de4fe776929 \
--hash=sha256:3e1a8a4657673bfae1e05e1e1d6e94b0cabe5ed0c7c144c8aa7b7dbb774ce5c1 \
--hash=sha256:e04aa19acc324a1a076abb4035dabe9b64badb19f76ad9c798bde39d41025cdc \
--hash=sha256:9885e3e4f13b2bd44aaf2a1a6390a11add9f48d5295f7a592393ceb8991577a3 \
--hash=sha256:370f569c57e1d888304052c18e58f4a927338eafdaef78613c685ca2ea0d1fa0 \
--hash=sha256:4b4bb134c7aa457e26cc6ea482b016fef45db71417d55cc6d8f43d799cdf9ef2 \
--hash=sha256:c77da50c9a91e23beb63c2a711ef9e9ca9a2060442757dffee34ea41847d8156 \
--hash=sha256:9ea7f579182d83d00fed0e5c11a4aa5ffe01460444219dedc448a36adf0c3917 \
--hash=sha256:5305792c7110e32ff155aed0df46aa60a60fc6e52cd4ee02cdeb67eaccd5356e \
--hash=sha256:a63d1ec9cadecce838467ce0631c17c15c7197ae61e49429434ba01d618caa83 \
--hash=sha256:715c9966eb8906bc67e450e962bd07a5254420077178f98258904da4004a172f \
--hash=sha256:d4d88a6fc091614b842a739b3db6ae15f95c77b308113bd6daefd4b05539b103 \
--hash=sha256:cf0dbc4d3fe3107358868a60f263c9d8c2e9ba5de8a934cac4164124f727e6ca
setuptools==70.3.0 \
--hash=sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc
trimesh==4.0.0 \
--hash=sha256:c1600e2a1121cff069e89d1403c85c88e49c9dc5b636e96c977a924de432ee50
wheel==0.41.2 \
--hash=sha256:75909db2664838d015e3d9139004ee16711748a52c8f336b52882266540215d8
absl-py==2.1.0 \
--hash=sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308
etils[epath]==1.10.0; python_version >= '3.10' \
--hash=sha256:0777fe60a234b4c65ca53470fc64f2dd2d0c6bca7fcc623fdaa8d7fa5a317098
etils[epath]==1.5.2; python_version == '3.9' \
--hash=sha256:6dc882d355e1e98a5d1a148d6323679dc47c9a5792939b9de72615aa4737eb0b
jax==0.4.34; python_version >= '3.10' \
--hash=sha256:b957ca1fc91f7343f91a186af9f19c7f342c946f95a8c11c7f1e5cdfe2e58d9e
jax==0.4.30; python_version == '3.9' \
--hash=sha256:289b30ae03b52f7f4baf6ef082a9f4e3e29c1080e22d13512c5ecf02d5f1a55b
jaxlib==0.4.34; python_version >= '3.10' \
--hash=sha256:6b43a974c5d91a19912d138f2658dd8dbb7d30dcdff5c961d896c673e872b611 \
--hash=sha256:87f25a477cd279840e53718403f97092eba0e8a945fcab47bcf435b6f9119dda \
--hash=sha256:7be673a876ebd1aef440fb7e3ebaf99a91abeb550c9728c644b7d7c7b5d7c108 \
--hash=sha256:c303f5acaf6c56ce5ff133a923c9b6247bdebedde15bd2c893c24be4d8f71306 \
--hash=sha256:72e22e99a5dc890a64443c3fc12f13f20091f578c405a76de077ba42b4c62cd7 \
--hash=sha256:901cb4040ed24eae40071d8114ea8d10dff436277fa74a1a5b9e7206f641151c \
--hash=sha256:48272e9034ff868d4328cf0055a07882fd2be93f59dfb6283af7de491f9d1290 \
--hash=sha256:1a30771d85fa77f9ab8f18e63240f455ab3a3f87660ed7b8d5eea6ceecbe5c1e \
--hash=sha256:096f0ca309d41fa692a9d1f2f9baab1c5c8ca0749876ebb3f748e738a27c7ff4 \
--hash=sha256:c7b3e724a30426a856070aba0192b5d199e95b4411070e7ad96ad8b196877b10 \
--hash=sha256:133070d4fec5525ffea4dc72956398c1cf647a04dcb37f8a935ee82af78d9965 \
--hash=sha256:3bcfa639ca3cfaf86c8ceebd5fc0d47300fd98a078014a1d0cc03133e1523d5f \
--hash=sha256:571ef03259835458111596a71a2f4a6fabf4ec34595df4cea555035362ac5bf0 \
--hash=sha256:c9d3adcae43a33aad4332be9c2aedc5ef751d1e755f917a5afb30c7872eacaa8 \
--hash=sha256:8ee3f93836e53c86556ccd9449a4ea43516ee05184d031a71dd692e81259f7d9 \
--hash=sha256:b0001c8f0e2b1c7bc99e4f314b524a340d25653505c1a1484d4041a9d3617f6f \
--hash=sha256:d840e64b85f8865404d6d225b9bb340e158df1457152a361b05680e24792b232 \
--hash=sha256:3e60bc826933082e99b19b87c21818a8d26fcdb01f418d47cedff554746fd6cc \
--hash=sha256:45d719a2ce0ebf21255a277b71d756f3609b7b5be70cddc5d88fd58c35219de0 \
--hash=sha256:b7a212a3cb5c6acc201c32ae4f4b5f5a9ac09457fbb77ba8db5ce7e7d4adc214
jaxlib==0.4.30; python_version == '3.9' \
--hash=sha256:54987e97a22db70f3829b437b9329e4799d653634bacc8b398554d3b90c76b2a \
--hash=sha256:f74a6b0e09df4b5e2ee399ebb9f0e01190e26e84ccb0a758fadb516415c07f18 \
--hash=sha256:11602d5556e8baa2f16314c36518e9be4dfae0c2c256a361403fb29dc9dc79a4 \
--hash=sha256:3d31e01191ce8052bd611aaf16ff967d8d0ec0b63f1ea4b199020cecb248d667 \
--hash=sha256:ea3a00005faafbe3c18b178d3b534208b3b4027b2be6230227e7b87ce399fc29
pip==24.3.1 \
--hash=sha256:3790624780082365f47549d032f3770eeb2b1e8bd1f7b2e02dace1afa361b4ed
pytest==8.3.3 \
--hash=sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2
pytest-xdist==3.6.1 \
--hash=sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7
scipy==1.14.1; python_version >= '3.10' \
--hash=sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84 \
--hash=sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e \
--hash=sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d \
--hash=sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e \
--hash=sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73 \
--hash=sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e \
--hash=sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79 \
--hash=sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f \
--hash=sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066 \
--hash=sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310 \
--hash=sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc \
--hash=sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5 \
--hash=sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07 \
--hash=sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d \
--hash=sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94 \
--hash=sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2 \
--hash=sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37 \
--hash=sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8 \
--hash=sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617 \
--hash=sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2 \
--hash=sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675 \
--hash=sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5 \
--hash=sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69 \
--hash=sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d \
--hash=sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3 \
--hash=sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0 \
--hash=sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3 \
--hash=sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389
scipy==1.13.1; python_version == '3.9' \
--hash=sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2 \
--hash=sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d \
--hash=sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004 \
--hash=sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24 \
--hash=sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5
setuptools==75.5.0 \
--hash=sha256:87cb777c3b96d638ca02031192d40390e0ad97737e27b6b4fa831bea86f2f829
trimesh==4.5.2 \
--hash=sha256:2e50f3a7fd135c3045da887a1b9f91230528f3ce11d2ec1ba44750d82d6b4f73
wheel==0.45.0 \
--hash=sha256:52f0baa5e6522155090a09c6bd95718cc46956d1b51d537ea5454249edb671c7
# Transitive dependencies of etils[epath]
fsspec==2023.9.2 \
--hash=sha256:603dbc52c75b84da501b9b2ec8c11e1f61c25984c4a0dda1f129ef391fbfc9b4
importlib-resources==6.1.0 \
--hash=sha256:aa50258bbfa56d4e33fbd8aa3ef48ded10d1735f11532b8df95388cc6bdb7e83
typing_extensions==4.8.0 \
--hash=sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0
zipp==3.19.1 \
--hash=sha256:2828e64edb5386ea6a52e7ba7cdb17bb30a73a858f5eb6eb93d8d36f5ea26091 \
--hash=sha256:35427f6d5594f4acf82d25541438348c26736fa9b3afa2754bcd63cdb99d8e8f
fsspec==2024.10.0 \
--hash=sha256:03b9a6785766a4de40368b88906366755e2819e758b83705c88cd7cb5fe81871
importlib-resources==6.4.5 \
--hash=sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717
typing_extensions==4.12.2 \
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d
zipp==3.21.0 \
--hash=sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931
# Transitive dependencies of jax and jaxlib
importlib-metadata==6.8.0; python_version < '3.10' \
--hash=sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb
ml-dtypes==0.3.1; python_version >= '3.9' \
--hash=sha256:3d8ca0acbd377082792d8b97081ba580abdad67c6afb7f827012c675b052f058 \
--hash=sha256:4828b62fa3bf1ae35faa40f3db9a38ec72fbce02f328a1d14c3a9da4606af364 \
--hash=sha256:d1a8dc3bac1da2a17d0e2e4cba36ee89721d0bd33ea4765af2eefb5f41409e0f \
--hash=sha256:a777928dcba8865ab4a8157eeb25d23aed7bc82e5fd74e1d5eca821d3f148b39 \
--hash=sha256:5e0b0b6bb07fa5ad11bb61d174667176bee5e05857225067aabfc5adc1b51d23 \
--hash=sha256:5727effa7650f7ab10906542d137cfb3244fdc3b2b519beff42f82def8ba59be \
--hash=sha256:42a8980afd8b7c8e270e8b5c260237286b5b26acd276fcb758d13cd7cb567e99 \
--hash=sha256:cb0c404e0dd3e25b56362c1c1e5de0ef717f727dde59fa721af4ce6ab2acca44 \
--hash=sha256:510d249a91face47211762eb294d6fe64f325356b965fb6388c1bf51bd339267 \
--hash=sha256:f83ff080df8910c0f987f615b03e4f8198638e0c00c6e679ea8892dda909763b \
--hash=sha256:fcae2c69715410d96906e1dfe8f017d9f78a0d10e0df91aae52e91f51fdfe45e \
--hash=sha256:da274599e4950a9b488d21571061f49a185537cc77f2d3f8121151d58a9e9f16 \
--hash=sha256:438437e2e614a3c91d75581653b6c40ec890e8b5994d7190a90c931740151c95 \
--hash=sha256:70984b473db6489ec1d8c79b082a1322105155193049d08a3b0c515094e9777b \
--hash=sha256:4d94b2d1bed77284694f7fd0479640fa7aa5d96433dca3cbcec407a5ef752e77 \
--hash=sha256:979d7d196d9a17e0135ae22878f74241fbd3522cef58d7b292f1fd5b32282201
numpy==1.26.0; python_version >= '3.9' \
--hash=sha256:166b36197e9debc4e384e9c652ba60c0bacc216d0fc89e78f973a9760b503388 \
--hash=sha256:f042f66d0b4ae6d48e70e28d487376204d3cbf43b84c03bac57e28dac6151581 \
--hash=sha256:e5e18e5b14a7560d8acf1c596688f4dfd19b4f2945b245a71e5af4ddb7422feb \
--hash=sha256:7f6bad22a791226d0a5c7c27a80a20e11cfe09ad5ef9084d4d3fc4a299cca505 \
--hash=sha256:ee84ca3c58fe48b8ddafdeb1db87388dce2c3c3f701bf447b05e4cfcc3679112 \
--hash=sha256:637c58b468a69869258b8ae26f4a4c6ff8abffd4a8334c830ffb63e0feefe99a \
--hash=sha256:306545e234503a24fe9ae95ebf84d25cba1fdc27db971aa2d9f1ab6bba19a9dd \
--hash=sha256:8c6adc33561bd1d46f81131d5352348350fc23df4d742bb246cdfca606ea1208 \
--hash=sha256:e062aa24638bb5018b7841977c360d2f5917268d125c833a686b7cbabbec496c \
--hash=sha256:eae430ecf5794cb7ae7fa3808740b015aa80747e5266153128ef055975a72b99 \
--hash=sha256:f8db2f125746e44dce707dd44d4f4efeea8d7e2b43aace3f8d1f235cfa2733dd \
--hash=sha256:0621f7daf973d34d18b4e4bafb210bbaf1ef5e0100b5fa750bd9cde84c7ac292 \
--hash=sha256:51be5f8c349fdd1a5568e72713a21f518e7d6707bcf8503b528b88d33b57dc68 \
--hash=sha256:767254ad364991ccfc4d81b8152912e53e103ec192d1bb4ea6b1f5a7117040be \
--hash=sha256:09aaee96c2cbdea95de76ecb8a586cb687d281c881f5f17bfc0fb7f5890f6b91 \
--hash=sha256:4a873a8180479bc829313e8d9798d5234dfacfc2e8a7ac188418189bb8eafbd2 \
--hash=sha256:914b28d3215e0c721dc75db3ad6d62f51f630cb0c277e6b3bcb39519bed10bd8 \
--hash=sha256:c78a22e95182fb2e7874712433eaa610478a3caf86f28c621708d35fa4fd6e7f \
--hash=sha256:86f737708b366c36b76e953c46ba5827d8c27b7a8c9d0f471810728e5a2fe57c \
--hash=sha256:020cdbee66ed46b671429c7265cf00d8ac91c046901c55684954c3958525dab2 \
--hash=sha256:d6fa6d17727169ff1385ad3cb8f290bbcc3f2097322d90507c1956a4f9f870fc
opt-einsum==3.3.0 \
--hash=sha256:2455e59e3947d3c275477df7f5205b30635e266fe6dc300e3d9f9646bfcea147
importlib-metadata==8.5.0; python_version == '3.9' \
--hash=sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b
ml-dtypes==0.5.0 \
--hash=sha256:cb5cc7b25acabd384f75bbd78892d0c724943f3e2e1986254665a1aa10982e07 \
--hash=sha256:54415257f00eb44fbcc807454efac3356f75644f1cbfc2d4e5522a72ae1dacab \
--hash=sha256:e04fde367b2fe901b1d47234426fe8819909bd1dd862a5adb630f27789c20599 \
--hash=sha256:d3b3db9990c3840986a0e70524e122cfa32b91139c3653df76121ba7776e015f \
--hash=sha256:afa08343069874a30812871d639f9c02b4158ace065601406a493a8511180c02 \
--hash=sha256:a38df8df61194aeaae1ab7579075779b4ad32cd1cffd012c28be227fa7f2a70a \
--hash=sha256:a988bac6572630e1e9c2edd9b1277b4eefd1c86209e52b0d061b775ac33902ff \
--hash=sha256:d4b1a70a3e5219790d6b55b9507606fc4e02911d1497d16c18dd721eb7efe7d0 \
--hash=sha256:dc74fd9995513d33eac63d64e436240f5494ec74d522a9f0920194942fc3d2d7 \
--hash=sha256:2e7534392682c3098bc7341648c650864207169c654aed83143d7a19c67ae06f \
--hash=sha256:76942f6aeb5c40766d5ea62386daa4148e6a54322aaf5b53eae9e7553240222f \
--hash=sha256:60275f2b51b56834e840c4809fca840565f9bf8e9a73f6d8c94f5b5935701215 \
--hash=sha256:968fede07d1f9b926a63df97d25ac656cac1a57ebd33701734eaf704bc55d8d8 \
--hash=sha256:c7a9152f5876fef565516aa5dd1dccd6fc298a5891b2467973905103eb5c7856 \
--hash=sha256:ab046f2ff789b1f11b2491909682c5d089934835f9a760fafc180e47dcb676b8 \
--hash=sha256:8c32138975797e681eb175996d64356bcfa124bdbb6a70460b9768c2b35a6fa4 \
--hash=sha256:7ee9c320bb0f9ffdf9f6fa6a696ef2e005d1f66438d6f1c1457338e00a02e8cf \
--hash=sha256:a03fc861b86cc586728e3d093ba37f0cc05e65330c3ebd7688e7bae8290f8859 \
--hash=sha256:099e09edd54e676903b4538f3815b5ab96f5b119690514602d96bfdb67172cbe \
--hash=sha256:5f2b59233a0dbb6a560b3137ed6125433289ccba2f8d9c3695a52423a369ed15
numpy==2.1.3; python_version >= '3.10' \
--hash=sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed \
--hash=sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56 \
--hash=sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43 \
--hash=sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe \
--hash=sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57 \
--hash=sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598 \
--hash=sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f \
--hash=sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a \
--hash=sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b \
--hash=sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512 \
--hash=sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564 \
--hash=sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8 \
--hash=sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958 \
--hash=sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e \
--hash=sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2 \
--hash=sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b \
--hash=sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a \
--hash=sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09 \
--hash=sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9 \
--hash=sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41 \
--hash=sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d \
--hash=sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0 \
--hash=sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098 \
--hash=sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3 \
--hash=sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd \
--hash=sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1 \
--hash=sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5 \
--hash=sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff \
numpy==2.0.2; python_version == '3.9' \
--hash=sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73 \
--hash=sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd \
--hash=sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1 \
--hash=sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729 \
--hash=sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b \
--hash=sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd \
--hash=sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c
opt-einsum==3.4.0 \
--hash=sha256:69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd
# Transitive dependencies of pytest
attrs==23.1.0; platform_system == 'Windows' \
--hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04
attrs==24.2.0; platform_system == 'Windows' \
--hash=sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2
colorama==0.4.6; platform_system == 'Windows' \
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
exceptiongroup==1.1.3; python_version < '3.11' \
--hash=sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3
exceptiongroup==1.2.2; python_version < '3.11' \
--hash=sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b
iniconfig==2.0.0 \
--hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374
packaging==23.2 \
--hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7
pluggy==1.3.0 \
--hash=sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7
pyparsing==3.1.1 \
--hash=sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb
tomli==2.0.1; python_version < '3.11' \
--hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc
packaging==24.2 \
--hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759
pluggy==1.5.0 \
--hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669
tomli==2.1.0; python_version < '3.11' \
--hash=sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391
# Transitive dependencies of pytest-xdist
execnet==2.0.2 \
--hash=sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41
execnet==2.1.1 \
--hash=sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc
+25 -3
View File
@@ -66,10 +66,10 @@
"$$\n",
"\n",
"$$\n",
"\\frac{\\partial x_t}{\\partial \\theta} = \\textcolor{Navy}{\\frac{\\partial f(x_{t-1}, a_{t-1})}{\\partial x_{t-1}}}\\frac{\\partial x_{t-1}}{\\partial \\theta} + \\textcolor{Navy}{\\frac{\\partial f(x_{t-1}, a_{t-1})}{\\partial a_{t-1}}} \\frac{\\partial a_{t-1}}{\\partial \\theta}\n",
"\\frac{\\partial x_t}{\\partial \\theta} = \\color{blue}{\\frac{\\partial f(x_{t-1}, a_{t-1})}{\\partial x_{t-1}}}\\frac{\\partial x_{t-1}}{\\partial \\theta} + \\color{blue}{\\frac{\\partial f(x_{t-1}, a_{t-1})}{\\partial a_{t-1}}} \\frac{\\partial a_{t-1}}{\\partial \\theta}\n",
"$$\n",
"\n",
"The navy-colored terms in the above expression are enabled by MJX's differentiability and are the key difference between FoPG's and ZoPG's. An important consideration is what these jacobians look like near contact points. To see why certain gradients within the jacobian can be pathological, imagine a hard sphere falling toward a block of marble. How does its velocity change with respect to distance ($\\frac{\\partial \\dot{z}_t}{\\partial z_t}$), the instant before it touches the ground? This is the case of an **uninformative gradient**, due to [hard contact](https://arxiv.org/html/2404.02887v1). Fortunately, the default contact settings in Mujoco are sufficiently [soft](https://mujoco.readthedocs.io/en/stable/computation/index.html#soft-contact-model) for learning via FoPG's. With soft contacts, the ground applies an increasing force on the ball as it penetrates it, unlike rigid contacts, which instantly provide enough force for deflection.\n",
"The blue-colored terms in the above expression are enabled by MJX's differentiability and are the key difference between FoPG's and ZoPG's. An important consideration is what these jacobians look like near contact points. To see why certain gradients within the jacobian can be pathological, imagine a hard sphere falling toward a block of marble. How does its velocity change with respect to distance ($\\frac{\\partial \\dot{z}_t}{\\partial z_t}$), the instant before it touches the ground? This is the case of an **uninformative gradient**, due to [hard contact](https://arxiv.org/html/2404.02887v1). Fortunately, the default contact settings in Mujoco are sufficiently [soft](https://mujoco.readthedocs.io/en/stable/computation/index.html#soft-contact-model) for learning via FoPG's. With soft contacts, the ground applies an increasing force on the ball as it penetrates it, unlike rigid contacts, which instantly provide enough force for deflection.\n",
"\n",
"A helpful way to think about FoPG's is via the chain rule and computation graphs, as illustrated below for how $r_2$ influences the policy gradient, again for the case that the reward does not depend on action:\n",
"\n",
@@ -85,7 +85,29 @@
"\n",
"Last, despite the sample efficiency, FoPG methods can still struggle with wall-clock time. Because the gradients have low variance, they do not benefit significantly from massive parallelization of data collection - unlike [RL](https://arxiv.org/abs/2109.11978). Additionally, the policy gradient is typically calculated via autodifferentiation. This can be 3-5x slower than unrolling the simulation forward, and memory intensive, with memory requirements scaling with $O(m \\cdot (m+n) \\cdot T)$, where m and n are the state and control dimensions, $m \\cdot (m+n)$ is the jacobian dimension, and T is the number of steps propogated through.\n",
"\n",
"Note that with certain models, using autodifferentiation through mjx.step currently causes [nan gradients](https://github.com/google-deepmind/mujoco/issues/1517). For now, we address this issue by using double-precision floats, at the cost of doubling the memory requirements and training time."
"Note that with certain models, using autodifferentiation through mjx.step currently causes [nan gradients](https://github.com/google-deepmind/mujoco/issues/1517). For now, we address this issue by using double-precision floats, at the cost of doubling the memory requirements and training time.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"**Publications**\n",
"\n",
"If you use this work in an academic context, please cite the following publication:\n",
"\n",
"```\n",
"@misc{luo2024residual,\n",
" title={Residual Policy Learning for Perceptive Quadruped Control Using Differentiable Simulation},\n",
" author={Luo, Jing Yuan and Song, Yunlong and Klemm, Victor and Shi, Fan and Scaramuzza, Davide and Hutter, Marco},\n",
" year={2024},\n",
" eprint={2410.03076},\n",
" archivePrefix={arXiv},\n",
" primaryClass={cs.RO},\n",
" url={https://doi.org/10.48550/arXiv.2410.03076}\n",
"}\n",
"```"
]
},
{
+20 -343
View File
@@ -321,7 +321,7 @@
"source": [
"Now let's run the same exact simulation on the GPU device using MJX!\n",
"\n",
"In the example below, we use `mjx.step` instead of `mujoco.mj_step`, and we also [`jax.jit`](https://jax.readthedocs.io/en/latest/jax-101/02-jitting.html) the `mjx.step` so that it runs efficiently on the GPU. After each step, we convert the `mjx.Data` back to `mjData` so that we can use the MuJoCo renderer.\n"
"In the example below, we use `mjx.step` instead of `mujoco.mj_step`, and we also [`jax.jit`](https://jax.readthedocs.io/en/latest/jax-101/02-jitting.html) the `mjx.step` so that it runs efficiently on the GPU. For each frame, we convert the `mjx.Data` back to `mjData` so that we can use the MuJoCo renderer.\n"
]
},
{
@@ -416,13 +416,14 @@
"cell_type": "code",
"execution_count": 0,
"metadata": {
"cellView": "form",
"id": "mtGMYNLE3QJN"
},
"outputs": [],
"source": [
"#@title Humanoid Env\n",
"\n",
"HUMANOID_ROOT_PATH = epath.Path(epath.resource_path('mujoco')) / 'mjx/test_data/humanoid'\n",
"\n",
"class Humanoid(PipelineEnv):\n",
"\n",
" def __init__(\n",
@@ -436,11 +437,9 @@
" exclude_current_positions_from_observation=True,\n",
" **kwargs,\n",
" ):\n",
" path = epath.Path(epath.resource_path('mujoco')) / (\n",
" 'mjx/test_data/humanoid'\n",
" )\n",
"#\n",
" mj_model = mujoco.MjModel.from_xml_path(\n",
" (path / 'humanoid.xml').as_posix())\n",
" (HUMANOID_ROOT_PATH / 'humanoid.xml').as_posix())\n",
" mj_model.opt.solver = mujoco.mjtSolver.mjSOL_CG\n",
" mj_model.opt.iterations = 6\n",
" mj_model.opt.ls_iterations = 6\n",
@@ -563,7 +562,7 @@
"\n",
"Let's instantiate the environment and visualize a short rollout.\n",
"\n",
"NOTE: Since episodes terminates early if the torso is below the healthy z-range, the only relevant contacts for this task are between the feet and the plane. We turn off other contacts."
"NOTE: Since episodes terminate early if the torso is below the healthy z-range, the only relevant contacts for this task are between the feet and the plane. We turn off other contacts."
]
},
{
@@ -887,7 +886,9 @@
"source": [
"## Quadruped Env\n",
"\n",
"Let's define a quadruped environment that takes advantage of the domain randomization function. Here we use the [Barkour vb Quadruped](https://github.com/google-deepmind/mujoco_menagerie/tree/main/google_barkour_vb) from [MuJoCo Menagerie](https://github.com/google-deepmind/mujoco_menagerie). We implement an environment that trains a joystick policy with Brax."
"Let's define a quadruped environment that takes advantage of the domain randomization function. Here we use the [Barkour vb Quadruped](https://github.com/google-deepmind/mujoco_menagerie/tree/main/google_barkour_vb) from [MuJoCo Menagerie](https://github.com/google-deepmind/mujoco_menagerie). We implement an environment that trains a joystick policy with Brax.\n",
"\n",
"NOTE: for a full suite of robotic environments, many of which were transferred onto robots, check out [MuJoCo Playground](https://github.com/google-deepmind/mujoco_playground)!\n"
]
},
{
@@ -1605,345 +1606,22 @@
" fps=1.0 / eval_env.dt / render_every)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "-Q5gsOOaBYd1"
},
"source": [
"# MuJoCo Playground: Robotics locomotion and manipulation environments + Sim-to-Real!"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "gluTlHURuC6i"
},
"source": [
"# Manipulation Environments and Policies\n",
"\n",
"By now, we have shown how MJX can be used to train policies for classic control and robotic locomotion. MJX can also be used for robotic manipulation!\n",
"\n",
"We demonstrate a task on the Franka Panda below, which trains a policy to pickup a cube and bring it to a mocap target position in about 3 minutes on an A100. We will be adding more support for manipulation environments in MJX (i.e. more performant collisions), so stay tuned!\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"id": "RCv16hZIu5Dm"
},
"outputs": [],
"source": [
"%%shell\n",
"if [ ! -d \"mujoco_menagerie\" ]; then\n",
" git clone https://github.com/google-deepmind/mujoco_menagerie\n",
"fi\n"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"cellView": "form",
"id": "AuU-9nUquEu4"
},
"outputs": [],
"source": [
"#@title Franka Panda BringToTarget Environment\n",
"\n",
"FRANKA_PANDA_ROOT_PATH = epath.Path('mujoco_menagerie/franka_emika_panda')\n",
"\n",
"\n",
"def default_config():\n",
" \"\"\"Returns reward config for the environment.\"\"\"\n",
"\n",
" return config_dict.create(\n",
" # Environment timestep. Should match the robot decision frequency.\n",
" dt=0.02,\n",
" # Lowers action magnitude for less-jerky motion. Also sometimes helps\n",
" # sample efficiency.\n",
" action_scale=0.04,\n",
" # The coefficients for all reward terms used for training.\n",
" reward_scales=config_dict.create(\n",
" # Gripper goes to the box.\n",
" gripper_box=4.0,\n",
" # Box goes to the target mocap.\n",
" box_target=8.0,\n",
" # Do not collide the gripper with the floor.\n",
" no_floor_collision=0.25,\n",
" # Arm stays close to target pose.\n",
" robot_target_qpos=0.3,\n",
" ),\n",
" )\n",
"\n",
"\n",
"def _load_sys(path: epath.Path) -> base.System:\n",
" \"\"\"Load a mujoco model from a path.\"\"\"\n",
" assets = {}\n",
" for f in path.parent.glob('*.xml'):\n",
" assets[f.name] = f.read_bytes()\n",
" for f in (path.parent / 'assets').glob('*'):\n",
" assets[f.name] = f.read_bytes()\n",
" xml = path.read_text()\n",
" model = mujoco.MjModel.from_xml_string(xml, assets)\n",
" return mjcf.load_model(model)\n",
"\n",
"\n",
"def _get_collision_info(\n",
" contact: Any, geom1: int, geom2: int) -> Tuple[jax.Array, jax.Array]:\n",
" if geom1 > geom2:\n",
" geom1, geom2 = geom2, geom1\n",
" mask = (jp.array([geom1, geom2]) == contact.geom).all(axis=1)\n",
" idx = jp.where(mask, contact.dist, 1e4).argmin()\n",
" dist = contact.dist[idx] * mask[idx]\n",
" normal = (dist < 0) * contact.frame[idx, 0, :3]\n",
" return dist, normal\n",
"\n",
"\n",
"def _geoms_colliding(\n",
" state: Optional[State], geom1: int, geom2: int\n",
") -> jax.Array:\n",
" return _get_collision_info(state.contact, geom1, geom2)[0] < 0\n",
"\n",
"\n",
"class PandaBringToTarget(PipelineEnv):\n",
" \"\"\"Environment for training franka panda to bring an object to target.\"\"\"\n",
"\n",
" def __init__(self, **kwargs):\n",
" global root_path\n",
" sys = _load_sys(FRANKA_PANDA_ROOT_PATH / 'mjx_single_cube.xml')\n",
" self._config = config = default_config()\n",
" nsteps = int(np.round(config.dt / sys.opt.timestep))\n",
" kwargs['backend'] = 'mjx'\n",
" kwargs['n_frames'] = nsteps\n",
" super().__init__(sys, **kwargs)\n",
"\n",
" # define constants\n",
" model = sys.mj_model\n",
" arm_joints = ['joint1', 'joint2', 'joint3', 'joint4', 'joint5',\n",
" 'joint6', 'joint7']\n",
" finger_joints = ['finger_joint1', 'finger_joint2']\n",
" all_joints = arm_joints + finger_joints\n",
" self._robot_arm_qposadr = np.array([\n",
" model.jnt_qposadr[model.joint(j).id] for j in arm_joints])\n",
" self._robot_qposadr = np.array([\n",
" model.jnt_qposadr[model.joint(j).id] for j in all_joints])\n",
" self._gripper_site = model.site('gripper').id\n",
" self._left_finger_geom = model.geom('left_finger_pad').id\n",
" self._right_finger_geom = model.geom('right_finger_pad').id\n",
" self._hand_geom = model.geom('hand_capsule').id\n",
" self._box_body = model.body('box').id\n",
" self._box_qposadr = model.jnt_qposadr[model.body('box').jntadr[0]]\n",
" # TODO(btaba): replace with mocap_pos once MJX version 3.2.3 is released.\n",
" self._target_id = model.body('mocap_target').id\n",
" self._floor_geom = model.geom('floor').id\n",
" self._init_q = sys.mj_model.keyframe('home').qpos\n",
" self._init_box_pos = jp.array(\n",
" self._init_q[self._box_qposadr : self._box_qposadr + 3],\n",
" dtype=jp.float32)\n",
" self._init_ctrl = sys.mj_model.keyframe('home').ctrl\n",
" self._lowers = model.actuator_ctrlrange[:, 0]\n",
" self._uppers = model.actuator_ctrlrange[:, 1]\n",
"\n",
" def reset(self, rng: jax.Array) -> State:\n",
" rng, rng_box, rng_target = jax.random.split(rng, 3)\n",
"\n",
" # intialize box position\n",
" box_pos = jax.random.uniform(\n",
" rng_box, (3,),\n",
" minval=jp.array([-0.2, -0.2, 0.0]),\n",
" maxval=jp.array([0.2, 0.2, 0.0])) + self._init_box_pos\n",
"\n",
" # initialize target position\n",
" target_pos = jax.random.uniform(\n",
" rng_target, (3,),\n",
" minval=jp.array([-0.2, -0.2, 0.2]),\n",
" maxval=jp.array([0.2, 0.2, 0.4])) + self._init_box_pos\n",
"\n",
" # initialize pipeline state\n",
" init_q = jp.array(self._init_q).at[\n",
" self._box_qposadr : self._box_qposadr + 3].set(box_pos)\n",
" pipeline_state = self.pipeline_init(\n",
" init_q, jp.zeros(self.sys.nv)\n",
" )\n",
" pipeline_state = pipeline_state.replace(ctrl=self._init_ctrl)\n",
" # set target mocap position\n",
" # TODO(btaba): replace with mocap_pos once MJX version 3.2.3 is released.\n",
" pipeline_state = pipeline_state.replace(\n",
" xpos=pipeline_state.xpos.at[self._target_id, :].set(target_pos))\n",
"\n",
" # initialize env state and info\n",
" metrics = {\n",
" 'out_of_bounds': jp.array(0.0),\n",
" **{k: 0.0 for k in self._config.reward_scales.keys()},\n",
" }\n",
" info = {'rng': rng, 'target_pos': target_pos, 'reached_box': 0.0}\n",
" obs = self._get_obs(pipeline_state, info)\n",
" reward, done = jp.zeros(2)\n",
" state = State(pipeline_state, obs, reward, done, metrics, info)\n",
" return state\n",
"\n",
" def step(self, state: State, action: jax.Array) -> State:\n",
" delta = action * self._config.action_scale\n",
" ctrl = state.pipeline_state.ctrl + delta\n",
" ctrl = jp.clip(ctrl, self._lowers, self._uppers)\n",
"\n",
" # step the physics\n",
" data = self.pipeline_step(state.pipeline_state, ctrl)\n",
"\n",
" # compute reward terms\n",
" target_pos = state.info['target_pos']\n",
" box_pos = data.xpos[self._box_body]\n",
" gripper_pos = data.site_xpos[self._gripper_site]\n",
" box_target = 1 - jp.tanh(5 * jp.linalg.norm(target_pos - box_pos))\n",
" gripper_box = 1 - jp.tanh(5 * jp.linalg.norm(box_pos - gripper_pos))\n",
" robot_target_qpos = 1 - jp.tanh(\n",
" jp.linalg.norm(\n",
" state.pipeline_state.qpos[self._robot_arm_qposadr]\n",
" - self._init_q[self._robot_arm_qposadr]\n",
" )\n",
" )\n",
"\n",
" hand_floor_collision = [\n",
" _geoms_colliding(state.pipeline_state, self._floor_geom, g)\n",
" for g in [\n",
" self._left_finger_geom,\n",
" self._right_finger_geom,\n",
" self._hand_geom,\n",
" ]\n",
" ]\n",
" floor_collision = sum(hand_floor_collision) > 0\n",
" no_floor_collision = 1 - floor_collision\n",
"\n",
" state.info['reached_box'] = 1.0 * jp.maximum(\n",
" state.info['reached_box'],\n",
" (jp.linalg.norm(box_pos - gripper_pos) < 0.012),\n",
" )\n",
"\n",
" rewards = {\n",
" 'box_target': box_target * state.info['reached_box'],\n",
" 'gripper_box': gripper_box,\n",
" 'no_floor_collision': no_floor_collision,\n",
" 'robot_target_qpos': robot_target_qpos,\n",
" }\n",
" rewards = {k: v * self._config.reward_scales[k] for k, v in rewards.items()}\n",
" reward = jp.clip(sum(rewards.values()), -1e4, 1e4)\n",
"\n",
" out_of_bounds = jp.any(jp.abs(box_pos) > 1.0)\n",
" out_of_bounds |= box_pos[2] < 0.0\n",
" state.metrics.update(\n",
" out_of_bounds=out_of_bounds.astype(float),\n",
" **rewards)\n",
"\n",
" obs = self._get_obs(data, state.info)\n",
" done = out_of_bounds | jp.isnan(data.qpos).any() | jp.isnan(data.qvel).any()\n",
" done = done.astype(float)\n",
" state = State(data, obs, reward, done, state.metrics, state.info)\n",
"\n",
" return state\n",
"\n",
" def _get_obs(self, data: PipelineState, info: dict[str, Any]) -> jax.Array:\n",
" gripper_pos = data.site_xpos[self._gripper_site]\n",
" gripper_mat = data.site_xmat[self._gripper_site].ravel()\n",
" obs = jp.concatenate([\n",
" data.qpos,\n",
" data.qvel,\n",
" gripper_pos,\n",
" gripper_mat[3:],\n",
" data.xmat[self._box_body].ravel()[3:],\n",
" data.xpos[self._box_body] - data.site_xpos[self._gripper_site],\n",
" info['target_pos'] - data.xpos[self._box_body],\n",
" data.ctrl - data.qpos[self._robot_qposadr[:-1]],\n",
" ])\n",
"\n",
" return obs\n",
"\n",
"envs.register_environment('PandaBringToTarget', PandaBringToTarget)"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"id": "76g9uILMQVkc"
},
"outputs": [],
"source": [
"# instantiate the environment\n",
"env_name = 'PandaBringToTarget'\n",
"env = envs.get_environment(env_name)\n",
"\n",
"# define the jit reset/step functions\n",
"jit_reset = jax.jit(env.reset)\n",
"jit_step = jax.jit(env.step)"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"id": "10_vs9IDvnke"
},
"outputs": [],
"source": [
"#@title Train Pick-up-cube Policy\n",
"\n",
"make_networks_factory = functools.partial(\n",
" ppo_networks.make_ppo_networks,\n",
" policy_hidden_layer_sizes=(32, 32, 32, 32))\n",
"\n",
"train_fn = functools.partial(\n",
" ppo.train, num_timesteps=20_000_000, num_evals=4, reward_scaling=0.1,\n",
" episode_length=150, normalize_observations=True, action_repeat=1,\n",
" unroll_length=10, num_minibatches=32, num_updates_per_batch=8,\n",
" discounting=0.97, learning_rate=1e-3, entropy_cost=2e-2, num_envs=2048,\n",
" batch_size=512, num_resets_per_eval=1,\n",
" network_factory=make_networks_factory, seed=0)\n",
"\n",
"\n",
"x_data, y_data, y_dataerr = [], [], []\n",
"times = [datetime.now()]\n",
"def progress(num_steps, metrics):\n",
" times.append(datetime.now())\n",
" x_data.append(num_steps)\n",
" y_data.append(metrics['eval/episode_reward'])\n",
" y_dataerr.append(metrics['eval/episode_reward_std'])\n",
"\n",
" plt.xlim([0, train_fn.keywords['num_timesteps'] * 1.25])\n",
" plt.ylim([0, 2000])\n",
" plt.xlabel('# environment steps')\n",
" plt.ylabel('reward per episode')\n",
" plt.title(f'y={y_data[-1]:.3f}')\n",
" plt.errorbar(x_data, y_data, yerr=y_dataerr)\n",
" plt.show()\n",
"\n",
"make_inference_fn, params, _= train_fn(environment=env, progress_fn=progress)\n",
"jit_inference_fn = jax.jit(make_inference_fn(params, deterministic=True))\n",
"\n",
"print(f'time to jit: {times[1] - times[0]}')\n",
"print(f'time to train: {times[-1] - times[1]}')\n"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"id": "jDJLcI0Bv5lD"
},
"outputs": [],
"source": [
"# initialize the state\n",
"rng = jax.random.PRNGKey(0)\n",
"state = jit_reset(rng)\n",
"rollout = [state.pipeline_state]\n",
"\n",
"# grab a trajectory\n",
"n_steps = 150\n",
"render_every = 2\n",
"\n",
"for i in range(n_steps):\n",
" act_rng, rng = jax.random.split(rng)\n",
" ctrl, _ = jit_inference_fn(state.obs, act_rng)\n",
" state = jit_step(state, ctrl)\n",
" rollout.append(state.pipeline_state)\n",
"\n",
" if state.done:\n",
" break\n",
"\n",
"media.show_video(env.render(rollout[::render_every]), fps=1.0 / env.dt / render_every)"
"By now, we have shown how MJX can be used to train policies for classic control and robotic locomotion. For a full suite of robotic locomotion and manipulation environments we encourage you to check out [MuJoCo Playground](https://github.com/google-deepmind/mujoco_playground). Many of the robotic environments have been transferred onto robots, as described on the [website](https://playground.mujoco.org/) and [technical report](https://playground.mujoco.org/assets/playground_technical_report.pdf).\n"
]
}
],
@@ -1953,8 +1631,7 @@
"gpuClass": "premium",
"gpuType": "V100",
"machine_shape": "hm",
"private_outputs": true,
"toc_visible": true
"private_outputs": true
},
"kernelspec": {
"display_name": "Python 3",
+12 -3
View File
@@ -94,9 +94,18 @@
<site name="pin_right" pos=".025 0 .025"/>
</body>
<composite type="particle" count="4 4 4" spacing="0.025" offset=".11 .01 .1">
<geom size=".012" rgba=".5 .8 .5 1" solref=".005 1"/>
</composite>
<replicate count="4" offset=".025 0 0">
<replicate count="4" offset="0 .025 0">
<replicate count="4" offset="0 0 .025">
<body pos=".06 -.04 .05">
<joint type="slide" axis="1 0 0"/>
<joint type="slide" axis="0 1 0"/>
<joint type="slide" axis="0 0 1"/>
<geom type="sphere" size=".012" rgba=".5 .8 .5 1" solref=".005 1" condim="1"/>
</body>
</replicate>
</replicate>
</replicate>
</worldbody>
<equality>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

-45
View File
@@ -1,45 +0,0 @@
<!-- Copyright 2021 DeepMind Technologies Limited
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mujoco model="Particle">
<!-- Degree of Freedom: 3000
Actuators: 0
-->
<include file="scene.xml"/>
<option solver="CG" tolerance="1e-6" timestep=".01"/>
<size memory="1G"/>
<visual>
<map stiffness="100"/>
</visual>
<default>
<default class="wall">
<geom type="plane" size=".5 .5 .05"/>
</default>
</default>
<worldbody>
<geom name="+x" class="wall" zaxis="1 0 0" pos="-.5 0 -.25"/>
<geom name="-x" class="wall" zaxis="-1 0 0" pos=".5 0 -.25"/>
<geom name="+y" class="wall" zaxis="0 1 0" pos="0 -.5 -.25"/>
<geom name="-y" class="wall" zaxis="0 -1 0" pos="0 .5 -.25"/>
<composite type="particle" count="10 10 10" spacing="0.07" offset="0 0 1">
<geom size=".025" rgba=".8 .2 .1 1"/>
</composite>
</worldbody>
</mujoco>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+617
View File
@@ -0,0 +1,617 @@
####
#
# OBJ File Generated by Meshlab
#
####
# Object cap.obj
#
# Vertices: 217
# Faces: 384
#
####
v -0.00000000 0.00000000 0.07411810
v 0.09659259 0.00000000 -0.00000001
v 0.04829629 0.08365163 0.00000001
v -0.04829629 0.08365163 0.00000001
v -0.09659259 0.00000000 -0.00000001
v -0.04829629 -0.08365163 0.00000001
v 0.04829629 -0.08365163 0.00000001
v 0.05341240 -0.00000000 0.05865872
v 0.08365164 0.04829629 -0.00000001
v 0.02670619 0.04625649 0.05865872
v 0.00000000 0.09659259 -0.00000001
v -0.02670619 0.04625649 0.05865872
v -0.08365164 0.04829629 -0.00000001
v -0.05341240 0.00000000 0.05865872
v -0.08365164 -0.04829629 -0.00000001
v -0.02670619 -0.04625649 0.05865872
v 0.00000000 -0.09659259 -0.00000001
v 0.02670619 -0.04625649 0.05865872
v 0.08365164 -0.04829629 -0.00000001
v 0.06724097 0.02381060 0.04420168
v 0.05424107 0.04632709 0.04420168
v 0.04028573 0.02325897 0.06263974
v 0.01299990 0.07013769 0.04420167
v -0.01299990 0.07013769 0.04420168
v -0.00000000 0.04651795 0.06263975
v -0.05424108 0.04632709 0.04420166
v -0.06724098 0.02381060 0.04420166
v -0.04028573 0.02325897 0.06263974
v -0.06724097 -0.02381060 0.04420168
v -0.05424107 -0.04632709 0.04420168
v -0.04028573 -0.02325897 0.06263974
v -0.01299990 -0.07013769 0.04420167
v 0.01299990 -0.07013769 0.04420168
v 0.00000000 -0.04651795 0.06263975
v 0.05424108 -0.04632709 0.04420166
v 0.06724098 -0.02381060 0.04420166
v 0.04028573 -0.02325897 0.06263974
v 0.02678733 -0.00000000 0.07046351
v 0.01339367 0.02319851 0.07046351
v 0.07868688 -0.00000000 0.03583015
v 0.09330128 0.02500000 -0.00000001
v 0.06830128 0.06830128 -0.00000004
v 0.03934343 0.06814483 0.03583015
v -0.01339367 0.02319851 0.07046351
v 0.02500000 0.09330128 -0.00000001
v -0.02500000 0.09330128 -0.00000001
v -0.03934344 0.06814483 0.03583015
v -0.02678734 0.00000000 0.07046351
v -0.06830128 0.06830128 -0.00000004
v -0.09330128 0.02500000 -0.00000001
v -0.07868689 0.00000000 0.03583013
v -0.01339367 -0.02319851 0.07046351
v -0.09330128 -0.02500000 -0.00000001
v -0.06830128 -0.06830128 -0.00000004
v -0.03934343 -0.06814483 0.03583015
v 0.01339367 -0.02319851 0.07046351
v -0.02500000 -0.09330128 -0.00000001
v 0.02500000 -0.09330128 -0.00000001
v 0.03934344 -0.06814483 0.03583015
v 0.06830128 -0.06830128 -0.00000004
v 0.09330128 -0.02500000 -0.00000001
v 0.06102579 0.03523326 0.04507211
v 0.04719808 0.03485627 0.05509577
v 0.05378547 0.02344661 0.05509576
v -0.00000000 0.07046651 0.04507211
v -0.00658737 0.05830287 0.05509577
v 0.00658736 0.05830287 0.05509577
v -0.06102579 0.03523326 0.04507211
v -0.05378547 0.02344662 0.05509576
v -0.04719809 0.03485627 0.05509576
v -0.06102579 -0.03523326 0.04507211
v -0.04719808 -0.03485627 0.05509577
v -0.05378547 -0.02344661 0.05509576
v 0.00000000 -0.07046651 0.04507211
v 0.00658737 -0.05830287 0.05509577
v -0.00658736 -0.05830287 0.05509577
v 0.06102579 -0.03523326 0.04507211
v 0.05378547 -0.02344662 0.05509576
v 0.04719809 -0.03485627 0.05509576
v 0.03349884 0.01162469 0.06762048
v 0.02681670 0.02319850 0.06762048
v 0.02009635 0.01160263 0.07138842
v 0.08512369 0.01233582 0.02512579
v 0.08049080 0.02435185 0.02823111
v 0.07300231 0.01205527 0.04138871
v 0.06133472 0.05753115 0.02823112
v 0.05324498 0.06755137 0.02512579
v 0.04694132 0.05719421 0.04138872
v 0.00668214 0.03482319 0.06762048
v -0.00668214 0.03482319 0.06762048
v -0.00000000 0.02320527 0.07138842
v 0.03187871 0.07988720 0.02512577
v 0.01915608 0.08188300 0.02823112
v 0.02606098 0.06924948 0.04138871
v -0.01915608 0.08188300 0.02823112
v -0.03187871 0.07988719 0.02512579
v -0.02606099 0.06924949 0.04138871
v -0.02681670 0.02319850 0.06762048
v -0.03349884 0.01162470 0.06762048
v -0.02009636 0.01160263 0.07138842
v -0.05324498 0.06755137 0.02512577
v -0.06133472 0.05753115 0.02823112
v -0.04694132 0.05719422 0.04138871
v -0.08049081 0.02435185 0.02823111
v -0.08512370 0.01233582 0.02512577
v -0.07300231 0.01205527 0.04138870
v -0.03349884 -0.01162469 0.06762048
v -0.02681670 -0.02319850 0.06762048
v -0.02009635 -0.01160263 0.07138842
v -0.08512370 -0.01233582 0.02512577
v -0.08049080 -0.02435185 0.02823111
v -0.07300231 -0.01205527 0.04138871
v -0.06133472 -0.05753115 0.02823112
v -0.05324498 -0.06755137 0.02512579
v -0.04694132 -0.05719421 0.04138872
v -0.00668214 -0.03482319 0.06762048
v 0.00668214 -0.03482319 0.06762048
v 0.00000000 -0.02320527 0.07138842
v -0.03187871 -0.07988720 0.02512577
v -0.01915608 -0.08188300 0.02823112
v -0.02606098 -0.06924948 0.04138871
v 0.01915608 -0.08188300 0.02823112
v 0.03187871 -0.07988719 0.02512579
v 0.02606099 -0.06924949 0.04138871
v 0.02681670 -0.02319850 0.06762048
v 0.03349884 -0.01162470 0.06762048
v 0.02009636 -0.01160263 0.07138842
v 0.05324498 -0.06755137 0.02512577
v 0.06133472 -0.05753115 0.02823112
v 0.04694132 -0.05719422 0.04138871
v 0.08049081 -0.02435185 0.02823111
v 0.08512369 -0.01233582 0.02512579
v 0.07300231 -0.01205527 0.04138871
v 0.06011157 0.01183397 0.05315326
v 0.04687075 0.01169339 0.06167607
v 0.07504904 0.03594581 0.02957566
v 0.06865450 0.04702147 0.02957566
v 0.04030430 0.04614115 0.05315326
v 0.03356215 0.03474456 0.06167607
v 0.01980726 0.05797512 0.05315328
v 0.01330860 0.04643795 0.06167607
v 0.00639453 0.08296728 0.02957566
v -0.00639453 0.08296728 0.02957566
v -0.01980726 0.05797513 0.05315326
v -0.01330860 0.04643795 0.06167607
v -0.04030430 0.04614115 0.05315326
v -0.03356215 0.03474456 0.06167607
v -0.06865451 0.04702147 0.02957564
v -0.07504904 0.03594581 0.02957565
v -0.06011157 0.01183397 0.05315326
v -0.04687076 0.01169339 0.06167606
v -0.06011157 -0.01183397 0.05315326
v -0.04687075 -0.01169339 0.06167607
v -0.07504904 -0.03594581 0.02957566
v -0.06865450 -0.04702147 0.02957566
v -0.04030430 -0.04614115 0.05315326
v -0.03356215 -0.03474456 0.06167607
v -0.01980726 -0.05797512 0.05315328
v -0.01330860 -0.04643795 0.06167607
v -0.00639453 -0.08296728 0.02957566
v 0.00639453 -0.08296728 0.02957566
v 0.01980726 -0.05797513 0.05315326
v 0.01330860 -0.04643795 0.06167607
v 0.04030430 -0.04614115 0.05315326
v 0.03356215 -0.03474456 0.06167607
v 0.06865451 -0.04702147 0.02957564
v 0.07504904 -0.03594581 0.02957565
v 0.06011157 -0.01183397 0.05315326
v 0.04687075 -0.01169339 0.06167607
v 0.01339461 -0.00000000 0.07321696
v 0.00669730 0.01160007 0.07321696
v 0.04015301 -0.00000000 0.06570269
v 0.02007650 0.03477353 0.06570269
v 0.06638318 -0.00000000 0.04890629
v 0.08950821 -0.00000000 0.01870825
v 0.09576622 0.01260786 -0.00000001
v 0.08923991 0.03696438 -0.00000001
v 0.03319159 0.05748951 0.04890630
v 0.07663205 0.05880184 -0.00000001
v 0.05880184 0.07663205 -0.00000001
v 0.04475410 0.07751638 0.01870825
v -0.00669731 0.01160007 0.07321696
v -0.02007651 0.03477353 0.06570269
v 0.03696438 0.08923991 -0.00000001
v 0.01260786 0.09576622 -0.00000001
v -0.03319159 0.05748951 0.04890630
v -0.01260786 0.09576622 -0.00000001
v -0.03696438 0.08923991 -0.00000001
v -0.04475411 0.07751638 0.01870823
v -0.01339461 0.00000000 0.07321696
v -0.04015301 0.00000000 0.06570269
v -0.05880184 0.07663205 -0.00000001
v -0.07663205 0.05880184 -0.00000001
v -0.06638319 0.00000000 0.04890629
v -0.08923991 0.03696438 -0.00000001
v -0.09576622 0.01260786 -0.00000001
v -0.08950821 0.00000000 0.01870823
v -0.00669730 -0.01160007 0.07321696
v -0.02007650 -0.03477353 0.06570269
v -0.09576622 -0.01260786 -0.00000001
v -0.08923991 -0.03696438 -0.00000001
v -0.03319159 -0.05748951 0.04890630
v -0.07663205 -0.05880184 -0.00000001
v -0.05880184 -0.07663205 -0.00000001
v -0.04475410 -0.07751638 0.01870825
v 0.00669731 -0.01160007 0.07321696
v 0.02007651 -0.03477353 0.06570269
v -0.03696438 -0.08923991 -0.00000001
v -0.01260786 -0.09576622 -0.00000001
v 0.03319159 -0.05748951 0.04890630
v 0.01260786 -0.09576622 -0.00000001
v 0.03696438 -0.08923991 -0.00000001
v 0.04475411 -0.07751638 0.01870823
v 0.05880184 -0.07663205 -0.00000001
v 0.07663205 -0.05880184 -0.00000001
v 0.08923991 -0.03696438 -0.00000001
v 0.09576622 -0.01260786 -0.00000001
# 217 vertices, 0 vertices normals
f 62 63 64
f 65 66 67
f 68 69 70
f 71 72 73
f 74 75 76
f 77 78 79
f 80 81 82
f 83 84 85
f 86 87 88
f 89 90 91
f 92 93 94
f 95 96 97
f 98 99 100
f 101 102 103
f 104 105 106
f 107 108 109
f 110 111 112
f 113 114 115
f 116 117 118
f 119 120 121
f 122 123 124
f 125 126 127
f 128 129 130
f 131 132 133
f 134 64 135
f 136 137 62
f 63 138 139
f 140 67 141
f 142 143 65
f 66 144 145
f 146 70 147
f 148 149 68
f 69 150 151
f 152 73 153
f 154 155 71
f 72 156 157
f 158 76 159
f 160 161 74
f 75 162 163
f 164 79 165
f 166 167 77
f 78 168 169
f 170 82 171
f 172 135 80
f 81 139 173
f 174 85 134
f 175 176 83
f 84 177 136
f 138 88 178
f 137 179 86
f 87 180 181
f 171 91 182
f 173 141 89
f 90 145 183
f 178 94 140
f 181 184 92
f 93 185 142
f 144 97 186
f 143 187 95
f 96 188 189
f 182 100 190
f 183 147 98
f 99 151 191
f 186 103 146
f 189 192 101
f 102 193 148
f 150 106 194
f 149 195 104
f 105 196 197
f 190 109 198
f 191 153 107
f 108 157 199
f 194 112 152
f 197 200 110
f 111 201 154
f 156 115 202
f 155 203 113
f 114 204 205
f 198 118 206
f 199 159 116
f 117 163 207
f 202 121 158
f 205 208 119
f 120 209 160
f 162 124 210
f 161 211 122
f 123 212 213
f 206 127 170
f 207 165 125
f 126 169 172
f 210 130 164
f 213 214 128
f 129 215 166
f 168 133 174
f 167 216 131
f 132 217 175
f 20 62 64
f 62 21 63
f 64 63 22
f 23 65 67
f 65 24 66
f 67 66 25
f 26 68 70
f 68 27 69
f 70 69 28
f 29 71 73
f 71 30 72
f 73 72 31
f 32 74 76
f 74 33 75
f 76 75 34
f 35 77 79
f 77 36 78
f 79 78 37
f 38 80 82
f 80 22 81
f 82 81 39
f 40 83 85
f 83 41 84
f 85 84 20
f 21 86 88
f 86 42 87
f 88 87 43
f 39 89 91
f 89 25 90
f 91 90 44
f 43 92 94
f 92 45 93
f 94 93 23
f 24 95 97
f 95 46 96
f 97 96 47
f 44 98 100
f 98 28 99
f 100 99 48
f 47 101 103
f 101 49 102
f 103 102 26
f 27 104 106
f 104 50 105
f 106 105 51
f 48 107 109
f 107 31 108
f 109 108 52
f 51 110 112
f 110 53 111
f 112 111 29
f 30 113 115
f 113 54 114
f 115 114 55
f 52 116 118
f 116 34 117
f 118 117 56
f 55 119 121
f 119 57 120
f 121 120 32
f 33 122 124
f 122 58 123
f 124 123 59
f 56 125 127
f 125 37 126
f 127 126 38
f 59 128 130
f 128 60 129
f 130 129 35
f 36 131 133
f 131 61 132
f 133 132 40
f 8 134 135
f 134 20 64
f 135 64 22
f 20 136 62
f 136 9 137
f 62 137 21
f 22 63 139
f 63 21 138
f 139 138 10
f 10 140 141
f 140 23 67
f 141 67 25
f 23 142 65
f 142 11 143
f 65 143 24
f 25 66 145
f 66 24 144
f 145 144 12
f 12 146 147
f 146 26 70
f 147 70 28
f 26 148 68
f 148 13 149
f 68 149 27
f 28 69 151
f 69 27 150
f 151 150 14
f 14 152 153
f 152 29 73
f 153 73 31
f 29 154 71
f 154 15 155
f 71 155 30
f 31 72 157
f 72 30 156
f 157 156 16
f 16 158 159
f 158 32 76
f 159 76 34
f 32 160 74
f 160 17 161
f 74 161 33
f 34 75 163
f 75 33 162
f 163 162 18
f 18 164 165
f 164 35 79
f 165 79 37
f 35 166 77
f 166 19 167
f 77 167 36
f 37 78 169
f 78 36 168
f 169 168 8
f 1 170 171
f 170 38 82
f 171 82 39
f 38 172 80
f 172 8 135
f 80 135 22
f 39 81 173
f 81 22 139
f 173 139 10
f 8 174 134
f 174 40 85
f 134 85 20
f 40 175 83
f 175 2 176
f 83 176 41
f 20 84 136
f 84 41 177
f 136 177 9
f 10 138 178
f 138 21 88
f 178 88 43
f 21 137 86
f 137 9 179
f 86 179 42
f 43 87 181
f 87 42 180
f 181 180 3
f 1 171 182
f 171 39 91
f 182 91 44
f 39 173 89
f 173 10 141
f 89 141 25
f 44 90 183
f 90 25 145
f 183 145 12
f 10 178 140
f 178 43 94
f 140 94 23
f 43 181 92
f 181 3 184
f 92 184 45
f 23 93 142
f 93 45 185
f 142 185 11
f 12 144 186
f 144 24 97
f 186 97 47
f 24 143 95
f 143 11 187
f 95 187 46
f 47 96 189
f 96 46 188
f 189 188 4
f 1 182 190
f 182 44 100
f 190 100 48
f 44 183 98
f 183 12 147
f 98 147 28
f 48 99 191
f 99 28 151
f 191 151 14
f 12 186 146
f 186 47 103
f 146 103 26
f 47 189 101
f 189 4 192
f 101 192 49
f 26 102 148
f 102 49 193
f 148 193 13
f 14 150 194
f 150 27 106
f 194 106 51
f 27 149 104
f 149 13 195
f 104 195 50
f 51 105 197
f 105 50 196
f 197 196 5
f 1 190 198
f 190 48 109
f 198 109 52
f 48 191 107
f 191 14 153
f 107 153 31
f 52 108 199
f 108 31 157
f 199 157 16
f 14 194 152
f 194 51 112
f 152 112 29
f 51 197 110
f 197 5 200
f 110 200 53
f 29 111 154
f 111 53 201
f 154 201 15
f 16 156 202
f 156 30 115
f 202 115 55
f 30 155 113
f 155 15 203
f 113 203 54
f 55 114 205
f 114 54 204
f 205 204 6
f 1 198 206
f 198 52 118
f 206 118 56
f 52 199 116
f 199 16 159
f 116 159 34
f 56 117 207
f 117 34 163
f 207 163 18
f 16 202 158
f 202 55 121
f 158 121 32
f 55 205 119
f 205 6 208
f 119 208 57
f 32 120 160
f 120 57 209
f 160 209 17
f 18 162 210
f 162 33 124
f 210 124 59
f 33 161 122
f 161 17 211
f 122 211 58
f 59 123 213
f 123 58 212
f 213 212 7
f 1 206 170
f 206 56 127
f 170 127 38
f 56 207 125
f 207 18 165
f 125 165 37
f 38 126 172
f 126 37 169
f 172 169 8
f 18 210 164
f 210 59 130
f 164 130 35
f 59 213 128
f 213 7 214
f 128 214 60
f 35 129 166
f 129 60 215
f 166 215 19
f 8 168 174
f 168 36 133
f 174 133 40
f 36 167 131
f 167 19 216
f 131 216 61
f 40 132 175
f 132 61 217
f 175 217 2
# 384 faces, 0 coords texture
# End of File

Before

Width:  |  Height:  |  Size: 634 KiB

After

Width:  |  Height:  |  Size: 634 KiB

+42
View File
@@ -0,0 +1,42 @@
<!-- Copyright 2024 DeepMind Technologies Limited
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mujoco model="Trilinear">
<include file="scene.xml"/>
<option solver="CG" tolerance="1e-6" timestep=".001" integrator="implicitfast"/>
<size memory="100M"/>
<visual>
<map stiffness="100"/>
</visual>
<worldbody>
<body>
<joint name="press" type="slide" axis="0 0 1" damping="500"/>
<geom type="box" size=".02 .2 .2" pos="0 0 .5"/>
</body>
<flexcomp type="mesh" file="bunny.obj" pos="0 0 .1" dim="2" euler="90 0 0"
radius=".001" rgba="0 .7 .7 1" mass=".05" name="softbody" dof="trilinear">
<elasticity young="1e3" poisson="0.1" damping="0.001"/>
<contact selfcollide="none" internal="false"/>
</flexcomp>
</worldbody>
<actuator>
<position name="press" joint="press" gear="-1 0 0 0 0 0" ctrlrange="-1 1" kp="1000"/>
</actuator>
</mujoco>
+48
View File
@@ -0,0 +1,48 @@
<!-- Copyright 2024 DeepMind Technologies Limited
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mujoco model="Trilinear">
<include file="scene.xml"/>
<option solver="CG" tolerance="1e-6" timestep=".001" integrator="implicitfast"/>
<size memory="100M"/>
<visual>
<map stiffness="100"/>
</visual>
<asset>
<texture name="texsponge" type="2d" file="sponge.png"/>
<material name="matsponge" texture="texsponge" specular="0.3"/>
</asset>
<worldbody>
<body>
<joint name="press" type="slide" axis="0 0 1" damping="500"/>
<geom type="box" size=".02 .2 .2" pos="0 0 .5"/>
</body>
<flexcomp type="mesh" file="bunny_with_uv.obj" pos="0 0 .1" dim="2" euler="90 0 0"
radius=".001" material="matsponge" mass=".05" name="softbody" dof="trilinear">
<elasticity young="1e3" poisson="0.1" damping="0.001"/>
<contact selfcollide="none" internal="false"/>
</flexcomp>
</worldbody>
<actuator>
<position name="press" joint="press" gear="-1 0 0 0 0 0" ctrlrange="-1 1" kp="1000"/>
</actuator>
</mujoco>

Some files were not shown because too many files have changed in this diff Show More