Merge branch 'google-deepmind:main' into usd-integration
This commit is contained in:
+51
-18
@@ -219,7 +219,7 @@ jobs:
|
||||
working-directory: python/dist
|
||||
run: >
|
||||
source ${{ matrix.tmpdir }}/venv/bin/activate &&
|
||||
pip install --no-deps --no-index mujoco-*.whl
|
||||
pip install --no-index mujoco-*.whl
|
||||
- name: Test Python bindings
|
||||
if: ${{ runner.os != 'Windows' }}
|
||||
shell: bash
|
||||
@@ -228,23 +228,56 @@ jobs:
|
||||
run: >
|
||||
source ${{ matrix.tmpdir }}/venv/bin/activate &&
|
||||
pytest -v --pyargs mujoco
|
||||
- name: Package MJX
|
||||
if: ${{ runner.os != 'Windows' }}
|
||||
shell: bash
|
||||
working-directory: mjx
|
||||
run:
|
||||
source ${{ matrix.tmpdir }}/venv/bin/activate &&
|
||||
python -m build .
|
||||
- name: Install MJX
|
||||
if: ${{ runner.os != 'Windows' }}
|
||||
shell: bash
|
||||
working-directory: mjx
|
||||
run:
|
||||
source ${{ matrix.tmpdir }}/venv/bin/activate &&
|
||||
pip install --require-hashes -r requirements.txt &&
|
||||
pip install --no-index dist/mujoco_mjx-*.whl
|
||||
- name: Test MJX
|
||||
if: ${{ runner.os != 'Windows' }}
|
||||
shell: bash
|
||||
run:
|
||||
source ${{ matrix.tmpdir }}/venv/bin/activate &&
|
||||
pytest -n auto -v --pyargs mujoco.mjx
|
||||
- name: Notify team chat
|
||||
shell: bash
|
||||
env:
|
||||
GCHAT_API: ${{ secrets.GCHAT_API }}
|
||||
JOB_URL: "${{ github.server_url }}/${{ github.repository }}/actions/runs/\
|
||||
${{ github.run_id }}"
|
||||
if: ${{ failure() && github.event_name == 'push' && env.GCHAT_API != '' }}
|
||||
run: >
|
||||
curl
|
||||
"$GCHAT_API&threadKey=$GITHUB_SHA&messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD"
|
||||
-X POST
|
||||
-H "Content-Type: application/json"
|
||||
-d "{
|
||||
'text': '<$JOB_URL|*FAILURE*>:
|
||||
job \`${{ matrix.os }}${{ matrix.additional_label }}\`
|
||||
commit \`$(echo $GITHUB_SHA | head -c6)\`\n
|
||||
\`\`\`Author: ${{ github.event.head_commit.author.name }}
|
||||
<${{ github.event.head_commit.author.email }}>
|
||||
\n\n${{ github.event.head_commit.message }}\`\`\`
|
||||
'}"
|
||||
GCHAT_API_URL: ${{ secrets.GCHAT_API }}
|
||||
JOB_URL: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
CHATMSG_AUTHOR_NAME: ${{ github.event.head_commit.author.name }}
|
||||
CHATMSG_AUTHOR_EMAIL: ${{ github.event.head_commit.author.email }}
|
||||
CHATMSG_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
||||
CHATMSG_JOB_ID: ${{ matrix.os }}${{ matrix.additional_label }}
|
||||
if: ${{ failure() && github.event_name == 'push' && env.GCHAT_API_URL != '' }}
|
||||
run: |
|
||||
CHATMSG="$(cat <<-'EOF' | python3
|
||||
import json
|
||||
import os
|
||||
env = lambda x: os.getenv(x, '')
|
||||
data = dict(
|
||||
result=env('JOB_URL'),
|
||||
job=env('CHATMSG_JOB_ID'),
|
||||
commit=env('GITHUB_SHA')[:6],
|
||||
name=env('CHATMSG_AUTHOR_NAME').replace('```', ''),
|
||||
email=env('CHATMSG_AUTHOR_EMAIL'),
|
||||
msg=env('CHATMSG_COMMIT_MESSAGE').replace('```', '')
|
||||
)
|
||||
text = '<{result}|*FAILURE*>: job `{job}` commit `{commit}`\n```Author: {name}<{email}>\n\n{msg}```'.format(**data)
|
||||
print(json.dumps({'text' : text}))
|
||||
EOF
|
||||
)" &&
|
||||
|
||||
curl "$GCHAT_API_URL&threadKey=$GITHUB_SHA&messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD" \
|
||||
-X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
--data-raw "${CHATMSG}"
|
||||
|
||||
@@ -55,6 +55,10 @@ running on Google Colab:
|
||||
- For a more advanced example, see the LQR tutorial which creates an LQR
|
||||
controller to balance a humanoid on one leg using MuJoCo's dynamics
|
||||
derivatives: [](https://colab.research.google.com/github/google-deepmind/mujoco/blob/main/python/LQR.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:
|
||||
[](https://colab.research.google.com/github/google-deepmind/mujoco/blob/main/mjx/tutorial.ipynb)
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
@@ -498,7 +498,7 @@ shown in the table below. Their names are in the format ``mjKEY_XXX``. They corr
|
||||
- Maximum number of UI sections.
|
||||
Defined in `mjui.h <https://github.com/google-deepmind/mujoco/blob/main/include/mujoco/mjui.h>`_.
|
||||
* - ``mjMAXUIITEM``
|
||||
- 80
|
||||
- 200
|
||||
- Maximum number of items per UI section.
|
||||
Defined in `mjui.h <https://github.com/google-deepmind/mujoco/blob/main/include/mujoco/mjui.h>`_.
|
||||
* - ``mjMAXUITEXT``
|
||||
|
||||
@@ -34,6 +34,8 @@ MuJoCo defines a large number of types:
|
||||
- Structs used by :ref:`engine plugins<tyPluginStructure>`.
|
||||
|
||||
- Several :ref:`tyFunction` for user-defined callbacks.
|
||||
- :ref:`tyNotes` regarding specific data structures that require detailed description.
|
||||
|
||||
|
||||
|
||||
.. _tyPrimitive:
|
||||
@@ -909,6 +911,7 @@ This structure contains the custom OpenGL rendering context, with the ids of all
|
||||
User Interface
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
For a high-level description of the UI framework, see :ref:`UI`.
|
||||
The names of these struct types are prefixed with ``mjui``, except for the main :ref:`mjUI` struct itself.
|
||||
|
||||
|
||||
@@ -917,7 +920,9 @@ The names of these struct types are prefixed with ``mjui``, except for the main
|
||||
mjuiState
|
||||
~~~~~~~~~
|
||||
|
||||
This structure contains the keyboard and mouse state used by the UI framework.
|
||||
This C struct represents the global state of the window, keyboard and mouse, input event descriptors, and all window
|
||||
rectangles (including the visible UI rectangles). There is only one ``mjuiState`` per application, even if there are
|
||||
multiple UIs. This struct would normally be defined as a global variable.
|
||||
|
||||
.. mujoco-include:: mjuiState
|
||||
|
||||
@@ -967,7 +972,9 @@ This structure defines one section of the UI.
|
||||
mjuiDef
|
||||
~~~~~~~
|
||||
|
||||
This structure defines one entry in the definition table used for simplified UI construction.
|
||||
This structure defines one entry in the definition table used for simplified UI construction. It contains everything
|
||||
needed to define one UI item. Some translation is performed by the helper functions, so that multiple mjuiDefs can be
|
||||
defined as a static table.
|
||||
|
||||
.. mujoco-include:: mjuiDef
|
||||
|
||||
@@ -977,7 +984,12 @@ This structure defines one entry in the definition table used for simplified UI
|
||||
mjUI
|
||||
~~~~
|
||||
|
||||
This structure defines the entire UI.
|
||||
This C struct represents an entire UI. The same application could have multiple UIs, for example on the left and the
|
||||
right of the window. This would normally be defined as a global variable. As explained earlier, it contains static
|
||||
allocation for a maximum number of supported UI sections (:ref:`mjuiSection<mjuiSection>`) each with a maximum number
|
||||
of supported items (:ref:`mjuiItem<mjuiItem>`). It also contains the color and spacing themes, enable/disable
|
||||
callback, virtual window descriptor, text edit state, mouse focus. Some of these fields are set only once when the UI
|
||||
is initialized, others change at runtime.
|
||||
|
||||
.. mujoco-include:: mjUI
|
||||
|
||||
|
||||
@@ -392,7 +392,9 @@ mj_objectAcceleration
|
||||
|
||||
.. mujoco-include:: mj_objectAcceleration
|
||||
|
||||
Compute object 6D acceleration (rot:lin) in object-centered frame, world/local orientation.
|
||||
Compute object 6D acceleration (rot:lin) in object-centered frame, world/local orientation. If acceleration or force
|
||||
sensors are not present in the model, :ref:`mj_rnePostConstraint` must be manually called in order to calculate
|
||||
:ref:`mjData`.cacc -- the total body acceleration, including contributions from the constraint solver.
|
||||
|
||||
.. _mj_contactForce:
|
||||
|
||||
@@ -803,7 +805,7 @@ mj_passive
|
||||
|
||||
.. mujoco-include:: mj_passive
|
||||
|
||||
Compute qfrc_passive from spring-dampers, viscosity and density.
|
||||
Compute qfrc_passive from spring-dampers, gravity compensation and fluid forces.
|
||||
|
||||
.. _mj_subtreeVel:
|
||||
|
||||
@@ -812,7 +814,10 @@ mj_subtreeVel
|
||||
|
||||
.. mujoco-include:: mj_subtreeVel
|
||||
|
||||
subtree linear velocity and angular momentum
|
||||
Sub-tree linear velocity and angular momentum: compute ``subtree_linvel``, ``subtree_angmom``.
|
||||
This function is triggered automatically if the subtree :ref:`velocity<sensor-subtreelinvel>` or
|
||||
:ref:`momentum<sensor-subtreeangmom>` sensors are present in the model.
|
||||
It is also triggered for :ref:`user sensors<sensor-user>` of :ref:`stage<sensor-user-needstage>` "vel".
|
||||
|
||||
.. _mj_rne:
|
||||
|
||||
@@ -821,7 +826,8 @@ mj_rne
|
||||
|
||||
.. mujoco-include:: mj_rne
|
||||
|
||||
RNE: compute M(qpos)*qacc + C(qpos,qvel); flg_acc=0 removes inertial term.
|
||||
Recursive Newton Euler: compute :math:`M(q) \ddot q + C(q,\dot q)`. ``flg_acc=0`` removes the inertial term (i.e.
|
||||
assumes :math:`\ddot q = 0`).
|
||||
|
||||
.. _mj_rnePostConstraint:
|
||||
|
||||
@@ -830,7 +836,21 @@ mj_rnePostConstraint
|
||||
|
||||
.. mujoco-include:: mj_rnePostConstraint
|
||||
|
||||
RNE with complete data: compute cacc, cfrc_ext, cfrc_int.
|
||||
Recursive Newton Euler with final computed forces and accelerations.
|
||||
Computes three body-level ``nv x 6`` arrays, all defined in the subtreecom-based
|
||||
:ref:`c-frame<tyNotesCom>` and arranged in ``[rotation(3), translation(3)]`` order.
|
||||
|
||||
- ``cacc``: Body acceleration, required for :ref:`mj_objectAcceleration`.
|
||||
- ``cfrc_int``: Interaction force with the parent body.
|
||||
- ``cfrc_ext``: External force acting on the body.
|
||||
|
||||
This function is triggered automatically if the following sensors are present in the model:
|
||||
:ref:`accelerometer<sensor-accelerometer>`, :ref:`force<sensor-force>`, :ref:`torque<sensor-torque>`,
|
||||
:ref:`framelinacc<sensor-framelinacc>`, :ref:`frameangacc<sensor-frameangacc>`.
|
||||
It is also triggered for :ref:`user sensors<sensor-user>` of :ref:`stage<sensor-user-needstage>` "acc".
|
||||
|
||||
The computed force arrays ``cfrc_int`` and ``cfrc_ext`` currently suffer from a know bug, they do not take into account
|
||||
the effect of spatial tendons, see :github:issue:`832`.
|
||||
|
||||
.. _mj_collision:
|
||||
|
||||
@@ -884,8 +904,8 @@ mj_constraintUpdate
|
||||
|
||||
.. mujoco-include:: mj_constraintUpdate
|
||||
|
||||
Compute efc_state, efc_force, qfrc_constraint, and (optionally) cone Hessians. If cost is not NULL, set \*cost = s(jar)
|
||||
where jar = Jac*qacc-aref.
|
||||
Compute ``efc_state``, ``efc_force``, ``qfrc_constraint``, and (optionally) cone Hessians.
|
||||
If ``cost`` is not ``NULL``, set ``*cost = s(jar)`` where ``jar = Jac*qacc - aref``.
|
||||
|
||||
.. _Raycollisions:
|
||||
|
||||
@@ -1945,6 +1965,8 @@ Find first rectangle containing mouse, -1: not found.
|
||||
UI framework
|
||||
^^^^^^^^^^^^
|
||||
|
||||
For a high-level description of the UI framework, see :ref:`UI`.
|
||||
|
||||
.. _mjui_themeSpacing:
|
||||
|
||||
mjui_themeSpacing
|
||||
@@ -1970,7 +1992,12 @@ mjui_add
|
||||
|
||||
.. mujoco-include:: mjui_add
|
||||
|
||||
Add definitions to UI.
|
||||
This is the helper function used to construct a UI. The second argument points to an array of :ref:`mjuiDef` structs,
|
||||
each corresponding to one item. The last (unused) item has its type set to -1, to mark termination. The items are added
|
||||
after the end of the last used section. There is also another version of this function
|
||||
(:ref:`mjui_addToSection<mjui_addToSection>`) which adds items to a specified section instead of adding them at the end
|
||||
of the UI. Keep in mind that there is a maximum preallocated number of sections and items per section, given by
|
||||
:ref:`mjMAXUISECT<glNumeric>` and :ref:`mjMAXUIITEM<glNumeric>`. Exceeding these maxima results in low-level errors.
|
||||
|
||||
.. _mjui_addToSection:
|
||||
|
||||
@@ -1997,7 +2024,12 @@ mjui_update
|
||||
|
||||
.. mujoco-include:: mjui_update
|
||||
|
||||
Update specific section/item; -1: update all.
|
||||
This is the main UI update function. It needs to be called whenever the user data (pointed to by the item data pointers)
|
||||
changes, or when the UI state itself changes. It is normally called by a higher-level function implemented by the user
|
||||
(``UiModify`` in :ref:`simulate.cc <saSimulate>`) which also recomputes the layout of all rectangles and associated
|
||||
auxiliary buffers. The function updates the pixels in the offscreen OpenGL buffer. To perform minimal updates, the user
|
||||
specifies the section and the item that was modified. A value of -1 means all items and/or sections need to be updated
|
||||
(which is needed following major changes.)
|
||||
|
||||
.. _mjui_event:
|
||||
|
||||
@@ -2006,7 +2038,10 @@ mjui_event
|
||||
|
||||
.. mujoco-include:: mjui_event
|
||||
|
||||
Handle UI event, return pointer to changed item, NULL if no change.
|
||||
This function is the low-level event handler. It makes the necessary changes in the UI and returns a pointer to the item
|
||||
that received the event (or ``NULL`` if no valid event was recorded). This is normally called within the event handler
|
||||
implemented by the user (``UiEvent`` in :ref:`simulate.cc <saSimulate>`), and then some action is taken by user code
|
||||
depending on which UI item was modified and what the state of that item is after the event is handled.
|
||||
|
||||
.. _mjui_render:
|
||||
|
||||
@@ -2015,7 +2050,9 @@ mjui_render
|
||||
|
||||
.. mujoco-include:: mjui_render
|
||||
|
||||
Copy UI image to current buffer.
|
||||
This function is called in the screen refresh loop. It copies the offscreen OpenGL buffer to the window framebuffer. If
|
||||
there are multiple UIs in the application, it should be called once for each UI. Thus ``mjui_render`` is called all the
|
||||
time, while :ref:`mjui_update` is called only when changes in the UI take place.
|
||||
|
||||
.. _Errorandmemory:
|
||||
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
This file contains each section text along with function doc overrides. By default the docs use the function doc
|
||||
pulled from the header files.
|
||||
|
||||
.. _Activation:
|
||||
|
||||
The functions in this section are maintained for backward compatibility with the now-removed activation mechanism.
|
||||
|
||||
.. _Virtualfilesystem:
|
||||
|
||||
Virtual file system (VFS) enables the user to load all necessary files in memory, including MJB binary model files, XML
|
||||
@@ -90,10 +86,40 @@ Solve linear system :math:`M x = y` using factorization: :math:`x = (L^T D L)^{-
|
||||
|
||||
Half of linear solve: :math:`x = \sqrt{D^{-1}} (L^T)^{-1} y`
|
||||
|
||||
.. _mj_subtreeVel:
|
||||
|
||||
Sub-tree linear velocity and angular momentum: compute ``subtree_linvel``, ``subtree_angmom``.
|
||||
This function is triggered automatically if the subtree :ref:`velocity<sensor-subtreelinvel>` or
|
||||
:ref:`momentum<sensor-subtreeangmom>` sensors are present in the model.
|
||||
It is also triggered for :ref:`user sensors<sensor-user>` of :ref:`stage<sensor-user-needstage>` "vel".
|
||||
|
||||
.. _mj_rne:
|
||||
|
||||
Recursive Newton Euler: compute :math:`M(q) \ddot q + C(q,\dot q)`. ``flg_acc=0`` removes the inertial term (i.e.
|
||||
assumes :math:`\ddot q = 0`).
|
||||
|
||||
.. _mj_rnePostConstraint:
|
||||
|
||||
Recursive Newton Euler with final computed forces and accelerations.
|
||||
Computes three body-level ``nv x 6`` arrays, all defined in the subtreecom-based
|
||||
:ref:`c-frame<tyNotesCom>` and arranged in ``[rotation(3), translation(3)]`` order.
|
||||
|
||||
- ``cacc``: Body acceleration, required for :ref:`mj_objectAcceleration`.
|
||||
- ``cfrc_int``: Interaction force with the parent body.
|
||||
- ``cfrc_ext``: External force acting on the body.
|
||||
|
||||
This function is triggered automatically if the following sensors are present in the model:
|
||||
:ref:`accelerometer<sensor-accelerometer>`, :ref:`force<sensor-force>`, :ref:`torque<sensor-torque>`,
|
||||
:ref:`framelinacc<sensor-framelinacc>`, :ref:`frameangacc<sensor-frameangacc>`.
|
||||
It is also triggered for :ref:`user sensors<sensor-user>` of :ref:`stage<sensor-user-needstage>` "acc".
|
||||
|
||||
The computed force arrays ``cfrc_int`` and ``cfrc_ext`` currently suffer from a know bug, they do not take into account
|
||||
the effect of spatial tendons, see :github:issue:`832`.
|
||||
|
||||
.. _mj_constraintUpdate:
|
||||
|
||||
Compute efc_state, efc_force, qfrc_constraint, and (optionally) cone Hessians. If cost is not NULL, set \*cost = s(jar)
|
||||
where jar = Jac*qacc-aref.
|
||||
Compute ``efc_state``, ``efc_force``, ``qfrc_constraint``, and (optionally) cone Hessians.
|
||||
If ``cost`` is not ``NULL``, set ``*cost = s(jar)`` where ``jar = Jac*qacc - aref``.
|
||||
|
||||
.. _Support:
|
||||
|
||||
@@ -154,6 +180,12 @@ This function can be used to apply a Cartesian force and torque to a point on a
|
||||
mjData.qfrc_applied of all applied forces. Note that the function requires a pointer to this vector, because sometimes
|
||||
we want to add the result to a different vector.
|
||||
|
||||
.. _mj_objectAcceleration:
|
||||
|
||||
Compute object 6D acceleration (rot:lin) in object-centered frame, world/local orientation. If acceleration or force
|
||||
sensors are not present in the model, :ref:`mj_rnePostConstraint` must be manually called in order to calculate
|
||||
mjData.cacc -- the total body acceleration, including contributions from the constraint solver.
|
||||
|
||||
.. _mj_differentiatePos:
|
||||
|
||||
This function subtracts two vectors in the format of qpos (and divides the result by dt), while respecting the
|
||||
@@ -218,6 +250,43 @@ of how to use these functions.
|
||||
|
||||
.. _UIframework:
|
||||
|
||||
For a high-level description of the UI framework, see :ref:`UI`.
|
||||
|
||||
.. _mjui_add:
|
||||
|
||||
This is the helper function used to construct a UI. The second argument points to an array of :ref:`mjuiDef` structs,
|
||||
each corresponding to one item. The last (unused) item has its type set to -1, to mark termination. The items are added
|
||||
after the end of the last used section. There is also another version of this function
|
||||
(:ref:`mjui_addToSection<mjui_addToSection>`) which adds items to a specified section instead of adding them at the end
|
||||
of the UI. Keep in mind that there is a maximum preallocated number of sections and items per section, given by
|
||||
:ref:`mjMAXUISECT<glNumeric>` and :ref:`mjMAXUIITEM<glNumeric>`. Exceeding these maxima results in low-level errors.
|
||||
|
||||
.. _mjui_update:
|
||||
|
||||
This is the main UI update function. It needs to be called whenever the user data (pointed to by the item data pointers)
|
||||
changes, or when the UI state itself changes. It is normally called by a higher-level function implemented by the user
|
||||
(``UiModify`` in :ref:`simulate.cc <saSimulate>`) which also recomputes the layout of all rectangles and associated
|
||||
auxiliary buffers. The function updates the pixels in the offscreen OpenGL buffer. To perform minimal updates, the user
|
||||
specifies the section and the item that was modified. A value of -1 means all items and/or sections need to be updated
|
||||
(which is needed following major changes.)
|
||||
|
||||
.. _mjui_event:
|
||||
|
||||
This function is the low-level event handler. It makes the necessary changes in the UI and returns a pointer to the item
|
||||
that received the event (or ``NULL`` if no valid event was recorded). This is normally called within the event handler
|
||||
implemented by the user (``UiEvent`` in :ref:`simulate.cc <saSimulate>`), and then some action is taken by user code
|
||||
depending on which UI item was modified and what the state of that item is after the event is handled.
|
||||
|
||||
|
||||
.. _mjui_render:
|
||||
|
||||
This function is called in the screen refresh loop. It copies the offscreen OpenGL buffer to the window framebuffer. If
|
||||
there are multiple UIs in the application, it should be called once for each UI. Thus ``mjui_render`` is called all the
|
||||
time, while :ref:`mjui_update` is called only when changes in the UI take place.
|
||||
|
||||
|
||||
|
||||
|
||||
.. _Errorandmemory:
|
||||
|
||||
.. _Standardmath:
|
||||
|
||||
+94
-64
@@ -920,6 +920,11 @@ documented below.
|
||||
The radius of the capsules used to render slider-crank mechanisms. The second part of the mechanism is automatically
|
||||
scaled relative to this setting.
|
||||
|
||||
.. _visual-scale-frustum:
|
||||
|
||||
:at:`frustum`: :at-val:`real, "10"`
|
||||
The distance of the zfar plane from the camera pinhole for rendering the frustum.
|
||||
|
||||
|
||||
.. _visual-rgba:
|
||||
|
||||
@@ -1046,6 +1051,11 @@ disables the rendering of the corresponding object.
|
||||
Color used to render the crank of slide-crank mechanisms, in model configurations where the specified rod length
|
||||
cannot be maintained, i.e., it is "broken".
|
||||
|
||||
.. _visual-rgba-frustum:
|
||||
|
||||
:at:`frustum`: :at-val:`real(4), "1 1 0 0.2"`
|
||||
Color used to render the camera frustum.
|
||||
|
||||
|
||||
.. _asset:
|
||||
|
||||
@@ -1072,9 +1082,9 @@ file.
|
||||
color in GL_MODULATE mode. The texture data can be loaded from PNG files, with provisions for loading cube and skybox
|
||||
textures. Alternatively the data can be generated by the compiler as a procedural texture. Because different texture
|
||||
types require different parameters, only a subset of the attributes below are used for any given texture.
|
||||
| MuJoCo 2.0 introduced a second file format for loading textures, in addition to PNG. If the file name extension is
|
||||
| A second file format is supported for loading textures, in addition to PNG. If the file name extension is
|
||||
different from .png or .PNG, or if the ``content_type`` attribute is set to ``image/vnd.mujoco.texture``, then MuJoCo
|
||||
assumes that the texture is in the new format. This is a custom binary file format, containing the following data:
|
||||
assumes that the texture is in this format. This is a custom binary file format, containing the following data:
|
||||
|
||||
.. code:: Text
|
||||
|
||||
@@ -1387,7 +1397,7 @@ attribute of the :ref:`compiler <compiler>` element which controls the automatic
|
||||
appearance (including texture mapping) is controlled by the :at:`material` and :at:`rgba` attributes of the referencing
|
||||
geom, similarly to height fields.
|
||||
|
||||
Starting with MuJoCo 2.0, meshes can have explicit texture coordinates instead of relying on the automated texture
|
||||
Meshes can have explicit texture coordinates instead of relying on the automated texture
|
||||
mapping mechanism. When provided, these explicit coordinates have priority. Note that texture coordinates can be
|
||||
specified with OBJ files and MSH files, as well as explicitly in the XML with the :at:`texcoord` attribute, but not via
|
||||
STL files. These mechanism cannot be mixed. So if you have an STL mesh, the only way to add texture coordinates to it is
|
||||
@@ -1421,17 +1431,17 @@ specified as OBJ or XML and an error message is returned.
|
||||
The size of the mesh is determined by the 3D coordinates of the vertex data in the mesh file, multiplied by the
|
||||
components of the :at:`scale` attribute below. Scaling is applied separately for each coordinate axis. Note that
|
||||
negative scaling values can be used to flip the mesh; this is a legitimate operation. The size parameters of the
|
||||
referening geoms are ignored, similarly to height fields. As of MuJoCo 2.0 we also provide a mechanism to translate and
|
||||
rotate the 3D coordinates, using the attributes refpos and refquat.
|
||||
referening geoms are ignored, similarly to height fields. We also provide a mechanism to translate and
|
||||
rotate the 3D coordinates, using the attributes :ref:`refpos<asset-mesh-refpos>` and :ref:`refquat<asset-mesh-refquat>`.
|
||||
|
||||
Another new feature in MuJoCo 2.0 is that a mesh can be defined without faces (a point cloud essentially). In that case
|
||||
A mesh can also be defined without faces (a point cloud essentially). In that case
|
||||
the convex hull is constructed automatically, even if the compiler attribute convexhull is false. This makes it easy to
|
||||
construct simple shapes directly in the XML. For example, a pyramid can be created as:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<asset>
|
||||
<mesh name="pyramid" vertex="0 0 0 1 0 0 0 1 0 0 0 1"/>
|
||||
<mesh name="tetrahedron" vertex="0 0 0 1 0 0 0 1 0 0 0 1"/>
|
||||
</asset>
|
||||
|
||||
Positioning and orienting is complicated by the fact that vertex data are often designed relative to coordinate frames
|
||||
@@ -1869,6 +1879,12 @@ adjust it properly through the XML.
|
||||
:at:`sdf_initpoints`: :at-val:`int, "40"`
|
||||
Number of starting points used for fining contacts with Signed Distance Field collisions.
|
||||
|
||||
.. _option-actuatorgroupdisable:
|
||||
|
||||
:at:`actuatorgroupdisable`: :at-val:`int(30), ""`
|
||||
List of actuator groups to disable. Actuators whose :ref:`group<actuator-general-group>` is in this list will produce
|
||||
no force. If they are stateful, their activation states will not be integrated. Internally this list is
|
||||
implemented as an integer bitfield, so values must be in the range ``0 <= group <= 30``.
|
||||
|
||||
.. _option-flag:
|
||||
|
||||
@@ -2161,10 +2177,10 @@ This element creates a joint. As explained in :ref:`Kinematic tree <CTree>`, a j
|
||||
between the body where it is defined and the body's parent. If multiple joints are defined in the same body, the
|
||||
corresponding spatial transformations (of the body frame relative to the parent frame) are applied in order. If no
|
||||
joints are defined, the body is welded to its parent. Joints cannot be defined in the world body. At runtime the
|
||||
positions and orientations of all joints defined in the model are stored in the vector mjData.qpos, in the order in
|
||||
which the appear in the kinematic tree. The linear and angular velocities are stored in the vector mjData.qvel. These
|
||||
two vectors have different dimensionality when free or ball joints are used, because such joints represent rotations as
|
||||
unit quaternions.
|
||||
positions and orientations of all joints defined in the model are stored in the vector ``mjData.qpos``, in the order in
|
||||
which the appear in the kinematic tree. The linear and angular velocities are stored in the vector ``mjData.qvel``.
|
||||
These two vectors have different dimensionality when free or ball joints are used, because such joints represent
|
||||
rotations as unit quaternions.
|
||||
|
||||
.. _body-joint-name:
|
||||
|
||||
@@ -2211,7 +2227,8 @@ unit quaternions.
|
||||
.. _body-joint-pos:
|
||||
|
||||
:at:`pos`: :at-val:`real(3), "0 0 0"`
|
||||
Position of the joint, specified in the frame of the parent body. For free joints this attribute is ignored.
|
||||
Position of the joint, specified in the frame of the body where the joint is defined.
|
||||
For free joints this attribute is ignored.
|
||||
|
||||
.. _body-joint-axis:
|
||||
|
||||
@@ -2654,7 +2671,7 @@ helps clarify the role of bodies and geoms in MuJoCo.
|
||||
.. _body-geom-pos:
|
||||
|
||||
:at:`pos`: :at-val:`real(3), "0 0 0"`
|
||||
Position of the geom, specified in the frame of the parent body.
|
||||
Position of the geom, specified in the frame of the body where the geom is defined.
|
||||
|
||||
.. _body-geom-quat:
|
||||
|
||||
@@ -2698,8 +2715,8 @@ helps clarify the role of bodies and geoms in MuJoCo.
|
||||
|
||||
:at:`fluidshape`: :at-val:`[none, ellipsoid], "none"`
|
||||
"ellipsoid" activates the geom-level fluid interaction model based on an ellipsoidal approximation of the geom
|
||||
shape. When active, the model based on :ref:`body inertia sizes <flInertia>` is disabled for the parent body.
|
||||
See section on :ref:`ellipsoid-based<flEllipsoid>` fluid interaction model for details.
|
||||
shape. When active, the model based on :ref:`body inertia sizes <flInertia>` is disabled for the body in which the
|
||||
geom is defined. See section on :ref:`ellipsoid-based<flEllipsoid>` fluid interaction model for details.
|
||||
|
||||
.. _body-geom-fluidcoef:
|
||||
|
||||
@@ -2879,15 +2896,15 @@ 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 parent (i.e., the body where the camera is defined). "track" means that the camera
|
||||
position is at a constant offset from the parent 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 above the body no matter how the body translates and rotates. "trackcom" is similar to "track"
|
||||
but the constant spatial offset is defined relative to the center of mass of the kinematic subtree starting at the
|
||||
parent body. This can be used to keep an entire mechanism in view. Note that the subtree center of mass for the world
|
||||
body is the center of mass of the entire model. So if a camera is defined in the world body in mode "trackcom", it
|
||||
will track the entire model. "targetbody" means that the camera position is fixed in the parent body, while the
|
||||
below are fixed relative to the 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
|
||||
above the body no matter how the body translates and rotates. "trackcom" is similar to "track" but the constant
|
||||
spatial offset is defined relative to the center of mass of the kinematic subtree starting at the body in which the
|
||||
camera is defined. This can be used to keep an entire mechanism in view. Note that the subtree center of mass for the
|
||||
world body is the center of mass of the entire model. So if a camera is defined in the world body in mode "trackcom",
|
||||
it will track the entire model. "targetbody" means that the camera position is fixed in the body frame, while the
|
||||
camera orientation is adjusted so that it always points towards the targeted body (which is specified with the target
|
||||
attribute below). This can be used for example to model an eye that fixates a moving object; the object will be the
|
||||
target, and the camera/eye will be defined in the body corresponding to the head. "targetbodycom" is the same as
|
||||
@@ -2965,6 +2982,8 @@ and the +Y axis points up. Thus the frame position and orientation are the key a
|
||||
|
||||
:at:`quat`, :at:`axisangle`, :at:`xyaxes`, :at:`zaxis`, :at:`euler`
|
||||
Orientation of the camera frame. See :ref:`COrientation`.
|
||||
Note that specifically for cameras, the :at:`xyaxes` attribute is semantically convenient as the X and Y axes
|
||||
correspond to the directions "right" and "up" in pixel space, respectively.
|
||||
|
||||
.. _body-camera-user:
|
||||
|
||||
@@ -3636,11 +3655,6 @@ saving the XML:
|
||||
</mujoco>
|
||||
|
||||
|
||||
.. _body-flexcomp-class:
|
||||
|
||||
:at:`class`: :at-val:`string, optional`
|
||||
Defaults class for setting unspecified attributes.
|
||||
|
||||
.. _body-flexcomp-name:
|
||||
|
||||
:at:`name`: :at-val:`string, required`
|
||||
@@ -3689,12 +3703,13 @@ saving the XML:
|
||||
**gmsh** is similar to mesh, but it loads a `GMSH file <https://gmsh.info//doc/texinfo/gmsh.html#MSH-file-format>`__
|
||||
in format 4.1 (ascii or binary). The file extension can be anything; the parser recognizes the format by examining
|
||||
the file header. This is a very rich file format, allowing all kinds of elements with different dimensionality and
|
||||
topology. MuJoCo only supports GMSH element types 1, 2, 4 which happen to correspond to our 1D, 2D and 3D flexes.
|
||||
Only the Nodes and Elements sections of the GMHS file are processed, and used to populate the point and element data
|
||||
of the flexcomp. The parser will generate an error if the GMSH file contains meshes that are not supported by MuJoCo.
|
||||
:at:`dim` is automatically set to the dimensionality specified in the GMSH file. Presently this is the only mechanism
|
||||
to load a large tetrahedral mesh in MuJoCo and generate a corresponding soft entity. If such a mesh is available in a
|
||||
different file format, use the freely available `GMSH software <https://gmsh.info/>`__ to convert it to GMSH 4.1.
|
||||
topology. MuJoCo only supports GMSH element types 1, 2, 4 which happen to correspond to our 1D, 2D and 3D flexes and
|
||||
assumes that the nodes are specified in a single block. Only the Nodes and Elements sections of the GMHS file are
|
||||
processed, and used to populate the point and element data of the flexcomp. The parser will generate an error if the
|
||||
GMSH file contains meshes that are not supported by MuJoCo. :at:`dim` is automatically set to the dimensionality
|
||||
specified in the GMSH file. Presently this is the only mechanism to load a large tetrahedral mesh in MuJoCo and
|
||||
generate a corresponding soft entity. If such a mesh is available in a different file format, use the freely
|
||||
available `GMSH software <https://gmsh.info/>`__ to convert it to GMSH 4.1.
|
||||
|
||||
**direct** allows the user to specify the point and element data of the flexcomp directly in the XML. Note that
|
||||
flexcomp will still generate moving bodies automatically, as well as automate other settings; so it still provides
|
||||
@@ -3715,21 +3730,18 @@ saving the XML:
|
||||
.. _body-flexcomp-point:
|
||||
|
||||
:at:`point`: :at-val:`real(3*npoint), optional`
|
||||
|
||||
The 3D coordinates of the points. This attribute is only used with type **direct**. All other flexcomp types generate
|
||||
their own points. The points are used to construct bodies and vertices as explained earlier.
|
||||
|
||||
.. _body-flexcomp-element:
|
||||
|
||||
:at:`element`: :at-val:`int((dim+1)*npoint), optional`
|
||||
|
||||
The zero-based point ids forming each flex elements. This attribute is only used with type **direct**. All other
|
||||
flexcomp types generate their own elements. This data is passed through to the automatically-generated flex.
|
||||
|
||||
.. _body-flexcomp-texcoord:
|
||||
|
||||
: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
|
||||
@@ -3779,7 +3791,6 @@ saving the XML:
|
||||
.. _body-flexcomp-euler:
|
||||
|
||||
:at:`axisangle`, :at:`xyaxes`, :at:`zaxis`, :at:`euler`
|
||||
|
||||
Alternative specification of rotation, that can be used instead of :at:`quat`.
|
||||
|
||||
.. _body-flexcomp-scale:
|
||||
@@ -3795,9 +3806,8 @@ saving the XML:
|
||||
.. _body-flexcomp-flatskin:
|
||||
|
||||
:at:`radius`, :at:`material`, :at:`rgba`, :at:`group`, :at:`flatskin`
|
||||
|
||||
These attributes are directly passed through to the automatically-generated :ref:`flex<deformable-flex>` object and have
|
||||
the same meaning.
|
||||
These attributes are directly passed through to the automatically-generated :ref:`flex<deformable-flex>` object and
|
||||
have the same meaning.
|
||||
|
||||
.. _flexcomp-contact:
|
||||
|
||||
@@ -3818,10 +3828,12 @@ the same meaning.
|
||||
.. _flexcomp-contact-margin:
|
||||
.. _flexcomp-contact-gap:
|
||||
|
||||
:at:`internal`, :at:`selfcollide`, :at:`activelayers`, :at:`contype`, :at:`conaffinity`, :at:`condim`, :at:`priority`,
|
||||
:at:`friction`, :at:`solmix`, :at:`solimp`, :at:`margin`, :at:`gap`
|
||||
.. |body/flexcomp/contact attrib list| replace::
|
||||
:at:`internal`, :at:`selfcollide`, :at:`activelayers`, :at:`contype`, :at:`conaffinity`, :at:`condim`,
|
||||
:at:`priority`, :at:`friction`, :at:`solmix`, :at:`solimp`, :at:`margin`, :at:`gap`
|
||||
|
||||
Same as in :ref:`flex/contact<flex-contact>`. All attributes are passed through to the automatically-generated flex.
|
||||
|body/flexcomp/contact attrib list|
|
||||
Same as in :ref:`flex/contact<flex-contact>`. All attributes are passed through to the automatically-generated flex.
|
||||
|
||||
.. _flexcomp-edge:
|
||||
|
||||
@@ -4119,7 +4131,7 @@ cases, the user will specify a :el:`flexcomp` which will then automatically cons
|
||||
.. _flex-edge:
|
||||
|
||||
:el-prefix:`flex/` |-| **edge** (?)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
'''''''''''''''''''''''''''''''''''
|
||||
|
||||
This element adjusts the passive or constraint properties of all edges of the flex. A flex edge can have a damping
|
||||
passive force and an :ref:`equality constraint<equality-flex>` associated with it, resulting in edge constraint forces.
|
||||
@@ -4141,7 +4153,7 @@ these mechanisms to be combined as desired.
|
||||
.. _flex-contact:
|
||||
|
||||
:el-prefix:`flex/` |-| **contact** (?)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
This element adjusts the contact properties of the flex. It is mostly identical to geom contact properties, with some
|
||||
extensions specific to flexes.
|
||||
@@ -4225,7 +4237,7 @@ Finally, the skin can be inflated by applying an offset to each vertex position
|
||||
Skins are one-sided for rendering purposes; this is because back-face culling is needed to avoid shading and aliasing
|
||||
artifacts. When the skin is a closed 3D shape this does not matter because the back sides cannot be seen. But if the
|
||||
skin is a 2D object, we have to specify both sides and offset them slightly to avoid artifacts. Note that the
|
||||
composite objects introduced in MuJoCo 2.0 generate skins automatically. So one can save an XML model with a composite
|
||||
composite objects generate skins automatically. So one can save an XML model with a composite
|
||||
object, and obtain an elaborate example of how a skin is specified in the XML.
|
||||
|
||||
Similar to meshes, skins can be specified directly in the XML via attributes documented later, or loaded from a binary
|
||||
@@ -4475,11 +4487,12 @@ of the other body, without any joint elements in the child body.
|
||||
.. _equality-weld-torquescale:
|
||||
|
||||
:at:`torquescale`: :at-val:`real, "1"`
|
||||
Relative torque-to-force ratio. This ratio is used by the weld to scale how much it "cares" about rotational
|
||||
displacements vs. translational displacements. Setting this value to 0 makes the :el:`weld` behave like a
|
||||
:el:`connect` constraint. Note that this value has units of length and can therefore be interpreted as follows.
|
||||
Imagining that the weld is implemented by a patch of glue sticking the two bodies together, :at:`torquescale` can be
|
||||
interpreted as the diameter of this glue patch.
|
||||
A constant that scales the angular residual (angular constraint violation). Notionally in units of
|
||||
:math:`\textrm{torque}/\textrm{force}=\textrm{length}`. Intuitively this coefficient defines how much the weld
|
||||
"cares" about rotational displacements vs. translational displacements. Setting this value to 0 makes the :el:`weld`
|
||||
behave like a :el:`connect` constraint. Note that this value has units of length and can therefore be understood as
|
||||
follows. Imagining that the weld is implemented by a flat patch of glue sticking the two bodies together,
|
||||
:at:`torquescale` can be interpreted as the diameter of this glue patch.
|
||||
|
||||
|
||||
.. _equality-joint:
|
||||
@@ -4623,7 +4636,7 @@ has multiple obstacle geoms they must be separated by sites - so as to avoid the
|
||||
tendon level. This example illustrates a multi-branch tendon acting as a finger extensor, with a counter-weight
|
||||
instead of an actuator: `tendon.xml <_static/tendon.xml>`__.
|
||||
|
||||
MuJoCo 2.0 introduced a second form of wrapping, where the tendon is constrained to pass through a geom rather than
|
||||
A second form of wrapping is where the tendon is constrained to pass *through* a geom rather than
|
||||
wrap around it. This is enabled automatically when a sidesite is specified and its position is inside the volume of
|
||||
the obstacle geom.
|
||||
|
||||
@@ -4871,7 +4884,7 @@ multiplied by the corresponding coef value, and added up to obtain the tendon le
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This is a grouping element for actuator definitions. Recall the discussion of MuJoCo's :ref:`Actuation model
|
||||
<geActuation>` in the Computation chapter, and the :ref:`Actuator shortcuts <CActuator>` discussed earlier in this
|
||||
<geActuation>` in the Computation chapter, and the :ref:`Actuator shortcuts <CActShortcuts>` discussed earlier in this
|
||||
chapter. The first 13 attributes of all actuator-related elements below are the same, so we document them only once,
|
||||
under the :el:`general` actuator.
|
||||
|
||||
@@ -5144,7 +5157,7 @@ specify them independently.
|
||||
:el-prefix:`actuator/` |-| **motor** (*)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This and the next three elements are the :ref:`Actuator shortcuts <CActuator>` discussed earlier. When a
|
||||
This and the next three elements are the :ref:`Actuator shortcuts <CActShortcuts>` discussed earlier. When a
|
||||
such shortcut is encountered, the parser creates a :el:`general` actuator and sets its dynprm, gainprm and biasprm
|
||||
attributes to the internal defaults shown above, regardless of any default settings. It then adjusts dyntype, gaintype
|
||||
and biastype depending on the shortcut, parses any custom attributes (beyond the common ones), and translates them
|
||||
@@ -5805,9 +5818,9 @@ Associate this actuator with an :ref:`engine plugin<exPlugin>`. Either :at:`plug
|
||||
|
||||
.. _actuator-plugin-user:
|
||||
|
||||
.. |actuator/plugin attrib list| replace:: :at:`name`, :at:`class`, :at:`group`, :at:`ctrllimited`, :at:`forcelimited`
|
||||
:at:`ctrlrange`, :at:`forcerange`, :at:`lengthrange`, :at:`gear`, :at:`cranklength`, :at:`joint`, :at:`jointinparent`
|
||||
:at:`site`, :at:`tendon`, :at:`cranksite`, :at:`slidersite`, :at:`user`
|
||||
.. |actuator/plugin attrib list| replace:: :at:`name`, :at:`class`, :at:`group`, :at:`ctrllimited`,
|
||||
:at:`forcelimited`, :at:`ctrlrange`, :at:`forcerange`, :at:`lengthrange`, :at:`gear`, :at:`cranklength`,
|
||||
:at:`joint`, :at:`jointinparent`, :at:`site`, :at:`tendon`, :at:`cranksite`, :at:`slidersite`, :at:`user`
|
||||
|
||||
|actuator/plugin attrib list|
|
||||
Same as in actuator/ :ref:`general <actuator-general>`.
|
||||
@@ -5867,6 +5880,8 @@ This element creates a 3-axis accelerometer. The sensor is mounted at a site, an
|
||||
as the site frame. This sensor outputs three numbers, which are the linear acceleration of the site (including gravity)
|
||||
in local coordinates.
|
||||
|
||||
The presence of this sensor in a model triggers a call to :ref:`mj_rnePostConstraint` during sensor computation.
|
||||
|
||||
.. _sensor-accelerometer-name:
|
||||
|
||||
.. _sensor-accelerometer-noise:
|
||||
@@ -5947,6 +5962,8 @@ the child body, and the force points from the child towards the parent. The comp
|
||||
forces acting on the system, including contacts as well as external perturbations. Using this sensor often requires
|
||||
creating a dummy body welded to its parent (i.e., having no joint elements).
|
||||
|
||||
The presence of this sensor in a model triggers a call to :ref:`mj_rnePostConstraint` during sensor computation.
|
||||
|
||||
.. _sensor-force-name:
|
||||
|
||||
.. _sensor-force-noise:
|
||||
@@ -5976,6 +5993,8 @@ creating a dummy body welded to its parent (i.e., having no joint elements).
|
||||
This element creates a 3-axis torque sensor. This is similar to the :ref:`force <sensor-force>` sensor above, but
|
||||
measures torque rather than force.
|
||||
|
||||
The presence of this sensor in a model triggers a call to :ref:`mj_rnePostConstraint` during sensor computation.
|
||||
|
||||
.. _sensor-torque-name:
|
||||
|
||||
.. _sensor-torque-noise:
|
||||
@@ -6779,6 +6798,8 @@ coordinates.
|
||||
This element creates a sensor that returns the 3D linear acceleration of the spatial frame of the object, in global
|
||||
coordinates.
|
||||
|
||||
The presence of this sensor in a model triggers a call to :ref:`mj_rnePostConstraint` during sensor computation.
|
||||
|
||||
.. _sensor-framelinacc-name:
|
||||
|
||||
.. _sensor-framelinacc-noise:
|
||||
@@ -6809,6 +6830,8 @@ coordinates.
|
||||
This element creates a sensor that returns the 3D angular acceleration of the spatial frame of the object, in global
|
||||
coordinates.
|
||||
|
||||
The presence of this sensor in a model triggers a call to :ref:`mj_rnePostConstraint` during sensor computation.
|
||||
|
||||
.. _sensor-frameangacc-name:
|
||||
|
||||
.. _sensor-frameangacc-noise:
|
||||
@@ -6864,6 +6887,8 @@ global coordinates.
|
||||
This element creates sensor that returns the linear velocity of the center of mass of the kinematic subtree rooted at a
|
||||
specified body, in global coordinates.
|
||||
|
||||
The presence of this sensor in a model triggers a call to :ref:`mj_subtreeVel` during sensor computation.
|
||||
|
||||
.. _sensor-subtreelinvel-name:
|
||||
|
||||
.. _sensor-subtreelinvel-noise:
|
||||
@@ -6889,6 +6914,8 @@ specified body, in global coordinates.
|
||||
This element creates sensor that returns the angular momentum around the center of mass of the kinematic subtree rooted
|
||||
at a specified body, in global coordinates.
|
||||
|
||||
The presence of this sensor in a model triggers a call to :ref:`mj_subtreeVel` during sensor computation.
|
||||
|
||||
.. _sensor-subtreeangmom-name:
|
||||
|
||||
.. _sensor-subtreeangmom-noise:
|
||||
@@ -6931,12 +6958,15 @@ This element creates sensor that returns the simulation time.
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This element creates a user sensor. MuJoCo does not know how to compute the output of this sensor. Instead the user
|
||||
should install the callback :ref:`mjcb_sensor` which is expected to fill in the sensor data in mjData.sensordata. The
|
||||
specification in the XML is used to allocate space for this sensor, and also determine which MuJoCo object it is
|
||||
should install the callback :ref:`mjcb_sensor` which is expected to fill in the sensor data in ``mjData.sensordata``.
|
||||
The specification in the XML is used to allocate space for this sensor, and also determine which MuJoCo object it is
|
||||
attached to and what stage of computation it needs before the data can be computed. Note that the MuJoCo object
|
||||
referenced here can be a tuple, which in turn can reference a custom collection of MuJoCo objects - for example several
|
||||
referenced here can be a tuple, which in turn can reference a custom collection of MuJoCo objects -- for example several
|
||||
bodies whose center of mass is of interest.
|
||||
|
||||
If a user sensor is of :ref:`stage<sensor-user-needstage>` "vel" or "acc", then :ref:`mj_subtreeVel` or
|
||||
:ref:`mj_rnePostConstraint` will be triggered, respectively.
|
||||
|
||||
.. _sensor-user-name:
|
||||
|
||||
.. _sensor-user-noise:
|
||||
@@ -7009,7 +7039,7 @@ Ascociate this sensor with an :ref:`engine plugin<exPlugin>`. Either :at:`plugin
|
||||
|
||||
.. _sensor-plugin-user:
|
||||
|
||||
.. |sensor/plugin attrib list| replace:: :at:`name`, :at:`cutoff`, :at:`objtype`, :at:`objname`, :at:`reftype`
|
||||
.. |sensor/plugin attrib list| replace:: :at:`name`, :at:`cutoff`, :at:`objtype`, :at:`objname`, :at:`reftype`,
|
||||
:at:`refname`, :at:`user`
|
||||
|
||||
|sensor/plugin attrib list|
|
||||
@@ -7514,7 +7544,7 @@ if omitted.
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This and the next three elements set the attributes of the :ref:`general <actuator-general>` element using
|
||||
:ref:`Actuator shortcuts <CActuator>`. It does not make sense to use more than one such shortcut in the same defaults
|
||||
:ref:`Actuator shortcuts <CActShortcuts>`. It does not make sense to use more than one such shortcut in the same defaults
|
||||
class, because they set the same underlying attributes, replacing any previous settings. All
|
||||
:ref:`motor <actuator-motor>` attributes are available here except: name, class, joint, jointinparent, site, tendon,
|
||||
slidersite, cranksite.
|
||||
|
||||
+10
-10
@@ -109,6 +109,8 @@
|
||||
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
|
||||
| | | | :ref:`framelength<visual-scale-framelength>` | :ref:`framewidth<visual-scale-framewidth>` | :ref:`constraint<visual-scale-constraint>` | :ref:`slidercrank<visual-scale-slidercrank>` | |
|
||||
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
|
||||
| | | | :ref:`frustum<visual-scale-frustum>` | | | | |
|
||||
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
|
||||
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| |_| visual |br| |_| |L| | | .. table:: |
|
||||
| :ref:`rgba | ? | :class: mjcf-attributes |
|
||||
@@ -124,7 +126,7 @@
|
||||
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
|
||||
| | | | :ref:`contacttorque<visual-rgba-contacttorque>` | :ref:`contactgap<visual-rgba-contactgap>` | :ref:`rangefinder<visual-rgba-rangefinder>` | :ref:`constraint<visual-rgba-constraint>` | |
|
||||
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
|
||||
| | | | :ref:`slidercrank<visual-rgba-slidercrank>` | :ref:`crankbroken<visual-rgba-crankbroken>` | | | |
|
||||
| | | | :ref:`slidercrank<visual-rgba-slidercrank>` | :ref:`crankbroken<visual-rgba-crankbroken>` | :ref:`frustum<visual-rgba-frustum>` | | |
|
||||
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
|
||||
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| mujoco |br| |L| | | *no attributes* |
|
||||
@@ -228,7 +230,7 @@
|
||||
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
|
||||
| | | | :ref:`iterations<option-iterations>` | :ref:`ls_iterations<option-ls_iterations>` | :ref:`noslip_iterations<option-noslip_iterations>` | :ref:`mpr_iterations<option-mpr_iterations>` | |
|
||||
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
|
||||
| | | | :ref:`sdf_iterations<option-sdf_iterations>` | :ref:`sdf_initpoints<option-sdf_initpoints>` | | | |
|
||||
| | | | :ref:`sdf_iterations<option-sdf_iterations>` | :ref:`sdf_initpoints<option-sdf_initpoints>` | :ref:`actuatorgroupdisable<option-actuatorgroupdisable>` | | |
|
||||
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
|
||||
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| |_| option |br| |_| |L| | | .. table:: |
|
||||
@@ -485,19 +487,17 @@
|
||||
| :ref:`flexcomp | \* | :class: mjcf-attributes |
|
||||
| <body-flexcomp>` | | |
|
||||
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
|
||||
| | | | :ref:`name<body-flexcomp-name>` | :ref:`class<body-flexcomp-class>` | :ref:`type<body-flexcomp-type>` | :ref:`group<body-flexcomp-group>` | |
|
||||
| | | | :ref:`name<body-flexcomp-name>` | :ref:`type<body-flexcomp-type>` | :ref:`group<body-flexcomp-group>` | :ref:`dim<body-flexcomp-dim>` | |
|
||||
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
|
||||
| | | | :ref:`dim<body-flexcomp-dim>` | :ref:`count<body-flexcomp-count>` | :ref:`spacing<body-flexcomp-spacing>` | :ref:`radius<body-flexcomp-radius>` | |
|
||||
| | | | :ref:`count<body-flexcomp-count>` | :ref:`spacing<body-flexcomp-spacing>` | :ref:`radius<body-flexcomp-radius>` | :ref:`rigid<body-flexcomp-rigid>` | |
|
||||
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
|
||||
| | | | :ref:`rigid<body-flexcomp-rigid>` | :ref:`mass<body-flexcomp-mass>` | :ref:`inertiabox<body-flexcomp-inertiabox>` | :ref:`scale<body-flexcomp-scale>` | |
|
||||
| | | | :ref:`mass<body-flexcomp-mass>` | :ref:`inertiabox<body-flexcomp-inertiabox>` | :ref:`scale<body-flexcomp-scale>` | :ref:`file<body-flexcomp-file>` | |
|
||||
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
|
||||
| | | | :ref:`file<body-flexcomp-file>` | :ref:`point<body-flexcomp-point>` | :ref:`element<body-flexcomp-element>` | :ref:`texcoord<body-flexcomp-texcoord>` | |
|
||||
| | | | :ref:`point<body-flexcomp-point>` | :ref:`element<body-flexcomp-element>` | :ref:`texcoord<body-flexcomp-texcoord>` | :ref:`material<body-flexcomp-material>` | |
|
||||
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
|
||||
| | | | :ref:`material<body-flexcomp-material>` | :ref:`rgba<body-flexcomp-rgba>` | :ref:`flatskin<body-flexcomp-flatskin>` | :ref:`pos<body-flexcomp-pos>` | |
|
||||
| | | | :ref:`rgba<body-flexcomp-rgba>` | :ref:`flatskin<body-flexcomp-flatskin>` | :ref:`pos<body-flexcomp-pos>` | :ref:`quat<body-flexcomp-quat>` | |
|
||||
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
|
||||
| | | | :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:`axisangle<body-flexcomp-axisangle>` | :ref:`xyaxes<body-flexcomp-xyaxes>` | :ref:`zaxis<body-flexcomp-zaxis>` | :ref:`euler<body-flexcomp-euler>` | |
|
||||
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
|
||||
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| |_2| flexcomp |br| |_2| |L| | | .. table:: |
|
||||
|
||||
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
<mujoco>
|
||||
<asset>
|
||||
<texture type="skybox" builtin="gradient" rgb1=".3 .5 .7" rgb2="0 0 0" width="32" height="512"/>
|
||||
<texture name="grid" type="2d" builtin="checker" width="512" height="512" rgb1=".1 .2 .3" rgb2=".2 .3 .4"/>
|
||||
<material name="grid" texture="grid" texrepeat="1 1" texuniform="true" reflectance=".2"/>
|
||||
</asset>
|
||||
|
||||
<statistic extent="1"/>
|
||||
|
||||
<!-- switch the integrator to "implicit" for better gyroscopic stabillity -->
|
||||
<option timestep="0.01" integrator="implicitfast"/>
|
||||
|
||||
<worldbody>
|
||||
<geom pos="0 0 -.5" type="plane" size="100 100 .1" zaxis=".5 0 1" material="grid"/>
|
||||
<body pos="0 0 .3">
|
||||
<freejoint/>
|
||||
<geom type="ellipsoid" size="0.15 0.1 0.07" euler="20 40 60"/>
|
||||
<light pos="0 0 1.5" mode="track"/>
|
||||
<camera pos="0 -1.498 0.1" xyaxes="1 0 0 0 0 1" mode="track"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
<mujoco model="energy conserving pendulum">
|
||||
<option integrator="RK4">
|
||||
<flag constraint="disable" energy="enable"/>
|
||||
</option>
|
||||
|
||||
<worldbody>
|
||||
<light pos="0 0 2"/>
|
||||
<geom pos="0 0 -.5" type="plane" size="1 1 .01"/>
|
||||
<body pos="0 0 0">
|
||||
<joint type="hinge" axis="0 1 0"/>
|
||||
<geom type="cylinder" size="0.02" fromto="0 -.02 0 0 .02 0"/>
|
||||
<geom type="capsule" size="0.02" fromto="0 0 0 .1 0 0"/>
|
||||
<body pos="0.1 0 0">
|
||||
<joint type="slide" axis="1 0 0" stiffness="200"/>
|
||||
<geom type="capsule" size="0.015" fromto="-.1 0 0 .1 0 0"/>
|
||||
<body pos=".1 0 0">
|
||||
<joint type="ball"/>
|
||||
<geom type="box" size=".02" fromto="0 0 0 0 .1 0"/>
|
||||
<body pos="0 .1 0">
|
||||
<joint axis="1 0 0"/>
|
||||
<geom type="capsule" size="0.02" fromto="0 0 0 0 .1 0"/>
|
||||
</body>
|
||||
</body>
|
||||
</body>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
+81
-4
@@ -2,13 +2,90 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
Upcoming version (not yet released)
|
||||
-----------------------------------
|
||||
Version 3.0.1 (November 15, 2023)
|
||||
---------------------------------
|
||||
|
||||
General
|
||||
^^^^^^^
|
||||
1. Added sub-terms of total passive forces in ``mjData.qfrc_passive`` to :ref:`mjData`:
|
||||
``qfrc_{spring, damper, gravcomp, fluid}``. The sum of these vectors equals ``qfrc_passive``.
|
||||
|
||||
.. youtube:: H9qG9Zf2W44
|
||||
:align: right
|
||||
:width: 240px
|
||||
|
||||
2. Added :ref:`actuatorgroupdisable<option-actuatorgroupdisable>` attribute and associated
|
||||
:ref:`mjOption.disableactuator<mjOption>` integer bitfield, which can be used to disable sets of actuators at runtime
|
||||
according to their :ref:`group<actuator-general-group>`. Fixes :github:issue:`1092`. See :ref:`CActDisable`.
|
||||
|
||||
- The first 6 actuator groups are toggleable in the :ref:`simulate<saSimulate>` viewer. See `example model
|
||||
<https://github.com/google-deepmind/mujoco/blob/main/test/engine/testdata/actuation/actuator_group_disable.xml>`__
|
||||
and associated screen-capture on the right.
|
||||
|
||||
3. Increased ``mjMAXUIITEM`` (maximum number of UI elements per section in Simulate) to 200.
|
||||
|
||||
MJX
|
||||
^^^
|
||||
4. Added support for Newton solver (``mjSOL_NEWTON`` in :ref:`mjtSolver`). The Newton solver significantly speeds up
|
||||
simulation on GPU:
|
||||
|
||||
.. list-table:: Steps-per-second, Conjugate Gradient vs. Newton on A100
|
||||
:header-rows: 1
|
||||
:align: left
|
||||
|
||||
* - Model
|
||||
- CG
|
||||
- Newton
|
||||
- Speedup
|
||||
* - `Humanoid <https://github.com/google-deepmind/mujoco/tree/main/mjx/mujoco/mjx/benchmark/model/humanoid>`__
|
||||
- 640,000
|
||||
- 1,020,000
|
||||
- **1.6 x**
|
||||
* - `Barkour v0 <https://github.com/google-deepmind/mujoco/tree/main/mjx/mujoco/mjx/benchmark/model/barkour_v0>`__
|
||||
- 1,290,000
|
||||
- 1,750,000
|
||||
- **1.35 x**
|
||||
* - `Shadow Hand <https://github.com/google-deepmind/mujoco/tree/main/mjx/mujoco/mjx/benchmark/model/shadow_hand>`__
|
||||
- 215,000
|
||||
- 270,000
|
||||
- **1.25 x**
|
||||
|
||||
Humanoid is the standard MuJoCo humanoid,
|
||||
`Google Barkour <https://blog.research.google/2023/05/barkour-benchmarking-animal-level.html>`__ and the Shadow Hand
|
||||
are both available in the :ref:`MuJoCo Menagerie<Menagerie>`.
|
||||
5. Added support for joint equality constraints (``mjEQ_JOINT`` in :ref:`mjtEq`).
|
||||
6. Fixed bug where mixed ``jnt_limited`` joints were not being constrained correctly.
|
||||
7. Made ``device_put`` type validation more verbose (fixes :github:issue:`1113`).
|
||||
8. Removed empty EFC rows from ``MJX``, for joints with no limits (fixes :github:issue:`1117`).
|
||||
9. Fixed bug in ``scan.body_tree`` that led to incorrect smooth dynamics for some kinematic tree layouts.
|
||||
|
||||
Python bindings
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
10. Fix the macOS ``mjpython`` launcher to work with the Python interpreter from Apple Command Line Tools.
|
||||
11. Fixed a crash when copying instances of ``mujoco.MjData`` for models that use plugins. Introduced a ``model``
|
||||
attribute to ``MjData`` which is reference to the model that was used to create that ``MjData`` instance.
|
||||
|
||||
Simulate
|
||||
^^^^^^^^
|
||||
12. :ref:`simulate<saSimulate>`: correct handling of "Pause update", "Fullscreen" and "VSync" buttons.
|
||||
|
||||
Documentation
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
.. youtube:: cE3s_IfO4g4
|
||||
:align: right
|
||||
:width: 240px
|
||||
|
||||
13. Added cell to the `tutorial colab <https://github.com/google-deepmind/mujoco#getting-started>`__ providing an
|
||||
example of procedural camera control:
|
||||
14. Added documentation for the :ref:`UI` framework.
|
||||
15. Fixed typos and supported fields in docs (fixes :github:issue:`1105` and :github:issue:`1106`).
|
||||
|
||||
|
||||
Bug fixes
|
||||
^^^^^^^^^
|
||||
|
||||
1. Fix in simulate: correct handling of "Pause update", "Fullscreen" and "VSync" buttons.
|
||||
16. Fixed bug relating to welds modified with :ref:`torquescale<equality-weld-torquescale>`.
|
||||
|
||||
Version 3.0.0 (October 18, 2023)
|
||||
--------------------------------
|
||||
|
||||
+213
-187
@@ -143,47 +143,59 @@ Our notation is summarized in the table below. Additional notation specific to c
|
||||
When available, we also show the fields of main data structures :ref:`mjModel` and :ref:`mjData` corresponding to the
|
||||
mathematical notation.
|
||||
|
||||
+-----------------+----------------+----------------+----------------------+
|
||||
| Symbol | Size | Description | MuJoCo field |
|
||||
+=================+================+================+======================+
|
||||
| :math:`n_Q` | | number of | ``mjModel.nq`` |
|
||||
| | | position | |
|
||||
| | | coordinates | |
|
||||
+-----------------+----------------+----------------+----------------------+
|
||||
| :math:`n_V` | | number of | ``mjModel.nv`` |
|
||||
| | | degrees of | |
|
||||
| | | freedom | |
|
||||
+-----------------+----------------+----------------+----------------------+
|
||||
| :math:`n_C` | | number of | ``mjData.nefc`` |
|
||||
| | | active | |
|
||||
| | | constraints | |
|
||||
+-----------------+----------------+----------------+----------------------+
|
||||
| :math:`q` | :math:`n_Q` | joint position | ``mjData.qpos`` |
|
||||
+-----------------+----------------+----------------+----------------------+
|
||||
| :math:`v` | :math:`n_V` | joint velocity | ``mjData.qvel`` |
|
||||
+-----------------+----------------+----------------+----------------------+
|
||||
| :math:`\tau` | :math:`n_V` | applied force: | |
|
||||
| | | passive, | |
|
||||
| | | actuation, | |
|
||||
| | | external | |
|
||||
+-----------------+----------------+----------------+----------------------+
|
||||
| :math:`c(q, v)` | :math:`n_V` | bias force: | ``mjData.qfrc_bias`` |
|
||||
| | | Coriolis, | |
|
||||
| | | centrifugal, | |
|
||||
| | | gravitational | |
|
||||
+-----------------+----------------+----------------+----------------------+
|
||||
| :math:`M(q)` | :math:`n_V | inertia in | ``mjData.qM`` |
|
||||
| | \times n_V` | joint space | |
|
||||
+-----------------+----------------+----------------+----------------------+
|
||||
| :math:`J(q)` | :math:`n_C | constraint | ``mjData.efc_J`` |
|
||||
| | \times n_V` | Jacobian | |
|
||||
+-----------------+----------------+----------------+----------------------+
|
||||
| :math:`r(q)` | :math:`n_C` | constraint | ``mjData.efc_pos`` |
|
||||
| | | residual | |
|
||||
+-----------------+----------------+----------------+----------------------+
|
||||
| :math:`f(q, v, | :math:`n_C` | constraint | ``mjData.efc_force`` |
|
||||
| \tau)` | | force | |
|
||||
+-----------------+----------------+----------------+----------------------+
|
||||
.. list-table::
|
||||
:widths: 2 2 7 4
|
||||
:header-rows: 1
|
||||
|
||||
* - Symbol
|
||||
- Size
|
||||
- Description
|
||||
- MuJoCo field
|
||||
* - :math:`n_Q`
|
||||
-
|
||||
- number of position coordinates
|
||||
- ``mjModel.nq``
|
||||
* - :math:`n_V`
|
||||
-
|
||||
- number of degrees of freedom
|
||||
- ``mjModel.nv``
|
||||
* - :math:`n_C`
|
||||
-
|
||||
- number of active constraints
|
||||
- ``mjData.nefc``
|
||||
* - :math:`q`
|
||||
- :math:`n_Q`
|
||||
- joint position
|
||||
- ``mjData.qpos``
|
||||
* - :math:`v`
|
||||
- :math:`n_V`
|
||||
- joint velocity
|
||||
- ``mjData.qvel``
|
||||
* - :math:`\tau`
|
||||
- :math:`n_V`
|
||||
- applied force: passive, actuation, external
|
||||
- ``mjData.qfrc_passive`` + ``mjData.qfrc_actuator`` + ``mjData.qfrc_applied``
|
||||
* - :math:`c(q, v)`
|
||||
- :math:`n_V`
|
||||
- bias force: Coriolis, centrifugal, gravitational
|
||||
- ``mjData.qfrc_bias``
|
||||
* - :math:`M(q)`
|
||||
- :math:`n_V \times n_V`
|
||||
- inertia in joint space
|
||||
- ``mjData.qM``
|
||||
* - :math:`J(q)`
|
||||
- :math:`n_C \times n_V`
|
||||
- constraint
|
||||
Jacobian
|
||||
- ``mjData.efc_J``
|
||||
* - :math:`r(q)`
|
||||
- :math:`n_C`
|
||||
- constraint residual
|
||||
- ``mjData.efc_pos``
|
||||
* - :math:`f(q, v,\tau)`
|
||||
- :math:`n_C`
|
||||
- constraint force
|
||||
- ``mjData.efc_force``
|
||||
|
||||
All model elements are enumerated at compile time and assembled into the above system-level vectors and matrices. In our
|
||||
earlier arm model :ref:`example <Examples>` the model has :math:`n_V = 13` degrees of freedom: 3 for the ball joint, one
|
||||
@@ -259,7 +271,7 @@ the force outputs are stored in ``mjData.actuator_force``, and the activation st
|
||||
|
||||
These three components of an actuator - transmission, activation dynamics, and force generation - determine how the
|
||||
actuator works. The user can set them independently for maximum flexibility, or use :ref:`Actuator shortcuts
|
||||
<CActuator>` which instantiate common actuator types.
|
||||
<CActShortcuts>` which instantiate common actuator types.
|
||||
|
||||
.. _geTransmission:
|
||||
|
||||
@@ -304,8 +316,8 @@ is attached; the possible attachment object types are :at:`joint`, :at:`tendon`,
|
||||
|
||||
.. _geActivation:
|
||||
|
||||
Activation dynamics
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
Stateful actuators
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Some actuators such as pneumatic and hydraulic cylinders as well as biological muscles have an internal state called
|
||||
"activation". This is a true dynamic state, beyond the joint positions :math:`q` and velocities :math:`v`. Including
|
||||
@@ -321,27 +333,33 @@ independent of the other actuators. The activation types currently implemented a
|
||||
.. math::
|
||||
\begin{aligned}
|
||||
\text{integrator}: & & \dot{w}_i &= u_i \\
|
||||
\text{filter}: & & \dot{w}_i &= (u_i - w_i) / t \\
|
||||
\text{filterexact}: & & \dot{w}_i &= (u_i - w_i) / t \\
|
||||
\text{filter}: & & \dot{w}_i &= (u_i - w_i) / \texttt{t} \\
|
||||
\text{filterexact}: & & \dot{w}_i &= (u_i - w_i) / \texttt{t} \\
|
||||
\text{muscle}: & & \dot{w}_i &= \textrm{muscle}(u_i, w_i, l_i, \dot{l}_i)
|
||||
\end{aligned}
|
||||
|
||||
where :math:`t` is an actuator-specific time constant stored in ``mjModel.actuator_dynprm``. In addition the type can
|
||||
be "user", in which case :math:`w_i` is computed by the user-defined callback :ref:`mjcb_act_dyn`. The type can also
|
||||
be "none" which corresponds to a regular actuator with no activation state. The dimensionality of :math:`w` equals
|
||||
where :math:`\texttt{t}` is an actuator-specific time-constant stored in ``mjModel.actuator_dynprm``. In addition, the
|
||||
type can be "user", in which case :math:`w_i` is computed by the user-defined callback :ref:`mjcb_act_dyn`. The type can
|
||||
also be "none" which corresponds to a regular actuator with no activation state. The dimensionality of :math:`w` equals
|
||||
the number of actuators whose activation type is different from "none".
|
||||
|
||||
For more information regarding muscle activation dynamics, see :ref:`CMuscle`.
|
||||
|
||||
For ``filterexact`` activation dynamics, Euler integration of :math:`\dot{w}` is replaced with the analytic integral:
|
||||
|
||||
.. math::
|
||||
\begin{aligned}
|
||||
\text{filter}: & & w_{i+1} &= w_i + h (u_i - w_i) / t \\
|
||||
\text{filterexact}: & & w_{i+1} &= w_i + (u_i - w_i) (1 - e^{-h / t}) \\
|
||||
\text{filter}: & & w_{i+1} &= w_i + h (u_i - w_i) / \texttt{t} \\
|
||||
\text{filterexact}: & & w_{i+1} &= w_i + (u_i - w_i) (1 - e^{-h / \texttt{t}}) \\
|
||||
\end{aligned}
|
||||
|
||||
The two expressions converge to the same value in the :math:`h \rightarrow 0` limit.
|
||||
The two expressions converge to the same value in the :math:`h \rightarrow 0` limit. Note that Euler-integrated filters
|
||||
diverge for :math:`\texttt{t} < h`, while exactly-integrated filters are stable for any positive :math:`\texttt{t}`.
|
||||
|
||||
Note that Euler-integrated filters diverge for :math:`t < h`, while exactly-integrated filters are stable for any
|
||||
:math:`t > 0`.
|
||||
:ref:`actearly<actuator-general-actearly>`:
|
||||
If the :ref:`actearly<actuator-general-actearly>` attribute is set to "true", ``mjData.actuator_force`` is computed
|
||||
based on :math:`w_{i+1}` (the next activation), reducing the delay between changes to :math:`u` and their effects on
|
||||
the acceleration by one time step (so the total dynamics are second-order rather than third order).
|
||||
|
||||
.. _geActuatorForce:
|
||||
|
||||
@@ -379,10 +397,6 @@ This quantity is stored in ``mjData.qfrc_actuator``. It is added to the applied
|
||||
with any user-defined forces in joint or Cartesian coordinates (which are stored in ``mjData.qfrc_applied`` and
|
||||
``mjData.xfrc_applied`` respectively).
|
||||
|
||||
Optionally, the :ref:`actearly<actuator-general-actearly>` attribute on an actuator computes ``mjData.qfrc_actuator``
|
||||
based on the value of :math:`w_{i+1}` after integration, reducing the delay between changes to :math:`u` and
|
||||
:math:`t`.
|
||||
|
||||
.. _gePassive:
|
||||
|
||||
Passive forces
|
||||
@@ -526,29 +540,48 @@ Fast implicit-in-velocity (``implicitfast``)
|
||||
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.
|
||||
|
||||
.. tip::
|
||||
The implicitfast integrator has similar computational cost to Euler, yet provides increased stability, and is
|
||||
therefore a strict improvement. It is the recommended integrator and will become the default in a future version.
|
||||
|
||||
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
|
||||
multistep methods. The only such integrator currently implemented is the fixed-step `4th-order Runge-Kutta method
|
||||
<https://en.wikipedia.org/wiki/Runge–Kutta_methods#Derivation_of_the_Runge–Kutta_fourth-order_method>`_, though users
|
||||
can easily implement other integrators by calling :ref:`mj_forward` and integrating accelerations themselves. We have
|
||||
observed that for energy-conserving systems (`example
|
||||
<https://github.com/google-deepmind/mujoco/blob/main/test/engine/testdata/derivative/energy_conserving_pendulum.xml>`_) RK4
|
||||
is qualitatively better than the single-step methods, both in terms of stability and accuracy, even when the timestep
|
||||
is decreased by a factor of 4 (so the computational effort is identical). In the presence of large velocity-
|
||||
dependent forces, if the chosen single-step method integrates those forces implicitly, single-step methods can be
|
||||
significantly more stable than RK4.
|
||||
<https://en.wikipedia.org/wiki/Runge–Kutta_methods#Derivation_of_the_Runge–Kutta_fourth-order_method>`__, though
|
||||
users can easily implement other integrators by calling :ref:`mj_forward` and integrating accelerations themselves.
|
||||
We have observed that for energy-conserving systems (`example <../_static/pendulum.xml>`__), RK4 is qualitatively
|
||||
better than the single-step methods, both in terms of stability and accuracy, even when the timestep is decreased by
|
||||
a factor of 4 (so the computational effort is identical). In the presence of large velocity- dependent forces, if the
|
||||
chosen single-step method integrates those forces implicitly, single-step methods can be significantly more stable
|
||||
than RK4.
|
||||
|
||||
.. note::
|
||||
The accuracy and stability of all integrators can be improved by reducing the time step :math:`h` which is stored in
|
||||
``mjModel.opt.timestep``. Of course this also slows down the simulation. The time step is perhaps the most important
|
||||
parameter that the user can adjust. If it is too large, the simulation will become unstable. If it is too small, CPU
|
||||
time will be wasted without meaningful improvement in accuracy. There is always a comfortable range where the time
|
||||
step is "just right", but that range is model-dependent.
|
||||
.. admonition:: Choosing timestep and integrator
|
||||
:class: tip
|
||||
|
||||
:ref:`timestep<option-timestep>`
|
||||
The accuracy and stability of all integrators can be improved by reducing the time step :math:`h`.
|
||||
Of course a smaller time step also slows down the simulation. The time step is perhaps the single most important
|
||||
parameter that the user can adjust. If it is too large, the simulation will become unstable. If it is too small, CPU
|
||||
time will be wasted without meaningful improvement in accuracy. There is always a comfortable range where the time
|
||||
step is "just right", but that range is model-dependent.
|
||||
|
||||
:ref:`integrator<option-integrator>`
|
||||
Summary: The recommended integrator is ``implicitfast`` which usually has the best tradeoff of stabillity and
|
||||
performance.
|
||||
|
||||
**Euler**:
|
||||
Use ``Euler`` for compatibillity with older models and :ref:`MJX<Mjx>`. Specifically for MJX,
|
||||
setting the :ref:`eulerdamp<option-flag-eulerdamp>` disable flag can :ref:`improve performance<MjxPerformance>`.
|
||||
**implicitfast**:
|
||||
The ``implicitfast`` integrator has similar computational cost to ``Euler``, yet provides
|
||||
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>`__
|
||||
shows an ellipsoid rolling on an inclined plane which quickly diverges with ``implicitfast`` but is stable with
|
||||
``implicit``.
|
||||
**RK4**:
|
||||
This integrator is best for systems which are energy conserving, or almost energy-conserving. `pendulum.xml
|
||||
<../_static/pendulum.xml>`__ shows a complicated pendulum mechanism which diverges quickly using ``Euler`` or
|
||||
``implicitfast`` yet conserves energy well under ``RK4``. Note that under ``implicit``, this model doesn't diverge
|
||||
but rather loses energy.
|
||||
|
||||
.. _geState:
|
||||
|
||||
@@ -850,33 +883,25 @@ spatial frame and normal distance are given by the collision detector.
|
||||
In addition to the above quantities which are computed online, each contact has several parameters obtained from the
|
||||
model definition.
|
||||
|
||||
+---------------------------+-----------------------------------+
|
||||
| Parameter | Description |
|
||||
+===========================+===================================+
|
||||
| ``condim`` | Dimensionality of the contact |
|
||||
| | force/torque in the contact |
|
||||
| | frame. It can be 1, 3, 4 or 6. |
|
||||
+---------------------------+-----------------------------------+
|
||||
| ``friction`` | Vector of friction coefficients, |
|
||||
| | with dimensionality ``condim-1``. |
|
||||
+---------------------------+-----------------------------------+
|
||||
| ``margin`` | The distance margin used to |
|
||||
| | determine if the contact should |
|
||||
| | be included in the global contact |
|
||||
| | array ``mjData.contact``. |
|
||||
+---------------------------+-----------------------------------+
|
||||
| ``gap`` | For custom computations it is |
|
||||
| | sometimes convenient to include |
|
||||
| | contacts in ``mjData.contact`` |
|
||||
| | but not generate contact forces. |
|
||||
| | This is what ``gap`` does: |
|
||||
| | contact forces are generated only |
|
||||
| | when the normal distance is below |
|
||||
| | margin-gap. |
|
||||
+---------------------------+-----------------------------------+
|
||||
| ``solref`` and ``solimp`` | :ref:`Solver <Solver>` |
|
||||
| | parameters explained later. |
|
||||
+---------------------------+-----------------------------------+
|
||||
.. list-table::
|
||||
:widths: 1 5
|
||||
:header-rows: 1
|
||||
|
||||
* - Parameter
|
||||
- Description
|
||||
* - ``condim``
|
||||
- Dimensionality of the contact force/torque in the contact frame. |br| It can be 1, 3, 4 or 6.
|
||||
* - ``friction``
|
||||
- Vector of friction coefficients with dimensionality ``condim-1``.
|
||||
* - ``margin``
|
||||
- The distance margin used to determine if the contact should be included in the global contact array
|
||||
``mjData.contact``.
|
||||
* - ``gap``
|
||||
- For custom computations it is sometimes convenient to include contacts in ``mjData.contact`` but not generate
|
||||
contact forces. This is what ``gap`` does: contact forces are generated only when the normal distance is below
|
||||
(margin - gap).
|
||||
* - ``solref`` and ``solimp``
|
||||
- :ref:`Solver <Solver>` parameters, explained later.
|
||||
|
||||
The contact friction cone can be either elliptic or pyramidal. This is a global setting determined by the choice of
|
||||
constraint solver: the elliptic solvers work with elliptic cones, while the pyramidal solvers work with pyramidal cones,
|
||||
@@ -967,11 +992,11 @@ to be solved numerically. In inverse dynamics, the problem becomes diagonal and
|
||||
|
||||
The primal formulation is based on a generalization of the Gauss principle of least constraint. In its basic form, the
|
||||
Gauss principle states that if we have unconstrained dynamics :math:`M \dot{v} = \tau` and impose acceleration
|
||||
constraint :math:`J \dot{v} = a^*`, the resulting acceleration will be
|
||||
constraint :math:`J \dot{v} = \ar`, the resulting acceleration will be
|
||||
|
||||
.. math::
|
||||
\dot{v} = \arg \min_x \left\| x-M^{-1} \tau \right\|^2_M \\
|
||||
\textrm{subject to} \; J x = a^*
|
||||
\textrm{subject to} \; J x = \ar
|
||||
|
||||
where the weighted :math:`L_2` norm is the usual :math:`\|x\|^2_M = x^T M x`. Thus the constraint causes the smallest
|
||||
possible deviation from the unconstrained acceleration :math:`M^{-1}\tau`, where the metric for measuring deviations in
|
||||
@@ -981,58 +1006,55 @@ will be done by generalizing both the cost function and the constraints in the G
|
||||
|
||||
We will use the following notation beyond the notation introduced earlier:
|
||||
|
||||
+----------------------+----------------------+----------------------+
|
||||
| Symbol | Size | Description |
|
||||
+======================+======================+======================+
|
||||
| :math:`z` | :math:`n_C` | constraint |
|
||||
| | | deformations |
|
||||
+----------------------+----------------------+----------------------+
|
||||
| :math:`\omega` | :math:`n_C` | velocity of |
|
||||
| | | constraint |
|
||||
| | | deformations |
|
||||
+----------------------+----------------------+----------------------+
|
||||
| :math:`d` | :math:`n_C` | constraint impedance |
|
||||
+----------------------+----------------------+----------------------+
|
||||
| :math:`b` | :math:`n_C` | virtual constraint |
|
||||
| | | damping |
|
||||
+----------------------+----------------------+----------------------+
|
||||
| :math:`k` | :math:`n_C` | virtual constraint |
|
||||
| | | stiffness |
|
||||
+----------------------+----------------------+----------------------+
|
||||
| :math:`A(q)` | :math:`n_C \times | inverse inertia in |
|
||||
| | n_C` | constraint space |
|
||||
+----------------------+----------------------+----------------------+
|
||||
| :math:`R(q)` | :math:`n_C \times | diagonal regularizer |
|
||||
| | n_C` | in constraint space |
|
||||
+----------------------+----------------------+----------------------+
|
||||
| :math:`a^*(q,v)` | :math:`n_C` | reference |
|
||||
| | | acceleration in |
|
||||
| | | constraint space |
|
||||
+----------------------+----------------------+----------------------+
|
||||
| :math:`a^0(q, v, | :math:`n_C` | unconstrained |
|
||||
| \tau)` | | acceleration in |
|
||||
| | | constraint space |
|
||||
+----------------------+----------------------+----------------------+
|
||||
| :math:`a^1(q, v, | :math:`n_C` | constrained |
|
||||
| \dot{v})` | | acceleration in |
|
||||
| | | constraint space |
|
||||
+----------------------+----------------------+----------------------+
|
||||
| :math:`\mathcal{K} | | product of all |
|
||||
| (q)` | | contact friction |
|
||||
| | | cones |
|
||||
+----------------------+----------------------+----------------------+
|
||||
| :math:`\eta` | | upper bounds on |
|
||||
| | | friction loss forces |
|
||||
+----------------------+----------------------+----------------------+
|
||||
| :math:`\Omega(q)` | | convex set of |
|
||||
| | | admissible |
|
||||
| | | constraint forces |
|
||||
+----------------------+----------------------+----------------------+
|
||||
| :math:`\mathcal{E}, | | index sets for |
|
||||
| \mathcal{F}, | | Equality, Friction |
|
||||
| \mathcal{C}` | | loss, Contact |
|
||||
| | | constraints |
|
||||
+----------------------+----------------------+----------------------+
|
||||
.. list-table::
|
||||
:widths: 1 1 4
|
||||
:header-rows: 1
|
||||
|
||||
* - Symbol
|
||||
- Size
|
||||
- Description
|
||||
* - :math:`z`
|
||||
- :math:`n_C`
|
||||
- constraint deformations
|
||||
* - :math:`\omega`
|
||||
- :math:`n_C`
|
||||
- velocity of constraint deformations
|
||||
* - :math:`k`
|
||||
- :math:`n_C`
|
||||
- virtual constraint stiffness
|
||||
* - :math:`b`
|
||||
- :math:`n_C`
|
||||
- virtual constraint damping
|
||||
* - :math:`d`
|
||||
- :math:`n_C`
|
||||
- constraint impedance
|
||||
* - :math:`A(q)`
|
||||
- :math:`n_C \times n_C`
|
||||
- inverse inertia in constraint space
|
||||
* - :math:`R(q)`
|
||||
- :math:`n_C \times n_C`
|
||||
- diagonal regularizer in constraint space
|
||||
* - :math:`\ar`
|
||||
- :math:`n_C`
|
||||
- reference acceleration in constraint space
|
||||
* - :math:`\au(q, v, \tau)`
|
||||
- :math:`n_C`
|
||||
- unconstrained acceleration in constraint space
|
||||
* - :math:`\ac(q, v, \dot{v})`
|
||||
- :math:`n_C`
|
||||
- constrained acceleration in constraint space
|
||||
* - :math:`\mathcal{K}(q)`
|
||||
-
|
||||
- product of all contact friction cones
|
||||
* - :math:`\eta`
|
||||
-
|
||||
- upper bounds on friction loss forces
|
||||
* - :math:`\Omega(q)`
|
||||
-
|
||||
- convex set of admissible constraint forces
|
||||
* - :math:`\mathcal{E}, \mathcal{F}, \mathcal{C}`
|
||||
-
|
||||
- index sets for Equality, Friction loss, Contact constraints
|
||||
|
||||
The index sets will be used to refer to parts of vectors and matrices. For example, :math:`J_\mathcal{C}` is the
|
||||
sub-matrix of all rows of the Jacobian that correspond to contact constraints.
|
||||
@@ -1048,7 +1070,7 @@ explain what it means and why it makes sense. That problem is
|
||||
.. math::
|
||||
(\dot{v}, \dot{\omega}) = \arg \min_{(x, y)}
|
||||
\left\|x-M^{-1}(\tau-c)\right\|^2_M +
|
||||
\left\|y-a^*\right\|^{\text{Huber}(\eta)}_{R^{-1}} \\
|
||||
\left\|y-\ar\right\|^{\text{Huber}(\eta)}_{R^{-1}} \\
|
||||
\textrm{subject to} \;
|
||||
J_\mathcal{E} x_\mathcal{E} - y_\mathcal{E} = 0, \;
|
||||
J_\mathcal{F} x_\mathcal{F} - y_\mathcal{F} = 0, \;
|
||||
@@ -1056,15 +1078,15 @@ explain what it means and why it makes sense. That problem is
|
||||
:label: eq:primal
|
||||
|
||||
The new players here are the diagonal regularizer :math:`R > 0` which makes the constraints soft, and the reference
|
||||
acceleration :math:`a^*` which stabilizes the constraints. The latter is similar in spirit to Baumgarte stabilization,
|
||||
acceleration :math:`\ar` which stabilizes the constraints. The latter is similar in spirit to Baumgarte stabilization,
|
||||
but instead of adding a constraint force directly, it modifies the optimization problem whose solution is the constraint
|
||||
force. Since this problem is itself constrained, the relation between :math:`a^*` and :math:`f` is generally non-linear.
|
||||
The quantities :math:`R` and :math:`a^*` are computed from the solver :ref:`parameters <soParameters>` as described
|
||||
force. Since this problem is itself constrained, the relation between :math:`\ar` and :math:`f` is generally non-linear.
|
||||
The quantities :math:`R` and :math:`\ar` are computed from the solver :ref:`parameters <soParameters>` as described
|
||||
later. For now we assume they are given.
|
||||
|
||||
The optimization variable :math:`x` stands for acceleration as in the Gauss principle, while :math:`y` is a slack
|
||||
variable in constraint space. It is needed to model soft constraints. If we forced the solution to reach
|
||||
:math:`y = a^*`, which we could do by taking the limit :math:`R \to 0`, we would obtain a hard constraint model. This
|
||||
:math:`y = \ar`, which we could do by taking the limit :math:`R \to 0`, we would obtain a hard constraint model. This
|
||||
limit is not allowed in MuJoCo, but nevertheless one can construct models that are phenomenologically hard.
|
||||
|
||||
The symbol :math:`\mathcal{K}^*` denotes the dual to the friction cone. It is motivated by mathematical reverse
|
||||
@@ -1095,7 +1117,7 @@ constraint space (which the constraint force aims to prevent), there is no posit
|
||||
\tilde{q} &= {q \brack z}, &
|
||||
\tilde{v} &= {v \brack \omega}, &
|
||||
\tilde{c} &= {c \brack 0}, \\
|
||||
\tilde{\tau} &= {\tau \brack {R^{-1} a^*}}, &
|
||||
\tilde{\tau} &= {\tau \brack {R^{-1} \ar}}, &
|
||||
\tilde{M} &= \left[\begin{array}{cc}
|
||||
M & 0 \\
|
||||
0 & R^{-1}
|
||||
@@ -1114,20 +1136,20 @@ Unpacking all the tildes yields the explicit form of the original and the deform
|
||||
.. math::
|
||||
\begin{aligned}
|
||||
M \dot{v} + c &= \tau +J^T f \\
|
||||
\dot{\omega} &= a^* - R f \\
|
||||
\dot{\omega} &= \ar - R f \\
|
||||
\end{aligned}
|
||||
|
||||
Thus :math:`R` has the meaning of inverse deformation inertia, while :math:`a^*` has the meaning of unforced deformation
|
||||
Thus :math:`R` has the meaning of inverse deformation inertia, while :math:`\ar` has the meaning of unforced deformation
|
||||
acceleration.
|
||||
|
||||
Does MuJoCo keep these deformation variables as part of the system state and integrate their dynamics together with the
|
||||
joint positions and velocities? No, although such an option may be worth providing in the future. Recall that we defined
|
||||
the functional dependence of the regularizer and the reference acceleration as :math:`R(q)` and :math:`a^*(q, v)`. This
|
||||
the functional dependence of the regularizer and the reference acceleration as :math:`R(q)` and :math:`\ar(q, v)`. This
|
||||
makes problem :eq:`eq:primal` dependent only on :math:`(q, v, \tau)`, and so the original dynamics are not actually
|
||||
affected by the deformation dynamics. Since the general constraint model we developed up to now makes no assumptions
|
||||
about how :math:`R` and :math:`a^*` are computed, our choice is consistent and improves simulator efficiency.
|
||||
about how :math:`R` and :math:`\ar` are computed, our choice is consistent and improves simulator efficiency.
|
||||
Nevertheless, given that these quantities turned out to be related to the deformation dynamics, it may be more natural
|
||||
to define them as :math:`R(z)` and :math:`a^* (z, \omega)` and simulate the entire augmented system. Below we clarify
|
||||
to define them as :math:`R(z)` and :math:`\ar (z, \omega)` and simulate the entire augmented system. Below we clarify
|
||||
some of the benefits of such a simulation.
|
||||
|
||||
When do the deformation dynamics "track" the original dynamics exactly? One can verify that this happens when the
|
||||
@@ -1159,7 +1181,7 @@ we obtain the unconstrained problem
|
||||
|
||||
.. math::
|
||||
\dot{v} = \arg \min_{x} \left\|x-M^{-1}(\tau-c)\right\|^2_M +
|
||||
s \left( J x - a^* \right)
|
||||
s \left( J x - \ar \right)
|
||||
:label: eq:reduced
|
||||
|
||||
The function :math:`s(\cdot)` plays the role of a soft-constraint penalty. It can be shown to be convex and
|
||||
@@ -1169,7 +1191,7 @@ Another appealing feature of the reduced formulation is that the inverse dynamic
|
||||
above problem is unconstrained and convex, the unique global minimum makes the gradient vanish. This yields the identity
|
||||
|
||||
.. math::
|
||||
M \dot{v} + c = \tau - J^T \nabla s \left( J \dot{v} - a^* \right)
|
||||
M \dot{v} + c = \tau - J^T \nabla s \left( J \dot{v} - \ar \right)
|
||||
|
||||
which is the analytical inverse dynamics in the presence of soft constraints. Comparing to the equations of motion
|
||||
:eq:`eq:motion`, we see that the constraint forces :math:`f` are given by the negative gradient of the function
|
||||
@@ -1191,7 +1213,7 @@ Lagrange dual to the primal problem defined above is
|
||||
|
||||
.. math::
|
||||
f = \arg\min_\lambda \frac{1}{2} \lambda^{T} \left( A+R \right) \lambda +
|
||||
\lambda^T \left( a^0 - a^* \right) \\
|
||||
\lambda^T \left( \au - \ar \right) \\
|
||||
\text{subject to} \; \lambda \in \Omega
|
||||
:label: eq:dual
|
||||
|
||||
@@ -1203,7 +1225,7 @@ where the inverse inertia in constraint space is
|
||||
and the unconstrained acceleration in constraint space is
|
||||
|
||||
.. math::
|
||||
a^0 = J M^{-1} (\tau-c) + \dot{J} v
|
||||
\au = J M^{-1} (\tau-c) + \dot{J} v
|
||||
|
||||
The constraint set :math:`\Omega` is as follows. :math:`\lambda_\mathcal{E}` is unconstrained, because it is the
|
||||
Lagrange multiplier for an equality constraint in the primal problem. For friction loss we have the box constraint
|
||||
@@ -1218,28 +1240,28 @@ problem are described later.
|
||||
As mentioned earlier, MuJoCo's constraint model has uniquely-defined inverse dynamics, and we already saw one way to
|
||||
derive it in the reduced formulation above. Here we derive it again from the dual formulation. Recall that in inverse
|
||||
dynamics we have access to :math:`(q, v, \dot{v})` instead of :math:`(q, v, \tau)`, so the unconstrained acceleration
|
||||
:math:`a^0` is unknown. However we can compute the constrained acceleration
|
||||
:math:`\au` is unknown. However we can compute the constrained acceleration
|
||||
|
||||
.. math::
|
||||
a^1 = J \dot{v} + \dot{J} v
|
||||
\ac = J \dot{v} + \dot{J} v
|
||||
|
||||
Inverse dynamics can now be computed by solving the optimization problem
|
||||
|
||||
.. math::
|
||||
f = \arg \min_\lambda \frac{1}{2} \lambda^{T} R \lambda +
|
||||
\lambda^T \left( a^1 - a^* \right) \\
|
||||
\lambda^T \left( \ac - \ar \right) \\
|
||||
\text{subject to} \; \lambda \in \Omega
|
||||
|
||||
By comparing the KKT conditions for these two convex optimization problems, one can verify that their solutions coincide
|
||||
when
|
||||
|
||||
.. math::
|
||||
a^1 = a^0 + Af
|
||||
\ac = \au + Af
|
||||
:label: eq:identity
|
||||
|
||||
This key identity is essentially Newton's second law projected in constraint space. It is derived by moving the term
|
||||
:math:`c` in the equations of motion :eq:`eq:motion` to the right hand side, multiplying by :math:`J M^{-1}` from the
|
||||
left, adding :math:`\dot{J} v` to both sides, and substituting the above definitions of :math:`A, a^0, a^1`. In terms of
|
||||
left, adding :math:`\dot{J} v` to both sides, and substituting the above definitions of :math:`A, \au, \ac`. In terms of
|
||||
implementation, we do not actually compute the acceleration term :math:`\dot{J} v`. This is because our optimization
|
||||
problems depend on differences of constraint-space accelerations, and so this term would cancel out even if we were to
|
||||
compute it.
|
||||
@@ -1316,12 +1338,12 @@ representations of the constraint Jacobian and related matrices.
|
||||
Parameters
|
||||
~~~~~~~~~~
|
||||
|
||||
Here we explain how the quantities :math:`R, a^*` are computed from model parameters. For the chosen parameterization to
|
||||
make sense, we first need to understand how these quantities affect the dynamics. We focus on the unconstrained
|
||||
minimizer of :eq:`eq:dual`, namely
|
||||
Here we explain how the quantities :math:`R, \ar` are computed from model parameters. For the chosen
|
||||
parameterization to make sense, we first need to understand how these quantities affect the dynamics. We focus on the
|
||||
unconstrained minimizer of :eq:`eq:dual`, namely
|
||||
|
||||
.. math::
|
||||
f^+ = (A+R)^{-1} (a^* - a^0)
|
||||
f^+ = (A+R)^{-1} (\ar - \au)
|
||||
|
||||
If it happens that :math:`f^+ \in \Omega`, then :math:`f^+ = f` is the actual constraint force generated by our model.
|
||||
We focus on this case because it is common, in the sense that the subset of the constraints in :math:`\Omega` that are
|
||||
@@ -1329,11 +1351,11 @@ active at any given time is usually small, and furthermore it is the only case t
|
||||
Substituting :math:`f^+` in the constraint dynamics :eq:`eq:identity` and rearranging terms yields
|
||||
|
||||
.. math::
|
||||
a^1 = A(A+R)^{-1} a^* + R (A+R)^{-1} a^0
|
||||
\ac = A(A+R)^{-1} \ar + R (A+R)^{-1} \au
|
||||
|
||||
Thus the constrained acceleration interpolates between the unconstrained and the reference acceleration. In particular,
|
||||
in the limit :math:`R \to 0` we have a hard constraint and :math:`a^1 = a^*`, while in the limit :math:`R \to \infty` we
|
||||
have have an infinitely soft constraint (i.e., no constraint) and :math:`a^1 = a^0`. It is then natural to introduce a
|
||||
in the limit :math:`R \to 0` we have a hard constraint and :math:`\ac = \ar`, while in the limit :math:`R \to \infty` we
|
||||
have have an infinitely soft constraint (i.e., no constraint) and :math:`\ac = \au`. It is then natural to introduce a
|
||||
model parameter which directly controls the interpolation. We call this parameter *impedance* and denote it :math:`d`.
|
||||
It is a vector with dimensionality :math:`n_C` satisfying :math:`0<d<1` element-wise. Once it is specified, we compute
|
||||
the diagonal elements of the regularizer as
|
||||
@@ -1349,7 +1371,7 @@ approximation happened to be exact, and :math:`A` itself happened to be diagonal
|
||||
constraint would satisfy
|
||||
|
||||
.. math::
|
||||
a^1_i = d_i a^*_i + (1-d_i) a^0_i
|
||||
\aci = d_i \ari + (1-d_i) \aui
|
||||
|
||||
and so we would achieve the desired interpolation effect. This of course does not hold exactly in general, but the goal
|
||||
here is to construct a sensible and intuitive parameterization of the constraint model and get the scaling right.
|
||||
@@ -1358,14 +1380,14 @@ Next we explain how the reference acceleration is computed. As already mentioned
|
||||
parameterized by *damping* and *stiffness* coefficients element-wise:
|
||||
|
||||
.. math::
|
||||
a^*_i = -b_i (J v)_i - k_i r_i
|
||||
\ari = -b_i (J v)_i - k_i r_i
|
||||
|
||||
Recall that :math:`r` is the position residual (which is zero for friction loss and friction dimensions of elliptic
|
||||
cones), while :math:`J v` is the joint velocity projected in constraint space; the indexing notation refers to one
|
||||
component of the projected velocity vector.
|
||||
|
||||
To summarize, the user specifies the vectors of impedance coefficients :math:`0<d<1`, damping coefficients :math:`b > 0`
|
||||
and stiffness coefficients :math:`k > 0`. The quantities :math:`R, a^*` are then computed by MuJoCo as shown above, and
|
||||
and stiffness coefficients :math:`k > 0`. The quantities :math:`R, \ar` are then computed by MuJoCo as shown above, and
|
||||
the selected optimization algorithm is applied to solve problem :eq:`eq:dual`. As explained in the :ref:`solver
|
||||
parameters <CSolver>` section of the Modeling chapter, MuJoCo offers additional automation for setting :math:`d, b, k`
|
||||
so as to achieve critical damping, or model a soft contact layer by varying :math:`d` with distance.
|
||||
@@ -1534,6 +1556,7 @@ The top-level function :ref:`mj_step` invokes the sequence of computations below
|
||||
the subtree centers of mass.
|
||||
#. Compute all passive forces: spring-dampers in joints and tendons, and fluid dynamics forces.
|
||||
#. Compute sensor data that depends on velocity, and the kinetic energy if enabled.
|
||||
If required by sensors, call :ref:`mj_subtreeVel`.
|
||||
#. Compute the reference constraint acceleration.
|
||||
#. Compute the vector of Coriolis, centrifugal and gravitational forces.
|
||||
#. Compute the actuator forces and activation dynamics if defined.
|
||||
@@ -1541,6 +1564,7 @@ The top-level function :ref:`mj_step` invokes the sequence of computations below
|
||||
#. Compute the constraint forces with the selected solver, and update the joint acceleration so as to account for the
|
||||
constraint forces. This yields the vector ``mjData.qacc`` which is the main output of forward dynamics.
|
||||
#. Compute sensor data that depends on force and acceleration if enabled.
|
||||
If required by sensors, call :ref:`mj_rnePostConstraint`.
|
||||
#. Check the acceleration for invalid or unacceptably large real values. If divergence is detected, the state is
|
||||
automatically reset and the corresponding warning is raised.
|
||||
#. Compare the results of forward and inverse dynamics, so as to diagnose poor solver convergence in the forward
|
||||
@@ -1567,6 +1591,7 @@ The top-level function :ref:`mj_inverse` invokes the following sequence of compu
|
||||
#. Compute the tendon and actuator velocities.
|
||||
#. Compute the body velocities and joint axes rates of change.
|
||||
#. Compute sensor data that depends on velocity, and the kinetic energy if enabled.
|
||||
If required by sensors, call :ref:`mj_subtreeVel`.
|
||||
#. Compute all passive forces.
|
||||
#. Compute the reference constraint acceleration.
|
||||
#. If the :ref:`invdiscrete<option-flag-invdiscrete>` flag is set and the :ref:`integrator<option-integrator>` is not
|
||||
@@ -1574,6 +1599,7 @@ The top-level function :ref:`mj_inverse` invokes the following sequence of compu
|
||||
#. Compute the constraint force. This is done analytically, without using a numerical solver.
|
||||
#. Compute the inverse dynamics for the unconstrained system.
|
||||
#. Compute sensor data that depends on force and acceleration if enabled.
|
||||
If required by sensors, call :ref:`mj_rnePostConstraint`.
|
||||
#. Compute the vector ``mjData.qfrc_inverse`` by combining all results. This is the main output of inverse dynamics. It
|
||||
equals the sum of external and actuation forces.
|
||||
|
||||
|
||||
@@ -191,8 +191,15 @@ favicons = [
|
||||
# -- Options for katex ------------------------------------------------------
|
||||
|
||||
# See: https://sphinxcontrib-katex.readthedocs.io/en/0.4.1/macros.html
|
||||
# {ar au, ac} are {reference, unconstrained, constrained} acceleration, resp.
|
||||
latex_macros = r"""
|
||||
\def \d #1{\operatorname{#1}}
|
||||
\def \ar {a_{\rm ref}}
|
||||
\def \au {a_0}
|
||||
\def \ac {a_1}
|
||||
\def \ari {a_{{\rm ref},i}}
|
||||
\def \aui {a_{0,i}}
|
||||
\def \aci {a_{1,i}}
|
||||
"""
|
||||
|
||||
# Translate LaTeX macros to KaTeX and add to options for HTML builder
|
||||
|
||||
@@ -19,9 +19,14 @@ body {
|
||||
}
|
||||
|
||||
/* Make the search bar query text readable in dark theme. */
|
||||
body:not([data-theme="light"]) {
|
||||
body[data-theme="dark"] {
|
||||
--color-sidebar-search-foreground: white;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body[data-theme="auto"] {
|
||||
--color-sidebar-search-foreground: white;
|
||||
}
|
||||
}
|
||||
|
||||
.schema-small {
|
||||
font-size: 93%;
|
||||
|
||||
@@ -84,7 +84,7 @@ struct mjContact_ { // result of collision detection functions
|
||||
// contact parameters set by near-phase collision function
|
||||
mjtNum dist; // distance between nearest points; neg: penetration
|
||||
mjtNum pos[3]; // position of contact point: midpoint between geoms
|
||||
mjtNum frame[9]; // normal is in [0-2]
|
||||
mjtNum frame[9]; // normal is in [0-2], points from geom[0] to geom[1]
|
||||
|
||||
// contact parameters set by mj_collideGeoms
|
||||
mjtNum includemargin; // include if dist<includemargin=margin-gap
|
||||
@@ -99,8 +99,8 @@ struct mjContact_ { // result of collision detection functions
|
||||
|
||||
// contact descriptors set by mj_collideXXX
|
||||
int dim; // contact space dimensionality: 1, 3, 4 or 6
|
||||
int geom1; // id of geom 1
|
||||
int geom2; // id of geom 2
|
||||
int geom1; // id of geom 1; deprecated, use geom[0]
|
||||
int geom2; // id of geom 2; deprecated, use geom[1]
|
||||
int geom[2]; // geom ids; -1 for flex
|
||||
int flex[2]; // flex ids; -1 for geom
|
||||
int elem[2]; // element ids; -1 for geom or flex vertex
|
||||
@@ -292,7 +292,11 @@ struct mjData_ {
|
||||
mjtNum* qfrc_bias; // C(qpos,qvel) (nv x 1)
|
||||
|
||||
// computed by mj_fwdVelocity/mj_passive
|
||||
mjtNum* qfrc_passive; // passive force (nv x 1)
|
||||
mjtNum* qfrc_spring; // passive spring force (nv x 1)
|
||||
mjtNum* qfrc_damper; // passive damper force (nv x 1)
|
||||
mjtNum* qfrc_gravcomp; // passive gravity compensation force (nv x 1)
|
||||
mjtNum* qfrc_fluid; // passive fluid force (nv x 1)
|
||||
mjtNum* qfrc_passive; // total passive force (nv x 1)
|
||||
|
||||
// computed by mj_sensorVel/mj_subtreeVel if needed
|
||||
mjtNum* subtree_linvel; // linear velocity of subtree com (nbody x 3)
|
||||
@@ -728,6 +732,7 @@ struct mjOption_ { // physics options
|
||||
int mpr_iterations; // maximum number of MPR solver iterations
|
||||
int disableflags; // bit flags for disabling standard features
|
||||
int enableflags; // bit flags for enabling optional features
|
||||
int disableactuator; // bit flags for disabling actuators by group id
|
||||
|
||||
// sdf collision settings
|
||||
int sdf_initpoints; // number of starting points for gradient descent
|
||||
@@ -796,6 +801,7 @@ struct mjVisual_ { // visualization options
|
||||
float framewidth; // bodyframe axis width
|
||||
float constraint; // constraint width
|
||||
float slidercrank; // slidercrank width
|
||||
float frustum; // frustum zfar plane
|
||||
} scale;
|
||||
|
||||
struct { // color of decor elements
|
||||
@@ -821,6 +827,7 @@ struct mjVisual_ { // visualization options
|
||||
float constraint[4]; // constraint
|
||||
float slidercrank[4]; // slidercrank
|
||||
float crankbroken[4]; // used when crank must be stretched/broken
|
||||
float frustum[4]; // camera frustum
|
||||
} rgba;
|
||||
};
|
||||
typedef struct mjVisual_ mjVisual;
|
||||
@@ -944,7 +951,7 @@ struct mjModel_ {
|
||||
int* body_treeid; // id of body's kinematic tree; -1: static (nbody x 1)
|
||||
int* body_geomnum; // number of geoms (nbody x 1)
|
||||
int* body_geomadr; // start addr of geoms; -1: no geoms (nbody x 1)
|
||||
mjtByte* body_simple; // 1: diagonal M; 2: diag M, no rotations (nbody x 1)
|
||||
mjtByte* body_simple; // 1: diag M; 2: diag M, sliders only (nbody x 1)
|
||||
mjtByte* body_sameframe; // inertial frame is same as body frame (nbody x 1)
|
||||
mjtNum* body_pos; // position offset rel. to parent body (nbody x 3)
|
||||
mjtNum* body_quat; // orientation offset rel. to parent body (nbody x 4)
|
||||
@@ -1122,6 +1129,7 @@ struct mjModel_ {
|
||||
mjtNum* flex_edgedamping; // edge damping (nflex x 1)
|
||||
mjtByte* flex_edgeequality; // is edge equality constraint defined (nflex x 1)
|
||||
mjtByte* flex_rigid; // are all verices in the same body (nflex x 1)
|
||||
mjtByte* flexedge_rigid; // are both edge vertices in same body (nflexedge x 1)
|
||||
mjtByte* flex_centered; // are all vertex coordinates (0,0,0) (nflex x 1)
|
||||
mjtByte* flex_flatskin; // render flex skin with flat shading (nflex x 1)
|
||||
int* flex_bvhadr; // address of bvh root; -1: no bvh (nflex x 1)
|
||||
@@ -1425,6 +1433,9 @@ struct mjpPlugin_ {
|
||||
// called during compilation for marching cubes
|
||||
mjtNum (*sdf_staticdistance)(const mjtNum point[3], const mjtNum* attributes);
|
||||
|
||||
// convert attributes and provide defaults if not present
|
||||
void (*sdf_attribute)(mjtNum attribute[], const char* name[], const char* value[]);
|
||||
|
||||
// bounding box of implicit surface
|
||||
void (*sdf_aabb)(mjtNum aabb[6], const mjtNum* attributes);
|
||||
};
|
||||
@@ -2130,6 +2141,7 @@ struct mjvSceneState_ {
|
||||
int nskin;
|
||||
int nflex;
|
||||
int nflexvert;
|
||||
int nflextexcoord;
|
||||
int nskinvert;
|
||||
int nskinface;
|
||||
int nskinbone;
|
||||
@@ -2194,6 +2206,7 @@ struct mjvSceneState_ {
|
||||
|
||||
mjtNum* cam_fovy;
|
||||
mjtNum* cam_ipd;
|
||||
float* cam_intrinsic;
|
||||
float* cam_sensorsize;
|
||||
|
||||
mjtByte* light_directional;
|
||||
@@ -2219,6 +2232,7 @@ struct mjvSceneState_ {
|
||||
int* flex_shell;
|
||||
int* flex_shellnum;
|
||||
int* flex_shelldataadr;
|
||||
int* flex_texcoordadr;
|
||||
int* flex_bvhadr;
|
||||
int* flex_bvhnum;
|
||||
mjtNum* flex_radius;
|
||||
|
||||
+21
-12
@@ -1,6 +1,8 @@
|
||||
==========
|
||||
MuJoCo XLA
|
||||
==========
|
||||
.. _Mjx:
|
||||
|
||||
================
|
||||
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
|
||||
@@ -94,7 +96,7 @@ Neither ``mjx.Model`` nor ``mjx.Data`` are meant to be constructed manually. An
|
||||
mjx_model = mjx.device_put(model)
|
||||
mjx_data = mjx.make_data(model)
|
||||
|
||||
Using ``mx.make_data`` may be preferable when constructing batched ``mjx.Data`` structures inside of a ``vmap``.
|
||||
Using ``mjx.make_data`` may be preferable when constructing batched ``mjx.Data`` structures inside of a ``vmap``.
|
||||
|
||||
.. _MjxFunctions:
|
||||
|
||||
@@ -186,7 +188,9 @@ The following features are **fully supported** in MJX:
|
||||
* - :ref:`Geom <mjtGeom>`
|
||||
- ``PLANE``, ``SPHERE``, ``CAPSULE``, ``BOX``, ``MESH``
|
||||
* - :ref:`Constraint <mjtConstraint>`
|
||||
- ``EQUALITY``, ``FRICTION_DOF``, ``LIMIT_JOINT``, ``CONTACT_PYRAMIDAL``
|
||||
- ``EQUALITY``, ``LIMIT_JOINT``, ``CONTACT_PYRAMIDAL``
|
||||
* - :ref:`Equality <mjtEq>`
|
||||
- ``CONNECT``, ``WELD``, ``JOINT``
|
||||
* - :ref:`Integrator <mjtIntegrator>`
|
||||
- ``EULER``, ``RK4``
|
||||
* - :ref:`Cone <mjtCone>`
|
||||
@@ -194,7 +198,7 @@ The following features are **fully supported** in MJX:
|
||||
* - :ref:`Condim <coContact>`
|
||||
- 3
|
||||
* - :ref:`Solver <mjtSolver>`
|
||||
- ``CG``
|
||||
- ``CG``, ``NEWTON``
|
||||
* - Fluid Model
|
||||
- :ref:`flInertia`
|
||||
|
||||
@@ -214,18 +218,22 @@ The following features are **in development** and coming soon:
|
||||
- ``TRN_TENDON``
|
||||
* - :ref:`Geom <mjtGeom>`
|
||||
- ``HFIELD``, ``ELLIPSOID``, ``CYLINDER``, ``SDF``
|
||||
* - :ref:`Constraint <mjtConstraint>`
|
||||
- ``CONTACT_FRICTIONLESS``, ``CONTACT_ELLIPTIC``, ``FRICTION_DOF``
|
||||
* - :ref:`Integrator <mjtIntegrator>`
|
||||
- ``IMPLICIT``, ``IMPLICITFAST``
|
||||
* - :ref:`Cone <mjtCone>`
|
||||
- ``ELLIPTIC``
|
||||
* - :ref:`Condim <coContact>`
|
||||
- 1, 4, 6
|
||||
* - :ref:`Solver <mjtSolver>`
|
||||
- ``NEWTON``
|
||||
* - Fluid Model
|
||||
- :ref:`flEllipsoid`
|
||||
* - :ref:`Tendons <tendon>`
|
||||
- :ref:`Spatial <tendon-spatial>`, :ref:`Fixed <tendon-fixed>`
|
||||
* - :ref:`Equality <mjtEq>`
|
||||
- ``TENDON``
|
||||
* - :ref:`Sensors <mjtSensor>`
|
||||
- All except ``mjSENS_PLUGIN``, ``mjSENS_USER``
|
||||
|
||||
The following features are **unsupported**:
|
||||
|
||||
@@ -296,10 +304,11 @@ Performance tuning
|
||||
For MJX to perform well, some configuration parameters should be adjusted from their default MuJoCo values:
|
||||
|
||||
:ref:`option` element
|
||||
For now, solver must be set to ``CG`` (but Newton is on its way!). The ``iterations`` and ``ls_iterations``
|
||||
attributes---which control solver and linesearch iterations, respectively---should be brought down to just low enough
|
||||
that the simulation remains stable. Accurate solver forces are not so important in reinforcement learning in which
|
||||
domain randomization is often used to add noise to physics for sim2real.
|
||||
The ``iterations`` and ``ls_iterations`` attributes---which control solver and linesearch iterations, respectively---
|
||||
should be brought down to just low enough that the simulation remains stable. Accurate solver forces are not so
|
||||
important in reinforcement learning in which domain randomization is often used to add noise to physics for sim-to-real.
|
||||
The ``NEWTON`` :ref:`Solver <mjtSolver>` often delivers reasonable convergence with one solver iteration, and performs
|
||||
well on GPU. ``CG`` is currently a better choice for TPU.
|
||||
|
||||
:ref:`contact-pair` element
|
||||
Consider explicitly marking geoms for collision detection to reduce the number of contacts that MJX must consider
|
||||
|
||||
+145
-90
@@ -190,7 +190,7 @@ element, it must be undefined in the active defaults class.
|
||||
|
||||
A final twist here is actuators. They are different because some of the actuator-related elements are actually
|
||||
shortcuts, and shortcuts interact with the defaults setting mechanism in a non-obvious way. This is explained in the
|
||||
:ref:`Actuator shortcuts <CActuator>` section below.
|
||||
:ref:`Actuator shortcuts <CActShortcuts>` section below.
|
||||
|
||||
.. _CFrame:
|
||||
|
||||
@@ -198,8 +198,8 @@ Coordinate frames
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The positions and orientations of all elements defined in the kinematic tree are expressed in local coordinates,
|
||||
relative to the parent body for bodies, and relative to the body that owns the element for geoms, joints, sites, cameras
|
||||
and lights.
|
||||
relative to the parent body for bodies, and relative to the body that contains the element for geoms, joints, sites,
|
||||
cameras and lights.
|
||||
|
||||
A related attribute is :ref:`compiler/angle<compiler-angle>`. It specifies whether angles in the MJCF file are expressed
|
||||
in degrees or radians (after compilation, angles are always expressed in radians).
|
||||
@@ -259,14 +259,14 @@ available in :ref:`option <option>`; it can be used to change all contact-relate
|
||||
experiment interactively with parameter settings or implement continuation methods for numerical optimization.
|
||||
|
||||
Here we focus on a single scalar constraint. Using slightly different notation from the Computation chapter, let
|
||||
:math:`a_1` denote the acceleration, :math:`v` the velocity, :math:`r` the position or residual (defined as 0 in
|
||||
:math:`\ac` denote the acceleration, :math:`v` the velocity, :math:`r` the position or residual (defined as 0 in
|
||||
friction dimensions), :math:`k` and :math:`b` the stiffness and damping of the virtual spring used to define the
|
||||
reference acceleration :math:`a_{\rm ref} = -b v - k r`. Let :math:`d` be the constraint impedance, and :math:`a_0` the
|
||||
reference acceleration :math:`\ar = -b v - k r`. Let :math:`d` be the constraint impedance, and :math:`\au` the
|
||||
acceleration in the absence of constraint force. Our earlier analysis revealed that the dynamics in constraint space are
|
||||
approximately
|
||||
|
||||
.. math::
|
||||
a_1 + d \cdot (b v + k r) = (1 - d)\cdot a_0
|
||||
\ac + d \cdot (b v + k r) = (1 - d)\cdot \au
|
||||
|
||||
Again, the parameters that are under the user's control are :math:`d, b, k`. The remaining quantities are functions of
|
||||
the system state and are computed automatically at each time step.
|
||||
@@ -278,15 +278,16 @@ Impedance
|
||||
|
||||
We begin by explaining the constraint impedance :math:`d`.
|
||||
|
||||
.. admonition:: Intuitive description
|
||||
.. admonition:: Intuitive description of the **impedance**
|
||||
|
||||
The *impedance* :math:`d \in (0, 1)` determines a constraint's **ability to generate force**.
|
||||
The *impedance* :math:`d \in (0, 1)` corresponds to a constraint's **ability to generate force**.
|
||||
Small values of :math:`d` correspond to weak constraints while large values of :math:`d`
|
||||
correspond to strong constraints. Impedance is set using the :at:`solimp` attribute.
|
||||
correspond to strong constraints. The impedance affects the constraint at all times, in particular when the system is
|
||||
at rest. Impedance is set using the :at:`solimp` attribute.
|
||||
|
||||
Recall that :math:`d` must lie between 0 and 1; internally MuJoCo clamps it to the range [:ref:`mjMINIMP mjMAXIMP
|
||||
<glNumeric>`] which is currently set to [0.0001 0.9999]. It causes the solver to interpolate between the unforced
|
||||
acceleration :math:`a_0` and reference acceleration :math:`a_{\rm ref}`. The user can set :math:`d` to a constant, or
|
||||
acceleration :math:`\au` and reference acceleration :math:`\ar`. The user can set :math:`d` to a constant, or
|
||||
take advantage of its interpolating property and make it position-dependent, i.e., a function of the constraint
|
||||
violation :math:`r`. Position-dependent impedance can be used to model soft contact layers around objects, or define
|
||||
equality constraints that become stronger with larger violation (so as to approximate backlash, for example). The shape
|
||||
@@ -333,15 +334,26 @@ Reference
|
||||
^^^^^^^^^
|
||||
|
||||
Next we explain the setting of the stiffness :math:`k` and damping :math:`b` which control the reference acceleration
|
||||
:math:`a_{\rm ref}`.
|
||||
:math:`\ar`.
|
||||
|
||||
.. admonition:: Intuitive description
|
||||
.. admonition:: Intuitive description of the **reference acceleration**
|
||||
|
||||
The *reference acceleration* :math:`a_{\rm ref}` determines **what the constraint is trying to achieve** (as opposed
|
||||
to how well it can achieve it). This acceleration is defined by two numbers, a stiffness :math:`k` and damping
|
||||
:math:`b` which can be set directly or re-parameterized as the time-constant and damping ratio of a
|
||||
mass-spring-damper system (a `harmonic oscillator <https://en.wikipedia.org/wiki/Harmonic_oscillator>`__).
|
||||
The reference acceleration is controlled by the :at:`solref` attribute.
|
||||
The *reference acceleration* :math:`\ar` determines the **motion that constraint is trying to achieve** in
|
||||
order to rectify violation. For example, consider a contact between a motionless free body pulled down by gravity
|
||||
onto a static plane geom. Since there is no motion, the penetration will be entirely determined by the impedance
|
||||
while the reference has no effect. Now imagine that the body is dropped onto the plane. Upon impact the constraint
|
||||
will generate a normal force which attempts to rectify the penetration using a particular motion; this motion is
|
||||
the reference acceleration.
|
||||
|
||||
Another way of understanding the reference acceleration is to think of the unmodeled deformation variables
|
||||
described in the :ref:`Computation chapter<soPrimal>`. Imagine two bodies pressed together, leading to deformation at
|
||||
the contact. Now pull the bodies apart very quickly; the motion of the deformation as it settles into its undeformed
|
||||
state is the reference acceleration.
|
||||
|
||||
This acceleration is defined by two numbers, a stiffness :math:`k` and damping :math:`b` which can be set directly or
|
||||
re-parameterized as the time-constant and damping ratio of a mass-spring-damper system (a `harmonic oscillator
|
||||
<https://en.wikipedia.org/wiki/Harmonic_oscillator>`__). The reference acceleration is controlled by the :at:`solref`
|
||||
attribute.
|
||||
|
||||
There are two formats for this attribute, determined by the sign of the numbers. If both numbers are positive the
|
||||
specification is considered to be in the :math:`(\text{timeconst}, \text{dampratio})` format. If negative it is in the
|
||||
@@ -441,7 +453,7 @@ solref, solimp
|
||||
If one of the two geoms has higher priority, its solref and solimp parameters are used. If both geoms have the same
|
||||
priority, the weighted average is used. The weights are proportional to the solmix attributes, i.e., weight1 =
|
||||
solmix1 / (solmix1 + solmix2) and similarly for weight2. There is one important exception to this weighted averaging
|
||||
rule. If solref for either geom is non-positive, i.e., it relies on the new direct format introduced in MuJoCo 2.0,
|
||||
rule. If solref for either geom is non-positive, i.e., it relies on the direct format,
|
||||
then the element-wise minimum is used regardless of solmix. This is because averaging solref parameters in different
|
||||
formats would be meaningless.
|
||||
|
||||
@@ -555,10 +567,44 @@ general guidelines and observations:
|
||||
setup operation for the main PGS and Noslip PGS is the same, thus the setup cost is paid only once when both are
|
||||
enabled.
|
||||
|
||||
.. _CActuator:
|
||||
.. _CActuators:
|
||||
|
||||
Actuator shortcuts
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
Actuators
|
||||
~~~~~~~~~
|
||||
|
||||
This section describes various aspects of using actuators in MuJoCo. See the :ref:`Actuation model <geActuation>`
|
||||
regarding the computational model.
|
||||
|
||||
.. _CActDisable:
|
||||
|
||||
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
|
||||
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
|
||||
:align: right
|
||||
:width: 40%
|
||||
|
||||
The :ref:`actuatorgroupdisable<option-actuatorgroupdisable>` MJCF attribute selects which groups are disabled by
|
||||
default, and :ref:`mjOption.disableactuator<mjOption>` can be set at runtime to switch the active set. Note that the
|
||||
total number of actuators ``mjModel.nu`` remains unchanged, as do the actuator indices, so it is up to the user to know
|
||||
that the respective ``mjData.ctrl`` values of disabled actuators will be ignored and produce no force. `This example
|
||||
model <https://github.com/google-deepmind/mujoco/blob/main/test/engine/testdata/actuation/actuator_group_disable.xml>`__
|
||||
has three actuator groups which can be toggled at runtime in the :ref:`simulate<saSimulate>` interactive viewer.
|
||||
See `example model
|
||||
<https://github.com/google-deepmind/mujoco/blob/main/test/engine/testdata/actuation/actuator_group_disable.xml>`__
|
||||
and associated screen-capture on the right.
|
||||
|
||||
.. _CActShortcuts:
|
||||
|
||||
Shortcuts
|
||||
^^^^^^^^^
|
||||
|
||||
As explained in the :ref:`Actuation model <geActuation>` section of the Computation chapter, MuJoCo offers a flexible
|
||||
actuator model with transmission, activation dynamics and force generation components that can be specified
|
||||
@@ -593,8 +639,8 @@ explicitly.
|
||||
|
||||
.. _CForceRange:
|
||||
|
||||
Actuator force clamping
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Force limits
|
||||
^^^^^^^^^^^^
|
||||
|
||||
Actuator forces are usually limited between lower and upper bounds. These limits can be enforced in three ways:
|
||||
|
||||
@@ -633,62 +679,14 @@ Force clamping at joint input with :ref:`joint/actuatorfrcrange<body-joint-actua
|
||||
|
||||
The three clamping options above are non-exclusive and can be combined as required.
|
||||
|
||||
.. _CActRange:
|
||||
|
||||
Activation clamping
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As described in the :ref:`Actuation model <geActuation>` section of the Computation chapter, MuJoCo supports actuators
|
||||
with internal dynamics whose states are called "activations". One useful application of these stateful actuators is the
|
||||
"integrated-velocity" actuator, implemented by the :ref:`intvelocity<actuator-intvelocity>` shortcut. Different from the
|
||||
:ref:`pure velocity<actuator-velocity>` actuators, which implement direct feedback on transmission target's velocity,
|
||||
*integrated-velocity* actuators couple an *integrator* with a *position-feedback* actuator. In this case the semantics
|
||||
of the activation state are "the setpoint of the position actuator", and the semantics of the control signal are "the
|
||||
velocity of the setpoint of the position actuator". Note that in real robotic systems this integrated-velocity actuator
|
||||
is the most common implementation of actuators with velocity semantics, rather than pure feedback on velocity which is
|
||||
often quite unstable (both in real life and in simulation).
|
||||
|
||||
In the case of integrated-velocity actuators, it is often desirable to *clamp* the activation state, since otherwise the
|
||||
position target would keep integrating beyond the joint limits, leading to loss of controllabillity. To see the effect
|
||||
of activation clamping, load the example model below:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<mujoco>
|
||||
<default>
|
||||
<joint axis="0 0 1" limited="true" range="-90 90" damping="0.3"/>
|
||||
<geom size=".1 .1 .1" type="box"/>
|
||||
</default>
|
||||
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint name="joint1"/>
|
||||
<geom/>
|
||||
</body>
|
||||
<body pos=".3 0 0">
|
||||
<joint name="joint2"/>
|
||||
<geom/>
|
||||
</body>
|
||||
</worldbody>
|
||||
|
||||
<actuator>
|
||||
<general name="unclamped" joint="joint1" gainprm="1" biastype="affine"
|
||||
biasprm="0 -1" dyntype="integrator"/>
|
||||
<intvelocity name="clamped" joint="joint2" actrange="-1.57 1.57"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
|
||||
Note that the :at:`actrange` attribute is always specified in native units (radians), even though the joint range
|
||||
can be either in degrees (the default) or radians, depending on the :ref:`compiler/angle <compiler>` attribute.
|
||||
|
||||
.. _CLengthRange:
|
||||
|
||||
Actuator length range
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
Length range
|
||||
^^^^^^^^^^^^
|
||||
|
||||
As of MuJoCo 2.0, the field mjModel.actuator_lengthrange contains the range of feasible actuator lengths (or more
|
||||
precisely, lengths of the actuator's transmission). This is needed to simulate :ref:`muscle actuators <CMuscle>` as
|
||||
explained below. Here we focus on what actuator_lengthrange means and how to set it.
|
||||
The field ``mjModel.actuator_lengthrange`` contains the range of feasible actuator lengths (or more
|
||||
precisely, lengths of the actuator's transmission). This is needed to simulate :ref:`muscle actuators <CMuscle>`.
|
||||
Here we focus on what actuator_lengthrange means and how to set it.
|
||||
|
||||
Unlike all other fields of mjModel which are exact physical or geometric quantities, actuator_lengthrange is an
|
||||
approximation. Intuitively it corresponds to the minimum and maximum length that the actuator's transmission can reach
|
||||
@@ -732,12 +730,69 @@ practice length ranges will almost always be used with muscle actuators attached
|
||||
joint limits defined in the model, effectively limiting the lengths of the muscle actuators. If you get a convergence
|
||||
error in such a model, the most likely explanation is that you forgot to include joint limits.
|
||||
|
||||
.. _CActivation:
|
||||
|
||||
Stateful actuators
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
As described in the :ref:`Actuation model <geActuation>` section of the Computation chapter, MuJoCo supports actuators
|
||||
with internal dynamics whose states are called "activations".
|
||||
|
||||
.. _CActRange:
|
||||
|
||||
Activation limits
|
||||
'''''''''''''''''
|
||||
|
||||
One useful application of stateful actuators is the
|
||||
"integrated-velocity" actuator, implemented by the :ref:`intvelocity<actuator-intvelocity>` shortcut. Different from the
|
||||
:ref:`pure velocity<actuator-velocity>` actuators, which implement direct feedback on transmission target's velocity,
|
||||
*integrated-velocity* actuators couple an *integrator* with a *position-feedback* actuator. In this case the semantics
|
||||
of the activation state are "the setpoint of the position actuator", and the semantics of the control signal are "the
|
||||
velocity of the setpoint of the position actuator". Note that in real robotic systems this integrated-velocity actuator
|
||||
is the most common implementation of actuators with velocity semantics, rather than pure feedback on velocity which is
|
||||
often quite unstable (both in real life and in simulation).
|
||||
|
||||
In the case of integrated-velocity actuators, it is often desirable to *clamp* the activation state, since otherwise the
|
||||
position target would keep integrating beyond the joint limits, leading to loss of controllabillity. To see the effect
|
||||
of activation clamping, load the example model below:
|
||||
|
||||
.. collapse:: Example model with activation limits
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<mujoco>
|
||||
<default>
|
||||
<joint axis="0 0 1" limited="true" range="-90 90" damping="0.3"/>
|
||||
<geom size=".1 .1 .1" type="box"/>
|
||||
</default>
|
||||
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint name="joint1"/>
|
||||
<geom/>
|
||||
</body>
|
||||
<body pos=".3 0 0">
|
||||
<joint name="joint2"/>
|
||||
<geom/>
|
||||
</body>
|
||||
</worldbody>
|
||||
|
||||
<actuator>
|
||||
<general name="unclamped" joint="joint1" gainprm="1" biastype="affine"
|
||||
biasprm="0 -1" dyntype="integrator"/>
|
||||
<intvelocity name="clamped" joint="joint2" actrange="-1.57 1.57"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
|
||||
Note that the :at:`actrange` attribute is always specified in native units (radians), even though the joint range
|
||||
can be either in degrees (the default) or radians, depending on the :ref:`compiler/angle <compiler>` attribute.
|
||||
|
||||
.. _CMuscle:
|
||||
|
||||
Muscle actuators
|
||||
~~~~~~~~~~~~~~~~
|
||||
Muscles
|
||||
'''''''
|
||||
|
||||
As of MuJoCo 2.0, we provide a set of tools for modeling biological muscles. Users who want to add muscles with minimum
|
||||
We provide a set of tools for modeling biological muscles. Users who want to add muscles with minimum
|
||||
effort can do so with a single line of XML in the actuator section:
|
||||
|
||||
.. code-block:: xml
|
||||
@@ -937,13 +992,13 @@ be 0. This effect can be approximated by scaling down the muscle force and also
|
||||
|
||||
Tendon wrapping is also more limited in MuJoCo. We allow spheres and infinite cylinders as wrapping objects, and require
|
||||
two wrapping objects to be separated by a fixed site in the tendon path. This is to avoid the need for iterative
|
||||
computations of tendon paths. As of MuJoCo 2.0 we also allow "side sites" to be placed inside the sphere or cylinder,
|
||||
computations of tendon paths. We also allow "side sites" to be placed inside the sphere or cylinder,
|
||||
which causes an inverse wrap: the tendon path is constrained to pass through the object instead of going around it. This
|
||||
can replace torus wrapping objects used in OpenSim to keep the tendon path within a given area. Overall, tendon wrapping
|
||||
is the most challenging part of converting an OpenSim model to a MuJoCo model, and requires some manual work. On the
|
||||
bright side, there is a small number of high-quality OpenSim models in use, so once they are converted we are done.
|
||||
|
||||
Below we illustrate the four types of tendon wrapping available in MuJoCo 2.0. Note that the curved sections of the
|
||||
Below we illustrate the four types of tendon wrapping available. Note that the curved sections of the
|
||||
wrapping tendons are rendered as straight, but the geometry pipeline works with the actual curves and computes their
|
||||
lengths and moments analytically:
|
||||
|
||||
@@ -981,7 +1036,7 @@ Cameras
|
||||
Besides the default, user-controllable, free camera, "fixed" cameras can be attached to the kinematic tree.
|
||||
|
||||
Extrinsics
|
||||
By default, camera frames are attached to the parent body. The optional :ref:`mode<body-camera-mode>` and
|
||||
By default, camera frames are attached to the containing body. The optional :ref:`mode<body-camera-mode>` and
|
||||
:ref:`target<body-camera-target>` attributes can be used to specify camera that track (move with) or target (look at)
|
||||
a body or subtree. Cameras look towards the negative Z axis of the camera frame, while positive X and Y correspond to
|
||||
*right* and *up* in the image plane, respectively.
|
||||
@@ -1003,8 +1058,7 @@ Intrinsics
|
||||
Composite objects
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Composite objects were introduced in MuJoCo 2.0, along with solver optimizations to speed up the simulation of such
|
||||
objects. They are not new model elements. Instead, they are (large) collections of existing elements designed to
|
||||
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
|
||||
@@ -1026,7 +1080,7 @@ of these equality constraints can be adjusted by the user, thereby adjusting the
|
||||
composite objects.
|
||||
|
||||
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>` which are also new in MuJoCo 2.0. The skin is generated
|
||||
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
|
||||
@@ -1034,13 +1088,12 @@ generator places all geoms, sites and tendons in group 3 whose visualization is
|
||||
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 (note that starting with MuJoCo 2.0 we have added a group property to sites, tendons and joints in
|
||||
addition to geoms).
|
||||
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 2.0 comes with an example of each composite object type. Below we go over these
|
||||
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;
|
||||
@@ -1277,6 +1330,8 @@ into the engine in future releases.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<option timestep=".001"/>
|
||||
|
||||
<extension>
|
||||
<plugin plugin="mujoco.elasticity.solid"/>
|
||||
</extension>
|
||||
@@ -1435,7 +1490,7 @@ mocap bodies around:
|
||||
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 2.0 distribution (recall that in
|
||||
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
|
||||
@@ -1587,7 +1642,7 @@ the same yet the damping component of the force is integrated implicitly.
|
||||
Restitution
|
||||
~~~~~~~~~~~
|
||||
|
||||
MuJoCo 2.0 introduced a new mechanism for specifying :at:`solref` as explained in :ref:`Solver parameters <CSolver>`.
|
||||
Another mechanism exists for specifying :at:`solref`, as explained in :ref:`Solver parameters <CSolver>`.
|
||||
When both numbers are non-positive, they are interpreted as (-stiffness, -damping) and scaled by the constraint
|
||||
impedance. To achieve perfect restitution for contacts and other constraints, set stiffness to some reasonably large
|
||||
value and damping to zero. Below is an example of a sphere bouncing on a plane with restitution coefficient of 1, so
|
||||
|
||||
+12
-7
@@ -944,15 +944,20 @@ can be obtained as:
|
||||
int qposadr = -1, qveladr = -1;
|
||||
|
||||
// make sure we have a floating body: it has a single free joint
|
||||
if( bodyid>=0 && m->body_jntnum[bodyid]==1 &&
|
||||
m->jnt_type[m->body_jntadr[bodyid]]==mjJNT_FREE )
|
||||
{
|
||||
// extract the addresses from the joint specification
|
||||
qposadr = m->jnt_qposadr[m->body_jntadr[bodyid]];
|
||||
qveladr = m->jnt_dofadr[m->body_jntadr[bodyid]];
|
||||
}
|
||||
if( bodyid>=0 && m->body_jntnum[bodyid]==1 && m->jnt_type[m->body_jntadr[bodyid]]==mjJNT_FREE ) {
|
||||
// extract the addresses from the joint specification
|
||||
qposadr = m->jnt_qposadr[m->body_jntadr[bodyid]];
|
||||
qveladr = m->jnt_dofadr[m->body_jntadr[bodyid]];
|
||||
}
|
||||
|
||||
Now if everything went well (i.e., "myfloatingbody" was indeed a floating body), qposadr and qveladr are the addresses
|
||||
in qpos and qvel where the data for our floating body/joint lives. 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). These
|
||||
numbers can now be set to the desired pose and velocity of the body.
|
||||
|
||||
The semantics of free joints are as follows. The linear postions 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
|
||||
for a certain orientation, so frame-local angular velocities are a natural parameterization.
|
||||
Accelerations are defined in the same space as the corresponding velocities.
|
||||
|
||||
@@ -250,20 +250,24 @@ A future version of this section will include:
|
||||
|
||||
Currently, there are three directories of first-party plugins:
|
||||
|
||||
* **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 3-dimensional bodies. The
|
||||
1D model is invariant under rotations and captures the large deformation of elastic cables, decoupling twisting and
|
||||
bending strains. The 3D solid is a
|
||||
`Saint Venant-Kirchhoff <https://en.wikipedia.org/wiki/Hyperelastic_material#Saint_Venant%E2%80%93Kirchhoff_model>`__
|
||||
model discretized with piecewise linear finite elements, which is suitable for large deformations with small strains.
|
||||
See also :ref:`composite <CComposite>` and :ref:`deformable <CDeformable>` objects.
|
||||
* **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 3-dimensional bodies. The 1D model is invariant under rotations and captures
|
||||
the large deformation of elastic cables, decoupling twisting and bending strains. The 3D solid is a `Saint
|
||||
Venant-Kirchhoff <https://en.wikipedia.org/wiki/Hyperelastic_material#Saint_Venant%E2%80%93Kirchhoff_model>`__ model
|
||||
discretized with piecewise linear finite elements, which is suitable for large deformations with small strains. See
|
||||
also :ref:`composite <CComposite>` and :ref:`deformable <CDeformable>` objects. 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>`__.
|
||||
* **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 maximum of 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.
|
||||
|
||||
@@ -35,7 +35,7 @@ Thread
|
||||
to process Tasks asynchronously. To enable use in MuJoCo, create a ThreadPool and assign it to the thread_pool field
|
||||
in mjData.
|
||||
UI framework
|
||||
The UI framework (new in MuJoCo 2.0) is written in C. UI elements are rendered in OpenGL. It has its own event
|
||||
The UI framework is written in C. UI elements are rendered in OpenGL. It has its own event
|
||||
mechanism and abstract hooks for keyboard and mouse input. The code samples use it with GLFW, but it can also be used
|
||||
with other window libraries.
|
||||
|
||||
@@ -275,5 +275,6 @@ now lazily resolved at runtime after the switch to GLAD, the "nogl" libraries ar
|
||||
|
||||
simulation
|
||||
visualization
|
||||
ui
|
||||
samples
|
||||
extension
|
||||
|
||||
+29
-28
@@ -73,33 +73,6 @@ Where the command line arguments are
|
||||
`start /affinity <https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/start>`__
|
||||
command on Windows (processor affinity cannot be specified through documented API means on macOS).
|
||||
|
||||
.. _saCompile:
|
||||
|
||||
`compile <https://github.com/google-deepmind/mujoco/blob/main/sample/compile.cc>`_
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This code sample evokes the built-in parser and compiler. It implements all possible model conversions from (MJCF, URDF,
|
||||
MJB) format to (MJCF, MJB, TXT) format. Models saved as MJCF use a canonical subset of our format as described in the
|
||||
:doc:`../modeling` chapter, and therefore MJCF-to-MJCF conversion will generally result in a different file.
|
||||
The TXT format is a human-readable road-map to the model. It cannot be loaded by MuJoCo, but can be a very useful aid
|
||||
during model development. It is in one-to-one correspondence with the compiled mjModel. Note also that one can use the
|
||||
function :ref:`mj_printData` to create a text file which is in one-to-one correspondence
|
||||
with mjData, although this is not done by the code sample.
|
||||
|
||||
.. _saBasic:
|
||||
|
||||
`basic <https://github.com/google-deepmind/mujoco/blob/main/sample/basic.cc>`_
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This code sample is a minimal interactive simulator. The model file must be provided as command-line argument. It
|
||||
opens an OpenGL window using the platform-independent GLFW library, and renders the simulation state at 60 fps while
|
||||
advancing the simulation in real-time. Press Backspace to reset the simulation. The mouse can be used to control the
|
||||
camera: left drag to rotate, right drag to translate in the vertical plane, shift right drag to translate in the
|
||||
horizontal plane, scroll or middle drag to zoom.
|
||||
|
||||
The :ref:`Visualization` programming guide below explains how visualization works. This code sample is a minimal
|
||||
illustration of the concepts in that guide.
|
||||
|
||||
.. _saSimulate:
|
||||
|
||||
`simulate <https://github.com/google-deepmind/mujoco/blob/main/simulate>`_
|
||||
@@ -108,10 +81,11 @@ illustration of the concepts in that guide.
|
||||
This code sample is a fully-featured interactive simulator. It opens an OpenGL window using the platform-independent
|
||||
GLFW library, and renders the simulation state in it. There is built-in help, simulation statistics, profiler, sensor
|
||||
data plots. The model file can be specified as a command-line argument, or loaded at runtime using drag-and-drop
|
||||
functionality. As of MuJoCo 2.0, this code sample uses the native UI to render various controls, and provides an
|
||||
functionality. This code sample uses the native UI to render various controls, and provides an
|
||||
illustration of how the new UI framework is intended to be used. Below is a screen-capture of ``simulate`` in action:
|
||||
|
||||
.. youtube:: 0ORsj_E17B0
|
||||
:width: 95%
|
||||
:align: center
|
||||
|
||||
Interaction is done with the mouse; built-in help with a summary of available commands is available by pressing the
|
||||
@@ -140,6 +114,33 @@ power settings, the OS may reduce the CPU frequency; this is because :ref:`simul
|
||||
the time in order to slow down to realtime. This results in inaccurate timings. To avoid this problem, change the
|
||||
Windows power plan so that the minimum processor state is 100%.
|
||||
|
||||
.. _saCompile:
|
||||
|
||||
`compile <https://github.com/google-deepmind/mujoco/blob/main/sample/compile.cc>`_
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This code sample evokes the built-in parser and compiler. It implements all possible model conversions from (MJCF, URDF,
|
||||
MJB) format to (MJCF, MJB, TXT) format. Models saved as MJCF use a canonical subset of our format as described in the
|
||||
:doc:`../modeling` chapter, and therefore MJCF-to-MJCF conversion will generally result in a different file.
|
||||
The TXT format is a human-readable road-map to the model. It cannot be loaded by MuJoCo, but can be a very useful aid
|
||||
during model development. It is in one-to-one correspondence with the compiled mjModel. Note also that one can use the
|
||||
function :ref:`mj_printData` to create a text file which is in one-to-one correspondence
|
||||
with mjData, although this is not done by the code sample.
|
||||
|
||||
.. _saBasic:
|
||||
|
||||
`basic <https://github.com/google-deepmind/mujoco/blob/main/sample/basic.cc>`_
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This code sample is a minimal interactive simulator. The model file must be provided as command-line argument. It
|
||||
opens an OpenGL window using the platform-independent GLFW library, and renders the simulation state at 60 fps while
|
||||
advancing the simulation in real-time. Press Backspace to reset the simulation. The mouse can be used to control the
|
||||
camera: left drag to rotate, right drag to translate in the vertical plane, shift right drag to translate in the
|
||||
horizontal plane, scroll or middle drag to zoom.
|
||||
|
||||
The :ref:`Visualization` programming guide below explains how visualization works. This code sample is a minimal
|
||||
illustration of the concepts in that guide.
|
||||
|
||||
.. _saRecord:
|
||||
|
||||
`record <https://github.com/google-deepmind/mujoco/blob/main/sample/record.cc>`_
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
.. _UI:
|
||||
|
||||
User Interface
|
||||
--------------
|
||||
|
||||
MuJoCo has a native UI framework. Its use is illustrated in the :ref:`simulate.cc <saSimulate>` viewer. It is
|
||||
designed to be fast in terms of updating and rendering, easy to use both for the developer and for the user,
|
||||
cross-platform, and integrated with the native MuJoCo renderer. In order to achieve these design goals, we have omitted
|
||||
many features and customization options that are available in other UI frameworks, and instead focused on efficiency and
|
||||
automation.
|
||||
|
||||
|
||||
|
||||
.. _uiDesign:
|
||||
|
||||
Design overview
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
Native OpenGL rendering
|
||||
We do not use any helper tools or libraries. Instead we provide C code for rendering all UI elements directly in
|
||||
OpenGL. We support multiple UIs, each of which is a virtual rectangle that can be taller than the visible window. The
|
||||
elements of each UI are rendered offscreen in auxiliary OpenGL buffers, via minimal updates, only when changes are
|
||||
necessary. At each screen refresh we then copy the pixels from these auxiliary buffers to the window framebuffer, and
|
||||
also implement vertical scroll bars when the window is smaller than the UI. This copy operation is done on the GPU and
|
||||
is very fast.
|
||||
|
||||
|
||||
Platform abstraction
|
||||
The software design has 3 layers: OpenGL rendering of UI elements working in conjunction with the MuJoCo renderer
|
||||
(which is fully cross-platform); abstract functions for access to windows, keyboard and mouse, defined as pure virtual
|
||||
functions in the ``PlatformUIAdapter`` class; and an implementation of those functions in the derived class
|
||||
``GlfwAdapter``. `GLFW <https://www.glfw.org/>`__ itself is cross-platform. Nevertheless, we have opted for this
|
||||
layered design in order to separate generic from platform-specific functionality. If GLFW needs to be replaced with
|
||||
another similar framework for some reason, only ``GlfwAdapter`` will need to be rewritten.
|
||||
|
||||
|
||||
Themes and appearance
|
||||
Individual UI elements do not allow customization in terms of appearance or layout. Instead, we use themes for colors
|
||||
and spacing, and arrange all UI elements automatically. Several built-in themes are provided and the user can design
|
||||
custom themes, however the entire UI uses a single theme for all elements. Appearance is minimalist: mostly colored
|
||||
rectangles with text. Bitmaps and other custom decorations are not supported. The UI element types are check boxes,
|
||||
radio button groups, selection lists, sliders, text edit boxes, static text, buttons, separators. These elements are
|
||||
grouped into sections which can be expanded and collapsed.
|
||||
|
||||
|
||||
Layout and rectangles
|
||||
Each UI is one virtual rectangle, whose width is determined by the theme and whose height is determined by the
|
||||
sections, items within each section, and also the expand/collapse state of each section. The sizes and auxiliary
|
||||
buffers for these virtual rectangles are handled automatically when the UI is updated. Each UI has a visible rectangle
|
||||
on the screen, and in addition there are other rectangles -- for 3D rendering, 2D figures, and possibly custom OpenGL
|
||||
rendering. All these visible rectangles are saved (in :ref:`mjuiState`) and are used to determine where mouse events
|
||||
should be directed. The rectangle layout is updated by a callback provided by the user.
|
||||
|
||||
|
||||
Static allocation and creation
|
||||
Rather than allocating and deallocating a large number of objects corresponding to UI elements and linking them
|
||||
together, we create a single C struct (type :ref:`mjUI`) with static allocation supporting some maximum number of
|
||||
sections and elements; and then keep a record of how many are in use. UI creation is simplified by helper functions
|
||||
whose input is a C struct (type :ref:`mjuiDef`) that is essentially a table where each row describes one UI element
|
||||
(see below). This makes it possible to construct elaborate user interfaces with surprisingly little C code.
|
||||
Programmatic UI creation is also possible, for example when populating a UI with sliders corresponding to MuJoCo model
|
||||
joints.
|
||||
|
||||
|
||||
Minimal state
|
||||
The UI is designed to be as stateless as possible, so as to simplify development. This has two aspects. First, instead
|
||||
of replicating user data within the UI elements, we store pointers to user data. For example, we might create a UI
|
||||
slider and set its data pointer to ``mjData* d->qpos+7``. This slider will visualize as well as control the 7th scalar
|
||||
component of the qpos vector of a MuJoCo model. Thus, when the simulation is updated, we have to remember to update
|
||||
the UI as well. And furthermore we have to disable UI editing when the simulation is being updated. But the advantage
|
||||
is that the UI becomes easier to construct, and there is no danger of discrepancies between user data and the UI.
|
||||
Second, the UI elements themselves are mostly stateless. Instead we keep track of a minimal set of global states, in
|
||||
particular mouse and keyboard state, section expand/collapse, contents of the text box being edited if any.
|
||||
|
||||
|
||||
Automated enable and disable
|
||||
While each UI item can be set in enabled or disabled state directly, we also provide automation as follows. Each UI
|
||||
item can be assigned an integer category. Then a :ref:`mjfItemEnable` callback determines whether each category should
|
||||
be enabled or disabled, based on some program-specific conditions. For example, sliders that can change the values of
|
||||
MuJoCo model joints should be disabled when the simulation state is being updated.
|
||||
|
||||
|
||||
|
||||
.. _uiAPI:
|
||||
|
||||
Main API
|
||||
~~~~~~~~
|
||||
|
||||
Click on the links below for detailed API reference of the main UI data structures and functions.
|
||||
|
||||
|
||||
**Main data structures:**
|
||||
|
||||
- :ref:`mjUI<mjUI>`: An entire UI.
|
||||
- :ref:`mjuiState<mjuiState>`: Global UI state.
|
||||
- :ref:`mjuiDef<mjuiDef>`: One entry in the definition table used for UI construction.
|
||||
|
||||
|
||||
**Main functions:**
|
||||
|
||||
- :ref:`mjui_update<mjui_update>`: Main UI update function.
|
||||
- :ref:`mjui_render<mjui_render>`: Renders the UI.
|
||||
- :ref:`mjui_event<mjui_event>`: Low-level event handler.
|
||||
- :ref:`mjui_add<mjui_add>`: Helper function used to construct a UI.
|
||||
+1
-1
@@ -215,7 +215,7 @@ Optionally, ``viewer.launch_passive`` accepts the following keyword arguments.
|
||||
viewer.sync()
|
||||
...
|
||||
|
||||
- ``show_left_ui_panel`` and ``show_right_ui_panel``: Boolean arguments indicating whether UI panels should be visible
|
||||
- ``show_left_ui`` and ``show_right_ui``: Boolean arguments indicating whether UI panels should be visible
|
||||
or hidden when the viewer is launched. Note that regardless of the values specified, the user can still toggle the
|
||||
visibility of these panels after launch by pressing Tab or Shift+Tab.
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ struct mjContact_ { // result of collision detection functions
|
||||
// contact parameters set by near-phase collision function
|
||||
mjtNum dist; // distance between nearest points; neg: penetration
|
||||
mjtNum pos[3]; // position of contact point: midpoint between geoms
|
||||
mjtNum frame[9]; // normal is in [0-2]
|
||||
mjtNum frame[9]; // normal is in [0-2], points from geom[0] to geom[1]
|
||||
|
||||
// contact parameters set by mj_collideGeoms
|
||||
mjtNum includemargin; // include if dist<includemargin=margin-gap
|
||||
@@ -115,8 +115,8 @@ struct mjContact_ { // result of collision detection functions
|
||||
|
||||
// contact descriptors set by mj_collideXXX
|
||||
int dim; // contact space dimensionality: 1, 3, 4 or 6
|
||||
int geom1; // id of geom 1
|
||||
int geom2; // id of geom 2
|
||||
int geom1; // id of geom 1; deprecated, use geom[0]
|
||||
int geom2; // id of geom 2; deprecated, use geom[1]
|
||||
int geom[2]; // geom ids; -1 for flex
|
||||
int flex[2]; // flex ids; -1 for geom
|
||||
int elem[2]; // element ids; -1 for geom or flex vertex
|
||||
@@ -320,7 +320,11 @@ struct mjData_ {
|
||||
mjtNum* qfrc_bias; // C(qpos,qvel) (nv x 1)
|
||||
|
||||
// computed by mj_fwdVelocity/mj_passive
|
||||
mjtNum* qfrc_passive; // passive force (nv x 1)
|
||||
mjtNum* qfrc_spring; // passive spring force (nv x 1)
|
||||
mjtNum* qfrc_damper; // passive damper force (nv x 1)
|
||||
mjtNum* qfrc_gravcomp; // passive gravity compensation force (nv x 1)
|
||||
mjtNum* qfrc_fluid; // passive fluid force (nv x 1)
|
||||
mjtNum* qfrc_passive; // total passive force (nv x 1)
|
||||
|
||||
// computed by mj_sensorVel/mj_subtreeVel if needed
|
||||
mjtNum* subtree_linvel; // linear velocity of subtree com (nbody x 3)
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
#define mjDISABLED(x) (m->opt.disableflags & (x))
|
||||
#define mjENABLED(x) (m->opt.enableflags & (x))
|
||||
|
||||
// is actuator disabled
|
||||
#define mjACTUATORDISABLED(i) (m->opt.disableactuator & (1 << m->actuator_group[i]))
|
||||
|
||||
// annotation for functions that accept printf-like variadic arguments
|
||||
#ifndef mjPRINTFLIKE
|
||||
#if defined(__GNUC__)
|
||||
|
||||
@@ -433,6 +433,7 @@ struct mjOption_ { // physics options
|
||||
int mpr_iterations; // maximum number of MPR solver iterations
|
||||
int disableflags; // bit flags for disabling standard features
|
||||
int enableflags; // bit flags for enabling optional features
|
||||
int disableactuator; // bit flags for disabling actuators by group id
|
||||
|
||||
// sdf collision settings
|
||||
int sdf_initpoints; // number of starting points for gradient descent
|
||||
@@ -505,6 +506,7 @@ struct mjVisual_ { // visualization options
|
||||
float framewidth; // bodyframe axis width
|
||||
float constraint; // constraint width
|
||||
float slidercrank; // slidercrank width
|
||||
float frustum; // frustum zfar plane
|
||||
} scale;
|
||||
|
||||
struct { // color of decor elements
|
||||
@@ -530,6 +532,7 @@ struct mjVisual_ { // visualization options
|
||||
float constraint[4]; // constraint
|
||||
float slidercrank[4]; // slidercrank
|
||||
float crankbroken[4]; // used when crank must be stretched/broken
|
||||
float frustum[4]; // camera frustum
|
||||
} rgba;
|
||||
};
|
||||
typedef struct mjVisual_ mjVisual;
|
||||
@@ -661,7 +664,7 @@ struct mjModel_ {
|
||||
int* body_treeid; // id of body's kinematic tree; -1: static (nbody x 1)
|
||||
int* body_geomnum; // number of geoms (nbody x 1)
|
||||
int* body_geomadr; // start addr of geoms; -1: no geoms (nbody x 1)
|
||||
mjtByte* body_simple; // 1: diagonal M; 2: diag M, no rotations (nbody x 1)
|
||||
mjtByte* body_simple; // 1: diag M; 2: diag M, sliders only (nbody x 1)
|
||||
mjtByte* body_sameframe; // inertial frame is same as body frame (nbody x 1)
|
||||
mjtNum* body_pos; // position offset rel. to parent body (nbody x 3)
|
||||
mjtNum* body_quat; // orientation offset rel. to parent body (nbody x 4)
|
||||
@@ -839,6 +842,7 @@ struct mjModel_ {
|
||||
mjtNum* flex_edgedamping; // edge damping (nflex x 1)
|
||||
mjtByte* flex_edgeequality; // is edge equality constraint defined (nflex x 1)
|
||||
mjtByte* flex_rigid; // are all verices in the same body (nflex x 1)
|
||||
mjtByte* flexedge_rigid; // are both edge vertices in same body (nflexedge x 1)
|
||||
mjtByte* flex_centered; // are all vertex coordinates (0,0,0) (nflex x 1)
|
||||
mjtByte* flex_flatskin; // render flex skin with flat shading (nflex x 1)
|
||||
int* flex_bvhadr; // address of bvh root; -1: no bvh (nflex x 1)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <mujoco/mjdata.h>
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include <mujoco/mjtnum.h>
|
||||
#include <mujoco/mjvisualize.h>
|
||||
|
||||
|
||||
@@ -117,6 +118,9 @@ struct mjpPlugin_ {
|
||||
// called during compilation for marching cubes
|
||||
mjtNum (*sdf_staticdistance)(const mjtNum point[3], const mjtNum* attributes);
|
||||
|
||||
// convert attributes and provide defaults if not present
|
||||
void (*sdf_attribute)(mjtNum attribute[], const char* name[], const char* value[]);
|
||||
|
||||
// bounding box of implicit surface
|
||||
void (*sdf_aabb)(mjtNum aabb[6], const mjtNum* attributes);
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#include <mujoco/mjrender.h>
|
||||
|
||||
#define mjMAXUISECT 10 // maximum number of sections
|
||||
#define mjMAXUIITEM 100 // maximum number of items per section
|
||||
#define mjMAXUIITEM 200 // maximum number of items per section
|
||||
#define mjMAXUITEXT 300 // maximum number of chars in edittext and other
|
||||
#define mjMAXUINAME 40 // maximum number of chars in name
|
||||
#define mjMAXUIMULTI 35 // maximum number of radio/select items in group
|
||||
|
||||
@@ -422,6 +422,7 @@ struct mjvSceneState_ {
|
||||
int nskin;
|
||||
int nflex;
|
||||
int nflexvert;
|
||||
int nflextexcoord;
|
||||
int nskinvert;
|
||||
int nskinface;
|
||||
int nskinbone;
|
||||
@@ -486,6 +487,7 @@ struct mjvSceneState_ {
|
||||
|
||||
mjtNum* cam_fovy;
|
||||
mjtNum* cam_ipd;
|
||||
float* cam_intrinsic;
|
||||
float* cam_sensorsize;
|
||||
|
||||
mjtByte* light_directional;
|
||||
@@ -511,6 +513,7 @@ struct mjvSceneState_ {
|
||||
int* flex_shell;
|
||||
int* flex_shellnum;
|
||||
int* flex_shelldataadr;
|
||||
int* flex_texcoordadr;
|
||||
int* flex_bvhadr;
|
||||
int* flex_bvhnum;
|
||||
mjtNum* flex_radius;
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
X( int, mpr_iterations ) \
|
||||
X( int, disableflags ) \
|
||||
X( int, enableflags ) \
|
||||
X( int, disableactuator ) \
|
||||
X( int, sdf_initpoints ) \
|
||||
X( int, sdf_iterations )
|
||||
|
||||
@@ -86,7 +87,7 @@
|
||||
X ( nflexelemdata ) \
|
||||
X ( nflexshelldata ) \
|
||||
X ( nflexevpair ) \
|
||||
X ( nflextexcoord ) \
|
||||
XMJV( nflextexcoord ) \
|
||||
XMJV( nmesh ) \
|
||||
X ( nmeshvert ) \
|
||||
X ( nmeshnormal ) \
|
||||
@@ -276,7 +277,7 @@
|
||||
X ( int, cam_targetbodyid, ncam, 1 ) \
|
||||
X ( int, cam_resolution, ncam, 2 ) \
|
||||
XMJV( float, cam_sensorsize, ncam, 2 ) \
|
||||
X ( float, cam_intrinsic, ncam, 4 ) \
|
||||
XMJV( float, cam_intrinsic, ncam, 4 ) \
|
||||
X ( mjtNum, cam_pos, ncam, 3 ) \
|
||||
X ( mjtNum, cam_quat, ncam, 4 ) \
|
||||
X ( mjtNum, cam_poscom0, ncam, 3 ) \
|
||||
@@ -329,7 +330,7 @@
|
||||
XMJV( int, flex_shelldataadr, nflex, 1 ) \
|
||||
X ( int, flex_evpairadr, nflex, 1 ) \
|
||||
X ( int, flex_evpairnum, nflex, 1 ) \
|
||||
X ( int, flex_texcoordadr, nflex, 1 ) \
|
||||
XMJV( int, flex_texcoordadr, nflex, 1 ) \
|
||||
X ( int, flex_vertbodyid, nflexvert, 1 ) \
|
||||
X ( int, flex_edge, nflexedge, 2 ) \
|
||||
XMJV( int, flex_elem, nflexelemdata, 1 ) \
|
||||
@@ -345,6 +346,7 @@
|
||||
X ( mjtNum, flex_edgedamping, nflex, 1 ) \
|
||||
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_flatskin, nflex, 1 ) \
|
||||
XMJV( int, flex_bvhadr, nflex, 1 ) \
|
||||
@@ -617,6 +619,10 @@
|
||||
X ( mjtNum, cvel, nbody, 6 ) \
|
||||
X ( mjtNum, cdof_dot, nv, 6 ) \
|
||||
X ( mjtNum, qfrc_bias, nv, 1 ) \
|
||||
X ( mjtNum, qfrc_spring, nv, 1 ) \
|
||||
X ( mjtNum, qfrc_damper, nv, 1 ) \
|
||||
X ( mjtNum, qfrc_gravcomp, nv, 1 ) \
|
||||
X ( mjtNum, qfrc_fluid, nv, 1 ) \
|
||||
X ( mjtNum, qfrc_passive, nv, 1 ) \
|
||||
X ( mjtNum, subtree_linvel, nbody, 3 ) \
|
||||
X ( mjtNum, subtree_angmom, nbody, 3 ) \
|
||||
|
||||
@@ -336,10 +336,10 @@ MJAPI void mj_solveM2(const mjModel* m, mjData* d, mjtNum* x, const mjtNum* y, i
|
||||
// Compute cvel, cdof_dot.
|
||||
MJAPI void mj_comVel(const mjModel* m, mjData* d);
|
||||
|
||||
// Compute qfrc_passive from spring-dampers, viscosity and density.
|
||||
// Compute qfrc_passive from spring-dampers, gravity compensation and fluid forces.
|
||||
MJAPI void mj_passive(const mjModel* m, mjData* d);
|
||||
|
||||
// subtree linear velocity and angular momentum
|
||||
// Sub-tree linear velocity and angular momentum: compute subtree_linvel, subtree_angmom.
|
||||
MJAPI void mj_subtreeVel(const mjModel* m, mjData* d);
|
||||
|
||||
// RNE: compute M(qpos)*qacc + C(qpos,qvel); flg_acc=0 removes inertial term.
|
||||
|
||||
@@ -1666,7 +1666,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
|
||||
),
|
||||
),
|
||||
),
|
||||
doc='Compute qfrc_passive from spring-dampers, viscosity and density.',
|
||||
doc='Compute qfrc_passive from spring-dampers, gravity compensation and fluid forces.', # pylint: disable=line-too-long
|
||||
)),
|
||||
('mj_subtreeVel',
|
||||
FunctionDecl(
|
||||
@@ -1686,7 +1686,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
|
||||
),
|
||||
),
|
||||
),
|
||||
doc='subtree linear velocity and angular momentum',
|
||||
doc='Sub-tree linear velocity and angular momentum: compute subtree_linvel, subtree_angmom.', # pylint: disable=line-too-long
|
||||
)),
|
||||
('mj_rne',
|
||||
FunctionDecl(
|
||||
|
||||
+78
-6
@@ -276,6 +276,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
|
||||
type=ValueType(name='int'),
|
||||
doc='bit flags for enabling optional features',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='disableactuator',
|
||||
type=ValueType(name='int'),
|
||||
doc='bit flags for disabling actuators by group id',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='sdf_initpoints',
|
||||
type=ValueType(name='int'),
|
||||
@@ -578,6 +583,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
|
||||
type=ValueType(name='float'),
|
||||
doc='slidercrank width',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='frustum',
|
||||
type=ValueType(name='float'),
|
||||
doc='frustum zfar plane',
|
||||
),
|
||||
),
|
||||
),
|
||||
doc='',
|
||||
@@ -762,6 +772,14 @@ STRUCTS: Mapping[str, StructDecl] = dict([
|
||||
),
|
||||
doc='used when crank must be stretched/broken',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='frustum',
|
||||
type=ArrayType(
|
||||
inner_type=ValueType(name='float'),
|
||||
extents=(4,),
|
||||
),
|
||||
doc='camera frustum',
|
||||
),
|
||||
),
|
||||
),
|
||||
doc='',
|
||||
@@ -1311,7 +1329,7 @@ STRUCTS: Mapping[str, StructDecl] = dict([
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='mjtByte'),
|
||||
),
|
||||
doc='1: diagonal M; 2: diag M, no rotations (nbody x 1)',
|
||||
doc='1: diag M; 2: diag M, sliders only (nbody x 1)',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='body_sameframe',
|
||||
@@ -2426,6 +2444,13 @@ STRUCTS: Mapping[str, StructDecl] = dict([
|
||||
),
|
||||
doc='are all verices in the same body (nflex x 1)',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='flexedge_rigid',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='mjtByte'),
|
||||
),
|
||||
doc='are both edge vertices in same body (nflexedge x 1)',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='flex_centered',
|
||||
type=PointerType(
|
||||
@@ -3846,7 +3871,7 @@ STRUCTS: Mapping[str, StructDecl] = dict([
|
||||
inner_type=ValueType(name='mjtNum'),
|
||||
extents=(9,),
|
||||
),
|
||||
doc='normal is in [0-2]',
|
||||
doc='normal is in [0-2], points from geom[0] to geom[1]',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='includemargin',
|
||||
@@ -3906,12 +3931,12 @@ STRUCTS: Mapping[str, StructDecl] = dict([
|
||||
StructFieldDecl(
|
||||
name='geom1',
|
||||
type=ValueType(name='int'),
|
||||
doc='id of geom 1',
|
||||
doc='id of geom 1; deprecated, use geom[0]',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='geom2',
|
||||
type=ValueType(name='int'),
|
||||
doc='id of geom 2',
|
||||
doc='id of geom 2; deprecated, use geom[1]',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='geom',
|
||||
@@ -4673,12 +4698,40 @@ STRUCTS: Mapping[str, StructDecl] = dict([
|
||||
),
|
||||
doc='C(qpos,qvel) (nv x 1)', # pylint: disable=line-too-long
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='qfrc_spring',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='mjtNum'),
|
||||
),
|
||||
doc='passive spring force (nv x 1)', # pylint: disable=line-too-long
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='qfrc_damper',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='mjtNum'),
|
||||
),
|
||||
doc='passive damper force (nv x 1)', # pylint: disable=line-too-long
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='qfrc_gravcomp',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='mjtNum'),
|
||||
),
|
||||
doc='passive gravity compensation force (nv x 1)', # pylint: disable=line-too-long
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='qfrc_fluid',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='mjtNum'),
|
||||
),
|
||||
doc='passive fluid force (nv x 1)', # pylint: disable=line-too-long
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='qfrc_passive',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='mjtNum'),
|
||||
),
|
||||
doc='passive force (nv x 1)', # pylint: disable=line-too-long
|
||||
doc='total passive force (nv x 1)', # pylint: disable=line-too-long
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='subtree_linvel',
|
||||
@@ -6190,6 +6243,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
|
||||
type=ValueType(name='int'),
|
||||
doc='',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='nflextexcoord',
|
||||
type=ValueType(name='int'),
|
||||
doc='',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='nskinvert',
|
||||
type=ValueType(name='int'),
|
||||
@@ -6557,6 +6615,13 @@ STRUCTS: Mapping[str, StructDecl] = dict([
|
||||
),
|
||||
doc='',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='cam_intrinsic',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='float'),
|
||||
),
|
||||
doc='',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='cam_sensorsize',
|
||||
type=PointerType(
|
||||
@@ -6718,6 +6783,13 @@ STRUCTS: Mapping[str, StructDecl] = dict([
|
||||
),
|
||||
doc='',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='flex_texcoordadr',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='int'),
|
||||
),
|
||||
doc='',
|
||||
),
|
||||
StructFieldDecl(
|
||||
name='flex_bvhadr',
|
||||
type=PointerType(
|
||||
@@ -8403,7 +8475,7 @@ STRUCTS: Mapping[str, StructDecl] = dict([
|
||||
name='item',
|
||||
type=ArrayType(
|
||||
inner_type=ValueType(name='mjuiItem'),
|
||||
extents=(100,),
|
||||
extents=(200,),
|
||||
),
|
||||
doc='preallocated array of items',
|
||||
),
|
||||
|
||||
@@ -336,16 +336,28 @@ def collision_candidates(m: Union[Model, mujoco.MjModel]) -> CandidateSet:
|
||||
|
||||
def ncon(m: Model) -> int:
|
||||
"""Returns the number of contacts computed in MJX given a model."""
|
||||
if m.opt.disableflags & DisableBit.CONTACT:
|
||||
return 0
|
||||
|
||||
candidates = collision_candidates(m)
|
||||
max_count = _max_contact_points(m)
|
||||
count = sum([
|
||||
len(v) * get_collision_fn(k[0:2]).ncon for k, v in candidates.items() # pytype: disable=attribute-error
|
||||
])
|
||||
|
||||
count = 0
|
||||
for k, v in candidates.items():
|
||||
fn = get_collision_fn(k[0:2])
|
||||
if fn is None:
|
||||
continue
|
||||
count += len(v) * fn.ncon # pytype: disable=attribute-error
|
||||
|
||||
return min(max_count, count) if max_count > -1 else count
|
||||
|
||||
|
||||
def collision(m: Model, d: Data) -> Data:
|
||||
"""Collides geometries."""
|
||||
ncon_ = ncon(m)
|
||||
if ncon_ == 0:
|
||||
return d.replace(contact=Contact.zero(), ncon=0)
|
||||
|
||||
candidate_set = collision_candidates(m)
|
||||
|
||||
contacts = []
|
||||
@@ -354,7 +366,7 @@ def collision(m: Model, d: Data) -> Data:
|
||||
contacts.append(_collide_geoms(m, d, geom_types, candidates))
|
||||
|
||||
if not contacts:
|
||||
return d.replace(contact=Contact.zero(), ncon=0)
|
||||
raise RuntimeError('No contacts found.')
|
||||
|
||||
contact = jax.tree_map(lambda *x: jp.concatenate(x), *contacts)
|
||||
|
||||
@@ -364,10 +376,13 @@ def collision(m: Model, d: Data) -> Data:
|
||||
_, idx = jax.lax.top_k(-contact.dist, k=max_contact_points)
|
||||
contact = jax.tree_map(lambda x, idx=idx: jp.take(x, idx, axis=0), contact)
|
||||
|
||||
ncon_ = contact.dist.shape[0]
|
||||
if ncon_ != contact.dist.shape[0]:
|
||||
raise RuntimeError('Number of contacts does not match ncon.')
|
||||
|
||||
# TODO(robotics-simulation): move this logic to device_put
|
||||
ns = d.ne + d.nf + d.nl
|
||||
contact = contact.replace(efc_address=np.arange(ns, ns + ncon_ * 4, 4))
|
||||
# TODO(robotics-simulation): add support for other friction dimensions
|
||||
contact = contact.replace(efc_address=np.arange(ns, ns + d.ncon * 4, 4))
|
||||
contact = contact.replace(dim=3 * np.ones(ncon_, dtype=np.int32))
|
||||
|
||||
return d.replace(contact=contact, ncon=ncon_)
|
||||
|
||||
@@ -24,11 +24,14 @@ import jax
|
||||
import jax.numpy as jp
|
||||
import mujoco
|
||||
from mujoco import mjx
|
||||
from mujoco.mjx._src import collision_driver
|
||||
from mujoco.mjx._src import test_util
|
||||
# pylint: disable=g-importing-member
|
||||
from mujoco.mjx._src.types import Contact
|
||||
from mujoco.mjx._src.types import Data
|
||||
from mujoco.mjx._src.types import DisableBit
|
||||
from mujoco.mjx._src.types import Model
|
||||
# pylint: emable=g-importing-member
|
||||
# pylint: enable=g-importing-member
|
||||
import numpy as np
|
||||
|
||||
|
||||
@@ -289,7 +292,7 @@ class ConvexTest(absltest.TestCase):
|
||||
<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 45">
|
||||
<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>
|
||||
@@ -450,6 +453,29 @@ class BodyPairFilterTest(absltest.TestCase):
|
||||
self.assertEqual(dx.contact.pos.shape[0], 1)
|
||||
|
||||
|
||||
class NconTest(parameterized.TestCase):
|
||||
"""Tests ncon."""
|
||||
|
||||
def test_ncon(self):
|
||||
m = test_util.load_test_file('ant.xml')
|
||||
d = mujoco.MjData(m)
|
||||
d.qpos[2] = 0.0
|
||||
|
||||
mx = mjx.device_put(m)
|
||||
ncon = collision_driver.ncon(mx)
|
||||
self.assertEqual(ncon, 4)
|
||||
|
||||
def test_disable_contact(self):
|
||||
m = test_util.load_test_file('ant.xml')
|
||||
d = mujoco.MjData(m)
|
||||
d.qpos[2] = 0.0
|
||||
|
||||
m.opt.disableflags = m.opt.disableflags | DisableBit.CONTACT
|
||||
mx = mjx.device_put(m)
|
||||
ncon = collision_driver.ncon(mx)
|
||||
self.assertEqual(ncon, 0)
|
||||
|
||||
|
||||
class TopKContactTest(absltest.TestCase):
|
||||
"""Tests top-k contacts."""
|
||||
|
||||
|
||||
+186
-143
@@ -14,13 +14,12 @@
|
||||
# ==============================================================================
|
||||
"""Core non-smooth constraint functions."""
|
||||
|
||||
from typing import Tuple
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import jax
|
||||
from jax import numpy as jp
|
||||
import mujoco
|
||||
from mujoco.mjx._src import math
|
||||
from mujoco.mjx._src import scan
|
||||
from mujoco.mjx._src import support
|
||||
# pylint: disable=g-importing-member
|
||||
from mujoco.mjx._src.dataclasses import PyTreeNode
|
||||
@@ -35,16 +34,15 @@ import numpy as np
|
||||
|
||||
|
||||
class _Efc(PyTreeNode):
|
||||
"""Support data for creating constraint matrices."""
|
||||
J: jax.Array
|
||||
R: jax.Array
|
||||
aref: jax.Array
|
||||
pos: jax.Array
|
||||
pos_norm: jax.Array
|
||||
invweight: jax.Array
|
||||
solref: jax.Array
|
||||
solimp: jax.Array
|
||||
frictionloss: jax.Array
|
||||
|
||||
@classmethod
|
||||
def zero(cls, m: Model) -> '_Efc':
|
||||
z = jp.empty((0,))
|
||||
return _Efc(J=jp.empty((0, m.nv)), R=z, aref=z, frictionloss=z)
|
||||
|
||||
|
||||
def _kbi(
|
||||
m: Model,
|
||||
@@ -84,22 +82,18 @@ def _kbi(
|
||||
return k, b, imp # corresponds to K, B, I of efc_KBIP
|
||||
|
||||
|
||||
def _instantiate_connect(m: Model, d: Data) -> _Efc:
|
||||
"""Returns jacobians and supporting data for connect equality constraints."""
|
||||
def _instantiate_equality_connect(m: Model, d: Data) -> Optional[_Efc]:
|
||||
"""Calculates constraint rows for connect equality constraints."""
|
||||
|
||||
if (m.opt.disableflags & DisableBit.EQUALITY) or m.neq == 0:
|
||||
return _Efc.zero(m)
|
||||
ids = np.nonzero(m.eq_type == EqType.CONNECT)[0]
|
||||
|
||||
connect_id = np.nonzero(m.eq_type == EqType.CONNECT)[0]
|
||||
if (m.opt.disableflags & DisableBit.EQUALITY) or ids.size == 0:
|
||||
return None
|
||||
|
||||
if connect_id.size == 0:
|
||||
return _Efc.zero(m)
|
||||
id1, id2, data = m.eq_obj1id[ids], m.eq_obj2id[ids], m.eq_data[ids]
|
||||
|
||||
body1id, body2id = m.eq_obj1id[connect_id], m.eq_obj2id[connect_id]
|
||||
data = m.eq_data[connect_id]
|
||||
solref, solimp = m.eq_solref[connect_id], m.eq_solimp[connect_id]
|
||||
|
||||
def fn(data, id1, id2, solref, solimp):
|
||||
@jax.vmap
|
||||
def fn(data, id1, id2):
|
||||
anchor1, anchor2 = data[0:3], data[3:6]
|
||||
# find global points
|
||||
pos1 = d.xmat[id1] @ anchor1 + d.xpos[id1]
|
||||
@@ -113,35 +107,31 @@ def _instantiate_connect(m: Model, d: Data) -> _Efc:
|
||||
jacp2, _ = support.jac(m, d, pos2, id2)
|
||||
j = (jacp1 - jacp2).T
|
||||
|
||||
# impedance, inverse constraint mass, reference acceleration
|
||||
k, b, imp = _kbi(m, solref, solimp, math.norm(cpos))
|
||||
invweight = m.body_invweight0[id1, 0] + m.body_invweight0[id2, 0]
|
||||
r = jp.maximum(invweight * (1 - imp) / imp, mujoco.mjMINVAL).repeat(3)
|
||||
aref = -b * (j @ d.qvel) - k * imp * cpos
|
||||
return j, cpos, jp.repeat(math.norm(cpos), 3)
|
||||
|
||||
return _Efc(J=j, R=r, aref=aref, frictionloss=jp.zeros_like(r))
|
||||
# concatenate to drop connect grouping dimension
|
||||
j, pos, pos_norm = jax.tree_map(jp.concatenate, fn(data, id1, id2))
|
||||
invweight = m.body_invweight0[id1, 0] + m.body_invweight0[id2, 0]
|
||||
invweight = jp.repeat(invweight, 3)
|
||||
solref = jp.tile(m.eq_solref[ids], (3, 1))
|
||||
solimp = jp.tile(m.eq_solimp[ids], (3, 1))
|
||||
frictionloss = jp.zeros_like(pos_norm)
|
||||
|
||||
efcs = jax.vmap(fn)(data, body1id, body2id, solref, solimp)
|
||||
|
||||
return jax.tree_map(jp.concatenate, efcs)
|
||||
return _Efc(j, pos, pos_norm, invweight, solref, solimp, frictionloss)
|
||||
|
||||
|
||||
def _instantiate_weld(m: Model, d: Data) -> _Efc:
|
||||
"""Returns jacobians and supporting data for connect weld constraints."""
|
||||
def _instantiate_equality_weld(m: Model, d: Data) -> Optional[_Efc]:
|
||||
"""Calculates constraint rows for weld equality constraints."""
|
||||
|
||||
if (m.opt.disableflags & DisableBit.EQUALITY) or m.neq == 0:
|
||||
return _Efc.zero(m)
|
||||
ids = np.nonzero(m.eq_type == EqType.WELD)[0]
|
||||
|
||||
weld_id = np.nonzero(m.eq_type == EqType.WELD)[0]
|
||||
if (m.opt.disableflags & DisableBit.EQUALITY) or ids.size == 0:
|
||||
return None
|
||||
|
||||
if weld_id.size == 0:
|
||||
return _Efc.zero(m)
|
||||
id1, id2, data = m.eq_obj1id[ids], m.eq_obj2id[ids], m.eq_data[ids]
|
||||
|
||||
body1id, body2id = m.eq_obj1id[weld_id], m.eq_obj2id[weld_id]
|
||||
data = m.eq_data[weld_id]
|
||||
solref, solimp = m.eq_solref[weld_id], m.eq_solimp[weld_id]
|
||||
|
||||
def fn(data, id1, id2, solref, solimp):
|
||||
@jax.vmap
|
||||
def fn(data, id1, id2):
|
||||
anchor1, anchor2 = data[0:3], data[3:6]
|
||||
relpose, torquescale = data[6:10], data[10]
|
||||
|
||||
@@ -168,119 +158,159 @@ def _instantiate_weld(m: Model, d: Data) -> _Efc:
|
||||
jacdifr = 0.5 * jax.vmap(jac_fn)(jacdifr)
|
||||
|
||||
j = jp.concatenate((jacdifp.T, jacdifr.T))
|
||||
pos = jp.concatenate((cpos, crot))
|
||||
pos = jp.concatenate((cpos, crot * torquescale))
|
||||
|
||||
# impedance, inverse constraint mass, reference acceleration
|
||||
k, b, imp = _kbi(m, solref, solimp, math.norm(pos.at[3:].mul(torquescale)))
|
||||
invweight = m.body_invweight0[id1] + m.body_invweight0[id2]
|
||||
r = jp.maximum(invweight * (1 - imp) / imp, mujoco.mjMINVAL).repeat(3)
|
||||
aref = -b * (j @ d.qvel) - k * imp * pos
|
||||
return j, pos, jp.repeat(math.norm(pos), 6)
|
||||
|
||||
return _Efc(J=j, R=r, aref=aref, frictionloss=jp.zeros_like(r))
|
||||
# concatenate to drop weld grouping dimension
|
||||
j, pos, pos_norm = jax.tree_map(jp.concatenate, fn(data, id1, id2))
|
||||
invweight = m.body_invweight0[id1] + m.body_invweight0[id2]
|
||||
invweight = jp.repeat(invweight, 3)
|
||||
solref = jp.tile(m.eq_solref[ids], (6, 1))
|
||||
solimp = jp.tile(m.eq_solimp[ids], (6, 1))
|
||||
frictionloss = jp.zeros_like(pos_norm)
|
||||
|
||||
efcs = jax.vmap(fn)(data, body1id, body2id, solref, solimp)
|
||||
|
||||
return jax.tree_map(jp.concatenate, efcs)
|
||||
return _Efc(j, pos, pos_norm, invweight, solref, solimp, frictionloss)
|
||||
|
||||
|
||||
def _instantiate_friction(m: Model, d: Data) -> _Efc:
|
||||
def _instantiate_equality_joint(m: Model, d: Data) -> Optional[_Efc]:
|
||||
"""Calculates constraint rows for joint equality constraints."""
|
||||
|
||||
ids = np.nonzero(m.eq_type == EqType.JOINT)[0]
|
||||
|
||||
if (m.opt.disableflags & DisableBit.EQUALITY) or ids.size == 0:
|
||||
return None
|
||||
|
||||
id1, id2, data = m.eq_obj1id[ids], m.eq_obj2id[ids], m.eq_data[ids]
|
||||
dofadr1, dofadr2 = m.jnt_dofadr[id1], m.jnt_dofadr[id2]
|
||||
qposadr1, qposadr2 = m.jnt_qposadr[id1], m.jnt_qposadr[id2]
|
||||
|
||||
@jax.vmap
|
||||
def fn(data, id2, dofadr1, dofadr2, qposadr1, qposadr2):
|
||||
pos1, pos2 = d.qpos[qposadr1], d.qpos[qposadr2]
|
||||
ref1, ref2 = m.qpos0[qposadr1], m.qpos0[qposadr2]
|
||||
pos2, ref2 = pos2 * (id2 > -1), ref2 * (id2 > -1)
|
||||
|
||||
dif = pos2 - ref2
|
||||
dif_power = jp.power(dif, jp.arange(0, 5))
|
||||
|
||||
deriv = jp.dot(data[1:5], dif_power[:4] * jp.arange(1, 5))
|
||||
j = jp.zeros((m.nv)).at[dofadr1].set(1.0).at[dofadr2].set(-deriv)
|
||||
pos = pos1 - ref1 - jp.dot(data[:5], dif_power)
|
||||
return j, pos
|
||||
|
||||
j, pos = fn(data, id2, dofadr1, dofadr2, qposadr1, qposadr2)
|
||||
invweight = m.dof_invweight0[dofadr1] + m.dof_invweight0[dofadr2] * (id2 > -1)
|
||||
solref, solimp = m.eq_solref[ids], m.eq_solimp[ids]
|
||||
frictionloss = jp.zeros_like(pos)
|
||||
|
||||
return _Efc(j, pos, pos, invweight, solref, solimp, frictionloss)
|
||||
|
||||
|
||||
def _instantiate_friction(m: Model, d: Data) -> Optional[_Efc]:
|
||||
# TODO(robotics-team): implement _instantiate_friction
|
||||
del d
|
||||
return _Efc.zero(m)
|
||||
del m, d
|
||||
return None
|
||||
|
||||
|
||||
def _instantiate_limit(m: Model, d: Data) -> _Efc:
|
||||
"""Returns jacobians and supporting data for joint limits."""
|
||||
def _instantiate_limit_ball(m: Model, d: Data) -> Optional[_Efc]:
|
||||
"""Calculates constraint rows for ball joint limits."""
|
||||
|
||||
if (m.opt.disableflags & DisableBit.LIMIT) or not m.jnt_limited.any():
|
||||
return _Efc.zero(m)
|
||||
ids = np.nonzero((m.jnt_type == JointType.BALL) & m.jnt_limited)[0]
|
||||
|
||||
def fn(jnt_typs, jnt_range, solref, solimp, margin, qpos, dofs, invweight0):
|
||||
js, rs, arefs = [], [], []
|
||||
qpos_i, dof_i = 0, 0
|
||||
if (m.opt.disableflags & DisableBit.LIMIT) or ids.size == 0:
|
||||
return None
|
||||
|
||||
for i in range(len(jnt_typs)):
|
||||
jnt_typ = JointType(jnt_typs[i])
|
||||
jnt_range = m.jnt_range[ids]
|
||||
jnt_margin = m.jnt_margin[ids]
|
||||
qposadr = np.array([np.arange(q, q + 4) for q in m.jnt_qposadr[ids]])
|
||||
dofadr = np.array([np.arange(d, d + 3) for d in m.jnt_dofadr[ids]])
|
||||
|
||||
if jnt_typ == JointType.FREE:
|
||||
return None # omit constraint rows for free joints
|
||||
elif jnt_typ == JointType.BALL:
|
||||
axis, angle = math.quat_to_axis_angle(qpos[qpos_i : qpos_i + 4])
|
||||
dist = jp.amax(jnt_range[i]) - angle
|
||||
j = jp.sum(
|
||||
jax.vmap(jp.multiply)(dofs[dof_i : dof_i + 3], -axis), axis=0
|
||||
)
|
||||
elif jnt_typ in (JointType.HINGE, JointType.SLIDE):
|
||||
dist_min = qpos[qpos_i] - jnt_range[i, 0]
|
||||
dist_max = jnt_range[i, 1] - qpos[qpos_i]
|
||||
dist = jp.minimum(dist_min, dist_max)
|
||||
j = dofs[dof_i] * ((dist_min < dist_max) * 2 - 1)
|
||||
else:
|
||||
raise RuntimeError(f'unrecognized joint type: {jnt_typ}')
|
||||
@jax.vmap
|
||||
def fn(jnt_range, jnt_margin, qposadr, dofadr):
|
||||
axis, angle = math.quat_to_axis_angle(d.qpos[qposadr])
|
||||
j = jp.zeros(m.nv).at[dofadr].set(-axis)
|
||||
pos = jp.amax(jnt_range) - angle - jnt_margin
|
||||
active = pos < 0
|
||||
return j * active, pos * active
|
||||
|
||||
dist = dist - margin[i]
|
||||
k, b, imp = _kbi(m, solref[i], solimp[i], dist)
|
||||
r = jp.maximum(invweight0[dof_i] * (1 - imp) / imp, mujoco.mjMINVAL)
|
||||
aref = -b * (j @ d.qvel) - k * imp * dist
|
||||
j, aref = j * (dist < 0), aref * (dist < 0)
|
||||
js, rs, arefs = js + [j], rs + [r], arefs + [aref]
|
||||
dof_i, qpos_i = dof_i + jnt_typ.dof_width(), qpos_i + jnt_typ.qpos_width()
|
||||
j, pos = fn(jnt_range, jnt_margin, qposadr, dofadr)
|
||||
invweight = m.dof_invweight0[m.jnt_dofadr[ids]]
|
||||
solref, solimp = m.jnt_solref[ids], m.jnt_solimp[ids]
|
||||
frictionloss = jp.zeros_like(pos)
|
||||
|
||||
return jp.stack(js), jp.stack(rs), jp.stack(arefs)
|
||||
|
||||
j, r, aref = scan.flat(
|
||||
m,
|
||||
fn,
|
||||
'jjjjjqvv',
|
||||
'jjj',
|
||||
m.jnt_type,
|
||||
m.jnt_range,
|
||||
m.jnt_solref,
|
||||
m.jnt_solimp,
|
||||
m.jnt_margin,
|
||||
d.qpos,
|
||||
jp.eye(m.nv),
|
||||
m.dof_invweight0,
|
||||
)
|
||||
|
||||
return _Efc(J=j, R=r, aref=aref, frictionloss=jp.zeros_like(r))
|
||||
return _Efc(j, pos, pos, invweight, solref, solimp, frictionloss)
|
||||
|
||||
|
||||
def _instantiate_contact(m: Model, d: Data) -> _Efc:
|
||||
"""Returns jacobians and supporitng data for contacts."""
|
||||
def _instantiate_limit_slide_hinge(m: Model, d: Data) -> Optional[_Efc]:
|
||||
"""Calculates constraint rows for slide and hinge joint limits."""
|
||||
|
||||
slide_hinge = np.isin(m.jnt_type, (JointType.SLIDE, JointType.HINGE))
|
||||
ids = np.nonzero(slide_hinge & m.jnt_limited)[0]
|
||||
|
||||
if (m.opt.disableflags & DisableBit.LIMIT) or ids.size == 0:
|
||||
return None
|
||||
|
||||
jnt_range = m.jnt_range[ids]
|
||||
jnt_margin = m.jnt_margin[ids]
|
||||
qposadr = m.jnt_qposadr[ids]
|
||||
dofadr = m.jnt_dofadr[ids]
|
||||
|
||||
@jax.vmap
|
||||
def fn(jnt_range, jnt_margin, qposadr, dofadr):
|
||||
dist_min = d.qpos[qposadr] - jnt_range[0]
|
||||
dist_max = jnt_range[1] - d.qpos[qposadr]
|
||||
j = jp.zeros(m.nv).at[dofadr].set((dist_min < dist_max) * 2 - 1)
|
||||
pos = jp.minimum(dist_min, dist_max) - jnt_margin
|
||||
active = pos < 0
|
||||
return j * active, pos * active
|
||||
|
||||
j, pos = fn(jnt_range, jnt_margin, qposadr, dofadr)
|
||||
invweight = m.dof_invweight0[dofadr]
|
||||
solref, solimp = m.jnt_solref[ids], m.jnt_solimp[ids]
|
||||
frictionloss = jp.zeros_like(pos)
|
||||
|
||||
return _Efc(j, pos, pos, invweight, solref, solimp, frictionloss)
|
||||
|
||||
|
||||
def _instantiate_contact(m: Model, d: Data) -> Optional[_Efc]:
|
||||
"""Calculates constraint rows for contacts."""
|
||||
|
||||
if (m.opt.disableflags & DisableBit.CONTACT) or d.ncon == 0:
|
||||
return _Efc.zero(m)
|
||||
|
||||
def fn(contact: Contact):
|
||||
dist = contact.dist - contact.includemargin
|
||||
k, b, imp = _kbi(m, contact.solref, contact.solimp, dist)
|
||||
return None
|
||||
|
||||
@jax.vmap
|
||||
def fn(c: Contact):
|
||||
dist = c.dist - c.includemargin
|
||||
geom_bodyid = jp.array(m.geom_bodyid)
|
||||
body1, body2 = geom_bodyid[contact.geom1], geom_bodyid[contact.geom2]
|
||||
diff = support.jac_dif_pair(m, d, contact.pos, body1, body2)
|
||||
body1, body2 = geom_bodyid[c.geom1], geom_bodyid[c.geom2]
|
||||
diff = support.jac_dif_pair(m, d, c.pos, body1, body2)
|
||||
t = m.body_invweight0[body1, 0] + m.body_invweight0[body2, 0]
|
||||
|
||||
# rotate Jacobian differences to contact frame
|
||||
diff_con = contact.frame @ diff.T
|
||||
diff_con = c.frame @ diff.T
|
||||
|
||||
# TODO(robotics-simulation): add support for other friction dimensions
|
||||
# 4 pyramidal friction directions
|
||||
js, rs = [], []
|
||||
for diff_tan, friction in zip(diff_con[1:], contact.friction[:2]):
|
||||
js, invweights = [], []
|
||||
for diff_tan, friction in zip(diff_con[1:], c.friction[:2]):
|
||||
for f in (friction, -friction):
|
||||
js.append(diff_con[0] + diff_tan * f)
|
||||
rs.append((t + f * f * t) * 2 * f * f * (1 - imp) / imp)
|
||||
invweights.append((t + f * f * t) * 2 * f * f)
|
||||
|
||||
j, r = jp.stack(js), jp.stack(rs)
|
||||
r = jp.maximum(r, mujoco.mjMINVAL)
|
||||
aref = -b * (j @ d.qvel) - k * imp * dist
|
||||
mask_fn = jax.vmap(lambda x, mask=(dist < 0): x * mask)
|
||||
j, aref = jax.tree_map(mask_fn, (j, aref))
|
||||
active = dist < 0
|
||||
j, invweight = jp.stack(js) * active, jp.stack(invweights)
|
||||
pos = jp.repeat(dist, 4) * active
|
||||
solref, solimp = jp.tile(c.solref, (4, 1)), jp.tile(c.solimp, (4, 1))
|
||||
|
||||
return _Efc(J=j, R=r, aref=aref, frictionloss=jp.zeros_like(r))
|
||||
return j, invweight, pos, solref, solimp
|
||||
|
||||
return jax.tree_map(jp.concatenate, jax.vmap(fn)(d.contact))
|
||||
res = fn(d.contact)
|
||||
# remove contact grouping dimension:
|
||||
j, invweight, pos, solref, solimp = jax.tree_map(jp.concatenate, res)
|
||||
frictionloss = jp.zeros_like(pos)
|
||||
|
||||
return _Efc(j, pos, pos, invweight, solref, solimp, frictionloss)
|
||||
|
||||
|
||||
def count_constraints(m: Model, d: Data) -> Tuple[int, int, int, int]:
|
||||
@@ -291,18 +321,19 @@ def count_constraints(m: Model, d: Data) -> Tuple[int, int, int, int]:
|
||||
if m.opt.disableflags & DisableBit.EQUALITY:
|
||||
ne = 0
|
||||
else:
|
||||
ne_weld = (m.eq_type == EqType.WELD).sum()
|
||||
ne_connect = (m.eq_type == EqType.CONNECT).sum()
|
||||
ne = ne_weld * 6 + ne_connect * 3
|
||||
ne_weld = (m.eq_type == EqType.WELD).sum()
|
||||
ne_joint = (m.eq_type == EqType.JOINT).sum()
|
||||
ne = ne_connect * 3 + ne_weld * 6 + ne_joint
|
||||
|
||||
nf = 0
|
||||
|
||||
if (m.opt.disableflags & DisableBit.LIMIT) or not m.jnt_limited.any():
|
||||
if m.opt.disableflags & DisableBit.LIMIT:
|
||||
nl = 0
|
||||
else:
|
||||
nl = (m.jnt_type != JointType.FREE).sum()
|
||||
nl = int(m.jnt_limited.sum())
|
||||
|
||||
if (m.opt.disableflags & DisableBit.CONTACT):
|
||||
if m.opt.disableflags & DisableBit.CONTACT:
|
||||
nc = 0
|
||||
else:
|
||||
nc = d.ncon * 4
|
||||
@@ -318,23 +349,35 @@ def make_constraint(m: Model, d: Data) -> Data:
|
||||
d = d.tree_replace({'contact.efc_address': np.arange(ns, ns + d.ncon * 4, 4)})
|
||||
|
||||
if m.opt.disableflags & DisableBit.CONSTRAINT:
|
||||
efc = _Efc.zero(m)
|
||||
efcs = ()
|
||||
else:
|
||||
efcs = (
|
||||
_instantiate_connect(m, d),
|
||||
_instantiate_weld(m, d),
|
||||
efcs = tuple(efc for efc in (
|
||||
_instantiate_equality_connect(m, d),
|
||||
_instantiate_equality_weld(m, d),
|
||||
_instantiate_equality_joint(m, d),
|
||||
_instantiate_friction(m, d),
|
||||
_instantiate_limit(m, d),
|
||||
_instantiate_limit_ball(m, d),
|
||||
_instantiate_limit_slide_hinge(m, d),
|
||||
_instantiate_contact(m, d),
|
||||
)
|
||||
efc = jax.tree_map(lambda *x: jp.concatenate(x), *efcs)
|
||||
) if efc is not None)
|
||||
|
||||
d = d.replace(
|
||||
efc_J=efc.J,
|
||||
efc_D=1 / efc.R,
|
||||
efc_aref=efc.aref,
|
||||
efc_frictionloss=efc.frictionloss,
|
||||
nefc=efc.aref.shape[0],
|
||||
)
|
||||
if not efcs:
|
||||
z = jp.empty(0)
|
||||
d = d.replace(efc_J=jp.empty((0, m.nv)))
|
||||
d = d.replace(efc_D=z, efc_aref=z, efc_frictionloss=z, nefc=0)
|
||||
return d
|
||||
|
||||
efc = jax.tree_map(lambda *x: jp.concatenate(x), *efcs)
|
||||
|
||||
@jax.vmap
|
||||
def fn(efc):
|
||||
k, b, imp = _kbi(m, efc.solref, efc.solimp, efc.pos_norm)
|
||||
r = jp.maximum(efc.invweight * (1 - imp) / imp, mujoco.mjMINVAL)
|
||||
aref = -b * (efc.J @ d.qvel) - k * imp * efc.pos
|
||||
return aref, r
|
||||
|
||||
aref, r = fn(efc)
|
||||
d = d.replace(efc_J=efc.J, efc_D=1 / r, efc_aref=aref)
|
||||
d = d.replace(efc_frictionloss=efc.frictionloss, nefc=r.shape[0])
|
||||
|
||||
return d
|
||||
|
||||
@@ -24,11 +24,12 @@ from mujoco.mjx._src import constraint
|
||||
from mujoco.mjx._src import test_util
|
||||
# pylint: disable=g-importing-member
|
||||
from mujoco.mjx._src.types import DisableBit
|
||||
from mujoco.mjx._src.types import SolverType
|
||||
# pylint: enable=g-importing-member
|
||||
import numpy as np
|
||||
|
||||
|
||||
def _assert_eq(a, b, name, step, fname, atol=1e-3, rtol=1e-3):
|
||||
def _assert_eq(a, b, name, step, fname, atol=5e-3, rtol=5e-3):
|
||||
err_msg = f'mismatch: {name} at step {step} in {fname}'
|
||||
np.testing.assert_allclose(a, b, err_msg=err_msg, atol=atol, rtol=rtol)
|
||||
|
||||
@@ -36,7 +37,7 @@ def _assert_eq(a, b, name, step, fname, atol=1e-3, rtol=1e-3):
|
||||
class ConstraintTest(parameterized.TestCase):
|
||||
|
||||
@parameterized.parameters(enumerate(test_util.TEST_FILES))
|
||||
def testconstraints(self, seed, fname):
|
||||
def test_constraints(self, seed, fname):
|
||||
"""Test constraints."""
|
||||
np.random.seed(seed)
|
||||
|
||||
@@ -79,6 +80,40 @@ class ConstraintTest(parameterized.TestCase):
|
||||
fname,
|
||||
)
|
||||
|
||||
_JNT_RANGE = """
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body pos="0 0 1">
|
||||
<joint type="slide" axis="1 0 0" range="-1.8 1.8" solreflimit=".08 1"
|
||||
damping="5e-4"/>
|
||||
<geom type="box" size="0.2 0.15 0.1" mass="1"/>
|
||||
<body>
|
||||
<joint axis="0 1 0" damping="2e-6"/>
|
||||
<geom type="capsule" fromto="0 0 0 0 0 1" size="0.045" mass=".1"/>
|
||||
</body>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
"""
|
||||
|
||||
def test_jnt_range(self):
|
||||
"""Tests that mixed joint ranges are respected."""
|
||||
# TODO(robotics-simulation): also test ball
|
||||
m = mujoco.MjModel.from_xml_string(self._JNT_RANGE)
|
||||
m.opt.solver = SolverType.CG.value
|
||||
d = mujoco.MjData(m)
|
||||
d.qpos = np.array([2.0, 15.0])
|
||||
|
||||
mx = mjx.device_put(m)
|
||||
dx = mjx.device_put(d)
|
||||
efc = jax.jit(constraint._instantiate_limit_slide_hinge)(mx, dx)
|
||||
|
||||
# first joint is outside the joint range
|
||||
np.testing.assert_array_almost_equal(efc.J[0, 0], -1.0)
|
||||
|
||||
# second joint has no range, so only one efc row
|
||||
self.assertEqual(efc.J.shape[0], 1)
|
||||
|
||||
def test_disable_refsafe(self):
|
||||
m = test_util.load_test_file('ant.xml')
|
||||
|
||||
@@ -112,7 +147,7 @@ class ConstraintTest(parameterized.TestCase):
|
||||
self.assertEqual(dx.efc_J.shape[0], 0)
|
||||
|
||||
def test_disable_equality(self):
|
||||
m = test_util.load_test_file('weld.xml')
|
||||
m = test_util.load_test_file('equality.xml')
|
||||
d = mujoco.MjData(m)
|
||||
|
||||
m.opt.disableflags = m.opt.disableflags | DisableBit.EQUALITY
|
||||
@@ -137,7 +172,7 @@ class ConstraintTest(parameterized.TestCase):
|
||||
m.opt.disableflags = m.opt.disableflags | DisableBit.CONTACT
|
||||
mx, dx = mjx.device_put(m), mjx.device_put(d)
|
||||
efc = constraint._instantiate_contact(mx, dx)
|
||||
self.assertEqual(efc.J.shape[0], 0)
|
||||
self.assertIsNone(efc)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -40,6 +40,10 @@ _MJ_TYPE_ATTR = {
|
||||
mujoco.MjModel.opt,
|
||||
mujoco.MjOption.integrator,
|
||||
),
|
||||
mujoco.mjtSolver: (
|
||||
mujoco.MjModel.opt,
|
||||
mujoco.MjOption.solver,
|
||||
),
|
||||
}
|
||||
|
||||
_TYPE_MAP = {
|
||||
@@ -103,10 +107,6 @@ def _option_derived(value: types.Option) -> Dict[str, Any]:
|
||||
|
||||
def _validate(m: mujoco.MjModel):
|
||||
"""Validates that an mjModel is compatible with MJX."""
|
||||
if m.opt.solver not in set(types.SolverType):
|
||||
name = mujoco.mjtSolver(m.opt.solver).name
|
||||
warnings.warn(f'Solver {name} is not supported, reverting to CG.')
|
||||
m.opt.solver = mujoco.mjtSolver.mjSOL_CG.value
|
||||
|
||||
# check enum types
|
||||
for mj_type, attrs in _MJ_TYPE_ATTR.items():
|
||||
@@ -116,8 +116,9 @@ def _validate(m: mujoco.MjModel):
|
||||
|
||||
typs = set(val) if isinstance(val, Iterable) else {val}
|
||||
unsupported_typs = typs - set(_TYPE_MAP[mj_type])
|
||||
if unsupported_typs:
|
||||
raise NotImplementedError(f'{unsupported_typs} not implemented.')
|
||||
unsupported = [mj_type(t) for t in unsupported_typs] # pylint: disable=too-many-function-args
|
||||
if unsupported:
|
||||
raise NotImplementedError(f'{unsupported} not implemented.')
|
||||
|
||||
# check condim
|
||||
if any(dim != 3 for dim in m.geom_condim) or any(
|
||||
@@ -125,6 +126,9 @@ def _validate(m: mujoco.MjModel):
|
||||
):
|
||||
raise NotImplementedError('Only condim=3 is supported.')
|
||||
|
||||
if m.ntendon:
|
||||
raise NotImplementedError('Tendons are not supported.')
|
||||
|
||||
# check collision geom types
|
||||
candidate_set = collision_driver.collision_candidates(m)
|
||||
for g1, g2, *_ in candidate_set:
|
||||
|
||||
@@ -110,56 +110,55 @@ class ValidateInputTest(absltest.TestCase):
|
||||
|
||||
def test_solver(self):
|
||||
m = mujoco.MjModel.from_xml_string(
|
||||
'<mujoco><option solver="Newton"/><worldbody/></mujoco>'
|
||||
'<mujoco><option solver="PGS"/><worldbody/></mujoco>'
|
||||
)
|
||||
with self.assertWarns(UserWarning):
|
||||
mx = mjx.device_put(m)
|
||||
self.assertEqual(mx.opt.solver, mujoco.mjtSolver.mjSOL_CG)
|
||||
with self.assertRaises(NotImplementedError):
|
||||
mjx.device_put(m)
|
||||
|
||||
def test_integrator(self):
|
||||
m = mujoco.MjModel.from_xml_string(
|
||||
'<mujoco><option integrator="implicit"/><worldbody/></mujoco>'
|
||||
)
|
||||
with self.assertRaises(NotImplementedError):
|
||||
_ = mjx.device_put(m)
|
||||
mjx.device_put(m)
|
||||
|
||||
def test_cone(self):
|
||||
m = mujoco.MjModel.from_xml_string(
|
||||
'<mujoco><option cone="elliptic"/><worldbody/></mujoco>'
|
||||
)
|
||||
with self.assertRaises(NotImplementedError):
|
||||
_ = mjx.device_put(m)
|
||||
mjx.device_put(m)
|
||||
|
||||
def test_trn(self):
|
||||
m = test_util.load_test_file('ant.xml')
|
||||
m.actuator_trntype[0] = mujoco.mjtTrn.mjTRN_SITE
|
||||
with self.assertRaises(NotImplementedError):
|
||||
_ = mjx.device_put(m)
|
||||
mjx.device_put(m)
|
||||
|
||||
def test_dyn(self):
|
||||
m = test_util.load_test_file('ant.xml')
|
||||
m.actuator_dyntype[0] = mujoco.mjtDyn.mjDYN_MUSCLE
|
||||
with self.assertRaises(NotImplementedError):
|
||||
_ = mjx.device_put(m)
|
||||
mjx.device_put(m)
|
||||
|
||||
def test_gain(self):
|
||||
m = test_util.load_test_file('ant.xml')
|
||||
m.actuator_gaintype[0] = mujoco.mjtGain.mjGAIN_MUSCLE
|
||||
with self.assertRaises(NotImplementedError):
|
||||
_ = mjx.device_put(m)
|
||||
mjx.device_put(m)
|
||||
|
||||
def test_bias(self):
|
||||
m = test_util.load_test_file('ant.xml')
|
||||
m.actuator_gaintype[0] = mujoco.mjtGain.mjGAIN_MUSCLE
|
||||
with self.assertRaises(NotImplementedError):
|
||||
_ = mjx.device_put(m)
|
||||
mjx.device_put(m)
|
||||
|
||||
def test_condim(self):
|
||||
m = test_util.load_test_file('ant.xml')
|
||||
for i in [1, 4, 6]:
|
||||
m.geom_condim[0] = i
|
||||
with self.assertRaises(NotImplementedError):
|
||||
_ = mjx.device_put(m)
|
||||
mjx.device_put(m)
|
||||
|
||||
def test_geoms(self):
|
||||
m = mujoco.MjModel.from_xml_string("""
|
||||
@@ -177,7 +176,31 @@ class ValidateInputTest(absltest.TestCase):
|
||||
</mujoco>
|
||||
""")
|
||||
with self.assertRaises(NotImplementedError):
|
||||
_ = mjx.device_put(m)
|
||||
mjx.device_put(m)
|
||||
|
||||
def test_tendon(self):
|
||||
m = mujoco.MjModel.from_xml_string("""
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body name="left_thigh" pos="0 0.1 -0.04">
|
||||
<joint axis="0 1 0" name="left_hip_y" type="hinge"/>
|
||||
<geom fromto="0 0 0 0 -0.01 -.34" name="left_thigh1" size="0.06" type="capsule"/>
|
||||
<body name="left_shin" pos="0 -0.01 -0.403">
|
||||
<joint axis="0 -1 0" name="left_knee" pos="0 0 .02" range="-160 -2" type="hinge"/>
|
||||
<geom fromto="0 0 0 0 0 -.3" name="left_shin1" size="0.049" type="capsule"/>
|
||||
</body>
|
||||
</body>
|
||||
</worldbody>
|
||||
<tendon>
|
||||
<fixed name="left_hipknee">
|
||||
<joint coef="-1" joint="left_hip_y"/>
|
||||
<joint coef="1" joint="left_knee"/>
|
||||
</fixed>
|
||||
</tendon>
|
||||
</mujoco>
|
||||
""")
|
||||
with self.assertRaises(NotImplementedError):
|
||||
mjx.device_put(m)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -37,7 +37,6 @@ from mujoco.mjx._src.types import GainType
|
||||
from mujoco.mjx._src.types import IntegratorType
|
||||
from mujoco.mjx._src.types import JointType
|
||||
from mujoco.mjx._src.types import Model
|
||||
from mujoco.mjx._src.types import SolverType
|
||||
# pylint: enable=g-importing-member
|
||||
import numpy as np
|
||||
|
||||
@@ -333,10 +332,7 @@ def forward(m: Model, d: Data) -> Data:
|
||||
d = d.replace(qacc=d.qacc_smooth)
|
||||
return d
|
||||
|
||||
if m.opt.solver == SolverType.CG:
|
||||
d = named_scope(solver.cg_solve)(m, d)
|
||||
else:
|
||||
raise NotImplementedError(f'solver {m.opt.solver} not implemented.')
|
||||
d = named_scope(solver.solve)(m, d)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
# ==============================================================================
|
||||
"""Tests for forward functions."""
|
||||
|
||||
import itertools
|
||||
|
||||
from absl.testing import absltest
|
||||
from absl.testing import parameterized
|
||||
import jax
|
||||
@@ -38,13 +36,12 @@ def _assert_attr_eq(a, b, attr, step, fname, atol=1e-3, rtol=1e-3):
|
||||
|
||||
class ForwardTest(parameterized.TestCase):
|
||||
|
||||
@parameterized.parameters(enumerate(test_util.TEST_FILES))
|
||||
def test_forward(self, seed, fname):
|
||||
@parameterized.parameters(
|
||||
filter(lambda s: s not in ('equality.xml',), test_util.TEST_FILES)
|
||||
)
|
||||
def test_forward(self, fname):
|
||||
"""Test mujoco mj forward function matches mujoco_mjx forward function."""
|
||||
if fname in ('weld.xml',):
|
||||
return
|
||||
|
||||
np.random.seed(seed)
|
||||
np.random.seed(test_util.TEST_FILES.index(fname))
|
||||
|
||||
m = test_util.load_test_file(fname)
|
||||
d = mujoco.MjData(m)
|
||||
@@ -62,36 +59,20 @@ class ForwardTest(parameterized.TestCase):
|
||||
_assert_attr_eq(d, dx, 'qfrc_smooth', i, fname)
|
||||
_assert_attr_eq(d, dx, 'qacc_smooth', i, fname)
|
||||
|
||||
@parameterized.parameters(itertools.product(test_util.TEST_FILES, (0, 1)))
|
||||
def test_step(self, fname, integrator_type):
|
||||
@parameterized.parameters(
|
||||
filter(lambda s: s not in ('equality.xml',), test_util.TEST_FILES)
|
||||
)
|
||||
def test_step(self, fname):
|
||||
"""Test mujoco mj step matches mujoco_mjx step."""
|
||||
if fname in (
|
||||
'mixed_joint_pendulum.xml',
|
||||
'ball_pendulum.xml',
|
||||
'convex.xml',
|
||||
'humanoid.xml',
|
||||
'triple_pendulum.xml', # TODO(b/301485081)
|
||||
'weld.xml',
|
||||
):
|
||||
# skip models with big constraint violations at step 0 or too slow to run
|
||||
return
|
||||
|
||||
np.random.seed(integrator_type)
|
||||
np.random.seed(test_util.TEST_FILES.index(fname))
|
||||
m = test_util.load_test_file(fname)
|
||||
step_jit_fn = jax.jit(forward.step)
|
||||
|
||||
m.opt.integrator = integrator_type
|
||||
int_typ = 'euler' if integrator_type == 0 else 'rk4'
|
||||
test_name = f'{fname} - {int_typ}'
|
||||
steps = 100 if int_typ == 'euler' else 30
|
||||
dt = m.opt.timestep
|
||||
m.opt.timestep = dt if int_typ == 'euler' else dt * 3
|
||||
|
||||
mx = mjx.device_put(m)
|
||||
d = mujoco.MjData(m)
|
||||
# give the system a little kick to ensure we have non-identity rotations
|
||||
d.qvel = np.random.normal(m.nv) * 0.05
|
||||
for i in range(steps):
|
||||
for i in range(100):
|
||||
# in order to avoid re-jitting, reuse the same mj_data shape
|
||||
qpos, qvel = d.qpos, d.qvel
|
||||
d = mujoco.MjData(m)
|
||||
@@ -101,10 +82,51 @@ class ForwardTest(parameterized.TestCase):
|
||||
mujoco.mj_step(m, d)
|
||||
dx = step_jit_fn(mx, dx)
|
||||
|
||||
_assert_attr_eq(d, dx, 'qpos', i, test_name, atol=1e-2)
|
||||
_assert_attr_eq(d, dx, 'qvel', i, test_name, atol=1e-2)
|
||||
_assert_attr_eq(d, dx, 'act', i, test_name)
|
||||
_assert_attr_eq(d, dx, 'time', i, test_name)
|
||||
_assert_attr_eq(d, dx, 'qvel', i, fname, atol=1e-2)
|
||||
_assert_attr_eq(d, dx, 'qpos', i, fname, atol=1e-2)
|
||||
_assert_attr_eq(d, dx, 'act', i, fname)
|
||||
_assert_attr_eq(d, dx, 'time', i, fname)
|
||||
|
||||
def test_rk4(self):
|
||||
m = mujoco.MjModel.from_xml_string("""
|
||||
<mujoco>
|
||||
<option integrator="RK4">
|
||||
<flag constraint="disable"/>
|
||||
</option>
|
||||
<worldbody>
|
||||
<light pos="0 0 1"/>
|
||||
<geom type="plane" size="1 1 .01" pos="0 0 -1"/>
|
||||
<body pos="0.15 0 0">
|
||||
<joint type="hinge" axis="0 1 0"/>
|
||||
<geom type="capsule" size="0.02" fromto="0 0 0 .1 0 0"/>
|
||||
<body pos="0.1 0 0">
|
||||
<joint type="slide" axis="1 0 0" stiffness="200"/>
|
||||
<geom type="capsule" size="0.015" fromto="-.1 0 0 .1 0 0"/>
|
||||
</body>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
""")
|
||||
step_jit_fn = jax.jit(forward.step)
|
||||
|
||||
mx = mjx.device_put(m)
|
||||
d = mujoco.MjData(m)
|
||||
# give the system a little kick to ensure we have non-identity rotations
|
||||
d.qvel = np.random.normal(m.nv) * 0.05
|
||||
for i in range(100):
|
||||
# in order to avoid re-jitting, reuse the same mj_data shape
|
||||
qpos, qvel = d.qpos, d.qvel
|
||||
d = mujoco.MjData(m)
|
||||
d.qpos, d.qvel = qpos, qvel
|
||||
dx = mjx.device_put(d)
|
||||
|
||||
mujoco.mj_step(m, d)
|
||||
dx = step_jit_fn(mx, dx)
|
||||
|
||||
_assert_attr_eq(d, dx, 'qvel', i, 'test_rk4', atol=1e-2)
|
||||
_assert_attr_eq(d, dx, 'qpos', i, 'test_rk4', atol=1e-2)
|
||||
_assert_attr_eq(d, dx, 'act', i, 'test_rk4')
|
||||
_assert_attr_eq(d, dx, 'time', i, 'test_rk4')
|
||||
|
||||
def test_disable_eulerdamp(self):
|
||||
m = test_util.load_test_file('ant.xml')
|
||||
|
||||
@@ -26,7 +26,7 @@ from mujoco import mjx
|
||||
import numpy as np
|
||||
|
||||
|
||||
def _assert_attr_eq(a, b, attr, step, fname, atol=1e-5, rtol=1e-5):
|
||||
def _assert_attr_eq(a, b, attr, step, fname, atol=1e-4, rtol=1e-4):
|
||||
err_msg = f'mismatch: {attr} at step {step} in {fname}'
|
||||
a, b = getattr(a, attr), getattr(b, attr)
|
||||
np.testing.assert_allclose(a, b, err_msg=err_msg, atol=atol, rtol=rtol)
|
||||
@@ -34,7 +34,7 @@ def _assert_attr_eq(a, b, attr, step, fname, atol=1e-5, rtol=1e-5):
|
||||
|
||||
class PassiveTest(parameterized.TestCase):
|
||||
|
||||
@parameterized.parameters(enumerate(('ant.xml', 'mixed_joint_pendulum.xml')))
|
||||
@parameterized.parameters(enumerate(('ant.xml', 'pendula.xml')))
|
||||
def test_stiffness_damping(self, seed, fname):
|
||||
"""Tests stiffness and damping on Ant."""
|
||||
np.random.seed(seed)
|
||||
@@ -60,7 +60,7 @@ class PassiveTest(parameterized.TestCase):
|
||||
_assert_attr_eq(d, dx, 'qfrc_passive', i, fname)
|
||||
|
||||
@parameterized.parameters(
|
||||
itertools.product(range(3), ('triple_pendulum.xml',))
|
||||
itertools.product(range(3), ('pendula.xml',))
|
||||
)
|
||||
def test_fluid(self, seed, fname):
|
||||
np.random.seed(seed)
|
||||
|
||||
+92
-58
@@ -162,7 +162,7 @@ def flat(
|
||||
) -> Y:
|
||||
r"""Scan a function across bodies or actuators.
|
||||
|
||||
Scan group data according to type and batch shape then calls vmap(f) on it.\
|
||||
Scan group data according to type and batch shape then calls vmap(f) on it.
|
||||
|
||||
Args:
|
||||
m: an mjx model
|
||||
@@ -340,48 +340,88 @@ def body_tree(
|
||||
IndexError: if function output shape does not match out_types shape
|
||||
"""
|
||||
_check_input(m, args, in_types)
|
||||
depth_fn = lambda i, p=m.body_parentid: int(i > 0) and 1 + depth_fn(p[i])
|
||||
typ_body_id = {
|
||||
'j': m.jnt_bodyid,
|
||||
'v': m.dof_bodyid,
|
||||
'q': _q_bodyid(m),
|
||||
}
|
||||
key_parents = {}
|
||||
|
||||
# build up groupings of bodies and type ids using (level, (jnt_type,)) keys
|
||||
key_typ_ids, key_body_ids = {}, {}
|
||||
for body_id in np.arange(m.nbody, dtype=np.int32):
|
||||
depth = depth_fn(body_id)
|
||||
# group together bodies that will be processed together. grouping key:
|
||||
# 1) the tree depth: parent bodies are processed first, so that they are
|
||||
# available as carry input to child bodies (or reverse if reverse=True)
|
||||
# 2) the types of arguments passed to f, both carry and *args:
|
||||
# * for 'b' arguments, there is no extra grouping
|
||||
# * for 'j' arguments, we group by joint type
|
||||
# * for 'q' arguments, we group by q width
|
||||
# * for 'v' arguments, we group by dof width
|
||||
depths = np.zeros(m.nbody, dtype=np.int32)
|
||||
|
||||
# create grouping key
|
||||
if any(t in 'jqv' for t in in_types + out_types):
|
||||
jnts = np.nonzero(typ_body_id['j'] == body_id)[0]
|
||||
jnts_p = np.nonzero(typ_body_id['j'] == m.body_parentid[body_id])[0]
|
||||
key = depth, tuple(m.jnt_type[jnts])
|
||||
parent_key = depth - 1, tuple(m.jnt_type[jnts_p])
|
||||
else:
|
||||
key, parent_key = (depth, ()), (depth - 1, ())
|
||||
# map key => body id
|
||||
key_body_ids = {}
|
||||
for body_id in range(m.nbody):
|
||||
parent_id = -1
|
||||
if body_id > 0:
|
||||
parent_id = m.body_parentid[body_id]
|
||||
depths[body_id] = 1 + depths[parent_id]
|
||||
|
||||
# create grouping key: depth, carry, args
|
||||
key = (depths[body_id],)
|
||||
|
||||
for i, t in enumerate(out_types + in_types):
|
||||
id_ = parent_id if i < len(out_types) else body_id
|
||||
if t == 'b':
|
||||
continue
|
||||
elif t == 'j':
|
||||
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':
|
||||
key += (len(np.nonzero(_q_bodyid(m) == id_)[0]),)
|
||||
|
||||
key_parents[key] = parent_key
|
||||
body_ids = key_body_ids.get(key, np.array([], dtype=np.int32))
|
||||
key_body_ids[key] = np.append(body_ids, body_id)
|
||||
|
||||
# add ids per type
|
||||
for t in set(in_types + out_types):
|
||||
out = key_typ_ids.setdefault(key, {})
|
||||
id_ = body_id if t == 'b' else np.nonzero(typ_body_id[t] == body_id)[0]
|
||||
id_ = np.expand_dims(id_, axis=0)
|
||||
out[t] = np.concatenate((out[t], id_)) if t in out else id_
|
||||
# find parent keys of each key. a key may have multiple parents if the
|
||||
# carry output keys of distinct parents are the same. e.g.:
|
||||
# - depth 0 body 1 (slide joint)
|
||||
# -- depth 1 body 1 (hinge joint)
|
||||
# - depth 0 body 2 (ball joint)
|
||||
# -- depth 1 body 2 (hinge joint)
|
||||
# given a scan with 'j' in the in_types, we would group depth 0 bodies
|
||||
# separately but we may group depth 1 bodies together
|
||||
key_parents = {}
|
||||
|
||||
key_typ_ids = list(sorted(key_typ_ids.items(), reverse=reverse))
|
||||
for key, body_ids in key_body_ids.items():
|
||||
body_ids = body_ids[body_ids != 0] # ignore worldbody, has no parent
|
||||
if body_ids.size == 0:
|
||||
continue
|
||||
# find any key which has a body id that is a parent of these body_ids
|
||||
pids = m.body_parentid[body_ids]
|
||||
parents = {k for k, v in key_body_ids.items() if np.isin(v, pids).any()}
|
||||
key_parents[key] = list(sorted(parents))
|
||||
|
||||
# key => take indices
|
||||
key_in_take, key_y_take = {}, {}
|
||||
for key, body_ids in key_body_ids.items():
|
||||
for i, typ in enumerate(in_types + out_types):
|
||||
if typ == 'b':
|
||||
ids = body_ids
|
||||
elif typ == 'j':
|
||||
ids = np.stack([np.nonzero(m.jnt_bodyid == b)[0] for b in body_ids])
|
||||
elif typ == 'v':
|
||||
ids = np.stack([np.nonzero(m.dof_bodyid == b)[0] for b in body_ids])
|
||||
elif typ == 'q':
|
||||
ids = np.stack([np.nonzero(_q_bodyid(m) == b)[0] for b in body_ids])
|
||||
else:
|
||||
raise ValueError(f'Unknown in_type: {typ}')
|
||||
if i < len(in_types):
|
||||
key_in_take.setdefault(key, []).append(ids)
|
||||
else:
|
||||
key_y_take.setdefault(key, []).append(np.hstack(ids))
|
||||
|
||||
# use this grouping to take the right data subsets and call vmap(f)
|
||||
keys = sorted(key_body_ids, reverse=reverse)
|
||||
key_y = {}
|
||||
for key, typ_ids in key_typ_ids:
|
||||
for key in keys:
|
||||
carry = None
|
||||
|
||||
if reverse:
|
||||
child_keys = [k for k, v in key_parents.items() if v == key]
|
||||
child_keys = [k for k, v in key_parents.items() if key in v]
|
||||
|
||||
for child_key in child_keys:
|
||||
y = key_y[child_key]
|
||||
@@ -394,39 +434,33 @@ def body_tree(
|
||||
|
||||
y = jax.tree_map(index_sum, y)
|
||||
carry = y if carry is None else jax.tree_map(jp.add, carry, y)
|
||||
else:
|
||||
parent_key = key_parents[key]
|
||||
y = key_y.get(parent_key)
|
||||
elif key in key_parents:
|
||||
ys = [key_y[p] for p in key_parents[key]]
|
||||
y = jax.tree_map(lambda *x: jp.concatenate(x), *ys)
|
||||
body_ids = np.concatenate([key_body_ids[p] for p in key_parents[key]])
|
||||
parent_ids = m.body_parentid[key_body_ids[key]]
|
||||
take_fn = lambda x, i=_index(body_ids, parent_ids): _take(x, i)
|
||||
carry = jax.tree_map(take_fn, y)
|
||||
|
||||
if y is not None:
|
||||
body_ids = key_body_ids[parent_key]
|
||||
parent_ids = m.body_parentid[key_body_ids[key]]
|
||||
take_fn = lambda x, i=_index(body_ids, parent_ids): _take(x, i)
|
||||
carry = jax.tree_map(take_fn, y)
|
||||
|
||||
f_args = [_take(arg, typ_ids[typ]) for arg, typ in zip(args, in_types)]
|
||||
f_args = [_take(arg, ids) for arg, ids in zip(args, key_in_take[key])]
|
||||
key_y[key] = _nvmap(f, carry, *f_args)
|
||||
|
||||
# slice None results from the final output
|
||||
key_typ_ids = [(k, v) for k, v in key_typ_ids if key_y[k] is not None]
|
||||
keys = [k for k in keys if key_y[k] is not None]
|
||||
|
||||
# concatenate back to a single tree and drop the grouping dimension
|
||||
ys = [key_y[key] for key, _ in key_typ_ids]
|
||||
f_ret_is_seq = isinstance(ys[0], (list, tuple))
|
||||
ys = ys if f_ret_is_seq else [[y] for y in ys]
|
||||
ys = [
|
||||
[v if typ == 'b' else jp.concatenate(v) for v, typ in zip(y, out_types)]
|
||||
for y in ys
|
||||
]
|
||||
ys = jax.tree_map(lambda *x: jp.concatenate(x), *ys)
|
||||
# concatenate ys, drop grouping dimensions, put back in order
|
||||
y = []
|
||||
for i, typ in enumerate(out_types):
|
||||
y_typ = [key_y[key] for key in keys]
|
||||
if len(out_types) > 1:
|
||||
y_typ = [y_[i] for y_ in y_typ]
|
||||
if typ != 'b':
|
||||
y_typ = jax.tree_map(jp.concatenate, y_typ)
|
||||
y_typ = jax.tree_map(lambda *x: jp.concatenate(x), *y_typ)
|
||||
y_take = np.argsort(np.concatenate([key_y_take[key][i] for key in keys]))
|
||||
_check_output(y_typ, y_take, typ, i)
|
||||
y.append(_take(y_typ, y_take))
|
||||
|
||||
# put concatenated results back into body order
|
||||
reordered_ys = []
|
||||
for i, (y, typ) in enumerate(zip(ys, out_types)):
|
||||
ids = np.concatenate([np.hstack(v[typ]) for _, v in key_typ_ids])
|
||||
take_ids = _index(ids, np.sort(ids))
|
||||
_check_output(y, take_ids, typ, i)
|
||||
reordered_ys.append(_take(y, take_ids))
|
||||
y = reordered_ys if f_ret_is_seq else reordered_ys[0]
|
||||
y = y[0] if len(out_types) == 1 else y
|
||||
|
||||
return y
|
||||
|
||||
@@ -27,12 +27,12 @@ from mujoco.mjx._src.types import DisableBit
|
||||
import numpy as np
|
||||
|
||||
|
||||
def _assert_eq(a, b, name, step, fname, atol=1e-5, rtol=1e-5):
|
||||
def _assert_eq(a, b, name, step, fname, atol=5e-4, rtol=5e-4):
|
||||
err_msg = f'mismatch: {name} at step {step} in {fname}'
|
||||
np.testing.assert_allclose(a, b, err_msg=err_msg, atol=atol, rtol=rtol)
|
||||
|
||||
|
||||
def _assert_attr_eq(a, b, attr, step, fname, atol=1e-5, rtol=1e-5):
|
||||
def _assert_attr_eq(a, b, attr, step, fname, atol=5e-4, rtol=5e-4):
|
||||
err_msg = f'mismatch: {attr} at step {step} in {fname}'
|
||||
a, b = getattr(a, attr), getattr(b, attr)
|
||||
np.testing.assert_allclose(a, b, err_msg=err_msg, atol=atol, rtol=rtol)
|
||||
@@ -43,7 +43,7 @@ class SmoothTest(parameterized.TestCase):
|
||||
@parameterized.parameters(enumerate(test_util.TEST_FILES))
|
||||
def test_smooth(self, seed, fname):
|
||||
"""Tests mujoco mj smooth functions match mujoco_mjx smooth functions."""
|
||||
if fname in ('convex.xml', 'weld.xml'):
|
||||
if fname in ('convex.xml', 'equality.xml'):
|
||||
return
|
||||
|
||||
np.random.seed(seed)
|
||||
@@ -101,7 +101,7 @@ class SmoothTest(parameterized.TestCase):
|
||||
# factor_m
|
||||
dx = factor_m_fn(mx, dx, dx.qM)
|
||||
_assert_attr_eq(d, dx, 'qLD', i, fname, atol=1e-3)
|
||||
_assert_attr_eq(d, dx, 'qLDiagInv', i, fname, atol=1e-3, rtol=1e-4)
|
||||
_assert_attr_eq(d, dx, 'qLDiagInv', i, fname, atol=1e-3)
|
||||
|
||||
# com_vel
|
||||
dx = com_vel_jit_fn(mx, dx)
|
||||
@@ -110,14 +110,14 @@ class SmoothTest(parameterized.TestCase):
|
||||
|
||||
# rne
|
||||
dx = rne_jit_fn(mx, dx)
|
||||
_assert_attr_eq(d, dx, 'qfrc_bias', i, fname, atol=1e-4)
|
||||
_assert_attr_eq(d, dx, 'qfrc_bias', i, fname)
|
||||
|
||||
# mul_m (auxilliary function, not part of smooth step)
|
||||
vec = np.random.random(m.nv)
|
||||
mjx_vec = mul_m_jit_fn(mx, dx, jp.array(vec))
|
||||
mj_vec = np.zeros(m.nv)
|
||||
mujoco.mj_mulM(m, d, mj_vec, vec)
|
||||
_assert_eq(mj_vec, mjx_vec, 'mul_m', i, fname, atol=1e-4)
|
||||
_assert_eq(mj_vec, mjx_vec, 'mul_m', i, fname)
|
||||
|
||||
# transmission
|
||||
dx = transmission_jit_fn(mx, dx)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
"""CG and Newton solvers."""
|
||||
"""Constraint solvers."""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
@@ -26,11 +26,12 @@ from mujoco.mjx._src.dataclasses import PyTreeNode
|
||||
from mujoco.mjx._src.types import Data
|
||||
from mujoco.mjx._src.types import DisableBit
|
||||
from mujoco.mjx._src.types import Model
|
||||
from mujoco.mjx._src.types import SolverType
|
||||
# pylint: enable=g-importing-member
|
||||
|
||||
|
||||
class _CGContext(PyTreeNode):
|
||||
"""Data updated during each cg solver iteration.
|
||||
class _Context(PyTreeNode):
|
||||
"""Data updated during each solver iteration.
|
||||
|
||||
Attributes:
|
||||
qacc: acceleration (from Data) (nv,)
|
||||
@@ -44,7 +45,7 @@ class _CGContext(PyTreeNode):
|
||||
search: linesearch vector (nv,)
|
||||
gauss: gauss Cost
|
||||
cost: constraint + Gauss cost
|
||||
prev_cost: cost from previous cg iter
|
||||
prev_cost: cost from previous iter
|
||||
solver_niter: number of solver iterations
|
||||
"""
|
||||
|
||||
@@ -63,17 +64,17 @@ class _CGContext(PyTreeNode):
|
||||
solver_niter: jax.Array
|
||||
|
||||
@classmethod
|
||||
def create(cls, m: Model, d: Data, grad: bool = True) -> '_CGContext':
|
||||
def create(cls, m: Model, d: Data, grad: bool = True) -> '_Context':
|
||||
jaref = d.efc_J @ d.qacc - d.efc_aref
|
||||
# TODO(robotics-team): determine nv at which sparse mul is faster
|
||||
M = smooth.dense_m(m, d) if m.nv < 100 else None # pylint: disable=invalid-name
|
||||
ma = smooth.mul_m(m, d, d.qacc) if M is None else M @ d.qacc
|
||||
nv_0 = jp.zeros((m.nv,))
|
||||
ctx = _CGContext(
|
||||
ctx = _Context(
|
||||
qacc=d.qacc,
|
||||
qfrc_constraint=d.qfrc_constraint,
|
||||
Jaref=jaref,
|
||||
efc_force=-jaref * d.efc_D,
|
||||
efc_force=jp.zeros(d.nefc),
|
||||
M=M,
|
||||
Ma=ma,
|
||||
grad=nv_0,
|
||||
@@ -84,9 +85,9 @@ class _CGContext(PyTreeNode):
|
||||
prev_cost=0.0,
|
||||
solver_niter=0,
|
||||
)
|
||||
ctx = _cg_update_constraint(m, d, ctx)
|
||||
ctx = _update_constraint(m, d, ctx)
|
||||
if grad:
|
||||
ctx = _cg_update_gradient(m, d, ctx)
|
||||
ctx = _update_gradient(m, d, ctx)
|
||||
ctx = ctx.replace(search=-ctx.Mgrad) # start with preconditioned gradient
|
||||
|
||||
return ctx
|
||||
@@ -110,7 +111,8 @@ class _LSPoint(PyTreeNode):
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
ctx: _CGContext,
|
||||
d: Data,
|
||||
ctx: _Context,
|
||||
alpha: jax.Array,
|
||||
jv: jax.Array,
|
||||
quad: jax.Array,
|
||||
@@ -119,8 +121,8 @@ class _LSPoint(PyTreeNode):
|
||||
"""Creates a linesearch point with first and second derivatives."""
|
||||
# roughly corresponds to CGEval in mujoco/src/engine/engine_solver.c
|
||||
|
||||
# TODO(robotics-team): change this to support equality, friction constraints
|
||||
active = (ctx.Jaref + alpha * jv) < 0
|
||||
# TODO(robotics-team): change this to support friction constraints
|
||||
active = ((ctx.Jaref + alpha * jv) < 0).at[:d.ne + d.nf].set(True)
|
||||
quad = jax.vmap(jp.multiply)(quad, active) # only active
|
||||
quad_total = quad_gauss + jp.sum(quad, axis=0)
|
||||
|
||||
@@ -131,7 +133,7 @@ class _LSPoint(PyTreeNode):
|
||||
|
||||
|
||||
class _LSContext(PyTreeNode):
|
||||
"""Data updated during each cg line search iteration.
|
||||
"""Data updated during each line search iteration.
|
||||
|
||||
Attributes:
|
||||
lo: low point bounding the line search interval
|
||||
@@ -162,29 +164,30 @@ def _while_loop_scan(cond_fun, body_fun, init_val, max_iter):
|
||||
return jax.lax.scan(_fun, init, None, length=max_iter)[0][0]
|
||||
|
||||
|
||||
def _cg_update_constraint(m: Model, d: Data, ctx: _CGContext) -> _CGContext:
|
||||
"""Updates constraint force and resulting cost given latst CG iteration.
|
||||
def _update_constraint(m: Model, d: Data, ctx: _Context) -> _Context:
|
||||
"""Updates constraint force and resulting cost given latst solver iteration.
|
||||
|
||||
Corresponds to CGupdateConstraint in mujoco/src/engine/engine_solver.c
|
||||
|
||||
Args:
|
||||
m: model defining constraints
|
||||
d: data which contains latest qacc and smooth terms
|
||||
ctx: current CG context
|
||||
ctx: current solver context
|
||||
|
||||
Returns:
|
||||
context with new constraint force and costs
|
||||
"""
|
||||
del m
|
||||
|
||||
# TODO(robotics-team): add equality, friction constraints
|
||||
# also consider moving to _constraint.py to match mujoco layout
|
||||
# TODO(robotics-team): add friction constraints
|
||||
|
||||
jaref = ctx.Jaref * (ctx.Jaref < 0) # non-negative constraints
|
||||
efc_force = -jaref * d.efc_D
|
||||
# only count active constraints
|
||||
active = (ctx.Jaref < 0).at[:d.ne + d.nf].set(True)
|
||||
|
||||
efc_force = d.efc_D * -ctx.Jaref * active
|
||||
qfrc_constraint = d.efc_J.T @ efc_force
|
||||
gauss = 0.5 * jp.dot(ctx.Ma - d.qfrc_smooth, ctx.qacc - d.qacc_smooth)
|
||||
cost = 0.5 * jp.sum(jaref * jaref * d.efc_D) + gauss
|
||||
cost = 0.5 * jp.sum(d.efc_D * ctx.Jaref * ctx.Jaref * active) + gauss
|
||||
|
||||
ctx = ctx.replace(
|
||||
qfrc_constraint=qfrc_constraint,
|
||||
@@ -197,22 +200,34 @@ def _cg_update_constraint(m: Model, d: Data, ctx: _CGContext) -> _CGContext:
|
||||
return ctx
|
||||
|
||||
|
||||
def _cg_update_gradient(m: Model, d: Data, ctx: _CGContext) -> _CGContext:
|
||||
"""Updates grad and M / grad given latest CG iteration.
|
||||
def _update_gradient(m: Model, d: Data, ctx: _Context) -> _Context:
|
||||
"""Updates grad and M / grad given latest solver iteration.
|
||||
|
||||
Corresponds to CGupdateGradient in mujoco/src/engine/engine_solver.c
|
||||
|
||||
Args:
|
||||
m: model defining constraints
|
||||
d: data which contains latest smooth terms
|
||||
ctx: current CG contet
|
||||
ctx: current solver context
|
||||
|
||||
Returns:
|
||||
context with new grad and M / grad
|
||||
Raises:
|
||||
NotImplementedError: for unsupported solver type
|
||||
"""
|
||||
|
||||
grad = ctx.Ma - d.qfrc_smooth - ctx.qfrc_constraint
|
||||
mgrad = smooth.solve_m(m, d, grad)
|
||||
|
||||
if m.opt.solver == SolverType.CG:
|
||||
mgrad = smooth.solve_m(m, d, grad)
|
||||
elif m.opt.solver == SolverType.NEWTON:
|
||||
active = (ctx.Jaref < 0).at[:d.ne + d.nf].set(True)
|
||||
h = (d.efc_J.T * d.efc_D * active) @ d.efc_J
|
||||
h = smooth.dense_m(m, d) + h
|
||||
h_ = jax.scipy.linalg.cho_factor(h)
|
||||
mgrad = jax.scipy.linalg.cho_solve(h_, grad)
|
||||
else:
|
||||
raise NotImplementedError(f"unsupported solver type: {m.opt.solver}")
|
||||
|
||||
ctx = ctx.replace(grad=grad, Mgrad=mgrad)
|
||||
|
||||
@@ -223,13 +238,13 @@ def _rescale(m: Model, value: jax.Array) -> jax.Array:
|
||||
return value / (m.stat.meaninertia * max(1, m.nv))
|
||||
|
||||
|
||||
def _cg_search(m: Model, d: Data, ctx: _CGContext) -> _CGContext:
|
||||
def _linesearch(m: Model, d: Data, ctx: _Context) -> _Context:
|
||||
"""Performs a zoom linesearch to find optimal search step size.
|
||||
|
||||
Args:
|
||||
m: model defining search options and other needed terms
|
||||
d: data with inertia matrix and other needed terms
|
||||
ctx: current CG context
|
||||
ctx: current solver context
|
||||
|
||||
Returns:
|
||||
updated context with new qacc, Ma, Jaref
|
||||
@@ -250,7 +265,7 @@ def _cg_search(m: Model, d: Data, ctx: _CGContext) -> _CGContext:
|
||||
quad = jp.stack((0.5 * ctx.Jaref * ctx.Jaref, jv * ctx.Jaref, 0.5 * jv * jv))
|
||||
quad = (quad * d.efc_D).T
|
||||
|
||||
point_fn = lambda alpha: _LSPoint.create(ctx, alpha, jv, quad, quad_gauss)
|
||||
point_fn = lambda alpha: _LSPoint.create(d, ctx, alpha, jv, quad, quad_gauss)
|
||||
|
||||
def cond(ctx: _LSContext) -> jax.Array:
|
||||
done = ctx.ls_iter >= m.opt.ls_iterations
|
||||
@@ -308,10 +323,10 @@ def _cg_search(m: Model, d: Data, ctx: _CGContext) -> _CGContext:
|
||||
return ctx
|
||||
|
||||
|
||||
def cg_solve(m: Model, d: Data) -> Data:
|
||||
def solve(m: Model, d: Data) -> Data:
|
||||
"""Finds forces that satisfy constraints using conjugate gradient descent."""
|
||||
|
||||
def cond(ctx: _CGContext) -> jax.Array:
|
||||
def cond(ctx: _Context) -> jax.Array:
|
||||
improvement = _rescale(m, ctx.prev_cost - ctx.cost)
|
||||
gradient = _rescale(m, math.norm(ctx.grad))
|
||||
|
||||
@@ -321,11 +336,11 @@ def cg_solve(m: Model, d: Data) -> Data:
|
||||
|
||||
return ~done
|
||||
|
||||
def body(ctx: _CGContext) -> _CGContext:
|
||||
ctx = _cg_search(m, d, ctx)
|
||||
def body(ctx: _Context) -> _Context:
|
||||
ctx = _linesearch(m, d, ctx)
|
||||
prev_grad, prev_Mgrad = ctx.grad, ctx.Mgrad # pylint: disable=invalid-name
|
||||
ctx = _cg_update_constraint(m, d, ctx)
|
||||
ctx = _cg_update_gradient(m, d, ctx)
|
||||
ctx = _update_constraint(m, d, ctx)
|
||||
ctx = _update_gradient(m, d, ctx)
|
||||
|
||||
# polak-ribiere:
|
||||
beta = jp.dot(ctx.grad, ctx.Mgrad - prev_Mgrad)
|
||||
@@ -339,12 +354,16 @@ def cg_solve(m: Model, d: Data) -> Data:
|
||||
# warmstart:
|
||||
qacc = d.qacc_smooth
|
||||
if not m.opt.disableflags & DisableBit.WARMSTART:
|
||||
warm = _CGContext.create(m, d.replace(qacc=d.qacc_warmstart), grad=False)
|
||||
smth = _CGContext.create(m, d.replace(qacc=d.qacc_smooth), grad=False)
|
||||
warm = _Context.create(m, d.replace(qacc=d.qacc_warmstart), grad=False)
|
||||
smth = _Context.create(m, d.replace(qacc=d.qacc_smooth), grad=False)
|
||||
qacc = jp.where(warm.cost < smth.cost, d.qacc_warmstart, d.qacc_smooth)
|
||||
d = d.replace(qacc=qacc)
|
||||
|
||||
ctx = jax.lax.while_loop(cond, body, _CGContext.create(m, d))
|
||||
ctx = _Context.create(m, d)
|
||||
if m.opt.iterations == 1:
|
||||
ctx = body(ctx)
|
||||
else:
|
||||
ctx = jax.lax.while_loop(cond, body, ctx)
|
||||
|
||||
d = d.replace(
|
||||
qacc_warmstart=ctx.qacc,
|
||||
|
||||
@@ -24,16 +24,10 @@ import numpy as np
|
||||
|
||||
TEST_FILES: List[str] = [
|
||||
'ant.xml',
|
||||
'ball_pendulum.xml',
|
||||
'cherry_pendulum.xml',
|
||||
'convex.xml',
|
||||
'equality.xml',
|
||||
'humanoid.xml',
|
||||
'mixed_joint_pendulum.xml',
|
||||
'single_pendulum.xml',
|
||||
'slide_pendulum.xml',
|
||||
'triple_pendulum.xml',
|
||||
'triple_pendulum_free.xml',
|
||||
'weld.xml',
|
||||
'pendula.xml',
|
||||
]
|
||||
|
||||
_ACTUATOR_TYPES = ['motor', 'velocity', 'position', 'general', 'intvelocity']
|
||||
|
||||
@@ -131,8 +131,9 @@ class SolverType(enum.IntEnum):
|
||||
Attributes:
|
||||
CG: Conjugate gradient (primal)
|
||||
"""
|
||||
# unsupported: PGS, NEWTON
|
||||
# unsupported: PGS
|
||||
CG = mujoco.mjtSolver.mjSOL_CG
|
||||
NEWTON = mujoco.mjtSolver.mjSOL_NEWTON
|
||||
|
||||
|
||||
class EqType(enum.IntEnum):
|
||||
@@ -145,7 +146,8 @@ class EqType(enum.IntEnum):
|
||||
"""
|
||||
CONNECT = mujoco.mjtEq.mjEQ_CONNECT
|
||||
WELD = mujoco.mjtEq.mjEQ_WELD
|
||||
# unsupported: JOINT, TENDON, DISTANCE
|
||||
JOINT = mujoco.mjtEq.mjEQ_JOINT
|
||||
# unsupported: TENDON, DISTANCE
|
||||
|
||||
|
||||
class TrnType(enum.IntEnum):
|
||||
@@ -380,7 +382,7 @@ class Model(PyTreeNode):
|
||||
nexclude: int
|
||||
neq: int
|
||||
nnumeric: int
|
||||
nM: int
|
||||
nM: int # pylint:disable=invalid-name
|
||||
opt: Option
|
||||
stat: Statistic
|
||||
qpos0: jax.Array
|
||||
@@ -419,14 +421,14 @@ class Model(PyTreeNode):
|
||||
dof_bodyid: np.ndarray
|
||||
dof_jntid: np.ndarray
|
||||
dof_parentid: np.ndarray
|
||||
dof_Madr: np.ndarray
|
||||
dof_Madr: np.ndarray # pylint:disable=invalid-name
|
||||
dof_solref: jax.Array
|
||||
dof_solimp: jax.Array
|
||||
dof_frictionloss: jax.Array
|
||||
dof_armature: jax.Array
|
||||
dof_damping: jax.Array
|
||||
dof_invweight0: jax.Array
|
||||
dof_M0: jax.Array
|
||||
dof_M0: jax.Array # pylint:disable=invalid-name
|
||||
geom_type: np.ndarray
|
||||
geom_contype: np.ndarray
|
||||
geom_conaffinity: np.ndarray
|
||||
@@ -635,14 +637,14 @@ class Data(PyTreeNode):
|
||||
crb: jax.Array
|
||||
actuator_length: jax.Array
|
||||
actuator_moment: jax.Array
|
||||
qM: jax.Array
|
||||
qLD: jax.Array
|
||||
qLDiagInv: jax.Array
|
||||
qLDiagSqrtInv: 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
|
||||
contact: Contact
|
||||
efc_J: jax.Array
|
||||
efc_J: jax.Array # pylint:disable=invalid-name
|
||||
efc_frictionloss: jax.Array
|
||||
efc_D: jax.Array
|
||||
efc_D: jax.Array # pylint:disable=invalid-name
|
||||
# position, velocity dependent:
|
||||
actuator_velocity: jax.Array
|
||||
cvel: jax.Array
|
||||
|
||||
@@ -33,21 +33,34 @@ _PATHS = {
|
||||
'shadow_hand': 'benchmark/model/shadow_hand/scene_right.xml',
|
||||
}
|
||||
|
||||
|
||||
_BATCH_SIZE = {
|
||||
('humanoid', 'TPU v5 lite'): 1024,
|
||||
('barkour', 'TPU v5 lite'): 1024,
|
||||
('shadow_hand', 'TPU v5 lite'): 1024,
|
||||
('humanoid', 'Tesla V100-SXM2-16GB'): 8192,
|
||||
('barkour', 'Tesla V100-SXM2-16GB'): 8192,
|
||||
('shadow_hand', 'Tesla V100-SXM2-16GB'): 4096,
|
||||
('humanoid', 'cpu'): 64,
|
||||
('barkour', 'tpu_v5e'): 1024,
|
||||
('humanoid', 'tpu_v5e'): 1024,
|
||||
('shadow_hand', 'tpu_v5e'): 1024,
|
||||
('barkour', 'gpu_a100'): 8192,
|
||||
('humanoid', 'gpu_a100'): 8192,
|
||||
('shadow_hand', 'gpu_a100'): 4096,
|
||||
('barkour', 'cpu'): 64,
|
||||
('humanoid', 'cpu'): 64,
|
||||
('shadow_hand', 'cpu'): 64,
|
||||
}
|
||||
|
||||
_SOLVER_CONFIG = {
|
||||
('barkour', 'tpu_v5e'): (mujoco.mjtSolver.mjSOL_CG, 4, 6),
|
||||
('humanoid', 'tpu_v5e'): (mujoco.mjtSolver.mjSOL_CG, 6, 6),
|
||||
('shadow_hand', 'tpu_v5e'): (mujoco.mjtSolver.mjSOL_CG, 8, 6),
|
||||
('humanoid', 'gpu_a100'): (mujoco.mjtSolver.mjSOL_NEWTON, 1, 4),
|
||||
('barkour', 'gpu_a100'): (mujoco.mjtSolver.mjSOL_NEWTON, 1, 4),
|
||||
('shadow_hand', 'gpu_a100'): (mujoco.mjtSolver.mjSOL_NEWTON, 1, 4),
|
||||
('barkour', 'cpu'): (mujoco.mjtSolver.mjSOL_NEWTON, 1, 4),
|
||||
('humanoid', 'cpu'): (mujoco.mjtSolver.mjSOL_NEWTON, 1, 4),
|
||||
('shadow_hand', 'cpu'): (mujoco.mjtSolver.mjSOL_NEWTON, 1, 4),
|
||||
}
|
||||
|
||||
|
||||
flags.DEFINE_string('model', 'humanoid', 'Model to benchmark')
|
||||
flags.DEFINE_string('device', 'cpu', 'Device benchmark is running on')
|
||||
flags.DEFINE_enum('device', 'cpu', ('cpu', 'tpu_v5e', 'gpu_a100'),
|
||||
'Device benchmark is running on')
|
||||
|
||||
|
||||
def _measure_fn(state, init_fn, step_fn, batch_size: int = 1024) -> float:
|
||||
@@ -95,6 +108,9 @@ def _run(state: benchmark.State):
|
||||
|
||||
f = epath.resource_path('mujoco.mjx') / _PATHS[FLAGS.model]
|
||||
m = mujoco.MjModel.from_xml_path(f.as_posix())
|
||||
m.opt.solver, m.opt.iterations, m.opt.ls_iterations = _SOLVER_CONFIG[
|
||||
(FLAGS.model, FLAGS.device)
|
||||
]
|
||||
m = mjx.device_put(m)
|
||||
|
||||
def init(rng):
|
||||
@@ -106,7 +122,7 @@ def _run(state: benchmark.State):
|
||||
def step(d):
|
||||
return mjx.step(m, d)
|
||||
|
||||
batch_size = _BATCH_SIZE[(FLAGS.model, jax.devices()[0].device_kind)]
|
||||
batch_size = _BATCH_SIZE[(FLAGS.model, FLAGS.device)]
|
||||
_measure_fn(state, init, step, batch_size=batch_size)
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<compiler angle="radian" meshdir="." texturedir="assets" autolimits="true"/>
|
||||
|
||||
<option timestep="0.002" iterations="4" ls_iterations="6" solver="CG">
|
||||
<option timestep="0.002" iterations="1" ls_iterations="4">
|
||||
<flag eulerdamp="disable"/>
|
||||
</option>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
-->
|
||||
|
||||
<mujoco model="Humanoid">
|
||||
<option timestep="0.005" solver="CG" iterations="6" ls_iterations="6">
|
||||
<option timestep="0.005" iterations="1" ls_iterations="4">
|
||||
<flag eulerdamp="disable"/>
|
||||
</option>
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
<pair geom1="foot2_right" geom2="floor"/>
|
||||
</contact>
|
||||
|
||||
<tendon>
|
||||
<!-- <tendon>
|
||||
<fixed name="hamstring_right" limited="true" range="-0.3 2">
|
||||
<joint joint="hip_y_right" coef=".5"/>
|
||||
<joint joint="knee_right" coef="-.5"/>
|
||||
@@ -204,7 +204,7 @@
|
||||
<joint joint="hip_y_left" coef=".5"/>
|
||||
<joint joint="knee_left" coef="-.5"/>
|
||||
</fixed>
|
||||
</tendon>
|
||||
</tendon> -->
|
||||
|
||||
<actuator>
|
||||
<motor name="abdomen_y" gear="40" joint="abdomen_y"/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<mujoco model="right_shadow_hand">
|
||||
<compiler angle="radian" meshdir="assets" autolimits="true"/>
|
||||
|
||||
<option impratio="10" solver="CG" iterations="8" ls_iterations="6">
|
||||
<option impratio="10" iterations="1" ls_iterations="4">
|
||||
<flag eulerdamp="disable"/>
|
||||
</option>
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
<mujoco model="ball_pendulum">
|
||||
<option timestep="0.02" solver="CG" iterations="6" ls_iterations="6"/>
|
||||
<compiler autolimits="true"/>
|
||||
<default>
|
||||
<geom contype="0" conaffinity="0"/>
|
||||
<joint damping="10"/>
|
||||
</default>
|
||||
<option solver="CG"/>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint axis="1 0 0" type="ball" range="0 10"/>
|
||||
<geom pos="0 0.5 0" size=".15" mass="1" type="sphere"/>
|
||||
<body pos="0.3 0.4 0.5">
|
||||
<joint axis="1 0 0" type="hinge" range="-20 20"/>
|
||||
<geom pos="0 0.5 0" size=".15" mass="2" type="sphere"/>
|
||||
<body pos="0 0.5 0">
|
||||
<joint axis="1 0 0" type="hinge" range="-30 30"/>
|
||||
<geom pos="0 0.5 0" size=".15" mass="3" type="sphere"/>
|
||||
</body>
|
||||
</body>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
@@ -1,20 +0,0 @@
|
||||
<mujoco model="cherry_pendulum">
|
||||
<option timestep="0.02" solver="CG" iterations="6" ls_iterations="6"/>
|
||||
<default>
|
||||
<geom contype="0" conaffinity="0"/>
|
||||
</default>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint axis="1 0 0" type="hinge"/>
|
||||
<geom pos="0 0.5 0" size=".15" mass="1" type="sphere"/>
|
||||
<body pos="0 0.5 0">
|
||||
<joint axis="1 0 0" type="hinge"/>
|
||||
<geom pos="0 0.5 0" size=".15" mass="2" type="sphere"/>
|
||||
</body>
|
||||
<body pos="0 0.5 0">
|
||||
<joint axis="1 0 0" type="hinge"/>
|
||||
<geom pos="0 0.5 0" size=".15" mass="3" type="sphere"/>
|
||||
</body>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
@@ -47,12 +47,25 @@
|
||||
<freejoint/>
|
||||
<geom class="free"/>
|
||||
</body>
|
||||
|
||||
<body name="box5" pos="4 0 0">
|
||||
<geom class="free"/>
|
||||
<joint name="joint1" axis="1 0 0" type="hinge" />
|
||||
</body>
|
||||
|
||||
<body name="box6" pos="4 0 0">
|
||||
<geom class="free"/>
|
||||
<joint name="joint2" axis="1 0 0" type="hinge" />
|
||||
</body>
|
||||
|
||||
|
||||
</worldbody>
|
||||
|
||||
<equality>
|
||||
<connect name="connect anchor" body1="box1" body2="beam1" anchor="0 0 -1" />
|
||||
<weld name="weld anchor weak torques" body1="box2" body2="beam2" torquescale="0.002" anchor="0 -2 0"/>
|
||||
<weld name="weld relpose" body1="box3" body2="beam3" relpose="0 0 0 1 -.3 0 0"/>
|
||||
<weld name="weld relpose+anchor" body1="box4" body2="beam4" relpose="0 0 0 1 -.3 0 0" anchor="0 0 -1"/>
|
||||
<connect name="connect anchor" body1="box1" body2="beam1" anchor="0 0 -1" />
|
||||
<weld name="weld anchor weak torques" body1="box2" body2="beam2" torquescale="0.002" anchor="0 -2 0"/>
|
||||
<weld name="weld relpose" body1="box3" body2="beam3" relpose="0 0 0 1 -.3 0 0"/>
|
||||
<weld name="weld relpose+anchor" body1="box4" body2="beam4" relpose="0 0 0 1 -.3 0 0" anchor="0 0 -1"/>
|
||||
<joint name="joint" joint1="joint1" joint2="joint2" polycoef="0 -1 0.1 0.15 0.2" />
|
||||
</equality>
|
||||
</mujoco>
|
||||
@@ -5,7 +5,7 @@
|
||||
<geom conaffinity="0" condim="3" contype="0" material="geom"/>
|
||||
<motor ctrllimited="true" ctrlrange="-.4 .4"/>
|
||||
</default>
|
||||
<option iterations="8" timestep="0.003" solver="CG"/>
|
||||
<option iterations="8" timestep="0.003"/>
|
||||
<size nkey="5" nuser_geom="1"/>
|
||||
<visual>
|
||||
<map fogend="5" fogstart="3"/>
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
<mujoco model="revolute">
|
||||
<option timestep="0.02" solver="CG" iterations="6" ls_iterations="6"/>
|
||||
<compiler autolimits="true"/>
|
||||
<default>
|
||||
<geom contype="0" conaffinity="0"/>
|
||||
<joint damping="20"/>
|
||||
</default>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint axis="1 0 0" type="ball" range="0 10"/>
|
||||
<geom pos="0 0.5 0" size=".15" mass="1" type="sphere"/>
|
||||
<body pos="0.3 0.4 0.5">
|
||||
<joint axis="1 0 0" type="hinge" range="-20 20"/>
|
||||
<joint axis="0 1 0" type="hinge" range="-20 20"/>
|
||||
<geom pos="0 0.5 0" size=".15" mass="2" type="sphere"/>
|
||||
<body pos="0 0.5 0">
|
||||
<joint axis="1 0 0" type="hinge" range="-30 30"/>
|
||||
<geom pos="0 0.5 0" size=".15" mass="3" type="sphere"/>
|
||||
</body>
|
||||
</body>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
@@ -0,0 +1,102 @@
|
||||
<!-- For validating dynamics of joints:
|
||||
|
||||
* free, ball, slide, hinge joints
|
||||
* stacked joints (e.g. hinge + slide, ball + slide, etc)
|
||||
* n-link kinematic chains
|
||||
* limits, armature, damping
|
||||
-->
|
||||
<mujoco model="pendula">
|
||||
<compiler autolimits="true"/>
|
||||
|
||||
<option timestep="0.02">
|
||||
<flag contact="disable" />
|
||||
</option>
|
||||
|
||||
<default>
|
||||
<geom type="box" pos=".1 .2 .3" size=".1 .2 .3"/>
|
||||
<joint damping="0.25" stiffness="0.1"/>
|
||||
</default>
|
||||
|
||||
<worldbody>
|
||||
<!-- a single free body -->
|
||||
<body pos="0 0 0">
|
||||
<freejoint/>
|
||||
<geom/>
|
||||
</body>
|
||||
|
||||
<!-- a single ball joint with a limit -->
|
||||
<body pos="0.5 0 0">
|
||||
<joint type="ball" range="0 35"/>
|
||||
<geom/>
|
||||
</body>
|
||||
|
||||
<!-- a single slide joint with a limit -->
|
||||
<body pos="1.0 0 0">
|
||||
<joint type="slide" axis="0.1 0.2 0.3" range="-1 1"/>
|
||||
<geom/>
|
||||
</body>
|
||||
|
||||
<!-- a single hinge joint with a limit -->
|
||||
<body pos="1.5 0 0">
|
||||
<joint type="hinge" axis="0.1 0.2 0.3" range="-35 50"/>
|
||||
<geom/>
|
||||
</body>
|
||||
|
||||
<!-- stacked joint: hinge + slide -->
|
||||
<body pos="2.0 0 0">
|
||||
<joint type="hinge" axis="0.1 0.2 0.3"/>
|
||||
<joint type="slide" axis="0.4 0.5 0.6" range="0 1"/>
|
||||
<geom/>
|
||||
</body>
|
||||
|
||||
<!-- stacked joint: slide + ball -->
|
||||
<body pos="2.5 0 0">
|
||||
<joint type="slide" axis="0.4 0.5 0.6" range="0 1"/>
|
||||
<joint type="ball"/>
|
||||
<geom/>
|
||||
</body>
|
||||
|
||||
<!-- triple pendulum of hinges -->
|
||||
<body pos="3.0 0 0">
|
||||
<joint axis="0.1 0.2 0.3" type="hinge"/>
|
||||
<geom/>
|
||||
<body pos="0 0 -0.8">
|
||||
<joint axis="0.4 0.5 0.6" type="hinge" armature="0.02" range="-20 20"/>
|
||||
<geom/>
|
||||
<body pos="0 -0.7 0">
|
||||
<joint axis="0.7 0.8 0.9" type="hinge" damping="0.75" range="-30 30"/>
|
||||
<geom/>
|
||||
</body>
|
||||
</body>
|
||||
</body>
|
||||
|
||||
<!-- cherry pendulum: two bodies attached to same parent body -->
|
||||
<body pos="3.5 0 0">
|
||||
<joint type="ball" damping="0.5" />
|
||||
<geom/>
|
||||
<body pos="0 0 -0.8">
|
||||
<joint axis="0.4 0.5 0.6" type="hinge" armature="0.02" range="-20 20"/>
|
||||
<geom/>
|
||||
</body>
|
||||
<body pos="0 -0.7 0">
|
||||
<joint axis="0.7 0.8 0.9" type="hinge" damping="0.75" range="-30 30"/>
|
||||
<geom/>
|
||||
</body>
|
||||
</body>
|
||||
|
||||
<!-- falling pendulum -->
|
||||
<body pos="4.0 0 0">
|
||||
<freejoint/>
|
||||
<geom/>
|
||||
<body pos="0 0 -0.8">
|
||||
<joint axis="0.4 0.5 0.6" type="slide" armature="0.02" range="-0.4 0.6"/>
|
||||
<geom/>
|
||||
<body pos="0 -0.7 0">
|
||||
<joint axis="0.7 0.8 0.9" type="hinge" damping="0.75" range="-30 30"/>
|
||||
<geom/>
|
||||
</body>
|
||||
</body>
|
||||
</body>
|
||||
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
@@ -1,13 +0,0 @@
|
||||
<mujoco model="pendulum">
|
||||
<option timestep="0.02" solver="CG" iterations="6" ls_iterations="6"/>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint name="slider" axis="1 0 0" type="hinge"/>
|
||||
<geom pos="0 0.5 0" size=".15" mass="1" type="sphere"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<!-- Tests that a single actuator doesn't get mangled in a physics step. -->
|
||||
<actuator>
|
||||
<motor name="slide" joint="slider" gear="10" ctrllimited="true" ctrlrange="-1 1"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
@@ -1,20 +0,0 @@
|
||||
<mujoco model="slide_pendulum">
|
||||
<option timestep="0.02" solver="CG" iterations="6" ls_iterations="6"/>
|
||||
<default>
|
||||
<geom contype="0" conaffinity="0"/>
|
||||
</default>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint axis="1 0 0" type="hinge"/>
|
||||
<geom pos="0 0.5 0" size=".15" mass="1" type="sphere"/>
|
||||
<body pos="0 0.5 0">
|
||||
<joint axis="1 0 0" type="hinge"/>
|
||||
<geom pos="0 0.5 0" size=".15" mass="2" type="sphere"/>
|
||||
<body pos="0 0.5 0">
|
||||
<joint axis="1 0 0" type="slide"/>
|
||||
<geom pos="0 0.5 0" size=".15" mass="3" type="sphere"/>
|
||||
</body>
|
||||
</body>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
@@ -1,21 +0,0 @@
|
||||
<mujoco model="triple_pendulum">
|
||||
<option timestep="0.02" solver="CG" iterations="6" ls_iterations="6"/>
|
||||
<compiler autolimits="true"/>
|
||||
<default>
|
||||
<geom contype="0" conaffinity="0"/>
|
||||
</default>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint axis="1 0 0" type="hinge" armature="0.01" range="-10 10"/>
|
||||
<geom pos="0 0.5 0" size=".15" mass="1" type="sphere"/>
|
||||
<body pos="0 0.5 0">
|
||||
<joint axis="1 0 0" type="hinge" armature="0.02" range="-20 20"/>
|
||||
<geom pos="0 0.5 0" size=".15" mass="2" type="sphere"/>
|
||||
<body pos="0 0.5 0">
|
||||
<joint axis="1 0 0" type="hinge" armature="0.03" range="-30 30"/>
|
||||
<geom pos="0 0.5 0" size=".15" mass="3" type="sphere"/>
|
||||
</body>
|
||||
</body>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
@@ -1,24 +0,0 @@
|
||||
<mujoco model="triple_pendulum_free">
|
||||
<option timestep="0.02" solver="CG" iterations="6" ls_iterations="6"/>
|
||||
<default>
|
||||
<geom contype="0" conaffinity="0"/>
|
||||
</default>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint type="free"/>
|
||||
<geom pos="0 0.5 0" size=".15" mass="1" type="sphere"/>
|
||||
</body>
|
||||
<body>
|
||||
<joint axis="1 0 0" type="hinge"/>
|
||||
<geom pos="0 0.5 0" size=".15" mass="1" type="sphere"/>
|
||||
<body pos="0 0.5 0">
|
||||
<joint axis="1 0 0" type="hinge"/>
|
||||
<geom pos="0 0.5 0" size=".15" mass="2" type="sphere"/>
|
||||
<body pos="0 0.5 0">
|
||||
<joint axis="1 0 0" type="hinge"/>
|
||||
<geom pos="0 0.5 0" size=".15" mass="3" type="sphere"/>
|
||||
</body>
|
||||
</body>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
@@ -14,8 +14,8 @@
|
||||
# ==============================================================================
|
||||
"""An example integration of MJX with the MuJoCo viewer."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
import time
|
||||
from typing import Sequence
|
||||
|
||||
from absl import app
|
||||
from absl import flags
|
||||
@@ -38,10 +38,6 @@ def main(argv: Sequence[str]) -> None:
|
||||
m = mujoco.MjModel.from_xml_path(_MODEL_PATH.value)
|
||||
d = mujoco.MjData(m)
|
||||
|
||||
# Override the solver option to CG since that is currently the only one
|
||||
# supported by MJX.
|
||||
m.opt.solver = mujoco.mjtSolver.mjSOL_CG
|
||||
|
||||
mx = mjx.device_put(m)
|
||||
dx = mjx.make_data(mx)
|
||||
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ dependencies = [
|
||||
"etils[epath]",
|
||||
"jax",
|
||||
"jaxlib",
|
||||
"mujoco>=3.0.1",
|
||||
"mujoco>=3.0.1.dev0",
|
||||
"scipy",
|
||||
"trimesh",
|
||||
]
|
||||
|
||||
@@ -33,8 +33,8 @@ jaxlib==0.4.18; python_version >= '3.9' \
|
||||
--hash=sha256:6cb20bbbdafd90e71ad0deb9295519a0175c108c8c557b84fb9fe94f751daee4 \
|
||||
--hash=sha256:116a0d6aedd3e856b52493d7e392fb1b40952b84fb72448fde1c1ab5687db667 \
|
||||
--hash=sha256:9593ff69f424947567e206f3e356b2a2df55ca68e6d815d5adc6cae308e8f652
|
||||
pip==23.2.1 \
|
||||
--hash=sha256:7ccf472345f20d35bdc9d1841ff5f313260c2c33fe417f48c30ac46cccabf5be
|
||||
pip==23.3.1 \
|
||||
--hash=sha256:55eb67bb6171d37447e82213be585b75fe2b12b359e993773aca4de9247a052b
|
||||
pytest==7.4.2 \
|
||||
--hash=sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002
|
||||
pytest-xdist==3.3.1 \
|
||||
|
||||
+440
-248
@@ -187,11 +187,9 @@
|
||||
},
|
||||
"source": [
|
||||
"# Training a Policy with MJX\n",
|
||||
"MJX is an implementation of MuJoCo written in [jax](https://jax.readthedocs.io/en/latest/index.html), enabling large batch training on GPU/TPU. In this notebook, we demonstrate training RL policies with MJX.\n",
|
||||
"MJX is an implementation of MuJoCo written in [JAX](https://jax.readthedocs.io/en/latest/index.html), enabling large batch training on GPU/TPU. In this notebook, we demonstrate how to train RL policies with MJX.\n",
|
||||
"\n",
|
||||
"First, we implement an environment `State` so that we can plug into the [brax](https://github.com/google/brax) environment API. `State` holds the observation, reward, metrics, and etc. Notably `State.pipeline_state` holds a `mjx.Data` object, which is analogous to `mjData` in MuJoCo.\n",
|
||||
"\n",
|
||||
"`MjxEnv` is an implementation of the `brax.envs.base.Env` class that initializes `mjx.Model` and `mjx.Data` objects. Inheriting from `brax.envs.base.Env` allows us to use the training agents implemented in brax. Notice that `MjxEnv` calls `mjx.step` for every `pipeline_step`, which is analgous to `mujoco.mj_step`."
|
||||
"First, we implement an environment `State` so that we can plug into the [Brax](https://github.com/google/brax) environment API. `State` holds the observation, reward, metrics, and environment info. Notably `State.pipeline_state` holds a `mjx.Data` object, which is analogous to `mjData` in MuJoCo.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -202,7 +200,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#@title State and MjxEnv\n",
|
||||
"#@title State\n",
|
||||
"\n",
|
||||
"@struct.dataclass\n",
|
||||
"class State(Base):\n",
|
||||
@@ -223,8 +221,30 @@
|
||||
" reward: jax.Array\n",
|
||||
" done: jax.Array\n",
|
||||
" metrics: Dict[str, jax.Array] = struct.field(default_factory=dict)\n",
|
||||
" info: Dict[str, Any] = struct.field(default_factory=dict)\n",
|
||||
" info: Dict[str, Any] = struct.field(default_factory=dict)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "acpXtDLNXLV9"
|
||||
},
|
||||
"source": [
|
||||
"\n",
|
||||
"Next, we implement `MjxEnv`, an environment class we'll use through the notebook. `MjxEnv` initializes a `mjx.Model` and `mjx.Data` object. Notice that `MjxEnv` calls `mjx.step` for every `pipeline_step`, which is analgous to `mujoco.mj_step`.\n",
|
||||
"\n",
|
||||
"`MjxEnv` also inherits from `brax.envs.base.Env` which allows us to use the training agents implemented in brax."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "ccujYeJ5XOhx"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#@title MjxEnv\n",
|
||||
"\n",
|
||||
"class MjxEnv(Env):\n",
|
||||
" \"\"\"API for driving an MJX system for training and inference in brax.\"\"\"\n",
|
||||
@@ -293,7 +313,7 @@
|
||||
" \"\"\"Returns 6d spatial transform and 6d velocity for all bodies.\"\"\"\n",
|
||||
" x = Transform(pos=data.xpos[1:, :], rot=data.xquat[1:, :])\n",
|
||||
" cvel = Motion(vel=data.cvel[1:, 3:], ang=data.cvel[1:, :3])\n",
|
||||
" offset = data.xipos[1:, :] - data.subtree_com[\n",
|
||||
" offset = data.xpos[1:, :] - data.subtree_com[\n",
|
||||
" self.model.body_rootid[np.arange(1, self.model.nbody)]]\n",
|
||||
" xd = Transform.create(pos=offset).vmap().do(cvel)\n",
|
||||
" return x, xd\n"
|
||||
@@ -305,7 +325,7 @@
|
||||
"id": "iPlFu4CiIgBN"
|
||||
},
|
||||
"source": [
|
||||
"Now we can define environment implementations for Humanoid and the [Barkour v0 Quadruped](https://github.com/google-deepmind/mujoco_menagerie/tree/main/google_barkour_v0). The environments define the reward and stepping logic. `reset` initializes a `State`, and `step` steps through the physics step and reward logic."
|
||||
"Finally we can implement a real environment. We choose to first implement the Humanoid environment. Notice that `reset` initializes a `State`, and `step` steps through the physics step and reward logic. The reward and stepping logic train the Humanoid to run forwards."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -336,6 +356,9 @@
|
||||
" )\n",
|
||||
" mj_model = mujoco.MjModel.from_xml_path(\n",
|
||||
" (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",
|
||||
"\n",
|
||||
" physics_steps_per_control_step = 5\n",
|
||||
" kwargs['physics_steps_per_control_step'] = kwargs.get(\n",
|
||||
@@ -444,11 +467,376 @@
|
||||
"envs.register_environment('humanoid', Humanoid)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "P1K6IznI2y83"
|
||||
},
|
||||
"source": [
|
||||
"## Visualize a Rollout\n",
|
||||
"\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."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "EhKLFK54C1CH"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# instantiate the environment\n",
|
||||
"env_name = 'humanoid'\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)\n",
|
||||
"\n",
|
||||
"# instantiate the renderer\n",
|
||||
"renderer = mujoco.Renderer(env.model)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "9f2ME2WbA5Ip"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#@title Define a render utility function\n",
|
||||
"\n",
|
||||
"def get_image(state: State, camera: str) -\u003e np.ndarray:\n",
|
||||
" \"\"\"Renders the environment state.\"\"\"\n",
|
||||
" d = mujoco.MjData(env.model)\n",
|
||||
" # write the mjx.Data into an mjData object\n",
|
||||
" mjx.device_get_into(d, state.pipeline_state)\n",
|
||||
" mujoco.mj_forward(env.model, d)\n",
|
||||
" # use the mjData object to update the renderer\n",
|
||||
" renderer.update_scene(d, camera=camera)\n",
|
||||
" return renderer.render()\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "Ph8u-v2Q2xLS"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# initialize the state\n",
|
||||
"state = jit_reset(jax.random.PRNGKey(0))\n",
|
||||
"rollout = [state]\n",
|
||||
"images = [get_image(state, camera='side')]\n",
|
||||
"\n",
|
||||
"# grab a trajectory\n",
|
||||
"for i in range(10):\n",
|
||||
" ctrl = -0.1 * jp.ones(env.sys.nu)\n",
|
||||
" state = jit_step(state, ctrl)\n",
|
||||
" rollout.append(state)\n",
|
||||
" images.append(get_image(state, camera='side'))\n",
|
||||
"\n",
|
||||
"media.show_video(images, fps=1.0 / env.dt)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "BQDG6NQ1CbZD"
|
||||
},
|
||||
"source": [
|
||||
"## Train Humanoid Policy\n",
|
||||
"\n",
|
||||
"Let's finally train a policy with PPO to make the Humanoid run forwards. Training takes about 13-14 minutes on a Tesla V100 GPU."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "xLiddQYPApBw"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"train_fn = functools.partial(\n",
|
||||
" ppo.train, num_timesteps=30_000_000, num_evals=5, reward_scaling=0.1,\n",
|
||||
" episode_length=1000, normalize_observations=True, action_repeat=1,\n",
|
||||
" unroll_length=10, num_minibatches=32, num_updates_per_batch=8,\n",
|
||||
" discounting=0.97, learning_rate=3e-4, entropy_cost=1e-3, num_envs=2048,\n",
|
||||
" batch_size=1024, seed=0)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"x_data = []\n",
|
||||
"y_data = []\n",
|
||||
"ydataerr = []\n",
|
||||
"times = [datetime.now()]\n",
|
||||
"\n",
|
||||
"max_y, min_y = 13000, 0\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",
|
||||
" ydataerr.append(metrics['eval/episode_reward_std'])\n",
|
||||
"\n",
|
||||
" plt.xlim([0, train_fn.keywords['num_timesteps'] * 1.25])\n",
|
||||
" plt.ylim([min_y, max_y])\n",
|
||||
"\n",
|
||||
" plt.xlabel('# environment steps')\n",
|
||||
" plt.ylabel('reward per episode')\n",
|
||||
" plt.title(f'y={y_data[-1]:.3f}')\n",
|
||||
"\n",
|
||||
" plt.errorbar(\n",
|
||||
" x_data, y_data, yerr=ydataerr)\n",
|
||||
" plt.show()\n",
|
||||
"\n",
|
||||
"make_inference_fn, params, _= train_fn(environment=env, progress_fn=progress)\n",
|
||||
"\n",
|
||||
"print(f'time to jit: {times[1] - times[0]}')\n",
|
||||
"print(f'time to train: {times[-1] - times[1]}')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "YYIch0HEApBx"
|
||||
},
|
||||
"source": [
|
||||
"## Save and Load Policy\n",
|
||||
"\n",
|
||||
"We can save and load the policy using the brax model API."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "Z8gI6qH6ApBx"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#@title Save Model\n",
|
||||
"model_path = '/tmp/mjx_brax_policy'\n",
|
||||
"model.save_params(model_path, params)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "h4reaWgxApBx"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#@title Load Model and Define Inference Function\n",
|
||||
"params = model.load_params(model_path)\n",
|
||||
"\n",
|
||||
"inference_fn = make_inference_fn(params)\n",
|
||||
"jit_inference_fn = jax.jit(inference_fn)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "0G357XIfApBy"
|
||||
},
|
||||
"source": [
|
||||
"## Visualize Policy\n",
|
||||
"\n",
|
||||
"Finally we can visualize the policy."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "osYasMw4ApBy"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"eval_env = envs.get_environment(env_name)\n",
|
||||
"\n",
|
||||
"jit_reset = jax.jit(eval_env.reset)\n",
|
||||
"jit_step = jax.jit(eval_env.step)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "d-UhypudApBy"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# initialize the state\n",
|
||||
"rng = jax.random.PRNGKey(0)\n",
|
||||
"state = jit_reset(rng)\n",
|
||||
"rollout = [state]\n",
|
||||
"images = [get_image(state, camera='side')]\n",
|
||||
"\n",
|
||||
"# grab a trajectory\n",
|
||||
"n_steps = 500\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)\n",
|
||||
" if i % render_every == 0:\n",
|
||||
" images.append(get_image(state, camera='side'))\n",
|
||||
"\n",
|
||||
" if state.done:\n",
|
||||
" break\n",
|
||||
"\n",
|
||||
"media.show_video(images, fps=1.0 / eval_env.dt / render_every)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "zR-heox6LARK"
|
||||
},
|
||||
"source": [
|
||||
"# MJX Policy in MuJoCo\n",
|
||||
"\n",
|
||||
"Note that we can also perform the physics step using the original MuJoCo python bindings to show that the policy trained in MJX works in MuJoCo."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "w6ixFi4dApBy"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"mj_model = eval_env.model\n",
|
||||
"mj_data = mujoco.MjData(mj_model)\n",
|
||||
"\n",
|
||||
"renderer = mujoco.Renderer(mj_model)\n",
|
||||
"ctrl = jp.zeros(mj_model.nu)\n",
|
||||
"\n",
|
||||
"images = []\n",
|
||||
"for i in range(n_steps):\n",
|
||||
" act_rng, rng = jax.random.split(rng)\n",
|
||||
"\n",
|
||||
" obs = eval_env._get_obs(mjx.device_put(mj_data), ctrl)\n",
|
||||
" ctrl, _ = jit_inference_fn(obs, act_rng)\n",
|
||||
"\n",
|
||||
" mj_data.ctrl = ctrl\n",
|
||||
" for _ in range(eval_env._physics_steps_per_control_step):\n",
|
||||
" mujoco.mj_step(mj_model, mj_data) # Physics step using MuJoCo mj_step.\n",
|
||||
"\n",
|
||||
" if i % render_every == 0:\n",
|
||||
" renderer.update_scene(mj_data, camera='side')\n",
|
||||
" images.append(renderer.render())\n",
|
||||
"\n",
|
||||
"media.show_video(images, fps=1.0 / eval_env.dt / render_every)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "65mIPj6DQNNa"
|
||||
},
|
||||
"source": [
|
||||
"# Domain Randomization\n",
|
||||
"\n",
|
||||
"We might also want to include randomization over certain `mjModel` parameters while training a policy. In MJX, we can easily create a batch of environments with randomized values populated in `mjx.Model`. Below, we show a function that randomizes friction and actuator gain/bias."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "h8mhzKjHQuoL"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def domain_randomize(sys, rng):\n",
|
||||
" \"\"\"Randomizes the mjx.Model.\"\"\"\n",
|
||||
" @jax.vmap\n",
|
||||
" def rand(rng):\n",
|
||||
" _, key = jax.random.split(rng, 2)\n",
|
||||
" # friction\n",
|
||||
" friction = jax.random.uniform(key, (1,), minval=0.6, maxval=1.4)\n",
|
||||
" friction = sys.geom_friction.at[:, 0].set(friction)\n",
|
||||
" # actuator\n",
|
||||
" _, key = jax.random.split(key, 2)\n",
|
||||
" gain_range = (-10, -5)\n",
|
||||
" param = jax.random.uniform(\n",
|
||||
" key, (1,), minval=gain_range[0], maxval=gain_range[1]\n",
|
||||
" ) + sys.actuator_gainprm[:, 0]\n",
|
||||
" gain = sys.actuator_gainprm.at[:, 0].set(param)\n",
|
||||
" bias = sys.actuator_biasprm.at[:, 1].set(-param)\n",
|
||||
" return friction, gain, bias\n",
|
||||
"\n",
|
||||
" friction, gain, bias = rand(rng)\n",
|
||||
"\n",
|
||||
" in_axes = jax.tree_map(lambda x: None, sys)\n",
|
||||
" in_axes = in_axes.tree_replace({\n",
|
||||
" 'geom_friction': 0,\n",
|
||||
" 'actuator_gainprm': 0,\n",
|
||||
" 'actuator_biasprm': 0,\n",
|
||||
" })\n",
|
||||
"\n",
|
||||
" sys = sys.tree_replace({\n",
|
||||
" 'geom_friction': friction,\n",
|
||||
" 'actuator_gainprm': gain,\n",
|
||||
" 'actuator_biasprm': bias,\n",
|
||||
" })\n",
|
||||
"\n",
|
||||
" return sys, in_axes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "gnsZo-GWSYYj"
|
||||
},
|
||||
"source": [
|
||||
"If we wanted 10 environments with randomized friction and actuator params, we can call `domain_randomize`, which returns a batched `mjModel` along with a dictionary specifying the axes that are batched."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "1K45Kp2ASV9s"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"rng = jax.random.PRNGKey(0)\n",
|
||||
"rng = jax.random.split(rng, 10)\n",
|
||||
"batched_sys, _ = domain_randomize(env.sys, rng)\n",
|
||||
"\n",
|
||||
"print('Single env friction shape: ', env.sys.geom_friction.shape)\n",
|
||||
"print('Batched env friction shape: ', batched_sys.geom_friction.shape)\n",
|
||||
"\n",
|
||||
"print('Friction on geom 0: ', env.sys.geom_friction[0, 0])\n",
|
||||
"print('Random frictions on geom 0: ', batched_sys.geom_friction[:, 0, 0])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "efnxNOnpQFuC"
|
||||
},
|
||||
"source": [
|
||||
"## Quadruped Env\n",
|
||||
"\n",
|
||||
"Let's define a quadruped environment that takes advantage of the domain randomization function. Here we use the [Barkour v0 Quadruped](https://github.com/google-deepmind/mujoco_menagerie/tree/main/google_barkour_v0) and an environment that trains a joystick policy."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"cellView": "form",
|
||||
"id": "y79PoJOCIl-O"
|
||||
},
|
||||
"outputs": [],
|
||||
@@ -483,13 +871,14 @@
|
||||
" # Penalize non-zero roll and pitch angles. L2 penalty.\n",
|
||||
" orientation=-5.0,\n",
|
||||
" # L2 regularization of joint torques, |tau|^2.\n",
|
||||
" torques=-0.0002,\n",
|
||||
" # torques=-0.0002,\n",
|
||||
" torques=-0.002,\n",
|
||||
" # Penalize the change in the action and encourage smooth\n",
|
||||
" # actions. L2 regularization |action - last_action|^2\n",
|
||||
" action_rate=-0.3,\n",
|
||||
" action_rate=-0.1,\n",
|
||||
" # Encourage long swing steps. However, it does not\n",
|
||||
" # encourage high clearances.\n",
|
||||
" feet_air_time=0.1,\n",
|
||||
" feet_air_time=0.2,\n",
|
||||
" # Encourage no motion at zero command, L2 regularization\n",
|
||||
" # |q - q_default|^2.\n",
|
||||
" stand_still=-0.5,\n",
|
||||
@@ -525,6 +914,9 @@
|
||||
" )\n",
|
||||
" mj_model = mujoco.MjModel.from_xml_path(\n",
|
||||
" (path / 'barkour_v0_mjx.xml').as_posix())\n",
|
||||
" mj_model.opt.solver = mujoco.mjtSolver.mjSOL_CG\n",
|
||||
" mj_model.opt.iterations = 4\n",
|
||||
" mj_model.opt.ls_iterations = 6\n",
|
||||
"\n",
|
||||
" physics_steps_per_control_step = 10\n",
|
||||
" kwargs['physics_steps_per_control_step'] = kwargs.get(\n",
|
||||
@@ -550,7 +942,7 @@
|
||||
" self.uppers = self._default_ap_pose + jp.array([0.2, 0.8, 0.8] * 4)\n",
|
||||
"\n",
|
||||
" def sample_command(self, rng: jax.Array) -\u003e jax.Array:\n",
|
||||
" lin_vel_x = [-0.6, 1.5] # min max [m/s]\n",
|
||||
" lin_vel_x = [-0.6, 1.0] # min max [m/s]\n",
|
||||
" lin_vel_y = [-0.8, 0.8] # min max [m/s]\n",
|
||||
" ang_vel_yaw = [-0.7, 0.7] # min max [rad/s]\n",
|
||||
"\n",
|
||||
@@ -857,171 +1249,19 @@
|
||||
"envs.register_environment('barkour', BarkourEnv)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "P1K6IznI2y83"
|
||||
},
|
||||
"source": [
|
||||
"## Visualize a Rollout\n",
|
||||
"\n",
|
||||
"Let's visualize an environment rollout by running a few env steps with sinusoidal actuation."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"cellView": "form",
|
||||
"id": "EhKLFK54C1CH"
|
||||
"id": "pi_yrcz-Qp3W"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# instantiate the environment\n",
|
||||
"\n",
|
||||
"env_name = 'barkour' # @param ['barkour', 'humanoid']\n",
|
||||
"env_name = 'barkour'\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)\n",
|
||||
"\n",
|
||||
"# instantiate the renderer\n",
|
||||
"renderer = mujoco.Renderer(env.model)\n",
|
||||
"\n",
|
||||
"def get_image(state: State):\n",
|
||||
" d = mujoco.MjData(env.model)\n",
|
||||
" # write the mjx.Data into an mjData object\n",
|
||||
" mjx.device_get_into(d, state.pipeline_state)\n",
|
||||
" mujoco.mj_forward(env.model, d)\n",
|
||||
" if env_name == 'barkour':\n",
|
||||
" camera='track'\n",
|
||||
" elif env_name == 'humanoid':\n",
|
||||
" camera = 'side'\n",
|
||||
" else:\n",
|
||||
" raise NotImplementedError(env_name)\n",
|
||||
" # use the mjData object to update the renderer\n",
|
||||
" renderer.update_scene(d, camera=camera)\n",
|
||||
" return renderer.render()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "Ph8u-v2Q2xLS"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# initialize the state\n",
|
||||
"state = jit_reset(jax.random.PRNGKey(0))\n",
|
||||
"rollout = [state]\n",
|
||||
"images = [get_image(state)]\n",
|
||||
"\n",
|
||||
"# grab a trajectory\n",
|
||||
"n_steps = 500\n",
|
||||
"render_every = 5\n",
|
||||
"\n",
|
||||
"for i in range(n_steps):\n",
|
||||
" ctrl = jp.ones(env.sys.nu) * jp.sin(2.0 * jp.pi * i / 500)\n",
|
||||
" state = jit_step(state, ctrl)\n",
|
||||
" rollout.append(state)\n",
|
||||
" if i % render_every == 0:\n",
|
||||
" images.append(get_image(state))\n",
|
||||
"\n",
|
||||
"media.show_video(images, fps=1.0 / env.dt / render_every)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "65mIPj6DQNNa"
|
||||
},
|
||||
"source": [
|
||||
"# Domain Randomization\n",
|
||||
"\n",
|
||||
"We have all the pieces to train a policy, but we might also want to include randomization over certain `mjModel` parameters. In MJX, we can easily create a batch of environments with randomized values populated in `mjx.Model`. Below, we show a function that randomizes friction and actuator gain/bias."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "h8mhzKjHQuoL"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def domain_randomize(sys, rng):\n",
|
||||
" \"\"\"Randomizes the mjx.Model.\"\"\"\n",
|
||||
" @jax.vmap\n",
|
||||
" def rand(rng):\n",
|
||||
" _, key = jax.random.split(rng, 2)\n",
|
||||
" # friction\n",
|
||||
" friction = jax.random.uniform(key, (1,), minval=0.8, maxval=1.2)\n",
|
||||
" friction = sys.geom_friction.at[:, 0].set(friction)\n",
|
||||
" # actuator\n",
|
||||
" _, key = jax.random.split(key, 2)\n",
|
||||
" gain_range = (-5, 5)\n",
|
||||
" param = jax.random.uniform(\n",
|
||||
" key, (1,), minval=gain_range[0], maxval=gain_range[1]\n",
|
||||
" ) + sys.actuator_gainprm[:, 0]\n",
|
||||
" gain = sys.actuator_gainprm.at[:, 0].set(param)\n",
|
||||
" bias = sys.actuator_biasprm.at[:, 1].set(-param)\n",
|
||||
" return friction, gain, bias\n",
|
||||
"\n",
|
||||
" friction, gain, bias = rand(rng)\n",
|
||||
"\n",
|
||||
" in_axes = jax.tree_map(lambda x: None, sys)\n",
|
||||
" in_axes = in_axes.tree_replace({\n",
|
||||
" 'geom_friction': 0,\n",
|
||||
" 'actuator_gainprm': 0,\n",
|
||||
" 'actuator_biasprm': 0,\n",
|
||||
" })\n",
|
||||
"\n",
|
||||
" sys = sys.tree_replace({\n",
|
||||
" 'geom_friction': friction,\n",
|
||||
" 'actuator_gainprm': gain,\n",
|
||||
" 'actuator_biasprm': bias,\n",
|
||||
" })\n",
|
||||
"\n",
|
||||
" return sys, in_axes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "gnsZo-GWSYYj"
|
||||
},
|
||||
"source": [
|
||||
"If we wanted 10 environments with randomized friction and actuator params, we can call `domain_randomize`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "1K45Kp2ASV9s"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"rng = jax.random.PRNGKey(0)\n",
|
||||
"rng = jax.random.split(rng, 10)\n",
|
||||
"batched_sys, _ = domain_randomize(env.sys, rng)\n",
|
||||
"\n",
|
||||
"print('Single env friction shape: ', env.sys.geom_friction.shape)\n",
|
||||
"print('Batched env friction shape: ', batched_sys.geom_friction.shape)\n",
|
||||
"\n",
|
||||
"print('Friction on geom 0: ', env.sys.geom_friction[0, 0])\n",
|
||||
"print('Random frictions on geom 0: ', batched_sys.geom_friction[:, 0, 0])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "LdqPYBlPVWwc"
|
||||
},
|
||||
"source": [
|
||||
"Note that we'll pass the `domain_randomize` function into the brax trainer; the brax training algorithms will call the randomizer over the environment batch dimension."
|
||||
"# re-instantiate the renderer\n",
|
||||
"renderer = mujoco.Renderer(env.model)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1030,9 +1270,9 @@
|
||||
"id": "nxaNFP9mA23H"
|
||||
},
|
||||
"source": [
|
||||
"# Train Policy\n",
|
||||
"## Train Policy\n",
|
||||
"\n",
|
||||
"Let's finally train a policy with PPO. Training takes about 12-13 minutes on a Tesla V100 GPU for both environments."
|
||||
"To train a policy with domain randomization, we pass in the domain randomization function into the brax train function; brax will call the domain randomization function when rolling out episodes. Training the quadruped takes about 14 minutes on a Tesla V100 GPU."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1043,59 +1283,28 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"make_networks_factory = functools.partial(\n",
|
||||
" ppo_networks.make_ppo_networks,\n",
|
||||
" policy_hidden_layer_sizes=(128, 128, 128, 128))\n",
|
||||
"train_fn = functools.partial(\n",
|
||||
" ppo.train,\n",
|
||||
" num_timesteps=60_000_000, num_evals=3, reward_scaling=1,\n",
|
||||
" episode_length=1000, normalize_observations=True,\n",
|
||||
" action_repeat=1, unroll_length=20, num_minibatches=8, gae_lambda=0.95,\n",
|
||||
" num_updates_per_batch=4, discounting=0.99, learning_rate=3e-4,\n",
|
||||
" entropy_cost=1e-2, num_envs=8192, batch_size=1024,\n",
|
||||
" network_factory=make_networks_factory,\n",
|
||||
" num_resets_per_eval=10,\n",
|
||||
" randomization_fn=domain_randomize, seed=0)\n",
|
||||
"\n",
|
||||
"if env_name == 'barkour':\n",
|
||||
" make_networks_factory = functools.partial(\n",
|
||||
" ppo_networks.make_ppo_networks,\n",
|
||||
" policy_hidden_layer_sizes=(128, 128, 128, 128))\n",
|
||||
" train_fn = functools.partial(\n",
|
||||
" ppo.train,\n",
|
||||
" num_timesteps=60_000_000, num_evals=3, reward_scaling=1,\n",
|
||||
" episode_length=1000, normalize_observations=True,\n",
|
||||
" action_repeat=1, unroll_length=20, num_minibatches=32, gae_lambda=0.95,\n",
|
||||
" num_updates_per_batch=4, discounting=0.99, learning_rate=3.0e-4,\n",
|
||||
" entropy_cost=1e-2, num_envs=8192, batch_size=1024,\n",
|
||||
" network_factory=make_networks_factory,\n",
|
||||
" num_resets_per_eval=10,\n",
|
||||
" randomization_fn=domain_randomize, seed=0)\n",
|
||||
"elif env_name == 'humanoid':\n",
|
||||
" train_fn = functools.partial(\n",
|
||||
" ppo.train, num_timesteps=30_000_000, num_evals=5, reward_scaling=0.1,\n",
|
||||
" episode_length=1000, normalize_observations=True, action_repeat=1,\n",
|
||||
" unroll_length=10, num_minibatches=32, num_updates_per_batch=8,\n",
|
||||
" discounting=0.97, learning_rate=3e-4, entropy_cost=1e-3, num_envs=2048,\n",
|
||||
" batch_size=1024, seed=0)\n",
|
||||
"else:\n",
|
||||
" raise NotImplementedError(\n",
|
||||
" f'env_name: {env_name} is not implemented in this notebook.')\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"max_y = {'barkour':30, 'humanoid': 13000}[env_name]\n",
|
||||
"min_y = {'barkour': -15}.get(env_name, 0)\n",
|
||||
"\n",
|
||||
"x_data = []\n",
|
||||
"y_data = []\n",
|
||||
"ydataerr = []\n",
|
||||
"times = [datetime.now()]\n",
|
||||
"max_y, min_y = 30, 0\n",
|
||||
"\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",
|
||||
" ydataerr.append(metrics['eval/episode_reward_std'])\n",
|
||||
"\n",
|
||||
" plt.xlim([0, train_fn.keywords['num_timesteps'] * 1.25])\n",
|
||||
" plt.ylim([min_y, max_y])\n",
|
||||
"\n",
|
||||
" plt.xlabel('# environment steps')\n",
|
||||
" plt.ylabel('reward per episode')\n",
|
||||
" plt.title(f'y={y_data[-1]:.3f}')\n",
|
||||
"\n",
|
||||
" plt.errorbar(\n",
|
||||
" x_data, y_data, yerr=ydataerr)\n",
|
||||
" plt.show()\n",
|
||||
"\n",
|
||||
"# Reset environments since internals may be overwritten by tracers due to\n",
|
||||
"# Reset environments since internals may be overwritten by tracers from the\n",
|
||||
"# domain randomization function.\n",
|
||||
"env = envs.get_environment(env_name)\n",
|
||||
"eval_env = envs.get_environment(env_name)\n",
|
||||
@@ -1107,30 +1316,6 @@
|
||||
"print(f'time to train: {times[-1] - times[1]}')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "-dRmWHsHB_1K"
|
||||
},
|
||||
"source": [
|
||||
"# Save and Load Policy\n",
|
||||
"\n",
|
||||
"We can save and load the policy using the brax model API."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "u9sdN4Xa5JoH"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#@title Save Model\n",
|
||||
"model_path = '/tmp/mjx_brax_policy'\n",
|
||||
"model.save_params(model_path, params)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
@@ -1139,7 +1324,9 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#@title Load Model and Define Inference Function\n",
|
||||
"# Save and reload params.\n",
|
||||
"model_path = '/tmp/mjx_brax_quadruped_policy'\n",
|
||||
"model.save_params(model_path, params)\n",
|
||||
"params = model.load_params(model_path)\n",
|
||||
"\n",
|
||||
"inference_fn = make_inference_fn(params)\n",
|
||||
@@ -1152,9 +1339,9 @@
|
||||
"id": "L01IrN4oCIkC"
|
||||
},
|
||||
"source": [
|
||||
"# Visualize Policy\n",
|
||||
"## Visualize Policy\n",
|
||||
"\n",
|
||||
"Finally we can visualize the policy. For the Barkour Quadruped, the joystick commands can be set through `x_vel`, `y_vel`, and `ang_vel`. `x_vel` and `y_vel` define the linear forward and sideways velocities with respect to the quadruped torso. `ang_vel` defines the angular velocity of the torso in the z direction."
|
||||
"For the Barkour Quadruped, the joystick commands can be set through `x_vel`, `y_vel`, and `ang_vel`. `x_vel` and `y_vel` define the linear forward and sideways velocities with respect to the quadruped torso. `ang_vel` defines the angular velocity of the torso in the z direction."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1183,7 +1370,7 @@
|
||||
"# @markdown Commands **only used for Barkour Env**:\n",
|
||||
"x_vel = 1.0 #@param {type: \"number\"}\n",
|
||||
"y_vel = 0.0 #@param {type: \"number\"}\n",
|
||||
"ang_vel = -0.0 #@param {type: \"number\"}\n",
|
||||
"ang_vel = -0.5 #@param {type: \"number\"}\n",
|
||||
"\n",
|
||||
"the_command = jp.array([x_vel, y_vel, ang_vel])\n",
|
||||
"\n",
|
||||
@@ -1192,7 +1379,7 @@
|
||||
"state = jit_reset(rng)\n",
|
||||
"state.info['command'] = the_command\n",
|
||||
"rollout = [state]\n",
|
||||
"images = [get_image(state)]\n",
|
||||
"images = [get_image(state, camera='track')]\n",
|
||||
"\n",
|
||||
"# grab a trajectory\n",
|
||||
"n_steps = 500\n",
|
||||
@@ -1204,7 +1391,7 @@
|
||||
" state = jit_step(state, ctrl)\n",
|
||||
" rollout.append(state)\n",
|
||||
" if i % render_every == 0:\n",
|
||||
" images.append(get_image(state))\n",
|
||||
" images.append(get_image(state, camera='track'))\n",
|
||||
"\n",
|
||||
"media.show_video(images, fps=1.0 / eval_env.dt / render_every)"
|
||||
]
|
||||
@@ -1216,7 +1403,12 @@
|
||||
"gpuClass": "premium",
|
||||
"gpuType": "V100",
|
||||
"private_outputs": true,
|
||||
"provenance": [],
|
||||
"provenance": [
|
||||
{
|
||||
"file_id": "1brcF4_qCRS2ASc-QQw1rsEwl5IjzGvq2",
|
||||
"timestamp": 1697763780236
|
||||
}
|
||||
],
|
||||
"toc_visible": true
|
||||
},
|
||||
"kernelspec": {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<compiler autolimits="true"/>
|
||||
|
||||
<option solver="CG" tolerance="1e-6" timestep=".001" integrator="implicitfast"/>
|
||||
<option solver="CG" tolerance="1e-6" timestep=".001"/>
|
||||
|
||||
<size memory="100M"/>
|
||||
|
||||
@@ -32,13 +32,13 @@
|
||||
|
||||
<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">
|
||||
radius=".0" rgba="0 .7 .7 1" name="softbody" dim="3" mass="25">
|
||||
<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"/>
|
||||
<config key="damping" value="0.002"/>
|
||||
</plugin>
|
||||
</flexcomp>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<h1>
|
||||
<a href="#"><img alt="MuJoCo" src="banner.png" width="100%"/></a>
|
||||
<a href="#"><img alt="MuJoCo" src="../../banner.png" width="100%"/></a>
|
||||
</h1>
|
||||
|
||||
## Elasticity plugins
|
||||
|
||||
@@ -64,8 +64,10 @@ int CreateStencils(std::vector<T>& elements,
|
||||
elements[t].edges[e] = it->second;
|
||||
}
|
||||
|
||||
if (!edgeidx.empty()) {
|
||||
assert(elements[t].edges[e] == edgeidx[T::kNumEdges*t+e]);
|
||||
if (!edgeidx.empty()) { // SHOULD NOT OCCUR
|
||||
if (elements[t].edges[e] != edgeidx[T::kNumEdges*t+e]) {
|
||||
mju_error("edge ordering is incoherent between flex and plugin");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,13 @@ inline void UpdateSquaredLengths(std::vector<mjtNum>& len,
|
||||
}
|
||||
}
|
||||
|
||||
inline void UpdateSquaredLengthsFlex(std::vector<mjtNum>& len,
|
||||
const mjtNum* flexedge_length) {
|
||||
for (int e = 0; e < len.size(); e++) {
|
||||
len[e] = flexedge_length[e]*flexedge_length[e];
|
||||
}
|
||||
}
|
||||
|
||||
struct Stencil2D {
|
||||
static constexpr int kNumEdges = 3;
|
||||
static constexpr int kNumVerts = 3;
|
||||
@@ -78,6 +85,54 @@ void inline GradSquaredLengths(mjtNum gradient[T::kNumEdges][2][3],
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void ComputeForce(mjtNum* qfrc_passive,
|
||||
const std::vector<T>& elements,
|
||||
const std::vector<mjtNum>& metric,
|
||||
const std::vector<mjtNum>& elongationglob,
|
||||
const mjtNum* xpos) {
|
||||
for (int t = 0; t < elements.size(); t++) {
|
||||
const int* v = elements[t].vertices;
|
||||
|
||||
// compute length gradient with respect to dofs
|
||||
mjtNum gradient[T::kNumEdges][2][3];
|
||||
GradSquaredLengths<T>(gradient, xpos, v);
|
||||
|
||||
// extract elongation of edges belonging to this element
|
||||
mjtNum elongation[T::kNumEdges];
|
||||
for (int e = 0; e < T::kNumEdges; e++) {
|
||||
int idx = elements[t].edges[e];
|
||||
elongation[e] = elongationglob[idx];
|
||||
}
|
||||
|
||||
// we now multiply the elongations by the precomputed metric tensor,
|
||||
// notice that if metric=diag(1/reference) then this would yield a
|
||||
// mass-spring model
|
||||
|
||||
// compute local force
|
||||
mjtNum force[T::kNumVerts*3] = {0};
|
||||
int offset = T::kNumEdges*T::kNumEdges;
|
||||
for (int ed1 = 0; ed1 < T::kNumEdges; ed1++) {
|
||||
for (int ed2 = 0; ed2 < T::kNumEdges; ed2++) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (int x = 0; x < 3; x++) {
|
||||
force[3 * T::edge[ed2][i] + x] +=
|
||||
elongation[ed1] * gradient[ed2][i][x] *
|
||||
metric[offset * t + T::kNumEdges * ed1 + ed2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// insert into global force
|
||||
for (int i = 0; i < T::kNumVerts; i++) {
|
||||
for (int x = 0; x < 3; x++) {
|
||||
qfrc_passive[3*v[i]+x] -= force[3*i+x];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// compute metric tensor of edge lengths inner product
|
||||
template <typename T>
|
||||
void inline MetricTensor(std::vector<mjtNum>& metric, int idx, mjtNum mu,
|
||||
|
||||
@@ -84,10 +84,12 @@ std::optional<Membrane> Membrane::Create(const mjModel* m, mjData* d,
|
||||
mjtNum E = strtod(mj_getPluginConfig(m, instance, "young"), nullptr);
|
||||
mjtNum thick =
|
||||
strtod(mj_getPluginConfig(m, instance, "thickness"), nullptr);
|
||||
mjtNum damp =
|
||||
strtod(mj_getPluginConfig(m, instance, "damping"), nullptr);
|
||||
std::vector<int> face, edge;
|
||||
String2Vector(mj_getPluginConfig(m, instance, "face"), face);
|
||||
String2Vector(mj_getPluginConfig(m, instance, "edge"), edge);
|
||||
return Membrane(m, d, instance, nu, E, thick, face, edge);
|
||||
return Membrane(m, d, instance, nu, E, thick, damp, face, edge);
|
||||
} else {
|
||||
mju_warning("Invalid parameter specification in shell plugin");
|
||||
return std::nullopt;
|
||||
@@ -96,9 +98,10 @@ std::optional<Membrane> Membrane::Create(const mjModel* m, mjData* d,
|
||||
|
||||
// plugin constructor
|
||||
Membrane::Membrane(const mjModel* m, mjData* d, int instance, mjtNum nu,
|
||||
mjtNum E, mjtNum thick, const std::vector<int>& simplex,
|
||||
mjtNum E, mjtNum thick, mjtNum damp,
|
||||
const std::vector<int>& simplex,
|
||||
const std::vector<int>& edgeidx)
|
||||
: thickness(thick) {
|
||||
: f0(-1), damping(damp), thickness(thick) {
|
||||
// count plugin bodies
|
||||
nv = ne = 0;
|
||||
for (int i = 1; i < m->nbody; i++) {
|
||||
@@ -152,50 +155,48 @@ Membrane::Membrane(const mjModel* m, mjData* d, int instance, mjtNum nu,
|
||||
// compute metric tensor
|
||||
MetricTensor<Stencil2D>(metric, t, mu, la, basis);
|
||||
}
|
||||
|
||||
// allocate array
|
||||
ne = edges.size();
|
||||
reference.assign(ne, 0);
|
||||
deformed.assign(ne, 0);
|
||||
previous.assign(ne, 0);
|
||||
elongation.assign(ne, 0);
|
||||
|
||||
// compute edge lengths at equilibrium (m->flexedge_length0 not yet available)
|
||||
UpdateSquaredLengths(reference, edges, m->body_pos+3*i0);
|
||||
|
||||
// save previous lengths
|
||||
previous = reference;
|
||||
}
|
||||
|
||||
void Membrane::Compute(const mjModel* m, mjData* d, int instance) {
|
||||
for (int t = 0; t < nt; t++) {
|
||||
int* v = elements[t].vertices;
|
||||
mjtNum kD = damping / m->opt.timestep;
|
||||
|
||||
// compute length gradient with respect to dofs
|
||||
mjtNum gradient[kNumEdges][2][3];
|
||||
GradSquaredLengths<Stencil2D>(gradient, d->xpos+3*i0, v);
|
||||
// update edge lengths
|
||||
if (f0 < 0) {
|
||||
UpdateSquaredLengths(deformed, edges, d->xpos+3*i0);
|
||||
} else {
|
||||
UpdateSquaredLengthsFlex(deformed,
|
||||
d->flexedge_length + m->flex_edgeadr[f0]);
|
||||
}
|
||||
|
||||
// compute elongation
|
||||
mjtNum elongation[kNumEdges];
|
||||
for (int e = 0; e < kNumEdges; e++) {
|
||||
int idx = elements[t].edges[e] + m->flex_edgeadr[f0];
|
||||
mjtNum deformed = d->flexedge_length[idx]*d->flexedge_length[idx];
|
||||
mjtNum reference = m->flexedge_length0[idx]*m->flexedge_length0[idx];
|
||||
elongation[e] = deformed - reference;
|
||||
}
|
||||
// we add generalized Rayleigh damping as decribed in Section 5.2 of
|
||||
// Kharevych et al., "Geometric, Variational Integrators for Computer
|
||||
// Animation" http://multires.caltech.edu/pubs/DiscreteLagrangian.pdf
|
||||
|
||||
// we now multiply the elongations by the precomputed metric tensor,
|
||||
// notice that if metric=diag(1/reference) then this would yield a
|
||||
// mass-spring model
|
||||
for (int idx = 0; idx < ne; idx++) {
|
||||
elongation[idx] = deformed[idx] - reference[idx] +
|
||||
( deformed[idx] - previous[idx] ) * kD;
|
||||
}
|
||||
|
||||
// compute local force
|
||||
mjtNum force[kNumVerts*3] = {0};
|
||||
int offset = kNumEdges*kNumEdges;
|
||||
for (int ed1 = 0; ed1 < kNumEdges; ed1++) {
|
||||
for (int ed2 = 0; ed2 < kNumEdges; ed2++) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (int x = 0; x < 3; x++) {
|
||||
force[3 * Stencil2D::edge[ed2][i] + x] +=
|
||||
elongation[ed1] * gradient[ed2][i][x] *
|
||||
metric[offset * t + kNumEdges * ed1 + ed2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// compute gradient of elastic energy and insert into passive force
|
||||
ComputeForce<Stencil2D>(d->qfrc_passive + m->body_dofadr[i0], elements,
|
||||
metric, elongation, d->xpos + 3 * i0);
|
||||
|
||||
// insert into global force
|
||||
for (int i = 0; i < kNumVerts; i++) {
|
||||
for (int x = 0; x < 3; x++) {
|
||||
d->qfrc_passive[m->body_dofadr[i0]+3*v[i]+x] -= force[3*i+x];
|
||||
}
|
||||
}
|
||||
// update stored lengths
|
||||
if (kD > 0) {
|
||||
previous = deformed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,7 +209,7 @@ void Membrane::RegisterPlugin() {
|
||||
plugin.name = "mujoco.elasticity.membrane";
|
||||
plugin.capabilityflags |= mjPLUGIN_PASSIVE;
|
||||
|
||||
const char* attributes[] = {"face", "edge", "young", "poisson", "thickness"};
|
||||
const char* attributes[] = {"face", "edge", "young", "poisson", "thickness", "damping"};
|
||||
plugin.nattribute = sizeof(attributes) / sizeof(attributes[0]);
|
||||
plugin.attributes = attributes;
|
||||
plugin.nstate = +[](const mjModel* m, int instance) { return 0; };
|
||||
|
||||
@@ -52,13 +52,18 @@ class Membrane {
|
||||
std::vector<std::pair<int, int> > edges; // edge to vertex map (ne x 2)
|
||||
|
||||
// precomputed quantities
|
||||
std::vector<mjtNum> metric; // geom-induced metric (nt x 9)
|
||||
std::vector<mjtNum> metric; // geom-induced metric (nt x 9)
|
||||
std::vector<mjtNum> reference; // reference lengths (ne x 1)
|
||||
std::vector<mjtNum> deformed; // deformed lengths (ne x 1)
|
||||
std::vector<mjtNum> previous; // previous-step lengths (ne x 1)
|
||||
std::vector<mjtNum> elongation; // edge elongation (ne x 1)
|
||||
|
||||
mjtNum damping;
|
||||
mjtNum thickness;
|
||||
|
||||
private:
|
||||
Membrane(const mjModel* m, mjData* d, int instance, mjtNum nu, mjtNum E,
|
||||
mjtNum thick, const std::vector<int>& simplex,
|
||||
mjtNum thick, mjtNum damp, const std::vector<int>& simplex,
|
||||
const std::vector<int>& edgeidx);
|
||||
};
|
||||
|
||||
|
||||
+21
-55
@@ -165,75 +165,41 @@ Solid::Solid(const mjModel* m, mjData* d, int instance, mjtNum nu, mjtNum E,
|
||||
reference.assign(ne, 0);
|
||||
deformed.assign(ne, 0);
|
||||
previous.assign(ne, 0);
|
||||
elongation.assign(ne, 0);
|
||||
|
||||
// compute edge lengths at equilibrium
|
||||
// compute edge lengths at equilibrium (m->flexedge_length0 not yet available)
|
||||
UpdateSquaredLengths(reference, edges, m->body_pos+3*i0);
|
||||
|
||||
// save previous lengths
|
||||
previous = reference;
|
||||
}
|
||||
|
||||
void Solid::Compute(const mjModel* m, mjData* d, int instance) {
|
||||
// update edges if no flex
|
||||
mjtNum kD = damping / m->opt.timestep;
|
||||
|
||||
// update edge lengths
|
||||
if (f0 < 0) {
|
||||
UpdateSquaredLengths(deformed, edges, d->xpos+3*i0);
|
||||
} else {
|
||||
UpdateSquaredLengthsFlex(deformed,
|
||||
d->flexedge_length + m->flex_edgeadr[f0]);
|
||||
}
|
||||
|
||||
// loop over all elements
|
||||
for (int t = 0; t < nt; t++) {
|
||||
int* v = elements[t].vertices;
|
||||
// we add generalized Rayleigh damping as decribed in Section 5.2 of
|
||||
// Kharevych et al., "Geometric, Variational Integrators for Computer
|
||||
// Animation" http://multires.caltech.edu/pubs/DiscreteLagrangian.pdf
|
||||
|
||||
// compute length gradient with respect to dofs
|
||||
mjtNum gradient[kNumEdges][2][3];
|
||||
GradSquaredLengths<Stencil3D>(gradient, d->xpos+3*i0, v);
|
||||
|
||||
// we add generalized Rayleigh damping as decribed in Section 5.2 of
|
||||
// Kharevych et al., "Geometric, Variational Integrators for Computer
|
||||
// Animation" http://multires.caltech.edu/pubs/DiscreteLagrangian.pdf
|
||||
|
||||
// compute elongation
|
||||
mjtNum elongation[kNumEdges];
|
||||
for (int e = 0; e < kNumEdges; e++) {
|
||||
if (f0 < 0) {
|
||||
int idx = elements[t].edges[e];
|
||||
mjtNum kD = damping / m->opt.timestep;
|
||||
elongation[e] = deformed[idx] - reference[idx] +
|
||||
( deformed[idx] - previous[idx] ) * kD;
|
||||
} else {
|
||||
int idx = elements[t].edges[e] + m->flex_edgeadr[f0];
|
||||
mjtNum deformed = d->flexedge_length[idx]*d->flexedge_length[idx];
|
||||
mjtNum reference = m->flexedge_length0[idx]*m->flexedge_length0[idx];
|
||||
elongation[e] = deformed - reference;
|
||||
}
|
||||
}
|
||||
|
||||
// we now multiply the elongations by the precomputed metric tensor,
|
||||
// notice that if metric=diag(1/reference) then this would yield a
|
||||
// mass-spring model
|
||||
|
||||
// compute local force
|
||||
mjtNum force[kNumVerts*3] = {0};
|
||||
int offset = kNumEdges*kNumEdges;
|
||||
for (int ed1 = 0; ed1 < kNumEdges; ed1++) {
|
||||
for (int ed2 = 0; ed2 < kNumEdges; ed2++) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (int x = 0; x < 3; x++) {
|
||||
force[3 * Stencil3D::edge[ed2][i] + x] +=
|
||||
elongation[ed1] * gradient[ed2][i][x] *
|
||||
metric[offset * t + kNumEdges * ed1 + ed2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// insert into global force
|
||||
for (int i = 0; i < kNumVerts; i++) {
|
||||
for (int x = 0; x < 3; x++) {
|
||||
d->qfrc_passive[m->body_dofadr[i0]+3*v[i]+x] -= force[3*i+x];
|
||||
}
|
||||
}
|
||||
for (int idx = 0; idx < ne; idx++) {
|
||||
elongation[idx] = deformed[idx] - reference[idx] +
|
||||
( deformed[idx] - previous[idx] ) * kD;
|
||||
}
|
||||
|
||||
// compute gradient of elastic energy and insert into passive force
|
||||
ComputeForce<Stencil3D>(d->qfrc_passive + m->body_dofadr[i0], elements,
|
||||
metric, elongation, d->xpos + 3 * i0);
|
||||
|
||||
// update stored lengths
|
||||
if (f0 < 0) {
|
||||
if (kD > 0) {
|
||||
previous = deformed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ class Solid {
|
||||
std::vector<mjtNum> reference; // reference lengths (ne x 1)
|
||||
std::vector<mjtNum> deformed; // deformed lengths (ne x 1)
|
||||
std::vector<mjtNum> previous; // previous-step lengths (ne x 1)
|
||||
std::vector<mjtNum> elongation; // edge elongation (ne x 1)
|
||||
|
||||
mjtNum damping;
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
<h1>
|
||||
<a href="#"><img alt="MuJoCo" src="../../banner.png" width="100%"/></a>
|
||||
</h1>
|
||||
|
||||
## Signed distance function (SDF) plugins
|
||||
|
||||
These are first-party plugins that implement implicit geometries using SDFs. They can be applied to **geoms** and
|
||||
**meshes** (in the **asset** section). Sample models can be found in [this folder](../../model/plugin/sdf/).
|
||||
|
||||
### Bolt
|
||||
|
||||
Implemented in [bolt.cc](bolt.cc). Example usage in [nutbolt.xml](../../model/plugin/sdf/nutbolt.xml).
|
||||
|
||||
This plugin implements a bolt with a hexagonal head, similar to https://www.shadertoy.com/view/XtffzX.
|
||||
|
||||
Parameters:
|
||||
|
||||
- `radius` [m]: bolt radius (default `0.26`).
|
||||
|
||||
### Bowl
|
||||
|
||||
Implemented in [bowl.cc](bowl.cc). Example usage in [bowl.xml](../../model/plugin/sdf/bowl.xml).
|
||||
|
||||
The plugin implements a cut hollow sphere from https://www.shadertoy.com/view/7tVXRt.
|
||||
|
||||
Parameters:
|
||||
|
||||
- `height` [m]: location of the cut plane (default `0.4`).
|
||||
- `radius` [m]: radius of the sphere (default `1`).
|
||||
- `thickness` [m]: thickness of the bowl (default `0.02`).
|
||||
|
||||
### Gear
|
||||
|
||||
Implemented in [gear.cc](gear.cc). Example usage in [gear.xml](../../model/plugin/sdf/gear.xml).
|
||||
|
||||
The plugin implements a 3D extrusion of the 2D gear geometry from https://www.shadertoy.com/view/3lG3WR.
|
||||
|
||||
Parameters:
|
||||
|
||||
- `alpha` [m]: initial angle of rotation of the gear (default `0`).
|
||||
- `diameter` [m]: gear diameter (default `2.8`).
|
||||
- `teeth` []: number of teeth (default `25`).
|
||||
|
||||
### Nut
|
||||
|
||||
Implemented in [nut.cc](nut.cc). Example usage in [nutbolt.xml](../../model/plugin/sdf/nutbolt.xml).
|
||||
|
||||
This plugin implements a hexagonal nut identical to the bolt head from https://www.shadertoy.com/view/XtffzX.
|
||||
|
||||
Parameters:
|
||||
|
||||
- `radius` [m]: nut radius (default `0.26`).
|
||||
|
||||
### Torus
|
||||
|
||||
Implemented in [torus.cc](torus.cc). Example usage in [torus.xml](../../model/plugin/sdf/torus.xml).
|
||||
|
||||
This plugin implements a torus.
|
||||
|
||||
Parameters:
|
||||
|
||||
- `radius1` [m]: major radius (default `0.35`).
|
||||
- `radius1` [m]: minor radius (default `0.15`).
|
||||
|
||||
### How to make your own SDF
|
||||
|
||||
Create your `MySDF.h` and `MySDF.cc` files in the SDF folder, where this README is located. Implement your SDF using the
|
||||
following interface:
|
||||
|
||||
```
|
||||
struct MySDFAttribute {
|
||||
static constexpr int nattribute =
|
||||
/* insert the number of attributes */;
|
||||
static constexpr char const* names[nattribute] =
|
||||
/* an array of attributes with the same order as the attribute array in your SDF class */;
|
||||
static constexpr mjtNum defaults[nattribute] =
|
||||
/* an array of default values for your attributes */;
|
||||
};
|
||||
|
||||
class MySDF {
|
||||
public:
|
||||
// creates a new MySDF instance or returns null on failure.
|
||||
static std::optional<MySDF> Create(const mjModel* m, mjData* d, int instance);
|
||||
MySDF(MySDF&&) = default;
|
||||
~MySDF() = default;
|
||||
|
||||
// functions that return the SDF and its gradient at a query point
|
||||
mjtNum Distance(const mjtNum point[3]) const;
|
||||
void Gradient(mjtNum grad[3], const mjtNum point[3]) const;
|
||||
|
||||
// a call to this needs to be added to register.cc
|
||||
static void RegisterPlugin();
|
||||
|
||||
// an array of attributes with the same order as in the struct above
|
||||
mjtNum attribute[MySDFAttribute::nattribute];
|
||||
|
||||
private:
|
||||
Torus(const mjModel* m, mjData* d, int instance);
|
||||
};
|
||||
```
|
||||
+21
-9
@@ -13,8 +13,10 @@
|
||||
// limitations under the License.
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include <mujoco/mjplugin.h>
|
||||
#include <mujoco/mjtnum.h>
|
||||
@@ -75,7 +77,13 @@ std::optional<Bolt> Bolt::Create(
|
||||
|
||||
// plugin constructor
|
||||
Bolt::Bolt(const mjModel* m, mjData* d, int instance) {
|
||||
radius = strtod(mj_getPluginConfig(m, instance, "radius"), nullptr);
|
||||
SdfDefault<BoltAttribute> defattribute;
|
||||
|
||||
for (int i=0; i < BoltAttribute::nattribute; i++) {
|
||||
attribute[i] = defattribute.GetDefault(
|
||||
BoltAttribute::names[i],
|
||||
mj_getPluginConfig(m, instance, BoltAttribute::names[i]));
|
||||
}
|
||||
}
|
||||
|
||||
// add new element in the vector storing iteration counts
|
||||
@@ -96,19 +104,19 @@ void Bolt::Visualize(const mjModel* m, mjData* d, const mjvOption* opt,
|
||||
|
||||
// sdf
|
||||
mjtNum Bolt::Distance(const mjtNum point[3]) const {
|
||||
return distance(point, &radius);
|
||||
return distance(point, attribute);
|
||||
}
|
||||
|
||||
// gradient of sdf
|
||||
void Bolt::Gradient(mjtNum grad[3], const mjtNum point[3]) const {
|
||||
mjtNum eps = 1e-8;
|
||||
mjtNum dist0 = distance(point, &radius);
|
||||
mjtNum dist0 = distance(point, attribute);
|
||||
mjtNum pointX[3] = {point[0]+eps, point[1], point[2]};
|
||||
mjtNum distX = distance(pointX, &radius);
|
||||
mjtNum distX = distance(pointX, attribute);
|
||||
mjtNum pointY[3] = {point[0], point[1]+eps, point[2]};
|
||||
mjtNum distY = distance(pointY, &radius);
|
||||
mjtNum distY = distance(pointY, attribute);
|
||||
mjtNum pointZ[3] = {point[0], point[1], point[2]+eps};
|
||||
mjtNum distZ = distance(pointZ, &radius);
|
||||
mjtNum distZ = distance(pointZ, attribute);
|
||||
|
||||
grad[0] = (distX - dist0) / eps;
|
||||
grad[1] = (distY - dist0) / eps;
|
||||
@@ -123,9 +131,8 @@ void Bolt::RegisterPlugin() {
|
||||
plugin.name = "mujoco.sdf.bolt";
|
||||
plugin.capabilityflags |= mjPLUGIN_SDF;
|
||||
|
||||
const char* attributes[] = {"radius"};
|
||||
plugin.nattribute = sizeof(attributes) / sizeof(attributes[0]);
|
||||
plugin.attributes = attributes;
|
||||
plugin.nattribute = BoltAttribute::nattribute;
|
||||
plugin.attributes = BoltAttribute::names;
|
||||
plugin.nstate = +[](const mjModel* m, int instance) { return 0; };
|
||||
|
||||
plugin.init = +[](const mjModel* m, mjData* d, int instance) {
|
||||
@@ -177,6 +184,11 @@ void Bolt::RegisterPlugin() {
|
||||
aabb[3] = aabb[4] = .6;
|
||||
aabb[5] = 1;
|
||||
};
|
||||
plugin.sdf_attribute =
|
||||
+[](mjtNum attribute[], const char* name[], const char* value[]) {
|
||||
SdfDefault<BoltAttribute> defattribute;
|
||||
defattribute.GetDefaults(attribute, name, value);
|
||||
};
|
||||
|
||||
mjp_registerPlugin(&plugin);
|
||||
}
|
||||
|
||||
+7
-1
@@ -28,6 +28,12 @@ namespace mujoco::plugin::sdf {
|
||||
// this plugin implements a modification of the signed distance function
|
||||
// from https://www.shadertoy.com/view/XtffzX of a bolt with a hexagonal head
|
||||
|
||||
struct BoltAttribute {
|
||||
static constexpr int nattribute = 1;
|
||||
static constexpr char const* names[nattribute] = {"radius"};
|
||||
static constexpr mjtNum defaults[nattribute] = {0.26};
|
||||
};
|
||||
|
||||
class Bolt {
|
||||
public:
|
||||
// Creates a new Bolt instance (allocated with `new`) or
|
||||
@@ -45,7 +51,7 @@ class Bolt {
|
||||
|
||||
static void RegisterPlugin();
|
||||
|
||||
mjtNum radius;
|
||||
mjtNum attribute[BoltAttribute::nattribute];
|
||||
|
||||
private:
|
||||
Bolt(const mjModel* m, mjData* d, int instance);
|
||||
|
||||
+25
-14
@@ -12,12 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <sstream>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include <mujoco/mjplugin.h>
|
||||
#include <mujoco/mjtnum.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "sdf.h"
|
||||
#include "bowl.h"
|
||||
|
||||
namespace mujoco::plugin::sdf {
|
||||
@@ -51,9 +53,16 @@ std::optional<Bowl> Bowl::Create(
|
||||
|
||||
// plugin constructor
|
||||
Bowl::Bowl(const mjModel* m, mjData* d, int instance) {
|
||||
radius = strtod(mj_getPluginConfig(m, instance, "radius"), nullptr);
|
||||
height = strtod(mj_getPluginConfig(m, instance, "height"), nullptr);
|
||||
thick = strtod(mj_getPluginConfig(m, instance, "thickness"), nullptr);
|
||||
SdfDefault<BowlAttribute> defattribute;
|
||||
|
||||
for (int i=0; i < BowlAttribute::nattribute; i++) {
|
||||
attribute[i] = defattribute.GetDefault(
|
||||
BowlAttribute::names[i],
|
||||
mj_getPluginConfig(m, instance, BowlAttribute::names[i]));
|
||||
}
|
||||
|
||||
mjtNum height = attribute[0];
|
||||
mjtNum radius = attribute[1];
|
||||
width = mju_sqrt(radius*radius - height*height);
|
||||
}
|
||||
|
||||
@@ -75,8 +84,7 @@ void Bowl::Visualize(const mjModel* m, mjData* d, const mjvOption* opt, mjvScene
|
||||
|
||||
// sdf
|
||||
mjtNum Bowl::Distance(const mjtNum point[3]) const {
|
||||
mjtNum attributes[3]= {height, radius, thick};
|
||||
return distance(point, attributes);
|
||||
return distance(point, attribute);
|
||||
}
|
||||
|
||||
// gradient of sdf
|
||||
@@ -98,16 +106,15 @@ void Bowl::Gradient(mjtNum grad[3], const mjtNum point[3]) const {
|
||||
// grad[1] = - grad_dist * point[1] / pnorm;
|
||||
// grad[2] = - grad_dist * point[2] / pnorm;
|
||||
// }
|
||||
mjtNum attributes[3]= {height, radius, thick};
|
||||
mjtNum eps = 1e-8;
|
||||
mjtNum dist0 = distance(point, attributes);
|
||||
mjtNum dist0 = distance(point, attribute);
|
||||
|
||||
mjtNum pointX[3] = {point[0]+eps, point[1], point[2]};
|
||||
mjtNum distX = distance(pointX, attributes);
|
||||
mjtNum distX = distance(pointX, attribute);
|
||||
mjtNum pointY[3] = {point[0], point[1]+eps, point[2]};
|
||||
mjtNum distY = distance(pointY, attributes);
|
||||
mjtNum distY = distance(pointY, attribute);
|
||||
mjtNum pointZ[3] = {point[0], point[1], point[2]+eps};
|
||||
mjtNum distZ = distance(pointZ, attributes);
|
||||
mjtNum distZ = distance(pointZ, attribute);
|
||||
|
||||
grad[0] = (distX - dist0) / eps;
|
||||
grad[1] = (distY - dist0) / eps;
|
||||
@@ -122,9 +129,8 @@ void Bowl::RegisterPlugin() {
|
||||
plugin.name = "mujoco.sdf.bowl";
|
||||
plugin.capabilityflags |= mjPLUGIN_SDF;
|
||||
|
||||
const char* attributes[] = {"radius", "height", "thickness"};
|
||||
plugin.nattribute = sizeof(attributes) / sizeof(attributes[0]);
|
||||
plugin.attributes = attributes;
|
||||
plugin.nattribute = BowlAttribute::nattribute;
|
||||
plugin.attributes = BowlAttribute::names;
|
||||
plugin.nstate = +[](const mjModel* m, int instance) { return 0; };
|
||||
|
||||
plugin.init = +[](const mjModel* m, mjData* d, int instance) {
|
||||
@@ -177,6 +183,11 @@ void Bowl::RegisterPlugin() {
|
||||
aabb[0] = aabb[1] = aabb[2] = 0;
|
||||
aabb[3] = aabb[4] = aabb[5] = radius + thick;
|
||||
};
|
||||
plugin.sdf_attribute =
|
||||
+[](mjtNum attribute[], const char* name[], const char* value[]) {
|
||||
SdfDefault<BowlAttribute> defattribute;
|
||||
defattribute.GetDefaults(attribute, name, value);
|
||||
};
|
||||
|
||||
mjp_registerPlugin(&plugin);
|
||||
}
|
||||
|
||||
+8
-3
@@ -25,6 +25,13 @@
|
||||
|
||||
namespace mujoco::plugin::sdf {
|
||||
|
||||
struct BowlAttribute {
|
||||
static constexpr int nattribute = 3;
|
||||
static constexpr char const* names[nattribute] = {"height", "radius",
|
||||
"thickness"};
|
||||
static constexpr mjtNum defaults[nattribute] = {.4, 1., .02};
|
||||
};
|
||||
|
||||
class Bowl {
|
||||
public:
|
||||
// Creates a new Bowl instance (allocated with `new`) or
|
||||
@@ -42,9 +49,7 @@ class Bowl {
|
||||
|
||||
static void RegisterPlugin();
|
||||
|
||||
mjtNum radius;
|
||||
mjtNum height;
|
||||
mjtNum thick;
|
||||
mjtNum attribute[BowlAttribute::nattribute];
|
||||
mjtNum width;
|
||||
|
||||
private:
|
||||
|
||||
+45
-28
@@ -12,14 +12,16 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <cstdio>
|
||||
#include <sstream>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include <mujoco/mjplugin.h>
|
||||
#include <mujoco/mjtnum.h>
|
||||
#include <mujoco/mjvisualize.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "sdf.h"
|
||||
#include "gear.h"
|
||||
|
||||
namespace mujoco::plugin::sdf {
|
||||
@@ -50,10 +52,11 @@ static mjtNum mod(mjtNum x, mjtNum y) {
|
||||
return x - y * floor(x/y);
|
||||
}
|
||||
|
||||
static mjtNum distance2D(const mjtNum p[3], const mjtNum attributes[3]) {
|
||||
static mjtNum distance2D(const mjtNum p[3],
|
||||
const mjtNum attributes[GearAttribute::nattribute]) {
|
||||
// see https://www.shadertoy.com/view/3lG3WR
|
||||
mjtNum D = 2.8; // should be an attribute
|
||||
mjtNum N = 25; // should be an attribute
|
||||
mjtNum D = attributes[1];
|
||||
mjtNum N = attributes[2];
|
||||
mjtNum psi = 3.096e-5 * N * N -6.557e-3 * N + 0.551; // pressure angle
|
||||
mjtNum alpha = attributes[0];
|
||||
|
||||
@@ -78,7 +81,10 @@ static mjtNum distance2D(const mjtNum p[3], const mjtNum attributes[3]) {
|
||||
|
||||
mjtNum h = 2.2 / Pd;
|
||||
|
||||
mjtNum innerR = Ro - h - 0.4;
|
||||
mjtNum innerR = Ro - h - 0.14*D;
|
||||
if (attributes[4] >= 0.0) {
|
||||
innerR = attributes[4] / 2.0;
|
||||
}
|
||||
|
||||
// Early exit
|
||||
if (innerR - rho > 0.0)
|
||||
@@ -126,33 +132,41 @@ static mjtNum distance2D(const mjtNum p[3], const mjtNum attributes[3]) {
|
||||
fib - (alphaStride - shift));
|
||||
|
||||
cogs = Intersection(baseWalls, cogs);
|
||||
cogs = smoothIntersection(gearOuter, cogs, 0.01);
|
||||
cogs = smoothIntersection(gearOuter, cogs, 0.0035*D);
|
||||
cogs = smoothUnion(gearLowBase, cogs, Rb - Ro + h);
|
||||
cogs = Subtraction(cogs, crownBase);
|
||||
|
||||
return extrusion(p, cogs, .1);
|
||||
return cogs;
|
||||
}
|
||||
|
||||
static mjtNum distance(const mjtNum p[3], const mjtNum attributes[3]) {
|
||||
return extrusion(p, distance2D(p, attributes), .1) - .005;
|
||||
static mjtNum distance(const mjtNum p[3],
|
||||
const mjtNum attributes[GearAttribute::nattribute]) {
|
||||
return extrusion(p, distance2D(p, attributes), attributes[3]/2.);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// factory function
|
||||
std::optional<Gear> Gear::Create(
|
||||
const mjModel* m, mjData* d, int instance) {
|
||||
if (CheckAttr("alpha", m, instance)) {
|
||||
return Gear(m, d, instance);
|
||||
std::optional<Gear> Gear::Create(const mjModel* m, mjData* d, int instance) {
|
||||
if (CheckAttr("alpha", m, instance) && CheckAttr("diameter", m, instance) &&
|
||||
CheckAttr("teeth", m, instance) &&
|
||||
CheckAttr("innerdiameter", m, instance)) {
|
||||
return Gear(m, d, instance);
|
||||
} else {
|
||||
mju_warning("Invalid parameter specification in Gear plugin");
|
||||
return std::nullopt;
|
||||
mju_warning("Invalid parameter specification in Gear plugin");
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
// plugin constructor
|
||||
Gear::Gear(const mjModel* m, mjData* d, int instance) {
|
||||
alpha = strtod(mj_getPluginConfig(m, instance, "alpha"), nullptr);
|
||||
SdfDefault<GearAttribute> defattribute;
|
||||
|
||||
for (int i=0; i < GearAttribute::nattribute; i++) {
|
||||
attribute[i] = defattribute.GetDefault(
|
||||
GearAttribute::names[i],
|
||||
mj_getPluginConfig(m, instance, GearAttribute::names[i]));
|
||||
}
|
||||
}
|
||||
|
||||
// plugin computation
|
||||
@@ -173,21 +187,20 @@ void Gear::Visualize(const mjModel* m, mjData* d, const mjvOption* opt,
|
||||
|
||||
// sdf
|
||||
mjtNum Gear::Distance(const mjtNum point[3]) const {
|
||||
return distance(point, &alpha);
|
||||
return distance(point, attribute);
|
||||
}
|
||||
|
||||
// gradient of sdf
|
||||
void Gear::Gradient(mjtNum grad[3], const mjtNum point[3]) const {
|
||||
mjtNum attributes[1]= {alpha};
|
||||
mjtNum eps = 1e-8;
|
||||
mjtNum dist0 = distance(point, attributes);
|
||||
mjtNum dist0 = distance(point, attribute);
|
||||
|
||||
mjtNum pointX[3] = {point[0]+eps, point[1], point[2]};
|
||||
mjtNum distX = distance(pointX, attributes);
|
||||
mjtNum distX = distance(pointX, attribute);
|
||||
mjtNum pointY[3] = {point[0], point[1]+eps, point[2]};
|
||||
mjtNum distY = distance(pointY, attributes);
|
||||
mjtNum distY = distance(pointY, attribute);
|
||||
mjtNum pointZ[3] = {point[0], point[1], point[2]+eps};
|
||||
mjtNum distZ = distance(pointZ, attributes);
|
||||
mjtNum distZ = distance(pointZ, attribute);
|
||||
|
||||
grad[0] = (distX - dist0) / eps;
|
||||
grad[1] = (distY - dist0) / eps;
|
||||
@@ -202,9 +215,8 @@ void Gear::RegisterPlugin() {
|
||||
plugin.name = "mujoco.sdf.gear";
|
||||
plugin.capabilityflags |= mjPLUGIN_SDF;
|
||||
|
||||
const char* attributes[] = {"alpha"};
|
||||
plugin.nattribute = sizeof(attributes) / sizeof(attributes[0]);
|
||||
plugin.attributes = attributes;
|
||||
plugin.nattribute = GearAttribute::nattribute;
|
||||
plugin.attributes = GearAttribute::names;
|
||||
plugin.nstate = +[](const mjModel* m, int instance) { return 0; };
|
||||
|
||||
plugin.init = +[](const mjModel* m, mjData* d, int instance) {
|
||||
@@ -253,8 +265,13 @@ void Gear::RegisterPlugin() {
|
||||
plugin.sdf_aabb =
|
||||
+[](mjtNum aabb[6], const mjtNum* attributes) {
|
||||
aabb[0] = aabb[1] = aabb[2] = 0;
|
||||
aabb[3] = aabb[4] = 1.7;
|
||||
aabb[5] = .11;
|
||||
aabb[3] = aabb[4] = attributes[1] / 2. * 1.25;
|
||||
aabb[5] = attributes[3] / 2. * 1.1;
|
||||
};
|
||||
plugin.sdf_attribute =
|
||||
+[](mjtNum attribute[], const char* name[], const char* value[]) {
|
||||
SdfDefault<GearAttribute> defattribute;
|
||||
defattribute.GetDefaults(attribute, name, value);
|
||||
};
|
||||
|
||||
mjp_registerPlugin(&plugin);
|
||||
|
||||
+9
-2
@@ -16,7 +16,6 @@
|
||||
#define MUJOCO_PLUGIN_SDF_GEAR_H_
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <mujoco/mjdata.h>
|
||||
#include <mujoco/mjmodel.h>
|
||||
@@ -26,6 +25,14 @@
|
||||
|
||||
namespace mujoco::plugin::sdf {
|
||||
|
||||
struct GearAttribute {
|
||||
static constexpr int nattribute = 5;
|
||||
static constexpr char const* names[nattribute] = {"alpha", "diameter",
|
||||
"teeth", "thickness",
|
||||
"innerdiameter"};
|
||||
static constexpr mjtNum defaults[nattribute] = { 0, 2.8, 25, .2 , -1};
|
||||
};
|
||||
|
||||
class Gear {
|
||||
public:
|
||||
// Creates a new Gear instance (allocated with `new`) or
|
||||
@@ -43,7 +50,7 @@ class Gear {
|
||||
|
||||
static void RegisterPlugin();
|
||||
|
||||
mjtNum alpha;
|
||||
mjtNum attribute[GearAttribute::nattribute];
|
||||
|
||||
private:
|
||||
Gear(const mjModel* m, mjData* d, int instance);
|
||||
|
||||
+23
-10
@@ -13,12 +13,15 @@
|
||||
// limitations under the License.
|
||||
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include <mujoco/mjplugin.h>
|
||||
#include <mujoco/mjtnum.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "sdf.h"
|
||||
#include "nut.h"
|
||||
|
||||
namespace mujoco::plugin::sdf {
|
||||
@@ -74,7 +77,13 @@ std::optional<Nut> Nut::Create(
|
||||
|
||||
// plugin constructor
|
||||
Nut::Nut(const mjModel* m, mjData* d, int instance) {
|
||||
radius = strtod(mj_getPluginConfig(m, instance, "radius"), nullptr);
|
||||
SdfDefault<NutAttribute> defattribute;
|
||||
|
||||
for (int i=0; i < NutAttribute::nattribute; i++) {
|
||||
attribute[i] = defattribute.GetDefault(
|
||||
NutAttribute::names[i],
|
||||
mj_getPluginConfig(m, instance, NutAttribute::names[i]));
|
||||
}
|
||||
}
|
||||
|
||||
// plugin computation
|
||||
@@ -95,19 +104,19 @@ void Nut::Visualize(const mjModel* m, mjData* d, const mjvOption* opt,
|
||||
|
||||
// sdf
|
||||
mjtNum Nut::Distance(const mjtNum point[3]) const {
|
||||
return distance(point, &radius);
|
||||
return distance(point, attribute);
|
||||
}
|
||||
|
||||
// gradient of sdf
|
||||
void Nut::Gradient(mjtNum grad[3], const mjtNum point[3]) const {
|
||||
mjtNum eps = 1e-8;
|
||||
mjtNum dist0 = distance(point, &radius);
|
||||
mjtNum dist0 = distance(point, attribute);
|
||||
mjtNum pointX[3] = {point[0]+eps, point[1], point[2]};
|
||||
mjtNum distX = distance(pointX, &radius);
|
||||
mjtNum distX = distance(pointX, attribute);
|
||||
mjtNum pointY[3] = {point[0], point[1]+eps, point[2]};
|
||||
mjtNum distY = distance(pointY, &radius);
|
||||
mjtNum distY = distance(pointY, attribute);
|
||||
mjtNum pointZ[3] = {point[0], point[1], point[2]+eps};
|
||||
mjtNum distZ = distance(pointZ, &radius);
|
||||
mjtNum distZ = distance(pointZ, attribute);
|
||||
|
||||
grad[0] = (distX - dist0) / eps;
|
||||
grad[1] = (distY - dist0) / eps;
|
||||
@@ -122,9 +131,8 @@ void Nut::RegisterPlugin() {
|
||||
plugin.name = "mujoco.sdf.nut";
|
||||
plugin.capabilityflags |= mjPLUGIN_SDF;
|
||||
|
||||
const char* attributes[] = {"radius"};
|
||||
plugin.nattribute = sizeof(attributes) / sizeof(attributes[0]);
|
||||
plugin.attributes = attributes;
|
||||
plugin.nattribute = NutAttribute::nattribute;
|
||||
plugin.attributes = NutAttribute::names;
|
||||
plugin.nstate = +[](const mjModel* m, int instance) { return 0; };
|
||||
|
||||
plugin.init = +[](const mjModel* m, mjData* d, int instance) {
|
||||
@@ -176,6 +184,11 @@ void Nut::RegisterPlugin() {
|
||||
aabb[3] = aabb[4] = .6;
|
||||
aabb[5] = 1;
|
||||
};
|
||||
plugin.sdf_attribute =
|
||||
+[](mjtNum attribute[], const char* name[], const char* value[]) {
|
||||
SdfDefault<NutAttribute> defattribute;
|
||||
defattribute.GetDefaults(attribute, name, value);
|
||||
};
|
||||
|
||||
mjp_registerPlugin(&plugin);
|
||||
}
|
||||
|
||||
+7
-1
@@ -28,6 +28,12 @@ namespace mujoco::plugin::sdf {
|
||||
// this plugin implements a modification of the signed distance function
|
||||
// from https://www.shadertoy.com/view/XtffzX of hexagonal nut
|
||||
|
||||
struct NutAttribute {
|
||||
static constexpr int nattribute = 1;
|
||||
static constexpr char const* names[nattribute] = {"radius"};
|
||||
static constexpr mjtNum defaults[nattribute] = {0.26};
|
||||
};
|
||||
|
||||
class Nut {
|
||||
public:
|
||||
// Creates a new Nut instance (allocated with `new`) or
|
||||
@@ -45,7 +51,7 @@ class Nut {
|
||||
|
||||
static void RegisterPlugin();
|
||||
|
||||
mjtNum radius;
|
||||
mjtNum attribute[NutAttribute::nattribute];
|
||||
|
||||
private:
|
||||
Nut(const mjModel* m, mjData* d, int instance);
|
||||
|
||||
+3
-1
@@ -13,10 +13,12 @@
|
||||
// limitations under the License.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
#include <mujoco/mjplugin.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "sdf.h"
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
#ifndef MUJOCO_PLUGIN_SDF_SDF_H_
|
||||
#define MUJOCO_PLUGIN_SDF_SDF_H_
|
||||
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <mujoco/mujoco.h>
|
||||
@@ -40,6 +43,43 @@ inline mjtNum Fract(mjtNum x) {
|
||||
// reads numeric attributes
|
||||
bool CheckAttr(const char* name, const mjModel* m, int instance);
|
||||
|
||||
// converts attributes to numeric or returns default if not present
|
||||
template <typename T>
|
||||
class SdfDefault {
|
||||
public:
|
||||
SdfDefault() {
|
||||
for (int i = 0; i < T::nattribute; i++) {
|
||||
default_[T::names[i]] = T::defaults[i];
|
||||
}
|
||||
}
|
||||
|
||||
// get a single default value
|
||||
mjtNum GetDefault(const char* name, const char* value) {
|
||||
if (std::string(value).empty()) {
|
||||
return default_[name];
|
||||
}
|
||||
try {
|
||||
mjtNum num = std::stod(value);
|
||||
return num;
|
||||
} catch (const std::invalid_argument& e) {
|
||||
mju_error("invalid attribute value for '%s'", name);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// populate attribute array
|
||||
void GetDefaults(mjtNum* attribute, const char* names[],
|
||||
const char* values[]) {
|
||||
for (int i = 0; i < default_.size(); i++) {
|
||||
attribute[i] = GetDefault(names[i], values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<std::string, mjtNum> default_;
|
||||
};
|
||||
|
||||
// stores the history of gradient descent iterations
|
||||
class SdfVisualizer {
|
||||
public:
|
||||
SdfVisualizer();
|
||||
|
||||
+20
-9
@@ -12,12 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <sstream>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include <mujoco/mjplugin.h>
|
||||
#include <mujoco/mjtnum.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "sdf.h"
|
||||
#include "torus.h"
|
||||
|
||||
namespace mujoco::plugin::sdf {
|
||||
@@ -43,19 +45,24 @@ std::optional<Torus> Torus::Create(
|
||||
|
||||
// plugin constructor
|
||||
Torus::Torus(const mjModel* m, mjData* d, int instance) {
|
||||
radius[0] = strtod(mj_getPluginConfig(m, instance, "radius1"), nullptr);
|
||||
radius[1] = strtod(mj_getPluginConfig(m, instance, "radius2"), nullptr);
|
||||
SdfDefault<TorusAttribute> defattribute;
|
||||
|
||||
for (int i=0; i < TorusAttribute::nattribute; i++) {
|
||||
attribute[i] = defattribute.GetDefault(
|
||||
TorusAttribute::names[i],
|
||||
mj_getPluginConfig(m, instance, TorusAttribute::names[i]));
|
||||
}
|
||||
}
|
||||
|
||||
// sdf
|
||||
mjtNum Torus::Distance(const mjtNum point[3]) const {
|
||||
return distance(point, radius);
|
||||
return distance(point, attribute);
|
||||
}
|
||||
|
||||
// gradient of sdf
|
||||
void Torus::Gradient(mjtNum grad[3], const mjtNum p[3]) const {
|
||||
mjtNum len_xy = mju_sqrt(p[0]*p[0] + p[1]*p[1]);
|
||||
mjtNum q = len_xy - radius[0];
|
||||
mjtNum q = len_xy - attribute[0];
|
||||
mjtNum grad_q[2] = { p[0] / len_xy, p[1] / len_xy };
|
||||
mjtNum len_qz = mju_sqrt(q*q + p[2]*p[2]);
|
||||
grad[0] = q*grad_q[0] / mjMAX(len_qz, mjMINVAL);
|
||||
@@ -71,9 +78,8 @@ void Torus::RegisterPlugin() {
|
||||
plugin.name = "mujoco.sdf.torus";
|
||||
plugin.capabilityflags |= mjPLUGIN_SDF;
|
||||
|
||||
const char* attributes[] = {"radius1", "radius2", "axis"};
|
||||
plugin.nattribute = sizeof(attributes) / sizeof(attributes[0]);
|
||||
plugin.attributes = attributes;
|
||||
plugin.nattribute = TorusAttribute::nattribute;
|
||||
plugin.attributes = TorusAttribute::names;
|
||||
plugin.nstate = +[](const mjModel* m, int instance) { return 0; };
|
||||
|
||||
plugin.init = +[](const mjModel* m, mjData* d, int instance) {
|
||||
@@ -117,8 +123,13 @@ void Torus::RegisterPlugin() {
|
||||
aabb[3] = aabb[4] = attributes[0] + attributes[1];
|
||||
aabb[5] = attributes[1];
|
||||
};
|
||||
plugin.sdf_attribute =
|
||||
+[](mjtNum attribute[], const char* name[], const char* value[]) {
|
||||
SdfDefault<TorusAttribute> defattribute;
|
||||
defattribute.GetDefaults(attribute, name, value);
|
||||
};
|
||||
|
||||
mjp_registerPlugin(&plugin);
|
||||
mjp_registerPlugin(&plugin);
|
||||
}
|
||||
|
||||
} // namespace mujoco::plugin::sdf
|
||||
|
||||
+7
-1
@@ -24,6 +24,12 @@
|
||||
|
||||
namespace mujoco::plugin::sdf {
|
||||
|
||||
struct TorusAttribute {
|
||||
static constexpr int nattribute = 2;
|
||||
static constexpr char const* names[nattribute] = {"radius1", "radius2"};
|
||||
static constexpr mjtNum defaults[nattribute] = { .35, .15 };
|
||||
};
|
||||
|
||||
class Torus {
|
||||
public:
|
||||
// Creates a new Torus instance or returns null on failure.
|
||||
@@ -36,7 +42,7 @@ class Torus {
|
||||
|
||||
static void RegisterPlugin();
|
||||
|
||||
mjtNum radius[2];
|
||||
mjtNum attribute[TorusAttribute::nattribute];
|
||||
|
||||
private:
|
||||
Torus(const mjModel* m, mjData* d, int instance);
|
||||
|
||||
@@ -41,8 +41,8 @@ numpy==1.26.0; python_version >= '3.9' \
|
||||
--hash=sha256:c78a22e95182fb2e7874712433eaa610478a3caf86f28c621708d35fa4fd6e7f \
|
||||
--hash=sha256:86f737708b366c36b76e953c46ba5827d8c27b7a8c9d0f471810728e5a2fe57c \
|
||||
--hash=sha256:020cdbee66ed46b671429c7265cf00d8ac91c046901c55684954c3958525dab2
|
||||
pip==23.2.1 \
|
||||
--hash=sha256:7ccf472345f20d35bdc9d1841ff5f313260c2c33fe417f48c30ac46cccabf5be
|
||||
pip==23.3.1 \
|
||||
--hash=sha256:55eb67bb6171d37447e82213be585b75fe2b12b359e993773aca4de9247a052b
|
||||
PyOpenGL==3.1.7 \
|
||||
--hash=sha256:a6ab19cf290df6101aaf7470843a9c46207789855746399d0af92521a0a92b7a
|
||||
pytest==7.4.2 \
|
||||
|
||||
@@ -2,8 +2,8 @@ absl-py==2.0.0 \
|
||||
--hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3
|
||||
build==1.0.3 \
|
||||
--hash=sha256:589bf99a67df7c9cf07ec0ac0e5e2ea5d4b37ac63301c4986d1acb126aa83f8f
|
||||
pip==23.2.1 \
|
||||
--hash=sha256:7ccf472345f20d35bdc9d1841ff5f313260c2c33fe417f48c30ac46cccabf5be
|
||||
pip==23.3.1 \
|
||||
--hash=sha256:55eb67bb6171d37447e82213be585b75fe2b12b359e993773aca4de9247a052b
|
||||
setuptools==68.2.2 \
|
||||
--hash=sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a
|
||||
|
||||
|
||||
@@ -265,7 +265,6 @@ target_sources(
|
||||
structs_header
|
||||
INTERFACE indexer_xmacro.h
|
||||
indexers.h
|
||||
mjdata_meta.h
|
||||
structs.h
|
||||
)
|
||||
set_target_properties(structs_header PROPERTIES PUBLIC_HEADER structs.h)
|
||||
|
||||
@@ -74,10 +74,21 @@ TEST_XML_SENSOR = r"""
|
||||
"""
|
||||
|
||||
TEST_XML_PLUGIN = r"""
|
||||
<mujoco model="test">
|
||||
<mujoco>
|
||||
<option gravity="0 0 0"/>
|
||||
<extension>
|
||||
<plugin plugin="mujoco.elasticity.cable"/>
|
||||
</extension>
|
||||
<worldbody>
|
||||
<composite type="cable" curve="s" count="41 1 1" size="1" offset="0 0 1" initial="none">
|
||||
<plugin plugin="mujoco.elasticity.cable">
|
||||
<config key="twist" value="1e6"/>
|
||||
<config key="bend" value="1e9"/>
|
||||
</plugin>
|
||||
<joint kind="main" damping="2"/>
|
||||
<geom type="capsule" size=".005" density="1"/>
|
||||
</composite>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
"""
|
||||
|
||||
@@ -1048,10 +1059,24 @@ Euler integrator, semi-implicit in velocity.
|
||||
while data_instances:
|
||||
d = data_instances.pop()
|
||||
self.assertEqual(sys.getrefcount(d), 2)
|
||||
del d
|
||||
while model_instances:
|
||||
m = model_instances.pop()
|
||||
self.assertEqual(sys.getrefcount(m), 2)
|
||||
|
||||
# This test is disabled on PyPy as it uses sys.getrefcount
|
||||
# However PyPy is not officially supported by MuJoCo
|
||||
@absltest.skipIf(sys.implementation.name == 'pypy',
|
||||
reason='requires sys.getrefcount')
|
||||
def test_mjdata_holds_ref_to_model(self):
|
||||
data = mujoco.MjData(mujoco.MjModel.from_xml_string('<mujoco/>'))
|
||||
model = data.model
|
||||
# references: one in `data.model, one in `model`, one in the temporary ref
|
||||
# passed to getrefcount.
|
||||
self.assertEqual(sys.getrefcount(data.model), 3)
|
||||
del data
|
||||
self.assertEqual(sys.getrefcount(model), 2)
|
||||
|
||||
def test_can_initialize_mjv_structs(self):
|
||||
self.assertIsInstance(mujoco.MjvScene(), mujoco.MjvScene)
|
||||
self.assertIsInstance(mujoco.MjvCamera(), mujoco.MjvCamera)
|
||||
@@ -1110,6 +1135,41 @@ Euler integrator, semi-implicit in velocity.
|
||||
bodyexclude=0,
|
||||
geomid=geomid)
|
||||
|
||||
def test_mj_multi_ray(self):
|
||||
nray = 3
|
||||
geom1 = np.zeros(1, np.int32)
|
||||
pnt = np.array([-0.3, 0, 0.1])
|
||||
vec = np.array([[1, 0, 0], [0, 0, 1], [0, 0, -1]], np.float64)
|
||||
dist_ex = np.array([0.2, -1, 0.1])
|
||||
geom_ex = np.array([1, -1, 0])
|
||||
geomid = np.zeros(nray, np.int32)
|
||||
dist = np.zeros(nray, np.float64)
|
||||
|
||||
mujoco.mj_forward(self.model, self.data)
|
||||
mujoco.mj_multiRay(
|
||||
m=self.model,
|
||||
d=self.data,
|
||||
pnt=pnt,
|
||||
vec=vec.flatten(),
|
||||
geomgroup=None,
|
||||
flg_static=1,
|
||||
bodyexclude=-1,
|
||||
geomid=geomid,
|
||||
dist=dist,
|
||||
nray=nray,
|
||||
cutoff=mujoco.mjMAXVAL)
|
||||
|
||||
for i in range(0, 3):
|
||||
self.assertEqual(
|
||||
dist[i],
|
||||
mujoco.mj_ray(
|
||||
self.model, self.data, pnt, vec[i], None, 1, -1, geom1
|
||||
),
|
||||
)
|
||||
self.assertEqual(geomid[i], geom1)
|
||||
self.assertEqual(geomid[i], geom_ex[i])
|
||||
self.assertAlmostEqual(dist[i], dist_ex[i])
|
||||
|
||||
def test_inverse_fd_none(self):
|
||||
eps = 1e-6
|
||||
flg_centered = 0
|
||||
@@ -1287,6 +1347,34 @@ Euler integrator, semi-implicit in velocity.
|
||||
self.assertEqual(data.geom(3).xpos[2], 4)
|
||||
self.assertEqual(data.geom(4).xpos[2], 5)
|
||||
|
||||
def test_load_plugin(self):
|
||||
model = mujoco.MjModel.from_xml_string(TEST_XML_PLUGIN)
|
||||
data = mujoco.MjData(model)
|
||||
mujoco.mj_forward(model, data)
|
||||
|
||||
def test_copy_mjdata_with_plugin(self):
|
||||
model = mujoco.MjModel.from_xml_string(TEST_XML_PLUGIN)
|
||||
data1 = mujoco.MjData(model)
|
||||
self.assertIs(data1.model, model)
|
||||
mujoco.mj_step(model, data1)
|
||||
data2 = copy.copy(data1)
|
||||
mujoco.mj_step(model, data1)
|
||||
mujoco.mj_step(model, data2)
|
||||
np.testing.assert_array_equal(data1.qpos, data2.qpos)
|
||||
self.assertIs(data1.model, data2.model)
|
||||
|
||||
def test_deepcopy_mjdata_with_plugin(self):
|
||||
model = mujoco.MjModel.from_xml_string(TEST_XML_PLUGIN)
|
||||
data1 = mujoco.MjData(model)
|
||||
self.assertIs(data1.model, model)
|
||||
mujoco.mj_step(model, data1)
|
||||
data2 = copy.deepcopy(data1)
|
||||
mujoco.mj_step(model, data1)
|
||||
mujoco.mj_step(model, data2)
|
||||
np.testing.assert_array_equal(data1.qpos, data2.qpos)
|
||||
self.assertIsNot(data1.model, data2.model)
|
||||
self.assertNotEqual(data1.model._address, data2.model._address)
|
||||
|
||||
def _assert_attributes_equal(self, actual_obj, expected_obj, attr_to_compare):
|
||||
for name in attr_to_compare:
|
||||
actual_value = getattr(actual_obj, name)
|
||||
@@ -1300,9 +1388,6 @@ Euler integrator, semi-implicit in velocity.
|
||||
self.fail("Attribute '{}' differs from expected value: {}".format(
|
||||
name, str(e)))
|
||||
|
||||
def test_load_plugin(self):
|
||||
mujoco.MjModel.from_xml_string(TEST_XML_PLUGIN)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
absltest.main()
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
|
||||
#include <Eigen/Core>
|
||||
#include <mujoco/mjxmacro.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "errors.h"
|
||||
#include "function_traits.h"
|
||||
#include "functions.h"
|
||||
#include "private.h"
|
||||
@@ -37,6 +39,7 @@ PYBIND11_MODULE(_functions, pymodule) {
|
||||
namespace py = ::pybind11;
|
||||
namespace traits = python_traits;
|
||||
|
||||
using EigenVectorI = Eigen::Vector<int, Eigen::Dynamic>;
|
||||
using EigenVectorX = Eigen::Vector<mjtNum, Eigen::Dynamic>;
|
||||
using EigenArrayXX = Eigen::Array<
|
||||
mjtNum, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
|
||||
@@ -567,7 +570,25 @@ PYBIND11_MODULE(_functions, pymodule) {
|
||||
Def<traits::mj_versionString>(pymodule);
|
||||
|
||||
// Ray collision
|
||||
Def<traits::mj_multiRay>(pymodule);
|
||||
Def<traits::mj_multiRay>(
|
||||
pymodule,
|
||||
[](const raw::MjModel* m, raw::MjData* d, const mjtNum(*pnt)[3],
|
||||
Eigen::Ref<const EigenVectorX> vec,
|
||||
std::optional<Eigen::Ref<const Eigen::Vector<mjtByte, mjNGROUP>>>
|
||||
geomgroup,
|
||||
mjtByte flg_static, int bodyexclude, Eigen::Ref<EigenVectorI> geomid,
|
||||
Eigen::Ref<EigenVectorX> dist, int nray, mjtNum cutoff) {
|
||||
if (dist.size() != nray || geomid.size() != nray) {
|
||||
throw py::type_error("dist and geomid should be of size nray");
|
||||
}
|
||||
if (vec.size() != 3 * nray) {
|
||||
throw py::type_error("vec should be of size 3*nray");
|
||||
}
|
||||
InterceptMjErrors(::mj_multiRay)(
|
||||
m, d, &(*pnt)[0], vec.data(),
|
||||
geomgroup.has_value() ? geomgroup->data() : nullptr, flg_static,
|
||||
bodyexclude, geomid.data(), dist.data(), nray, cutoff);
|
||||
});
|
||||
Def<traits::mj_ray>(
|
||||
pymodule,
|
||||
[](const raw::MjModel* m, const raw::MjData* d, const mjtNum(*pnt)[3],
|
||||
@@ -1367,7 +1388,7 @@ PYBIND11_MODULE(_functions, pymodule) {
|
||||
}
|
||||
|
||||
#undef MJ_M
|
||||
#define MJ_M(x) d.metadata().x
|
||||
#define MJ_M(x) d.model().get()->x
|
||||
#undef MJ_D
|
||||
#define MJ_D(x) data->x
|
||||
#define X(type, name, nr, nc) \
|
||||
@@ -1379,7 +1400,7 @@ PYBIND11_MODULE(_functions, pymodule) {
|
||||
}
|
||||
|
||||
MJDATA_ARENA_POINTERS_PRIMAL
|
||||
if (d.metadata().is_dual) {
|
||||
if (mj_isDual(d.model().get())) {
|
||||
MJDATA_ARENA_POINTERS_DUAL
|
||||
}
|
||||
#undef X
|
||||
|
||||
+13
-21
@@ -13,16 +13,13 @@
|
||||
// limitations under the License.
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "indexers.h"
|
||||
#include "mjdata_meta.h"
|
||||
#include "raw.h"
|
||||
#include "util/crossplatform.h"
|
||||
|
||||
@@ -76,7 +73,6 @@ IDToName MakeIDToName(int count, IntPtr name_offsets, CharPtr names) {
|
||||
// the MuJoCo category of the entity itself, e.g. nbody indicates that the
|
||||
// field belongs to a body.
|
||||
// T: Scalar data type of the field.
|
||||
// M: Either raw::MjModel or MjDataMetadata.
|
||||
//
|
||||
// Args:
|
||||
// base_ptr: Pointer to the first entry in the entire field.
|
||||
@@ -87,35 +83,35 @@ IDToName MakeIDToName(int count, IntPtr name_offsets, CharPtr names) {
|
||||
// additional dimension of the size len(qvel) of the particular joint.
|
||||
// m: Used for dereferencing MjSize.
|
||||
// owner: The base object whose lifetime is tied to the returned array.
|
||||
template <auto MjSize, typename T, typename M>
|
||||
template <auto MjSize, typename T>
|
||||
py::array_t<T> MakeArray(T* base_ptr, int index, std::vector<int>&& shape,
|
||||
const M& m, py::handle owner) {
|
||||
const raw::MjModel& m, py::handle owner) {
|
||||
int offset;
|
||||
if (MjSize == &M::nq) {
|
||||
if (MjSize == &raw::MjModel::nq) {
|
||||
offset = m.jnt_qposadr[index];
|
||||
shape.insert(
|
||||
shape.begin(),
|
||||
((index < m.njnt-1) ? m.jnt_qposadr[index+1] : m.nq) - offset);
|
||||
} else if (MjSize == &M::nv) {
|
||||
} else if (MjSize == &raw::MjModel::nv) {
|
||||
offset = m.jnt_dofadr[index];
|
||||
shape.insert(
|
||||
shape.begin(),
|
||||
((index < m.njnt-1) ? m.jnt_dofadr[index+1] : m.nv) - offset);
|
||||
} else if (MjSize == &M::nhfielddata) {
|
||||
} else if (MjSize == &raw::MjModel::nhfielddata) {
|
||||
offset = m.hfield_adr[index];
|
||||
shape.insert(shape.begin(), m.hfield_ncol[index]);
|
||||
shape.insert(shape.begin(), m.hfield_nrow[index]);
|
||||
} else if (MjSize == &M::ntexdata) {
|
||||
} else if (MjSize == &raw::MjModel::ntexdata) {
|
||||
offset = m.tex_adr[index];
|
||||
shape.insert(shape.begin(), m.tex_width[index]);
|
||||
shape.insert(shape.begin(), m.tex_height[index]);
|
||||
} else if (MjSize == &M::nsensordata) {
|
||||
} else if (MjSize == &raw::MjModel::nsensordata) {
|
||||
offset = m.sensor_adr[index];
|
||||
shape.insert(shape.begin(), m.sensor_dim[index]);
|
||||
} else if (MjSize == &M::nnumericdata) {
|
||||
} else if (MjSize == &raw::MjModel::nnumericdata) {
|
||||
offset = m.numeric_adr[index];
|
||||
shape.insert(shape.begin(), m.numeric_size[index]);
|
||||
} else if (MjSize == &M::ntupledata) {
|
||||
} else if (MjSize == &raw::MjModel::ntupledata) {
|
||||
offset = m.tuple_adr[index];
|
||||
shape.insert(shape.begin(), m.tuple_size[index]);
|
||||
} else {
|
||||
@@ -135,9 +131,7 @@ py::array_t<T> MakeArray(T* base_ptr, int index, std::vector<int>&& shape,
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// M is either a raw::MjModel or MjDataMetadata.
|
||||
template <typename M>
|
||||
NameToIDMappings::NameToIDMappings(const M& m)
|
||||
NameToIDMappings::NameToIDMappings(const raw::MjModel& m)
|
||||
: body(MakeNameToID(m.nbody, m.name_bodyadr, m.names)),
|
||||
jnt(MakeNameToID(m.njnt, m.name_jntadr, m.names)),
|
||||
geom(MakeNameToID(m.ngeom, m.name_geomadr, m.names)),
|
||||
@@ -160,9 +154,7 @@ NameToIDMappings::NameToIDMappings(const M& m)
|
||||
tuple(MakeNameToID(m.ntuple, m.name_tupleadr, m.names)),
|
||||
key(MakeNameToID(m.nkey, m.name_keyadr, m.names)) {}
|
||||
|
||||
// M is either a raw::MjModel or MjDataMetadata.
|
||||
template <typename M>
|
||||
IDToNameMappings::IDToNameMappings(const M& m)
|
||||
IDToNameMappings::IDToNameMappings(const raw::MjModel& m)
|
||||
: body(MakeIDToName(m.nbody, m.name_bodyadr, m.names)),
|
||||
jnt(MakeIDToName(m.njnt, m.name_jntadr, m.names)),
|
||||
geom(MakeIDToName(m.ngeom, m.name_geomadr, m.names)),
|
||||
@@ -223,7 +215,7 @@ MJMODEL_VIEW_GROUPS
|
||||
MJMODEL_VIEW_GROUPS
|
||||
#undef XGROUP
|
||||
|
||||
MjDataIndexer::MjDataIndexer(raw::MjData* d, const MjDataMetadata* m,
|
||||
MjDataIndexer::MjDataIndexer(raw::MjData* d, const raw::MjModel* m,
|
||||
py::handle owner)
|
||||
: d_(d),
|
||||
m_(m),
|
||||
@@ -369,7 +361,7 @@ MJMODEL_KEYFRAME
|
||||
#define X(type, prefix, var, dim0, dim1) \
|
||||
py::array_t<type> XGROUP::var() { \
|
||||
if (!var##_.has_value()) { \
|
||||
var##_.emplace(MakeArray<&MjDataMetadata::dim0>( \
|
||||
var##_.emplace(MakeArray<&raw::MjModel::dim0>( \
|
||||
d_->prefix##var, index_, MAKE_SHAPE(dim1), *m_, owner_)); \
|
||||
} \
|
||||
return *var##_; \
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <absl/container/flat_hash_map.h>
|
||||
#include <mujoco/mjxmacro.h>
|
||||
#include "indexer_xmacro.h"
|
||||
#include "mjdata_meta.h"
|
||||
#include "raw.h"
|
||||
#include <pybind11/numpy.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
@@ -35,9 +34,7 @@ using NameToID = absl::flat_hash_map<std::string, int>;
|
||||
using IDToName = std::vector<std::string>;
|
||||
|
||||
struct NameToIDMappings {
|
||||
// M is either a raw::MjModel or MjDataMetadata.
|
||||
template <typename M>
|
||||
explicit NameToIDMappings(const M& m);
|
||||
explicit NameToIDMappings(const raw::MjModel& m);
|
||||
|
||||
NameToID body;
|
||||
NameToID jnt;
|
||||
@@ -63,9 +60,7 @@ struct NameToIDMappings {
|
||||
};
|
||||
|
||||
struct IDToNameMappings {
|
||||
// M is either a raw::MjModel or MjDataMetadata.
|
||||
template <typename M>
|
||||
explicit IDToNameMappings(const M& m);
|
||||
explicit IDToNameMappings(const raw::MjModel& m);
|
||||
|
||||
IDToName body;
|
||||
IDToName jnt;
|
||||
@@ -163,7 +158,7 @@ class MjModelIndexer {
|
||||
class MjDataGroupedViewsBase {
|
||||
public:
|
||||
MjDataGroupedViewsBase(int index, std::string_view name, raw::MjData* d,
|
||||
const MjDataMetadata* m,
|
||||
const raw::MjModel* m,
|
||||
pybind11::handle owner)
|
||||
: index_(index), name_(name), d_(d), m_(m), owner_(owner) {}
|
||||
|
||||
@@ -175,7 +170,7 @@ class MjDataGroupedViewsBase {
|
||||
int index_;
|
||||
std::string name_;
|
||||
raw::MjData* d_;
|
||||
const MjDataMetadata* m_;
|
||||
const raw::MjModel* m_;
|
||||
pybind11::handle owner_;
|
||||
};
|
||||
|
||||
@@ -201,7 +196,7 @@ MJDATA_VIEW_GROUPS
|
||||
// (e.g. a particular geom or joint) either by name or by ID.
|
||||
class MjDataIndexer {
|
||||
public:
|
||||
MjDataIndexer(raw::MjData* d, const MjDataMetadata* m,
|
||||
MjDataIndexer(raw::MjData* d, const raw::MjModel* m,
|
||||
pybind11::handle owner);
|
||||
|
||||
#define XGROUP(MjDataGroupedViews, field, nfield, FIELD_XMACROS) \
|
||||
@@ -213,7 +208,7 @@ class MjDataIndexer {
|
||||
|
||||
private:
|
||||
raw::MjData* d_;
|
||||
const MjDataMetadata* m_;
|
||||
const raw::MjModel* m_;
|
||||
pybind11::handle owner_;
|
||||
NameToIDMappings name_to_id_;
|
||||
IDToNameMappings id_to_name_;
|
||||
|
||||
@@ -1,108 +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.
|
||||
|
||||
#ifndef MUJOCO_PYTHON_MJDATA_META_H_
|
||||
#define MUJOCO_PYTHON_MJDATA_META_H_
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include <mujoco/mujoco.h>
|
||||
#include <mujoco/mjxmacro.h>
|
||||
#include "raw.h"
|
||||
#include "util/crossplatform.h"
|
||||
|
||||
namespace mujoco::python {
|
||||
namespace _impl {
|
||||
template <typename T> class MjWrapper;
|
||||
} // namespace _impl
|
||||
|
||||
#define MJDATA_METADATA \
|
||||
X( int, jnt_qposadr, njnt ) \
|
||||
X( int, jnt_dofadr, njnt ) \
|
||||
X( int, hfield_nrow, nhfield ) \
|
||||
X( int, hfield_ncol, nhfield ) \
|
||||
X( int, hfield_adr, nhfield ) \
|
||||
X( int, tex_height, ntex ) \
|
||||
X( int, tex_width, ntex ) \
|
||||
X( int, tex_adr, ntex ) \
|
||||
X( int, sensor_dim, nsensor ) \
|
||||
X( int, sensor_adr, nsensor ) \
|
||||
X( int, numeric_adr, nnumeric ) \
|
||||
X( int, numeric_size, nnumeric ) \
|
||||
X( int, tuple_adr, ntuple ) \
|
||||
X( int, tuple_size, ntuple ) \
|
||||
X( int, name_bodyadr, nbody ) \
|
||||
X( int, name_jntadr, njnt ) \
|
||||
X( int, name_geomadr, ngeom ) \
|
||||
X( int, name_siteadr, nsite ) \
|
||||
X( int, name_camadr, ncam ) \
|
||||
X( int, name_lightadr, nlight ) \
|
||||
X( int, name_meshadr, nmesh ) \
|
||||
X( int, name_skinadr, nskin ) \
|
||||
X( int, name_hfieldadr, nhfield ) \
|
||||
X( int, name_texadr, ntex ) \
|
||||
X( int, name_matadr, nmat ) \
|
||||
X( int, name_pairadr, npair ) \
|
||||
X( int, name_excludeadr, nexclude ) \
|
||||
X( int, name_eqadr, neq ) \
|
||||
X( int, name_tendonadr, ntendon ) \
|
||||
X( int, name_actuatoradr, nu ) \
|
||||
X( int, name_sensoradr, nsensor ) \
|
||||
X( int, name_numericadr, nnumeric ) \
|
||||
X( int, name_textadr, ntext ) \
|
||||
X( int, name_tupleadr, ntuple ) \
|
||||
X( int, name_keyadr, nkey ) \
|
||||
X( char, names, nnames )
|
||||
|
||||
// A subset of mjModel fields that are required to reconstruct an MjDataWrapper.
|
||||
struct MjDataMetadata {
|
||||
public:
|
||||
friend class _impl::MjWrapper<raw::MjData>;
|
||||
explicit MjDataMetadata(const raw::MjModel* m)
|
||||
:
|
||||
#define X(var) var(m->var),
|
||||
MJMODEL_INTS
|
||||
#undef X
|
||||
|
||||
#define X(dtype, var, n) \
|
||||
var([](dtype* src, int len) { \
|
||||
dtype* dst = new dtype[len]; \
|
||||
std::memcpy(dst, src, len * sizeof(dtype)); \
|
||||
return dst; \
|
||||
}(m->var, m->n)),
|
||||
|
||||
MJDATA_METADATA
|
||||
#undef X
|
||||
is_dual(mj_isDual(m)) {
|
||||
}
|
||||
|
||||
#define X(var) decltype(raw::MjModel::var) var;
|
||||
MJMODEL_INTS
|
||||
#undef X
|
||||
#define X(type, var, n) std::shared_ptr<type[]> var;
|
||||
MJDATA_METADATA
|
||||
#undef X
|
||||
|
||||
bool is_dual;
|
||||
|
||||
private:
|
||||
MjDataMetadata() = default;
|
||||
MjDataMetadata(const MjDataMetadata& other) = default;
|
||||
MjDataMetadata(MjDataMetadata&& other) = default;
|
||||
};
|
||||
|
||||
} // namespace mujoco::python
|
||||
|
||||
#endif // MUJOCO_PYTHON_MJDATA_META_H_
|
||||
@@ -25,6 +25,8 @@ import ctypes
|
||||
import importlib.util
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
if platform.system() != 'Darwin':
|
||||
@@ -48,7 +50,32 @@ def main(argv):
|
||||
|
||||
# Conda doesn't create a separate shared library for Python.
|
||||
# We instead use the Python binary itself, which can be dlopened just as well.
|
||||
os.environ['MJPYTHON_LIBPYTHON'] = get_executable_path()
|
||||
libpython_path = get_executable_path()
|
||||
os.environ['MJPYTHON_LIBPYTHON'] = libpython_path
|
||||
|
||||
# In some installations (e.g. CommandLineTools), the Python interpreter loads
|
||||
# dylibs from @executable_path-relative paths. This will not resolve
|
||||
# correctly since @executable_path will be the directory containing the
|
||||
# mjpython binary when we execve. We therefore preemptively resolve all
|
||||
# @executable_path-relative paths now and add them to
|
||||
# DYLD_FALLBACK_LIBRARY_PATH.
|
||||
libpython_dir = os.path.dirname(libpython_path)
|
||||
dyld_fallback_paths = (
|
||||
os.environ.get('DYLD_FALLBACK_LIBRARY_PATH', '').split(':'))
|
||||
pattern = re.compile(r'@executable_path/(.+) \(offset \d+\)\Z')
|
||||
otool_out = subprocess.run(
|
||||
['otool', '-l', libpython_path],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
).stdout.decode()
|
||||
for line in otool_out.split('\n'):
|
||||
m = pattern.search(line)
|
||||
if m is not None:
|
||||
new_path = os.path.dirname(os.path.join(libpython_dir, m.group(1)))
|
||||
if new_path not in dyld_fallback_paths:
|
||||
dyld_fallback_paths.insert(0, new_path)
|
||||
|
||||
os.environ['DYLD_FALLBACK_LIBRARY_PATH'] = ':'.join(dyld_fallback_paths)
|
||||
|
||||
# argv[0] is currently the path to this script.
|
||||
# Replace it with sys.executable to preserve e.g. virtualenv path.
|
||||
|
||||
+88
-77
@@ -37,10 +37,11 @@
|
||||
#include "errors.h"
|
||||
#include "function_traits.h"
|
||||
#include "indexers.h"
|
||||
#include "mjdata_meta.h"
|
||||
#include "private.h"
|
||||
#include "raw.h"
|
||||
#include "serialization.h"
|
||||
#include <pybind11/cast.h>
|
||||
#include <pybind11/detail/common.h>
|
||||
#include <pybind11/numpy.h>
|
||||
#include <pybind11/operators.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
@@ -147,7 +148,8 @@ MjVisualRgbaWrapper::MjWrapper()
|
||||
X(rangefinder),
|
||||
X(constraint),
|
||||
X(slidercrank),
|
||||
X(crankbroken) {}
|
||||
X(crankbroken),
|
||||
X(frustum) {}
|
||||
|
||||
MjVisualRgbaWrapper::MjWrapper(raw::MjVisualRgba* ptr, py::handle owner)
|
||||
: WrapperBase(ptr, owner),
|
||||
@@ -172,7 +174,8 @@ MjVisualRgbaWrapper::MjWrapper(raw::MjVisualRgba* ptr, py::handle owner)
|
||||
X(rangefinder),
|
||||
X(constraint),
|
||||
X(slidercrank),
|
||||
X(crankbroken) {}
|
||||
X(crankbroken),
|
||||
X(frustum) {}
|
||||
#undef X
|
||||
|
||||
MjVisualRgbaWrapper::MjWrapper(const MjVisualRgbaWrapper& other)
|
||||
@@ -451,7 +454,8 @@ void MjModelWrapper::Serialize(std::ostream& output) const {
|
||||
WriteBytes(output, buffer.data(), model_size);
|
||||
}
|
||||
|
||||
MjModelWrapper MjModelWrapper::Deserialize(std::istream& input) {
|
||||
std::unique_ptr<MjModelWrapper> MjModelWrapper::Deserialize(
|
||||
std::istream& input) {
|
||||
CheckInput(input, "mjModel");
|
||||
|
||||
char serializationVersion = ReadChar(input);
|
||||
@@ -477,7 +481,7 @@ MjModelWrapper MjModelWrapper::Deserialize(std::istream& input) {
|
||||
if (!model) {
|
||||
throw py::value_error("Invalid serialized mjModel.");
|
||||
}
|
||||
return MjModelWrapper(model);
|
||||
return std::unique_ptr<MjModelWrapper>(new MjModelWrapper(model));
|
||||
}
|
||||
|
||||
// ==================== MJCONTACT ==============================================
|
||||
@@ -552,13 +556,13 @@ MjDataWrapper* MjDataWrapper::FromRawPointer(raw::MjData* m) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
MjDataWrapper::MjWrapper(const MjModelWrapper& model)
|
||||
: WrapperBase(InterceptMjErrors(mj_makeData)(model.get()),
|
||||
MjDataWrapper::MjWrapper(MjModelWrapper* model)
|
||||
: WrapperBase(InterceptMjErrors(mj_makeData)(model->get()),
|
||||
&MjDataCapsuleDestructor),
|
||||
#undef MJ_M
|
||||
#define MJ_M(x) model.get()->x
|
||||
#define MJ_M(x) model->get()->x
|
||||
#define X(dtype, var, dim0, dim1) \
|
||||
var(InitPyArray(X_ARRAY_SHAPE(model.get()->dim0, dim1), ptr_->var, owner_)),
|
||||
var(InitPyArray(X_ARRAY_SHAPE(model->get()->dim0, dim1), ptr_->var, owner_)),
|
||||
MJDATA_POINTERS
|
||||
#undef MJ_M
|
||||
#define MJ_M(x) (x)
|
||||
@@ -569,8 +573,9 @@ MjDataWrapper::MjWrapper(const MjModelWrapper& model)
|
||||
#define X(dtype, var, dim0, dim1) var(InitPyArray(ptr_->var, owner_)),
|
||||
MJDATA_VECTOR
|
||||
#undef X
|
||||
metadata_(model.get()),
|
||||
indexer_(ptr_, &metadata_, owner_) {
|
||||
model_(model),
|
||||
model_ref_(py::cast(model_)),
|
||||
indexer_(ptr_, model_->get(), owner_) {
|
||||
bool is_newly_inserted = false;
|
||||
{
|
||||
py::gil_scoped_acquire gil;
|
||||
@@ -585,9 +590,9 @@ MjDataWrapper::MjWrapper(const MjModelWrapper& model)
|
||||
MjDataWrapper::MjWrapper(const MjDataWrapper& other)
|
||||
: WrapperBase(other.Copy(), &MjDataCapsuleDestructor),
|
||||
#undef MJ_M
|
||||
#define MJ_M(x) other.metadata_.x
|
||||
#define MJ_M(x) other.model_->get()->x
|
||||
#define X(dtype, var, dim0, dim1) \
|
||||
var(InitPyArray(X_ARRAY_SHAPE(other.metadata_.dim0, dim1), ptr_->var, \
|
||||
var(InitPyArray(X_ARRAY_SHAPE(other.model_->get()->dim0, dim1), ptr_->var, \
|
||||
owner_)),
|
||||
MJDATA_POINTERS
|
||||
#undef MJ_M
|
||||
@@ -599,8 +604,9 @@ MjDataWrapper::MjWrapper(const MjDataWrapper& other)
|
||||
#define X(dtype, var, dim0, dim1) var(InitPyArray(ptr_->var, owner_)),
|
||||
MJDATA_VECTOR
|
||||
#undef X
|
||||
metadata_(other.metadata_),
|
||||
indexer_(ptr_, &metadata_, owner_) {
|
||||
model_(other.model_),
|
||||
model_ref_(other.model_ref_),
|
||||
indexer_(ptr_, model_->get(), owner_) {
|
||||
bool is_newly_inserted = false;
|
||||
{
|
||||
py::gil_scoped_acquire gil;
|
||||
@@ -615,9 +621,9 @@ MjDataWrapper::MjWrapper(const MjDataWrapper& other)
|
||||
MjDataWrapper::MjWrapper(MjDataWrapper&& other)
|
||||
: WrapperBase(other.ptr_, other.owner_),
|
||||
#undef MJ_M
|
||||
#define MJ_M(x) other.metadata_.x
|
||||
#define MJ_M(x) other.model_->get()->x
|
||||
#define X(dtype, var, dim0, dim1) \
|
||||
var(InitPyArray(X_ARRAY_SHAPE(other.metadata_.dim0, dim1), ptr_->var, \
|
||||
var(InitPyArray(X_ARRAY_SHAPE(other.model_->get()->dim0, dim1), ptr_->var, \
|
||||
owner_)),
|
||||
MJDATA_POINTERS
|
||||
#undef MJ_M
|
||||
@@ -629,8 +635,9 @@ MjDataWrapper::MjWrapper(MjDataWrapper&& other)
|
||||
#define X(dtype, var, dim0, dim1) var(InitPyArray(ptr_->var, owner_)),
|
||||
MJDATA_VECTOR
|
||||
#undef X
|
||||
metadata_(other.metadata_),
|
||||
indexer_(ptr_, &metadata_, owner_) {
|
||||
model_(other.model_),
|
||||
model_ref_(std::move(other.model_ref_)),
|
||||
indexer_(ptr_, model_->get(), owner_) {
|
||||
bool is_newly_inserted = false;
|
||||
{
|
||||
py::gil_scoped_acquire gil;
|
||||
@@ -644,12 +651,13 @@ MjDataWrapper::MjWrapper(MjDataWrapper&& other)
|
||||
other.ptr_ = nullptr;
|
||||
}
|
||||
|
||||
MjDataWrapper::MjWrapper(MjDataMetadata&& metadata, raw::MjData* d)
|
||||
: WrapperBase(d, &MjDataCapsuleDestructor),
|
||||
MjDataWrapper::MjWrapper(const MjDataWrapper& other, MjModelWrapper* model)
|
||||
: WrapperBase(other.Copy(), &MjDataCapsuleDestructor),
|
||||
#undef MJ_M
|
||||
#define MJ_M(x) metadata.x
|
||||
#define X(dtype, var, dim0, dim1) \
|
||||
var(InitPyArray(X_ARRAY_SHAPE(metadata.dim0, dim1), ptr_->var, owner_)),
|
||||
#define MJ_M(x) other.model_->get()->x
|
||||
#define X(dtype, var, dim0, dim1) \
|
||||
var(InitPyArray(X_ARRAY_SHAPE(other.model_->get()->dim0, dim1), ptr_->var, \
|
||||
owner_)),
|
||||
MJDATA_POINTERS
|
||||
#undef MJ_M
|
||||
#define MJ_M(x) (x)
|
||||
@@ -660,8 +668,39 @@ MjDataWrapper::MjWrapper(MjDataMetadata&& metadata, raw::MjData* d)
|
||||
#define X(dtype, var, dim0, dim1) var(InitPyArray(ptr_->var, owner_)),
|
||||
MJDATA_VECTOR
|
||||
#undef X
|
||||
metadata_(std::move(metadata)),
|
||||
indexer_(ptr_, &metadata_, owner_) {
|
||||
model_(model),
|
||||
model_ref_(py::cast(model_)),
|
||||
indexer_(ptr_, model_->get(), owner_) {
|
||||
bool is_newly_inserted = false;
|
||||
{
|
||||
py::gil_scoped_acquire gil;
|
||||
is_newly_inserted = MjDataRawPointerMap().insert({ptr_, this}).second;
|
||||
}
|
||||
if (!is_newly_inserted) {
|
||||
throw UnexpectedError(
|
||||
"MjDataRawPointerMap already contains this raw mjData*");
|
||||
}
|
||||
}
|
||||
|
||||
MjDataWrapper::MjWrapper(MjModelWrapper* model, raw::MjData* d)
|
||||
: WrapperBase(d, &MjDataCapsuleDestructor),
|
||||
#undef MJ_M
|
||||
#define MJ_M(x) model->get()->x
|
||||
#define X(dtype, var, dim0, dim1) \
|
||||
var(InitPyArray(X_ARRAY_SHAPE(model->get()->dim0, dim1), ptr_->var, owner_)),
|
||||
MJDATA_POINTERS
|
||||
#undef MJ_M
|
||||
#define MJ_M(x) (x)
|
||||
#undef X
|
||||
|
||||
contact(MjContactList(ptr_->contact, NConMax(ptr_), &ptr_->ncon, owner_)),
|
||||
|
||||
#define X(dtype, var, dim0, dim1) var(InitPyArray(ptr_->var, owner_)),
|
||||
MJDATA_VECTOR
|
||||
#undef X
|
||||
model_(model),
|
||||
model_ref_(py::cast(model_)),
|
||||
indexer_(ptr_, model_->get(), owner_) {
|
||||
bool is_newly_inserted = false;
|
||||
{
|
||||
py::gil_scoped_acquire gil;
|
||||
@@ -692,19 +731,7 @@ void MjDataWrapper::Serialize(std::ostream& output) const {
|
||||
// TODO: Replace this custom serialization with a protobuf
|
||||
WriteChar(output, kSerializationVersion);
|
||||
|
||||
// Write all size fields
|
||||
#define X(var) WriteInt(output, this->metadata_.var);
|
||||
MJMODEL_INTS
|
||||
#undef X
|
||||
|
||||
WriteInt(output, this->metadata_.is_dual);
|
||||
|
||||
#define X(dtype, var, n) \
|
||||
WriteBytes(output, this->metadata_.var.get(), \
|
||||
this->metadata_.n * sizeof(dtype));
|
||||
|
||||
MJDATA_METADATA
|
||||
#undef X
|
||||
model_->Serialize(output);
|
||||
|
||||
// Write struct and scalar fields
|
||||
#define X(var) WriteBytes(output, &ptr_->var, sizeof(ptr_->var))
|
||||
@@ -727,15 +754,15 @@ void MjDataWrapper::Serialize(std::ostream& output) const {
|
||||
|
||||
// Write buffer contents
|
||||
{
|
||||
MJDATA_POINTERS_PREAMBLE((&this->metadata_))
|
||||
MJDATA_POINTERS_PREAMBLE((this->model_->get()))
|
||||
|
||||
#define X(type, name, nr, nc) \
|
||||
WriteBytes(output, ptr_->name, sizeof(type)*(this->metadata_.nr)*(nc));
|
||||
WriteBytes(output, ptr_->name, sizeof(type)*(this->model_->get()->nr)*(nc));
|
||||
MJDATA_POINTERS
|
||||
#undef X
|
||||
|
||||
#undef MJ_M
|
||||
#define MJ_M(x) this->metadata_.x
|
||||
#define MJ_M(x) this->model_->get()->x
|
||||
#undef MJ_D
|
||||
#define MJ_D(x) this->ptr_->x
|
||||
#define X(type, name, nr, nc) \
|
||||
@@ -745,7 +772,7 @@ void MjDataWrapper::Serialize(std::ostream& output) const {
|
||||
|
||||
MJDATA_ARENA_POINTERS_CONTACT
|
||||
MJDATA_ARENA_POINTERS_PRIMAL
|
||||
if (this->metadata_.is_dual) {
|
||||
if (mj_isDual(this->model_->get())) {
|
||||
MJDATA_ARENA_POINTERS_DUAL
|
||||
}
|
||||
if (this->ptr_->nisland) {
|
||||
@@ -767,28 +794,12 @@ MjDataWrapper MjDataWrapper::Deserialize(std::istream& input) {
|
||||
throw py::value_error("Incompatible serialization version.");
|
||||
}
|
||||
|
||||
// Read all size and address fields
|
||||
MjDataMetadata metadata;
|
||||
raw::MjModel m{0};
|
||||
// Read the model that was used to create the mjData.
|
||||
std::unique_ptr<MjModelWrapper> m_wrapper =
|
||||
MjModelWrapper::Deserialize(input);
|
||||
raw::MjModel& m = *m_wrapper->get();
|
||||
|
||||
#define X(var) \
|
||||
metadata.var = ReadInt(input); \
|
||||
CheckInput(input, "mjData"); \
|
||||
m.var = metadata.var;
|
||||
|
||||
MJMODEL_INTS
|
||||
#undef X
|
||||
|
||||
metadata.is_dual = ReadInt(input);
|
||||
|
||||
#define X(dtype, var, n) \
|
||||
metadata.var.reset(new dtype[metadata.n]); \
|
||||
ReadBytes(input, metadata.var.get(), metadata.n * sizeof(dtype)); \
|
||||
CheckInput(input, "mjData"); \
|
||||
m.var = metadata.var.get();
|
||||
|
||||
MJDATA_METADATA
|
||||
#undef X
|
||||
bool is_dual = mj_isDual(&m);
|
||||
|
||||
raw::MjData* d = mj_makeData(&m);
|
||||
if (!d) {
|
||||
@@ -839,7 +850,7 @@ MjDataWrapper MjDataWrapper::Deserialize(std::istream& input) {
|
||||
|
||||
MJDATA_ARENA_POINTERS_CONTACT
|
||||
MJDATA_ARENA_POINTERS_PRIMAL
|
||||
if (metadata.is_dual) {
|
||||
if (is_dual) {
|
||||
MJDATA_ARENA_POINTERS_DUAL
|
||||
}
|
||||
if (d->nisland) {
|
||||
@@ -859,18 +870,12 @@ MjDataWrapper MjDataWrapper::Deserialize(std::istream& input) {
|
||||
throw py::value_error("Invalid serialized mjData.");
|
||||
}
|
||||
|
||||
return MjDataWrapper(std::move(metadata), d);
|
||||
return MjDataWrapper(m_wrapper.release(), d);
|
||||
}
|
||||
|
||||
raw::MjData* MjDataWrapper::Copy() const {
|
||||
raw::MjModel m{0};
|
||||
#define X(var) m.var = this->metadata_.var;
|
||||
MJMODEL_INTS
|
||||
#undef X
|
||||
#define X(dtype, var, n) m.var = this->metadata_.var.get();
|
||||
MJDATA_METADATA
|
||||
#undef X
|
||||
return InterceptMjErrors(mj_copyData)(NULL, &m, this->ptr_);
|
||||
const raw::MjModel* m = model_->get();
|
||||
return InterceptMjErrors(mj_copyData)(NULL, m, this->ptr_);
|
||||
}
|
||||
|
||||
// ==================== MJSTATISTIC ============================================
|
||||
@@ -1488,6 +1493,7 @@ PYBIND11_MODULE(_structs, m) {
|
||||
X(framewidth);
|
||||
X(constraint);
|
||||
X(slidercrank);
|
||||
X(frustum);
|
||||
#undef X
|
||||
|
||||
py::class_<MjVisualRgbaWrapper> mjVisualRgba(mjVisual, "Rgba");
|
||||
@@ -1522,6 +1528,7 @@ PYBIND11_MODULE(_structs, m) {
|
||||
X(constraint);
|
||||
X(slidercrank);
|
||||
X(crankbroken);
|
||||
X(frustum);
|
||||
#undef X
|
||||
|
||||
#define X(var) \
|
||||
@@ -1886,15 +1893,19 @@ This is useful for example when the MJB is not available as a file on disk.)"));
|
||||
|
||||
// ==================== MJDATA ===============================================
|
||||
py::class_<MjDataWrapper> mjData(m, "MjData");
|
||||
mjData.def(py::init<const MjModelWrapper&>());
|
||||
mjData.def(py::init<MjModelWrapper*>());
|
||||
mjData.def_property_readonly("_address", [](const MjDataWrapper& d) {
|
||||
return reinterpret_cast<std::uintptr_t>(d.get());
|
||||
});
|
||||
mjData.def_property_readonly("model", [](const MjDataWrapper& d) {
|
||||
return &d.model();
|
||||
});
|
||||
mjData.def("__copy__", [](const MjDataWrapper& other) {
|
||||
return MjDataWrapper(other);
|
||||
});
|
||||
mjData.def("__deepcopy__", [](const MjDataWrapper& other, py::dict) {
|
||||
return MjDataWrapper(other);
|
||||
MjModelWrapper* model_copy = new MjModelWrapper(other.model());
|
||||
return MjDataWrapper(other, model_copy);
|
||||
});
|
||||
mjData.def(py::pickle(
|
||||
[](const MjDataWrapper& d) { // __getstate__
|
||||
@@ -1923,7 +1934,7 @@ This is useful for example when the MJB is not available as a file on disk.)"));
|
||||
#undef X
|
||||
|
||||
#undef MJ_M
|
||||
#define MJ_M(x) d.metadata().x
|
||||
#define MJ_M(x) d.model().get()->x
|
||||
#undef MJ_D
|
||||
#define MJ_D(x) d.get()->x
|
||||
#define X(dtype, var, dim0, dim1) \
|
||||
|
||||
+14
-6
@@ -29,10 +29,10 @@
|
||||
#include <mujoco/mujoco.h>
|
||||
#include <mujoco/mjxmacro.h>
|
||||
#include "indexers.h"
|
||||
#include "mjdata_meta.h"
|
||||
#include "raw.h"
|
||||
#include <pybind11/numpy.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/pytypes.h>
|
||||
|
||||
namespace mujoco::python {
|
||||
namespace _impl {
|
||||
@@ -232,6 +232,7 @@ class MjWrapper<raw::MjVisualRgba> : public WrapperBase<raw::MjVisualRgba> {
|
||||
X(constraint);
|
||||
X(slidercrank);
|
||||
X(crankbroken);
|
||||
X(frustum);
|
||||
#undef X
|
||||
};
|
||||
|
||||
@@ -446,7 +447,8 @@ class MjWrapper<raw::MjModel> : public WrapperBase<raw::MjModel> {
|
||||
MjModelIndexer& indexer() { return indexer_; }
|
||||
|
||||
void Serialize(std::ostream& output) const;
|
||||
static MjWrapper<raw::MjModel> Deserialize(std::istream& input);
|
||||
static std::unique_ptr<MjWrapper<raw::MjModel>> Deserialize(
|
||||
std::istream& input);
|
||||
|
||||
static MjWrapper LoadXMLFile(
|
||||
const std::string& filename,
|
||||
@@ -563,12 +565,14 @@ struct is_mj_struct_list<raw::MjContact> {
|
||||
template <>
|
||||
class MjWrapper<raw::MjData>: public WrapperBase<raw::MjData> {
|
||||
public:
|
||||
explicit MjWrapper(const MjModelWrapper& model);
|
||||
explicit MjWrapper(MjModelWrapper* model);
|
||||
MjWrapper(const MjWrapper& other);
|
||||
MjWrapper(MjWrapper&&);
|
||||
// Used for deepcopy
|
||||
MjWrapper(const MjWrapper& other, MjModelWrapper* model);
|
||||
~MjWrapper();
|
||||
|
||||
const MjDataMetadata& metadata() const { return metadata_; }
|
||||
const MjModelWrapper& model() const { return *model_; }
|
||||
MjDataIndexer& indexer() { return indexer_; }
|
||||
|
||||
void Serialize(std::ostream& output) const;
|
||||
@@ -597,10 +601,14 @@ class MjWrapper<raw::MjData>: public WrapperBase<raw::MjData> {
|
||||
protected:
|
||||
// Internal constructor which takes ownership of given mjData pointer.
|
||||
// Used for deserialization.
|
||||
explicit MjWrapper(MjDataMetadata&& metadata, raw::MjData* d);
|
||||
explicit MjWrapper(MjModelWrapper* model, raw::MjData* d);
|
||||
raw::MjData* Copy() const;
|
||||
|
||||
MjDataMetadata metadata_;
|
||||
// A reference to the model that was used to create this mjData.
|
||||
MjModelWrapper* model_;
|
||||
// A py::object pointing to the same model as model_, to make sure Python
|
||||
// doesn't doesn't garbage collect it until this mjData is released.
|
||||
pybind11::object model_ref_;
|
||||
MjDataIndexer indexer_;
|
||||
};
|
||||
|
||||
|
||||
+216
-4
@@ -72,7 +72,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#@title Check if installation was successful\n",
|
||||
"#@title Set up rendering, check installation\n",
|
||||
"\n",
|
||||
"from google.colab import files\n",
|
||||
"\n",
|
||||
@@ -921,7 +921,7 @@
|
||||
"id": "g1MKUEL_eSCM"
|
||||
},
|
||||
"source": [
|
||||
"Below is a model of a chaotic pendulum, similar to [this one](https://www.exploratorium.edu/exhibits/chaotic-pendulum) in the San Francisco Exploratorium. "
|
||||
"Below is a model of a chaotic pendulum, similar to [this one](https://www.exploratorium.edu/exhibits/chaotic-pendulum) in the San Francisco Exploratorium."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1242,7 +1242,7 @@
|
||||
"free_body_MJCF = \"\"\"\n",
|
||||
"\u003cmujoco\u003e\n",
|
||||
" \u003casset\u003e\n",
|
||||
" \u003ctexture name=\"grid\" type=\"2d\" builtin=\"checker\" rgb1=\".1 .2 .3\" \n",
|
||||
" \u003ctexture name=\"grid\" type=\"2d\" builtin=\"checker\" rgb1=\".1 .2 .3\"\n",
|
||||
" rgb2=\".2 .3 .4\" width=\"300\" height=\"300\" mark=\"edge\" markrgb=\".2 .3 .4\"/\u003e\n",
|
||||
" \u003cmaterial name=\"grid\" texture=\"grid\" texrepeat=\"2 2\" texuniform=\"true\"\n",
|
||||
" reflectance=\".2\"/\u003e\n",
|
||||
@@ -1447,7 +1447,7 @@
|
||||
" \u003cworldbody\u003e\n",
|
||||
" \u003clight name=\"light\" pos=\"-.2 0 1\"/\u003e\n",
|
||||
" \u003cgeom name=\"ground\" type=\"plane\" size=\".5 .5 10\" material=\"grid\"\n",
|
||||
" zaxis=\"-.3 0 1\" friction=\".1\"/\u003e \n",
|
||||
" zaxis=\"-.3 0 1\" friction=\".1\"/\u003e\n",
|
||||
" \u003ccamera name=\"y\" pos=\"-.1 -.6 .3\" xyaxes=\"1 0 0 0 1 2\"/\u003e\n",
|
||||
" \u003cbody pos=\"0 0 .1\"\u003e\n",
|
||||
" \u003cjoint/\u003e\n",
|
||||
@@ -1941,6 +1941,218 @@
|
||||
" frames.append(pixels)\n",
|
||||
"media.show_video(frames, fps=framerate)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "Zzzugf-qPExb"
|
||||
},
|
||||
"source": [
|
||||
"## Camera control\n",
|
||||
"\n",
|
||||
"Cameras can be controlled dynamically in order to achieve cinematic effects. Run the three cells below to see the difference between rendering from a static and moving camera.\n",
|
||||
"\n",
|
||||
"The camera-control code smoothly transitions between two trajectories, one orbiting a fixed point, the other tracking a moving object. Parameter values in the code were obtained by iterating quickly on low-res videos."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"cellView": "form",
|
||||
"id": "-SW-K9WuPGrp"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#@title Load the \"dominos\" model\n",
|
||||
"dominos_xml = \"\"\"\n",
|
||||
"\u003cmujoco\u003e\n",
|
||||
" \u003casset\u003e\n",
|
||||
" \u003ctexture type=\"skybox\" builtin=\"gradient\" rgb1=\".3 .5 .7\" rgb2=\"0 0 0\" width=\"32\" height=\"512\"/\u003e\n",
|
||||
" \u003ctexture name=\"grid\" type=\"2d\" builtin=\"checker\" width=\"512\" height=\"512\" rgb1=\".1 .2 .3\" rgb2=\".2 .3 .4\"/\u003e\n",
|
||||
" \u003cmaterial name=\"grid\" texture=\"grid\" texrepeat=\"2 2\" texuniform=\"true\" reflectance=\".2\"/\u003e\n",
|
||||
" \u003c/asset\u003e\n",
|
||||
"\n",
|
||||
" \u003cstatistic meansize=\".01\"/\u003e\n",
|
||||
"\n",
|
||||
" \u003cvisual\u003e\n",
|
||||
" \u003cglobal offheight=\"2160\" offwidth=\"3840\"/\u003e\n",
|
||||
" \u003cquality offsamples=\"8\"/\u003e\n",
|
||||
" \u003c/visual\u003e\n",
|
||||
"\n",
|
||||
" \u003cdefault\u003e\n",
|
||||
" \u003cgeom type=\"box\" solref=\".005 1\"/\u003e\n",
|
||||
" \u003cdefault class=\"static\"\u003e\n",
|
||||
" \u003cgeom rgba=\".3 .5 .7 1\"/\u003e\n",
|
||||
" \u003c/default\u003e\n",
|
||||
" \u003c/default\u003e\n",
|
||||
"\n",
|
||||
" \u003coption timestep=\"5e-4\"/\u003e\n",
|
||||
"\n",
|
||||
" \u003cworldbody\u003e\n",
|
||||
" \u003clight pos=\".3 -.3 .8\" mode=\"trackcom\" diffuse=\"1 1 1\" specular=\".3 .3 .3\"/\u003e\n",
|
||||
" \u003clight pos=\"0 -.3 .4\" mode=\"targetbodycom\" target=\"box\" diffuse=\".8 .8 .8\" specular=\".3 .3 .3\"/\u003e\n",
|
||||
" \u003cgeom name=\"floor\" type=\"plane\" size=\"3 3 .01\" pos=\"-0.025 -0.295 0\" material=\"grid\"/\u003e\n",
|
||||
" \u003cgeom name=\"ramp\" pos=\".25 -.45 -.03\" size=\".04 .1 .07\" euler=\"-30 0 0\" class=\"static\"/\u003e\n",
|
||||
" \u003ccamera name=\"top\" pos=\"-0.37 -0.78 0.49\" xyaxes=\"0.78 -0.63 0 0.27 0.33 0.9\"/\u003e\n",
|
||||
"\n",
|
||||
" \u003cbody name=\"ball\" pos=\".25 -.45 .1\"\u003e\n",
|
||||
" \u003cfreejoint name=\"ball\"/\u003e\n",
|
||||
" \u003cgeom name=\"ball\" type=\"sphere\" size=\".02\" rgba=\".65 .81 .55 1\"/\u003e\n",
|
||||
" \u003c/body\u003e\n",
|
||||
"\n",
|
||||
" \u003cbody pos=\".26 -.3 .03\" euler=\"0 0 -90.0\"\u003e\n",
|
||||
" \u003cfreejoint/\u003e\n",
|
||||
" \u003cgeom size=\".0015 .015 .03\" rgba=\"1 .5 .5 1\"/\u003e\n",
|
||||
" \u003c/body\u003e\n",
|
||||
"\n",
|
||||
" \u003cbody pos=\".26 -.27 .04\" euler=\"0 0 -81.0\"\u003e\n",
|
||||
" \u003cfreejoint/\u003e\n",
|
||||
" \u003cgeom size=\".002 .02 .04\" rgba=\"1 1 .5 1\"/\u003e\n",
|
||||
" \u003c/body\u003e\n",
|
||||
"\n",
|
||||
" \u003cbody pos=\".24 -.21 .06\" euler=\"0 0 -63.0\"\u003e\n",
|
||||
" \u003cfreejoint/\u003e\n",
|
||||
" \u003cgeom size=\".003 .03 .06\" rgba=\".5 1 .5 1\"/\u003e\n",
|
||||
" \u003c/body\u003e\n",
|
||||
"\n",
|
||||
" \u003cbody pos=\".2 -.16 .08\" euler=\"0 0 -45.0\"\u003e\n",
|
||||
" \u003cfreejoint/\u003e\n",
|
||||
" \u003cgeom size=\".004 .04 .08\" rgba=\".5 1 1 1\"/\u003e\n",
|
||||
" \u003c/body\u003e\n",
|
||||
"\n",
|
||||
" \u003cbody pos=\".15 -.12 .1\" euler=\"0 0 -27.0\"\u003e\n",
|
||||
" \u003cfreejoint/\u003e\n",
|
||||
" \u003cgeom size=\".005 .05 .1\" rgba=\".5 .5 1 1\"/\u003e\n",
|
||||
" \u003c/body\u003e\n",
|
||||
"\n",
|
||||
" \u003cbody pos=\".09 -.1 .12\" euler=\"0 0 -9.0\"\u003e\n",
|
||||
" \u003cfreejoint/\u003e\n",
|
||||
" \u003cgeom size=\".006 .06 .12\" rgba=\"1 .5 1 1\"/\u003e\n",
|
||||
" \u003c/body\u003e\n",
|
||||
"\n",
|
||||
" \u003cbody name=\"seasaw_wrapper\" pos=\"-.23 -.1 0\" euler=\"0 0 30\"\u003e\n",
|
||||
" \u003cgeom size=\".01 .01 .015\" pos=\"0 .05 .015\" class=\"static\"/\u003e\n",
|
||||
" \u003cgeom size=\".01 .01 .015\" pos=\"0 -.05 .015\" class=\"static\"/\u003e\n",
|
||||
" \u003cgeom type=\"cylinder\" size=\".01 .0175\" pos=\"-.09 0 .0175\" class=\"static\"/\u003e\n",
|
||||
" \u003cbody name=\"seasaw\" pos=\"0 0 .03\"\u003e\n",
|
||||
" \u003cjoint axis=\"0 1 0\"/\u003e\n",
|
||||
" \u003cgeom type=\"cylinder\" size=\".005 .039\" zaxis=\"0 1 0\" rgba=\".84 .15 .33 1\"/\u003e\n",
|
||||
" \u003cgeom size=\".1 .02 .005\" pos=\"0 0 .01\" rgba=\".84 .15 .33 1\"/\u003e\n",
|
||||
" \u003c/body\u003e\n",
|
||||
" \u003c/body\u003e\n",
|
||||
"\n",
|
||||
" \u003cbody name=\"box\" pos=\"-.3 -.14 .05501\" euler=\"0 0 -30\"\u003e\n",
|
||||
" \u003cfreejoint name=\"box\"/\u003e\n",
|
||||
" \u003cgeom name=\"box\" size=\".01 .01 .01\" rgba=\".0 .7 .79 1\"/\u003e\n",
|
||||
" \u003c/body\u003e\n",
|
||||
" \u003c/worldbody\u003e\n",
|
||||
"\u003c/mujoco\u003e\n",
|
||||
"\"\"\"\n",
|
||||
"model = mujoco.MjModel.from_xml_string(dominos_xml)\n",
|
||||
"data = mujoco.MjData(model)\n",
|
||||
"renderer = mujoco.Renderer(model, height=1024, width=1440)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"cellView": "form",
|
||||
"id": "a2WruafiPhPk"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#@title Render from fixed camera\n",
|
||||
"duration = 2.5 # (seconds)\n",
|
||||
"framerate = 60 # (Hz)\n",
|
||||
"\n",
|
||||
"# Simulate and display video.\n",
|
||||
"frames = []\n",
|
||||
"mujoco.mj_resetData(model, data) # Reset state and time.\n",
|
||||
"while data.time \u003c duration:\n",
|
||||
" mujoco.mj_step(model, data)\n",
|
||||
" if len(frames) \u003c data.time * framerate:\n",
|
||||
" renderer.update_scene(data, camera='top')\n",
|
||||
" pixels = renderer.render()\n",
|
||||
" frames.append(pixels)\n",
|
||||
"media.show_video(frames, fps=framerate)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"cellView": "form",
|
||||
"id": "Kie3y-27bQ3J"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#@title Render from moving camera\n",
|
||||
"duration = 3 # (seconds)\n",
|
||||
"\n",
|
||||
"# find time when box is thrown (speed \u003e 2cm/s)\n",
|
||||
"throw_time = 0.0\n",
|
||||
"mujoco.mj_resetData(model, data)\n",
|
||||
"while data.time \u003c duration and not throw_time:\n",
|
||||
" mujoco.mj_step(model, data)\n",
|
||||
" box_speed = np.linalg.norm(data.joint('box').qvel[:3])\n",
|
||||
" if box_speed \u003e 0.02:\n",
|
||||
" throw_time = data.time\n",
|
||||
"assert throw_time \u003e 0\n",
|
||||
"\n",
|
||||
"def mix(time, t0=0.0, width=1.0):\n",
|
||||
" \"\"\"Sigmoidal mixing function.\"\"\"\n",
|
||||
" t = (time - t0) / width\n",
|
||||
" s = 1 / (1 + np.exp(-t))\n",
|
||||
" return 1 - s, s\n",
|
||||
"\n",
|
||||
"def unit_cos(t):\n",
|
||||
" \"\"\"Unit cosine sigmoid from (0,0) to (1,1).\"\"\"\n",
|
||||
" return 0.5 - np.cos(np.pi*np.clip(t, 0, 1))/2\n",
|
||||
"\n",
|
||||
"def orbit_motion(t):\n",
|
||||
" \"\"\"Return orbit trajectory.\"\"\"\n",
|
||||
" distance = 0.9\n",
|
||||
" azimuth = 140 + 100 * unit_cos(t)\n",
|
||||
" elevation = -30\n",
|
||||
" lookat = data.geom('floor').xpos.copy()\n",
|
||||
" return distance, azimuth, elevation, lookat\n",
|
||||
"\n",
|
||||
"def track_motion():\n",
|
||||
" \"\"\"Return box-track trajectory.\"\"\"\n",
|
||||
" distance = 0.08\n",
|
||||
" azimuth = 280\n",
|
||||
" elevation = -10\n",
|
||||
" lookat = data.geom('box').xpos.copy()\n",
|
||||
" return distance, azimuth, elevation, lookat\n",
|
||||
"\n",
|
||||
"def cam_motion():\n",
|
||||
" \"\"\"Return sigmoidally-mixed {orbit, box-track} trajectory.\"\"\"\n",
|
||||
" d0, a0, e0, l0 = orbit_motion(data.time / throw_time)\n",
|
||||
" d1, a1, e1, l1 = track_motion()\n",
|
||||
" mix_time = 0.3\n",
|
||||
" w0, w1 = mix(data.time, throw_time, mix_time)\n",
|
||||
" return w0*d0+w1*d1, w0*a0+w1*a1, w0*e0+w1*e1, w0*l0+w1*l1\n",
|
||||
"\n",
|
||||
"# Make a camera.\n",
|
||||
"cam = mujoco.MjvCamera()\n",
|
||||
"mujoco.mjv_defaultCamera(cam)\n",
|
||||
"\n",
|
||||
"# Simulate and display video.\n",
|
||||
"framerate = 60 # (Hz)\n",
|
||||
"slowdown = 4 # 4x slow-down\n",
|
||||
"mujoco.mj_resetData(model, data)\n",
|
||||
"frames = []\n",
|
||||
"while data.time \u003c duration:\n",
|
||||
" mujoco.mj_step(model, data)\n",
|
||||
" if len(frames) \u003c data.time * framerate * slowdown:\n",
|
||||
" cam.distance, cam.azimuth, cam.elevation, cam.lookat = cam_motion()\n",
|
||||
" renderer.update_scene(data, cam)\n",
|
||||
" pixels = renderer.render()\n",
|
||||
" frames.append(pixels)\n",
|
||||
"media.show_video(frames, fps=framerate)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
+10
-6
@@ -233,12 +233,15 @@ int main(int argc, char** argv) {
|
||||
|
||||
// profiler, top-level
|
||||
printf(" Internal profiler%s, %ss per step\n", nthread > 1 ? " for thread 0" : "", mu_str);
|
||||
mjtNum tstep = d[0]->timer[mjTIMER_STEP].duration/d[0]->timer[mjTIMER_STEP].number;
|
||||
int number = d[0]->timer[mjTIMER_STEP].number;
|
||||
mjtNum tstep = number ? d[0]->timer[mjTIMER_STEP].duration/number : 0.0;
|
||||
mjtNum components = 0, total = 0;
|
||||
for (int i=0; i <= mjTIMER_ADVANCE; i++) {
|
||||
if (d[0]->timer[i].number > 0) {
|
||||
mjtNum istep = d[0]->timer[i].duration/d[0]->timer[i].number;
|
||||
std::printf(" %17s : %6.1f (%6.2f %%)\n", mjTIMERSTRING[i], istep, 100*istep/tstep);
|
||||
int number = d[0]->timer[i].number;
|
||||
mjtNum istep = number ? d[0]->timer[i].duration/number : 0.0;
|
||||
mjtNum percent = number ? 100*istep/tstep : 0.0;
|
||||
std::printf(" %17s : %6.1f (%6.2f %%)\n", mjTIMERSTRING[i], istep, percent);
|
||||
|
||||
// save step time, add up timing of components
|
||||
if (i == 0) total = istep;
|
||||
@@ -276,9 +279,10 @@ int main(int argc, char** argv) {
|
||||
// components of mjTIMER_POS_COLLISION
|
||||
if (i == mjTIMER_POS_COLLISION) {
|
||||
for (int j : {mjTIMER_COL_BROAD, mjTIMER_COL_MID, mjTIMER_COL_NARROW}) {
|
||||
mjtNum jstep = d[0]->timer[j].duration/d[0]->timer[j].number;
|
||||
std::printf(" %-11s : %6.1f (%6.2f %%)\n",
|
||||
mjTIMERSTRING[j]+4, jstep, 100*jstep/tstep);
|
||||
int number = d[0]->timer[j].number;
|
||||
mjtNum jstep = number ? d[0]->timer[j].duration/number : 0.0;
|
||||
mjtNum percent = number ? 100*jstep/tstep : 0.0;
|
||||
std::printf(" %-11s : %6.1f (%6.2f %%)\n", mjTIMERSTRING[j]+4, jstep, percent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+95
-26
@@ -130,8 +130,8 @@ const mjuiDef defFile[] = {
|
||||
const char help_content[] =
|
||||
"Space\n"
|
||||
"+ -\n"
|
||||
"Right arrow\n"
|
||||
"Left arrow\n"
|
||||
"Left / Right arrow\n"
|
||||
"Tab / Shift-Tab\n"
|
||||
"[ ]\n"
|
||||
"Esc\n"
|
||||
"Double-click\n"
|
||||
@@ -148,25 +148,25 @@ const char help_content[] =
|
||||
"F3\n"
|
||||
"F4\n"
|
||||
"F5\n"
|
||||
"UI right hold\n"
|
||||
"UI right-button hold\n"
|
||||
"UI title double-click";
|
||||
|
||||
const char help_title[] =
|
||||
"Play / Pause\n"
|
||||
"Speed up / down\n"
|
||||
"Step forward\n"
|
||||
"Step back\n"
|
||||
"Speed Up / Down\n"
|
||||
"Step Back / Forward\n"
|
||||
"Toggle Left / Right UI\n"
|
||||
"Cycle cameras\n"
|
||||
"Free camera\n"
|
||||
"Select\n"
|
||||
"Select parent\n"
|
||||
"Center\n"
|
||||
"Center camera\n"
|
||||
"Tracking camera\n"
|
||||
"Zoom\n"
|
||||
"View rotate\n"
|
||||
"View translate\n"
|
||||
"Object rotate\n"
|
||||
"Object translate\n"
|
||||
"View Orbit\n"
|
||||
"View Pan\n"
|
||||
"Object Rotate\n"
|
||||
"Object Translate\n"
|
||||
"Help\n"
|
||||
"Info\n"
|
||||
"Profiler\n"
|
||||
@@ -716,6 +716,16 @@ void MakePhysicsSection(mj::Simulate* sim, int oldstate) {
|
||||
{mjITEM_EDITNUM, "Friction", 2, &(opt->o_friction), "5"},
|
||||
{mjITEM_END}
|
||||
};
|
||||
mjuiDef defDisableActuator[] = {
|
||||
{mjITEM_SEPARATOR, "Actuator Group Enable", 1},
|
||||
{mjITEM_CHECKBYTE, "Act Group 0", 2, sim->enableactuator+0, " 0"},
|
||||
{mjITEM_CHECKBYTE, "Act Group 1", 2, sim->enableactuator+1, " 1"},
|
||||
{mjITEM_CHECKBYTE, "Act Group 2", 2, sim->enableactuator+2, " 2"},
|
||||
{mjITEM_CHECKBYTE, "Act Group 3", 2, sim->enableactuator+3, " 3"},
|
||||
{mjITEM_CHECKBYTE, "Act Group 4", 2, sim->enableactuator+4, " 4"},
|
||||
{mjITEM_CHECKBYTE, "Act Group 5", 2, sim->enableactuator+5, " 5"},
|
||||
{mjITEM_END}
|
||||
};
|
||||
|
||||
// add physics
|
||||
mjui_add(&sim->ui0, defPhysics);
|
||||
@@ -736,9 +746,11 @@ void MakePhysicsSection(mj::Simulate* sim, int oldstate) {
|
||||
defFlag[0].pdata = sim->enable + i;
|
||||
mjui_add(&sim->ui0, defFlag);
|
||||
}
|
||||
|
||||
// add contact override
|
||||
mjui_add(&sim->ui0, defOverride);
|
||||
|
||||
// add actuator group enable/disable
|
||||
mjui_add(&sim->ui0, defDisableActuator);
|
||||
}
|
||||
|
||||
|
||||
@@ -1002,7 +1014,7 @@ void MakeJointSection(mj::Simulate* sim, int oldstate) {
|
||||
|
||||
// add scalar joints, exit if UI limit reached
|
||||
int itemcnt = 0;
|
||||
for (int i=0; i < sim->jnt_type_.size() && itemcnt<mjMAXUIITEM; i++)
|
||||
for (int i=0; i < sim->jnt_type_.size() && itemcnt<mjMAXUIITEM; i++) {
|
||||
if ((sim->jnt_type_[i]==mjJNT_HINGE || sim->jnt_type_[i]==mjJNT_SLIDE)) {
|
||||
// skip if joint group is disabled
|
||||
if (!sim->opt.jointgroup[mjMAX(0, mjMIN(mjNGROUP-1, sim->jnt_group_[i]))]) {
|
||||
@@ -1035,6 +1047,7 @@ void MakeJointSection(mj::Simulate* sim, int oldstate) {
|
||||
mjui_add(&sim->ui1, defSlider);
|
||||
itemcnt++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// make control section of UI
|
||||
@@ -1051,15 +1064,21 @@ void MakeControlSection(mj::Simulate* sim, int oldstate) {
|
||||
|
||||
// add section
|
||||
mjui_add(&sim->ui1, defControl);
|
||||
defSlider[0].state = 2;
|
||||
|
||||
// add controls, exit if UI limit reached (Clear button already added)
|
||||
int itemcnt = 1;
|
||||
for (int i=0; i < sim->actuator_ctrlrange_.size() && itemcnt<mjMAXUIITEM; i++) {
|
||||
// skip if actuator group is disabled
|
||||
if (!sim->opt.actuatorgroup[mjMAX(0, mjMIN(mjNGROUP-1, sim->actuator_group_[i]))]) {
|
||||
// skip if actuator vis group is disabled
|
||||
int group = sim->actuator_group_[i];
|
||||
if (!sim->opt.actuatorgroup[mjMAX(0, mjMIN(mjNGROUP-1, group))]) {
|
||||
continue;
|
||||
}
|
||||
// grey out if actuator group is disabled
|
||||
if (group >= 0 && group <= 30 && sim->m_->opt.disableactuator & (1 << group)) {
|
||||
defSlider[0].state = 0;
|
||||
} else {
|
||||
defSlider[0].state = 2;
|
||||
}
|
||||
|
||||
// set data and name
|
||||
if (!sim->is_passive_) {
|
||||
@@ -1189,19 +1208,27 @@ void CopyCamera(mj::Simulate* sim) {
|
||||
void UpdateSettings(mj::Simulate* sim, const mjModel* m) {
|
||||
// physics flags
|
||||
for (int i=0; i<mjNDISABLE; i++) {
|
||||
int new_value = ((m->opt.disableflags & (1<<i)) !=0);
|
||||
int new_value = ((m->opt.disableflags & (1<<i)) != 0);
|
||||
if (sim->disable[i] != new_value) {
|
||||
sim->disable[i] = new_value;
|
||||
sim->pending_.ui_update_physics = true;
|
||||
}
|
||||
}
|
||||
for (int i=0; i<mjNENABLE; i++) {
|
||||
int new_value = ((m->opt.enableflags & (1<<i)) !=0);
|
||||
int new_value = ((m->opt.enableflags & (1<<i)) != 0);
|
||||
if (sim->enable[i] != new_value) {
|
||||
sim->enable[i] = new_value;
|
||||
sim->pending_.ui_update_physics = true;
|
||||
}
|
||||
}
|
||||
for (int i=0; i<mjNGROUP; i++) {
|
||||
int enabled = ((m->opt.disableactuator & (1<<i)) == 0);
|
||||
if (sim->enableactuator[i] != enabled) {
|
||||
sim->enableactuator[i] = enabled;
|
||||
sim->pending_.ui_update_physics = true;
|
||||
sim->pending_.ui_remake_ctrl = true;
|
||||
}
|
||||
}
|
||||
|
||||
// camera
|
||||
int old_camera = sim->camera;
|
||||
@@ -1254,7 +1281,7 @@ int UiPredicate(int category, void* userdata) {
|
||||
return sim->m_ || sim->is_passive_;
|
||||
|
||||
case 3: // require model and nkey
|
||||
return !sim->is_passive_ && sim->nkey_;
|
||||
return (sim->m_ || sim->is_passive_) && sim->nkey_;
|
||||
|
||||
case 4: // require model and paused
|
||||
return sim->m_ && !sim->run;
|
||||
@@ -1404,17 +1431,39 @@ void UiEvent(mjuiState* state) {
|
||||
|
||||
// update disable flags in mjOption
|
||||
opt->disableflags = 0;
|
||||
for (int i=0; i<mjNDISABLE; i++)
|
||||
for (int i=0; i<mjNDISABLE; i++) {
|
||||
if (sim->disable[i]) {
|
||||
opt->disableflags |= (1<<i);
|
||||
}
|
||||
}
|
||||
|
||||
// update enable flags in mjOption
|
||||
opt->enableflags = 0;
|
||||
for (int i=0; i<mjNENABLE; i++)
|
||||
for (int i=0; i<mjNENABLE; i++) {
|
||||
if (sim->enable[i]) {
|
||||
opt->enableflags |= (1<<i);
|
||||
}
|
||||
}
|
||||
|
||||
// update disableactuator bitflag in mjOption
|
||||
bool group_changed = false;
|
||||
for (int i=0; i<mjNGROUP; i++) {
|
||||
if ((!sim->enableactuator[i]) != (opt->disableactuator & (1<<i))) {
|
||||
group_changed = true;
|
||||
if (!sim->enableactuator[i]) {
|
||||
// disable actuator group i
|
||||
opt->disableactuator |= (1<<i);
|
||||
} else {
|
||||
// enable actuator group i
|
||||
opt->disableactuator &= ~(1<<i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remake control section if actuator disable group changed
|
||||
if (group_changed) {
|
||||
sim->pending_.ui_remake_ctrl = true;
|
||||
}
|
||||
}
|
||||
|
||||
// rendering section
|
||||
@@ -1461,10 +1510,7 @@ void UiEvent(mjuiState* state) {
|
||||
|
||||
// remake control section if actuator group changed
|
||||
if (it->name[0]=='A' && it->name[1]=='c') {
|
||||
sim->ui1.nsect = SECT_CONTROL;
|
||||
MakeControlSection(sim, sim->ui1.sect[SECT_CONTROL].state);
|
||||
sim->ui1.nsect = NSECT1;
|
||||
UiModify(&sim->ui1, state, &sim->platform_ui->mjr_context());
|
||||
sim->pending_.ui_remake_ctrl = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1625,6 +1671,18 @@ void UiEvent(mjuiState* state) {
|
||||
sim->speed_changed = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case mjKEY_TAB: // toggle left/right UI
|
||||
if (!state->shift) {
|
||||
// toggle left UI
|
||||
sim->ui0_enable = !sim->ui0_enable;
|
||||
UiModify(&sim->ui0, state, &sim->platform_ui->mjr_context());
|
||||
} else {
|
||||
// toggle right UI
|
||||
sim->ui1_enable = !sim->ui1_enable;
|
||||
UiModify(&sim->ui1, state, &sim->platform_ui->mjr_context());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -1778,7 +1836,7 @@ void Simulate::Sync() {
|
||||
range.emplace(m_->actuator_ctrlrange[2*i], m_->actuator_ctrlrange[2*i + 1]);
|
||||
}
|
||||
if (actuator_ctrlrange_[i] != range) {
|
||||
pending_.ui_update_ctrl = true;
|
||||
pending_.ui_remake_ctrl = true;
|
||||
actuator_ctrlrange_[i].swap(range);
|
||||
}
|
||||
}
|
||||
@@ -1839,6 +1897,7 @@ void Simulate::Sync() {
|
||||
X(mpr_iterations);
|
||||
X(disableflags);
|
||||
X(enableflags);
|
||||
X(disableactuator);
|
||||
X(sdf_initpoints);
|
||||
X(sdf_iterations);
|
||||
|
||||
@@ -2387,6 +2446,16 @@ void Simulate::Render() {
|
||||
pending_.ui_update_joint = false;
|
||||
}
|
||||
|
||||
if (pending_.ui_remake_ctrl) {
|
||||
if (this->ui1_enable && this->ui1.sect[SECT_CONTROL].state) {
|
||||
this->ui1.nsect = SECT_CONTROL;
|
||||
MakeControlSection(this, this->ui1.sect[SECT_CONTROL].state);
|
||||
this->ui1.nsect = NSECT1;
|
||||
UiModify(&this->ui1, &this->uistate, &this->platform_ui->mjr_context());
|
||||
}
|
||||
pending_.ui_remake_ctrl = false;
|
||||
}
|
||||
|
||||
if (pending_.ui_update_ctrl) {
|
||||
if (this->ui1_enable && this->ui1.sect[SECT_CONTROL].state) {
|
||||
mjui_update(SECT_CONTROL, -1, &this->ui1, &this->uistate, &this->platform_ui->mjr_context());
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user