Dynamically allocate contact and efc_ arrays on a new memory arena.
- Add private function `mj_arenaAlloc`. This is used internally to allocate memory from the arena. - Add private function `mj_nefc` to count constraints. This function returns a tight upper bound on `d->nefc`. The number of counted constraints can be slightly bigger than exact `d->nefc` in the case of constraints with empty Jacobian, as when placing a frictional tendon between two world sites. - Add new `memory` attribute to the `size` XML element for specification of arena memory size. This attribute is mutually exclusive with `nstack` and `njmax` specifications, which are now deprecated (but left around for the time being for legacy compatibility). - Move `d->stack` to the end of the new arena space. The stack now grows in reverse from the end. PiperOrigin-RevId: 479341539 Change-Id: Ie019c202e0908577ffc6f833a37920858116f667
This commit is contained in:
committed by
Copybara-Service
parent
4d85a464cc
commit
58fd72f53d
@@ -4188,6 +4188,8 @@ mj_rnePostConstraint
|
|||||||
|
|
||||||
RNE with complete data: compute cacc, cfrc_ext, cfrc_int.
|
RNE with complete data: compute cacc, cfrc_ext, cfrc_int.
|
||||||
|
|
||||||
|
.. _mj_collision:
|
||||||
|
|
||||||
mj_collision
|
mj_collision
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|||||||
+20
-24
@@ -479,7 +479,7 @@ from its default.
|
|||||||
This flag enables the simulation of sensor noise. When disabled (which is the default) noise is not added to
|
This flag enables the simulation of sensor noise. When disabled (which is the default) noise is not added to
|
||||||
sensordata, even if the sensors specify non-zero noise amplitudes. When enabled, zero-mean Gaussian noise is added to
|
sensordata, even if the sensors specify non-zero noise amplitudes. When enabled, zero-mean Gaussian noise is added to
|
||||||
the underlying deterministic sensor data. Its standard deviation is determined by the noise parameter of each sensor.
|
the underlying deterministic sensor data. Its standard deviation is determined by the noise parameter of each sensor.
|
||||||
:at:`multiccd`: :at-val:`[disable, enable], "disable"` **(experimental feature)**
|
:at:`multiccd`: :at-val:`[disable, enable], "disable"` |nbsp| |nbsp| |nbsp| (experimental feature)
|
||||||
This flag enables multiple-contact collision detection for geom pairs that use the general-purpose convex-convex
|
This flag enables multiple-contact collision detection for geom pairs that use the general-purpose convex-convex
|
||||||
collider based on :ref:`libccd <coChecking>` e.g., mesh-mesh collisions. This can be useful when the contacting geoms
|
collider based on :ref:`libccd <coChecking>` e.g., mesh-mesh collisions. This can be useful when the contacting geoms
|
||||||
have a flat surface, and the single contact point generated by the convex-convex collider cannot accurately capture
|
have a flat surface, and the single contact point generated by the convex-convex collider cannot accurately capture
|
||||||
@@ -497,29 +497,25 @@ This element specifies size parameters that cannot be inferred from the number o
|
|||||||
fields of mjOption which can be modified at runtime, sizes are structural parameters and should not be modified after
|
fields of mjOption which can be modified at runtime, sizes are structural parameters and should not be modified after
|
||||||
compilation.
|
compilation.
|
||||||
|
|
||||||
:at:`njmax`: :at-val:`int, "-1"`
|
:at:`memory`: :at-val:`string, "-1"`
|
||||||
This and the next two attributes specify the maximum sizes of the dynamic arrays in mjData, i.e., arrays whose
|
This attribute specifies the size of memory allocated for dynamic arrays in the ``mjData.arena`` memory space, in
|
||||||
effective length varies at runtime. This attribute specifies the maximum number of scalar constraints (or
|
bytes. The default setting of ``-1`` instructs the compiler to guess how much space to allocate. Appending the digits
|
||||||
equivalently, rows of the constraint Jacobian) that can be handled at runtime. If the number of active constraints is
|
with one of the letters {K, M, G, T, P, E} sets the unit to be {kilo, mega, giga, tera, peta, exa}-byte,
|
||||||
about to exceed this maximum (usually because too many contacts become active) the extra constraints are discarded
|
respectively. Thus "16M" means "allocate 16 megabytes of ``arena`` memory".
|
||||||
and a warning is generated. The number of active constraints is stored in mjData.nefc. The default setting of -1
|
See the :ref:`Memory allocation <CSize>` section for details.
|
||||||
instructs the compiler to guess how much space to allocate (using heuristics that can be improved). This default is
|
:at:`njmax`: :at-val:`int, "-1"` |nbsp| |nbsp| |nbsp| (legacy)
|
||||||
effectively an undefined state. If the user specifies a positive value, the compiler heuristics are disabled and the
|
This is a deprecated legacy attribute. In versions prior to 2.3.0, it determined the maximum allowed number
|
||||||
specified value is used. Modern computers have sufficient memory to handle very large models (larger than one would
|
of constraints. Currently it means "allocate as much memory as would have previously been required for this number of
|
||||||
normally have the patience to simulate) so tuning this setting aggressively is not necessary. When size-related
|
constraints". Specifying both :at:`njmax` and :at:`memory` leads to an error.
|
||||||
warnings or errors are generated, simply increase the value of the corresponding attribute.
|
:at:`nconmax`: :at-val:`int, "-1"` |nbsp| |nbsp| |nbsp| (legacy)
|
||||||
:at:`nconmax`: :at-val:`int, "-1"`
|
This attribute specifies the maximum number of contacts that will be generated at runtime. If the number of active
|
||||||
This attribute specifies the maximum number of contacts (both frictional and frictionless) that can be handled at
|
contacts is about to exceed this value, the extra contacts are discarded and a warning is generated. This is a
|
||||||
runtime. If the number of active contacts is about to exceed this value, the extra contacts are discarded and a
|
deprecated legacy attribute which prior to version 2.3.0 affected memory allocation. It is kept for backwards
|
||||||
warning is generated. The actual number of contacts is stored in mjData.ncon. If this value is negative, the compiler
|
compatibillity and debugging purposes.
|
||||||
will use a heuristic to guess an appropriate number.
|
:at:`nstack`: :at-val:`int, "-1"` |nbsp| |nbsp| |nbsp| (legacy)
|
||||||
:at:`nstack`: :at-val:`int, "-1"`
|
This is a deprecated legacy attribute. In versions prior to 2.3.0, it determined the maximum size of the
|
||||||
This attribute specifies the size of the preallocated stack in mjData, in units of sizeof(mjtNum) which is currently
|
:ref:`stack <siStack>`. Currently it is synonymous with the :at:`memory` attribute above, but is in units of
|
||||||
defined as double; thus the size in bytes is 8 times larger. The custom stack is used by all MuJoCo functions that
|
``sizeof(mjtNum)`` rather than bytes. Specifying both :at:`nstack` and :at:`memory` leads to an error.
|
||||||
need dynamically allocated memory. We do not use heap memory allocation at runtime, so as to speed up processing as
|
|
||||||
well as avoid heap fragmentation. Note that the internal allocator keeps track of how much stack space has ever been
|
|
||||||
utilized, in the field mjData.maxstackuse of mjData. If the stack size is exceeded at runtime, MuJoCo will generate
|
|
||||||
an error. If this value is negative, the compiler will use a heuristic to guess an appropriate number.
|
|
||||||
:at:`nuserdata`: :at-val:`int, "0"`
|
:at:`nuserdata`: :at-val:`int, "0"`
|
||||||
The size of the field mjData.userdata of mjData. This field should be used to store custom dynamic variables. See
|
The size of the field mjData.userdata of mjData. This field should be used to store custom dynamic variables. See
|
||||||
also :ref:`CUser`.
|
also :ref:`CUser`.
|
||||||
|
|||||||
+14
-3
@@ -9,9 +9,17 @@ Upcoming version (not yet released)
|
|||||||
General
|
General
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
|
|
||||||
.. youtube:: RHnXD6uO3Mg
|
- The ``contact`` array and arrays prefixed with ``efc_`` in ``mjData`` were moved out of the ``buffer`` into a new
|
||||||
:align: right
|
``arena`` memory space. These arrays are no longer allocated with fixed sizes when ``mjData`` is created.
|
||||||
:height: 150px
|
Instead, the exact memory requirement is determined during each call to :ref:`mj_forward` (specifically,
|
||||||
|
in :ref:`mj_collision` and :ref:`mj_makeConstraint`) and the arrays are allocated from the ``arena`` space. The
|
||||||
|
``stack`` now also shares its available memory with ``arena``. This change reduces the memory footprint of ``mjData``
|
||||||
|
in models that do not use the PGS solver, and will allow for significant memory reductions in the future.
|
||||||
|
See the :ref:`Memory allocation <CSize>` section for details.
|
||||||
|
|
||||||
|
.. youtube:: RHnXD6uO3Mg
|
||||||
|
:align: right
|
||||||
|
:height: 150px
|
||||||
|
|
||||||
- Added colab notebook tutorial showing how to balance the humanoid on one leg with a Linear Quadratic Regulator. The
|
- Added colab notebook tutorial showing how to balance the humanoid on one leg with a Linear Quadratic Regulator. The
|
||||||
notebook uses MuJoCo's native Python bindings, and includes a draft ``Renderer`` class, for easy rendering in Python.
|
notebook uses MuJoCo's native Python bindings, and includes a draft ``Renderer`` class, for easy rendering in Python.
|
||||||
@@ -59,6 +67,9 @@ Python bindings
|
|||||||
`named accessor <https://mujoco.readthedocs.io/en/latest/python.html#named-access>`_ objects. These provide more
|
`named accessor <https://mujoco.readthedocs.io/en/latest/python.html#named-access>`_ objects. These provide more
|
||||||
Pythonic API access to ``mj_name2id`` and ``mj_id2name`` respectively.
|
Pythonic API access to ``mj_name2id`` and ``mj_id2name`` respectively.
|
||||||
|
|
||||||
|
- The length of ``MjData.contact`` is now ``ncon`` rather than ``nconmax``, allowing it to be straightforwardly used as
|
||||||
|
an iterator without needing to check ``ncon``.
|
||||||
|
|
||||||
|
|
||||||
Version 2.2.2 (September 7, 2022)
|
Version 2.2.2 (September 7, 2022)
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|||||||
+51
-26
@@ -1276,6 +1276,57 @@ MuJoCo Forum for an example; the plots below are generated with that model.
|
|||||||
|
|
||||||
|image18| |image19|
|
|image18| |image19|
|
||||||
|
|
||||||
|
.. _CSize:
|
||||||
|
|
||||||
|
Memory allocation
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
MuJoCo preallocates all the memory needed at runtime in ``mjData``, and does not access the heap allocator after
|
||||||
|
model creation. Memory in ``mjData`` is allocated by :ref:`mj_makeData` in two contiguous blocks:
|
||||||
|
|
||||||
|
- ``mjData.buffer`` contains fixed-size arrays.
|
||||||
|
- ``mjData.arena`` contains dynamically-sized arrays.
|
||||||
|
|
||||||
|
There are two types of dynamic arrays allocated in the ``arena`` memory space.
|
||||||
|
|
||||||
|
- contacts and constraint-related arrays are laid out from the beginning of the ``arena``.
|
||||||
|
- :ref:`stack <siStack>` arrays are laid out from the end of the ``arena``.
|
||||||
|
|
||||||
|
By allocating dynamic quantities from both sides of the ``arena`` space, variable-sized memory allocation is controlled
|
||||||
|
by a single number: the :at:`memory` attribute of the :ref:`size <size>` MJCF element. Unlike the fixed-size arrays in
|
||||||
|
the ``buffer``, variable-sized arrays in the arena can be ``NULL``, for example after a call to :ref:`mj_resetData`.
|
||||||
|
When ``arena`` memory runs out, one of three things will happen, depending on the type of memory requested:
|
||||||
|
|
||||||
|
- If memory runs out during contact allocation, a warning will be raised and subsequent contacts will not be added in
|
||||||
|
this step, but simulation continues as usual.
|
||||||
|
- If memory runs out during constraint-related allocation, a warning will be raised and the constraint solver will be
|
||||||
|
disabled in this step, but simulation continues as usual. Note that physics without the constraint solver will
|
||||||
|
generally be very different, but allowing the simulation to continue can still be useful, e.g. during
|
||||||
|
scene initialization when many bodies are temporarily overlapping.
|
||||||
|
- If memory runs out during stack array allocation, a hard error will occur.
|
||||||
|
|
||||||
|
Unlike the size of the ``buffer``, the size of the ``arena`` cannot be pre-computed, since the number of contacts and
|
||||||
|
stack usage is not known in advance. So how should one choose it? The following simple heuristic is currently used,
|
||||||
|
though it may be improved in the future: enough memory is allocated for 100 contacts and 500 scalar constraints, under
|
||||||
|
worst-case conditions. If this heuristic is insufficient, we recommend the following procedure. Increase the ``arena``
|
||||||
|
memory significantly using the :at:`memory` attribute, and inspect the actual memory used at runtime.
|
||||||
|
``mjData.maxuse_arena`` keeps track of the maximum ``arena`` memory utilization since the last reset. The :ref:`simulate
|
||||||
|
<saSimulate>` viewer shows this number as a fraction of the total arena space (in the info window in the lower-left
|
||||||
|
corner). So one can start with a large number, simulate for a while, and if the fractions are small go back to the XML
|
||||||
|
and reduce the allocation size. Keep in mind though that memory utilization can change dramatically in the course of the
|
||||||
|
simulation, depending on how many constraints are active and which constraint solver is used. The CG solver is the most
|
||||||
|
memory efficient, followed by the Newton solver, while the PGS solver is the most memory intensive. When we design
|
||||||
|
models, we usually aim for 50% utilization in the worst-case scenario encountered while exploring the model. If you only
|
||||||
|
intend to use the CG solver, you can get away with significantly smaller arena allocation.
|
||||||
|
|
||||||
|
.. attention::
|
||||||
|
|
||||||
|
Memory allocation behaviour changed in MuJoCo 2.3.0. Before this version, the :at:`njmax`, :at:`nconmax` and
|
||||||
|
:at:`nstack` attributes of the :ref:`size <size>` MJCF element had the semantics of maximum memory allocated for
|
||||||
|
contacts, constraints and stack, respectively. If you are using an earlier version of MuJoCo, please switch to an
|
||||||
|
`earlier <https://mujoco.readthedocs.io/en/2.2.2/modeling.html#model-sizes>`_ documentation version to read about the
|
||||||
|
previous behaviour.
|
||||||
|
|
||||||
.. _Tips:
|
.. _Tips:
|
||||||
|
|
||||||
Tips and tricks
|
Tips and tricks
|
||||||
@@ -1377,32 +1428,6 @@ in a visible way, and the energy fluctuates around the initial value instead of
|
|||||||
</body>
|
</body>
|
||||||
</worldbody>
|
</worldbody>
|
||||||
|
|
||||||
.. _CSize:
|
|
||||||
|
|
||||||
Model sizes
|
|
||||||
~~~~~~~~~~~
|
|
||||||
|
|
||||||
MuJoCo preallocates all the memory needed at runtime in mjData, and does not access the C/C++ memory manager after
|
|
||||||
model creation. It is therefore essential to allocate enough memory. The allocation is controlled by three size
|
|
||||||
parameters specified in the :ref:`size <size>` element, namely the stack size :at:`nstack`, the
|
|
||||||
maximum number of contacts :at:`nconmax`, and the maximum number of scalar constraints :at:`njmax`. The default
|
|
||||||
size settings use heuristics to allocate sufficient memory, but the true memory needs for a given model can only be
|
|
||||||
determined during simulation. If nstack is insufficient the simulator calls mju_error and gives up. If nconmax or
|
|
||||||
njmax are insufficient the remaining contacts or other constraints are discarded, and the simulation continues but the
|
|
||||||
results are not as desired. If on the other hand the allocation is too large, clearing mjData with mj_reset takes
|
|
||||||
longer, and in multi-threaded applications simulating many large models in parallel the machine could run out of
|
|
||||||
memory, or cache performance could be adversely affected. And even if nothing bad happens, allocating a lot more
|
|
||||||
memory than needed is just poor style.
|
|
||||||
|
|
||||||
So how do we know how much memory to allocate? mjData has fields maxuse_stack, maxuse_con and maxuse_efc which keep
|
|
||||||
track of the maximum memory utilization in each category since the last reset. The code sample :ref:`simulate.cc <saSimulate>`
|
|
||||||
shows this data as a fraction of the maximum allocation (in the info window in the lower-left corner). So one can start with
|
|
||||||
the defaults, simulate for a while, and if the fractions are too small go back to the XML and set the allocation sizes
|
|
||||||
explicitly. Keep in mind though that memory utilization can change dramatically in the course of the simulation,
|
|
||||||
depending on how many constraints are active and also which constraint solver is used.
|
|
||||||
For example if the stack size is just sufficient for the CG solver, the Newton and PGS solvers will run out of stack.
|
|
||||||
When we design models, we usually aim for 50% utilization in the worst-case scenario encountered while exploring the
|
|
||||||
model. If you only intend to use the CG solver, you can get away with significantly smaller stack allocation.
|
|
||||||
|
|
||||||
.. |image0| image:: images/modeling/impedance.png
|
.. |image0| image:: images/modeling/impedance.png
|
||||||
:width: 600px
|
:width: 600px
|
||||||
|
|||||||
+13
-20
@@ -503,10 +503,10 @@ preallocated data arrays for all intermediate results, as well as an :ref:`inter
|
|||||||
to allocate all necessary heap memory at the beginning of the simulation, and free it after the simulation is done, so
|
to allocate all necessary heap memory at the beginning of the simulation, and free it after the simulation is done, so
|
||||||
that we never have to call the C memory allocation and deallocation functions during the simulation. This is done for
|
that we never have to call the C memory allocation and deallocation functions during the simulation. This is done for
|
||||||
speed, avoidance of memory fragmentation, future GPU portability, and ease of managing the state of the entire
|
speed, avoidance of memory fragmentation, future GPU portability, and ease of managing the state of the entire
|
||||||
simulator during a reset. It also means however that the maximal sizes :at:`njmax`, :at:`nconmax` and
|
simulator during a reset. It also means however that the maximal variable-memory allocation given by the
|
||||||
:at:`nstack` in the XML element :ref:`size <size>`, which affect the allocation of mjData, must be
|
:at:`memory` attribute in the :ref:`size <size>` MJCF element, which affects the allocation of ``mjData``, must be
|
||||||
set to sufficiently large values. If these maximal sizes are exceeded during the simulation, they are not increased
|
set to a sufficiently large value. If this maximal size is exceeded during simulation, it is not increased
|
||||||
dynamically, but instead errors or warnings are generated. See also :ref:`diagnostics <siDiagnostics>` below.
|
dynamically, but instead an error is generated. See also :ref:`diagnostics <siDiagnostics>` below.
|
||||||
|
|
||||||
First we must call one of the functions that allocates and initializes mjModel and returns a pointer to it. The
|
First we must call one of the functions that allocates and initializes mjModel and returns a pointer to it. The
|
||||||
available options are
|
available options are
|
||||||
@@ -1308,22 +1308,15 @@ termination have similar order-of-magnitude as the numbers in ``mjData.fwdinv``,
|
|||||||
different diagnostics.
|
different diagnostics.
|
||||||
|
|
||||||
Since MuJoCo's runtime works with compiled models, memory is preallocated when a model is compiled or loaded. Recall the
|
Since MuJoCo's runtime works with compiled models, memory is preallocated when a model is compiled or loaded. Recall the
|
||||||
:ref:`size <size>` element in MJCF, which has the attributes :at:`njmax`, :at:`nconmax` and :at:`nstack`. They determine
|
:at:`memory` attribute of the :ref:`size <size>` element in MJCF. It determines the preallocated space for dynamic
|
||||||
the maximum number of scalar constraints that can be active simultaneously, the maximum number of contact points that
|
arrays. How is the user supposed to know what the appropriate value is? If there were a reliable recipe we would have
|
||||||
can be included in ``mjData.contact``, and the size of the internal stack. How is the user supposed to know what the
|
implemented it in the compiler, but there isn't one. The theoretical worst-case, namely all geoms contacting all other
|
||||||
appropriate settings are? If there were a reliable recipe we would have implemented it in the compiler, but there isn't
|
geoms, calls for huge allocation which is almost never needed in practice. Our approach is to provide default settings
|
||||||
one. The theoretical worst-case, namely all geoms contacting all other geoms, calls for huge allocation which is almost
|
in MJCF which are sufficient for most models, and allow the user to adjust them manually with the above attribute. If
|
||||||
never needed in practice. So our approach is to provide default settings in MJCF which are sufficient for most models,
|
the simulator runs out of dynamic memory at runtime it will trigger an error. When such errors are triggered, the user
|
||||||
and allow the user to adjust them manually with the above attributes. If the simulator runs out of stack space at
|
should increase :at:`memory`. The field ``mjData.maxuse_arena`` is designed to help with this adjustment. It keeps track
|
||||||
runtime it will trigger an error. If it runs out of space for contacts or scalar constraints, it will trigger a warning
|
of the maximum arena use since the last reset. So one strategy is to make very large allocation, then monitor
|
||||||
and omit the contacts and constraints that do not fit in the allocated buffers. When such errors or warnings are
|
``mjData.maxuse_memory`` statistics during typical simulations, and use it to reduce the allocation.
|
||||||
triggered, the user should adjust the sizes. The fields ``mjData.maxuse_stack``, ``mjData.maxuse_con``,
|
|
||||||
``mjData.maxuse_efc`` are designed to help with this adjustment. They keep track of the maximum stack allocation,
|
|
||||||
number of contacts and number of scalar constraints respectively since the last reset. So one strategy is to make very
|
|
||||||
large allocation, then monitor these ``maxuse_XXX`` statistics during typical simulations, and use them to reduce the
|
|
||||||
allocation. Of course modern computers have so much memory that most users will not bother with such adjustment once
|
|
||||||
they get rid of the out-of-memory errors and warnings, but nevertheless we provide this mechanism for the
|
|
||||||
perfectionist.
|
|
||||||
|
|
||||||
The kinetic and potential energy are computed and stored in ``mjData.energy`` when the corresponding flag in
|
The kinetic and potential energy are computed and stored in ``mjData.energy`` when the corresponding flag in
|
||||||
``mjModel.opt.enableflags`` is set. This can be used as another diagnostic. In general, simulation instability is
|
``mjModel.opt.enableflags`` is set. This can be used as another diagnostic. In general, simulation instability is
|
||||||
|
|||||||
+48
-39
@@ -15,6 +15,7 @@
|
|||||||
#ifndef MUJOCO_MJDATA_H_
|
#ifndef MUJOCO_MJDATA_H_
|
||||||
#define MUJOCO_MJDATA_H_
|
#define MUJOCO_MJDATA_H_
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <mujoco/mjtnum.h>
|
#include <mujoco/mjtnum.h>
|
||||||
@@ -124,15 +125,19 @@ typedef struct mjSolverStat_ mjSolverStat;
|
|||||||
|
|
||||||
struct mjData_ {
|
struct mjData_ {
|
||||||
// constant sizes
|
// constant sizes
|
||||||
int nstack; // number of mjtNums that can fit in stack
|
int nstack; // number of mjtNums that can fit in the arena+stack space
|
||||||
int nbuffer; // size of main buffer in bytes
|
int nbuffer; // size of main buffer in bytes
|
||||||
int nplugin; // number of plugin instances
|
int nplugin; // number of plugin instances
|
||||||
|
|
||||||
// stack pointer
|
// stack pointer
|
||||||
int pstack; // first available mjtNum address in stack
|
size_t pstack; // first available mjtNum address in stack
|
||||||
|
|
||||||
|
// arena pointer
|
||||||
|
size_t parena; // first available byte in arena
|
||||||
|
|
||||||
// memory utilization stats
|
// memory utilization stats
|
||||||
int maxuse_stack; // maximum stack allocation
|
int maxuse_stack; // maximum stack allocation
|
||||||
|
size_t maxuse_arena; // maximum arena allocation
|
||||||
int maxuse_con; // maximum number of contacts
|
int maxuse_con; // maximum number of contacts
|
||||||
int maxuse_efc; // maximum number of scalar constraints
|
int maxuse_efc; // maximum number of scalar constraints
|
||||||
|
|
||||||
@@ -157,8 +162,8 @@ struct mjData_ {
|
|||||||
//-------------------------------- end of info header
|
//-------------------------------- end of info header
|
||||||
|
|
||||||
// buffers
|
// buffers
|
||||||
void* buffer; // main buffer; all pointers point in it (nbuffer bytes)
|
void* buffer; // main buffer; all pointers point in it (nbuffer bytes)
|
||||||
mjtNum* stack; // stack buffer (nstack mjtNums)
|
void* arena; // arena+stack buffer (nstack*sizeof(mjtNum) bytes)
|
||||||
|
|
||||||
//-------------------------------- main inputs and outputs of the computation
|
//-------------------------------- main inputs and outputs of the computation
|
||||||
|
|
||||||
@@ -240,36 +245,6 @@ struct mjData_ {
|
|||||||
mjtNum* qLDiagInv; // 1/diag(D) (nv x 1)
|
mjtNum* qLDiagInv; // 1/diag(D) (nv x 1)
|
||||||
mjtNum* qLDiagSqrtInv; // 1/sqrt(diag(D)) (nv x 1)
|
mjtNum* qLDiagSqrtInv; // 1/sqrt(diag(D)) (nv x 1)
|
||||||
|
|
||||||
// computed by mj_fwdPosition/mj_collision
|
|
||||||
mjContact* contact; // list of all detected contacts (nconmax x 1)
|
|
||||||
|
|
||||||
// computed by mj_fwdPosition/mj_makeConstraint
|
|
||||||
int* efc_type; // constraint type (mjtConstraint) (njmax x 1)
|
|
||||||
int* efc_id; // id of object of specified type (njmax x 1)
|
|
||||||
int* efc_J_rownnz; // number of non-zeros in Jacobian row (njmax x 1)
|
|
||||||
int* efc_J_rowadr; // row start address in colind array (njmax x 1)
|
|
||||||
int* efc_J_rowsuper; // number of subsequent rows in supernode (njmax x 1)
|
|
||||||
int* efc_J_colind; // column indices in Jacobian (njmax x nv)
|
|
||||||
int* efc_JT_rownnz; // number of non-zeros in Jacobian row T (nv x 1)
|
|
||||||
int* efc_JT_rowadr; // row start address in colind array T (nv x 1)
|
|
||||||
int* efc_JT_rowsuper; // number of subsequent rows in supernode T (nv x 1)
|
|
||||||
int* efc_JT_colind; // column indices in Jacobian T (nv x njmax)
|
|
||||||
mjtNum* efc_J; // constraint Jacobian (njmax x nv)
|
|
||||||
mjtNum* efc_JT; // constraint Jacobian transposed (nv x njmax)
|
|
||||||
mjtNum* efc_pos; // constraint position (equality, contact) (njmax x 1)
|
|
||||||
mjtNum* efc_margin; // inclusion margin (contact) (njmax x 1)
|
|
||||||
mjtNum* efc_frictionloss; // frictionloss (friction) (njmax x 1)
|
|
||||||
mjtNum* efc_diagApprox; // approximation to diagonal of A (njmax x 1)
|
|
||||||
mjtNum* efc_KBIP; // stiffness, damping, impedance, imp' (njmax x 4)
|
|
||||||
mjtNum* efc_D; // constraint mass (njmax x 1)
|
|
||||||
mjtNum* efc_R; // inverse constraint mass (njmax x 1)
|
|
||||||
|
|
||||||
// computed by mj_fwdPosition/mj_projectConstraint
|
|
||||||
int* efc_AR_rownnz; // number of non-zeros in AR (njmax x 1)
|
|
||||||
int* efc_AR_rowadr; // row start address in colind array (njmax x 1)
|
|
||||||
int* efc_AR_colind; // column indices in sparse AR (njmax x njmax)
|
|
||||||
mjtNum* efc_AR; // J*inv(M)*J' + R (njmax x njmax)
|
|
||||||
|
|
||||||
//-------------------------------- POSITION, VELOCITY dependent
|
//-------------------------------- POSITION, VELOCITY dependent
|
||||||
|
|
||||||
// computed by mj_fwdVelocity
|
// computed by mj_fwdVelocity
|
||||||
@@ -287,8 +262,8 @@ struct mjData_ {
|
|||||||
mjtNum* qfrc_passive; // passive force (nv x 1)
|
mjtNum* qfrc_passive; // passive force (nv x 1)
|
||||||
|
|
||||||
// computed by mj_fwdVelocity/mj_referenceConstraint
|
// computed by mj_fwdVelocity/mj_referenceConstraint
|
||||||
mjtNum* efc_vel; // velocity in constraint space: J*qvel (njmax x 1)
|
mjtNum* efc_vel; // velocity in constraint space: J*qvel (nefc x 1)
|
||||||
mjtNum* efc_aref; // reference pseudo-acceleration (njmax x 1)
|
mjtNum* efc_aref; // reference pseudo-acceleration (nefc x 1)
|
||||||
|
|
||||||
// computed by mj_sensorVel/mj_subtreeVel if needed
|
// computed by mj_sensorVel/mj_subtreeVel if needed
|
||||||
mjtNum* subtree_linvel; // linear velocity of subtree com (nbody x 3)
|
mjtNum* subtree_linvel; // linear velocity of subtree com (nbody x 3)
|
||||||
@@ -320,9 +295,6 @@ struct mjData_ {
|
|||||||
mjtNum* qacc_smooth; // unconstrained acceleration (nv x 1)
|
mjtNum* qacc_smooth; // unconstrained acceleration (nv x 1)
|
||||||
|
|
||||||
// computed by mj_fwdConstraint/mj_inverse
|
// computed by mj_fwdConstraint/mj_inverse
|
||||||
mjtNum* efc_b; // linear cost term: J*qacc_smooth - aref (njmax x 1)
|
|
||||||
mjtNum* efc_force; // constraint force in constraint space (njmax x 1)
|
|
||||||
int* efc_state; // constraint state (mjtConstraintState) (njmax x 1)
|
|
||||||
mjtNum* qfrc_constraint; // constraint force (nv x 1)
|
mjtNum* qfrc_constraint; // constraint force (nv x 1)
|
||||||
|
|
||||||
// computed by mj_inverse
|
// computed by mj_inverse
|
||||||
@@ -333,6 +305,43 @@ struct mjData_ {
|
|||||||
mjtNum* cacc; // com-based acceleration (nbody x 6)
|
mjtNum* cacc; // com-based acceleration (nbody x 6)
|
||||||
mjtNum* cfrc_int; // com-based interaction force with parent (nbody x 6)
|
mjtNum* cfrc_int; // com-based interaction force with parent (nbody x 6)
|
||||||
mjtNum* cfrc_ext; // com-based external force on body (nbody x 6)
|
mjtNum* cfrc_ext; // com-based external force on body (nbody x 6)
|
||||||
|
|
||||||
|
//-------------------------------- ARENA-ALLOCATED ARRAYS
|
||||||
|
|
||||||
|
// computed by mj_collision
|
||||||
|
mjContact* contact; // list of all detected contacts (ncon x 1)
|
||||||
|
|
||||||
|
// computed by mj_makeConstraint
|
||||||
|
int* efc_type; // constraint type (mjtConstraint) (nefc x 1)
|
||||||
|
int* efc_id; // id of object of specified type (nefc x 1)
|
||||||
|
int* efc_J_rownnz; // number of non-zeros in Jacobian row (nefc x 1)
|
||||||
|
int* efc_J_rowadr; // row start address in colind array (nefc x 1)
|
||||||
|
int* efc_J_rowsuper; // number of subsequent rows in supernode (nefc x 1)
|
||||||
|
int* efc_J_colind; // column indices in Jacobian (nefc x nv)
|
||||||
|
int* efc_JT_rownnz; // number of non-zeros in Jacobian row T (nv x 1)
|
||||||
|
int* efc_JT_rowadr; // row start address in colind array T (nv x 1)
|
||||||
|
int* efc_JT_rowsuper; // number of subsequent rows in supernode T (nv x 1)
|
||||||
|
int* efc_JT_colind; // column indices in Jacobian T (nv x nefc)
|
||||||
|
mjtNum* efc_J; // constraint Jacobian (nefc x nv)
|
||||||
|
mjtNum* efc_JT; // constraint Jacobian transposed (nv x nefc)
|
||||||
|
mjtNum* efc_pos; // constraint position (equality, contact) (nefc x 1)
|
||||||
|
mjtNum* efc_margin; // inclusion margin (contact) (nefc x 1)
|
||||||
|
mjtNum* efc_frictionloss; // frictionloss (friction) (nefc x 1)
|
||||||
|
mjtNum* efc_diagApprox; // approximation to diagonal of A (nefc x 1)
|
||||||
|
mjtNum* efc_KBIP; // stiffness, damping, impedance, imp' (nefc x 4)
|
||||||
|
mjtNum* efc_D; // constraint mass (nefc x 1)
|
||||||
|
mjtNum* efc_R; // inverse constraint mass (nefc x 1)
|
||||||
|
|
||||||
|
// computed by mj_fwdConstraint/mj_inverse
|
||||||
|
mjtNum* efc_b; // linear cost term: J*qacc_smooth - aref (nefc x 1)
|
||||||
|
mjtNum* efc_force; // constraint force in constraint space (nefc x 1)
|
||||||
|
int* efc_state; // constraint state (mjtConstraintState) (nefc x 1)
|
||||||
|
|
||||||
|
// computed by mj_projectConstraint
|
||||||
|
int* efc_AR_rownnz; // number of non-zeros in AR (nefc x 1)
|
||||||
|
int* efc_AR_rowadr; // row start address in colind array (nefc x 1)
|
||||||
|
int* efc_AR_colind; // column indices in sparse AR (nefc x nefc)
|
||||||
|
mjtNum* efc_AR; // J*inv(M)*J' + R (nefc x nefc)
|
||||||
};
|
};
|
||||||
typedef struct mjData_ mjData;
|
typedef struct mjData_ mjData;
|
||||||
|
|
||||||
|
|||||||
+50
-31
@@ -434,8 +434,7 @@
|
|||||||
|
|
||||||
// define symbols needed in MJDATA_POINTERS (corresponding to number of columns)
|
// define symbols needed in MJDATA_POINTERS (corresponding to number of columns)
|
||||||
#define MJDATA_POINTERS_PREAMBLE( m ) \
|
#define MJDATA_POINTERS_PREAMBLE( m ) \
|
||||||
int nv = m->nv; \
|
int nv = m->nv;
|
||||||
int njmax = m->njmax;
|
|
||||||
|
|
||||||
|
|
||||||
// pointer fields of mjData
|
// pointer fields of mjData
|
||||||
@@ -490,38 +489,12 @@
|
|||||||
X( mjtNum, qLD, nM, 1 ) \
|
X( mjtNum, qLD, nM, 1 ) \
|
||||||
X( mjtNum, qLDiagInv, nv, 1 ) \
|
X( mjtNum, qLDiagInv, nv, 1 ) \
|
||||||
X( mjtNum, qLDiagSqrtInv, nv, 1 ) \
|
X( mjtNum, qLDiagSqrtInv, nv, 1 ) \
|
||||||
X( mjContact, contact, nconmax, 1 ) \
|
|
||||||
X( int, efc_type, njmax, 1 ) \
|
|
||||||
X( int, efc_id, njmax, 1 ) \
|
|
||||||
X( int, efc_J_rownnz, njmax, 1 ) \
|
|
||||||
X( int, efc_J_rowadr, njmax, 1 ) \
|
|
||||||
X( int, efc_J_rowsuper, njmax, 1 ) \
|
|
||||||
X( int, efc_J_colind, njmax, MJ_M(nv) ) \
|
|
||||||
X( int, efc_JT_rownnz, nv, 1 ) \
|
|
||||||
X( int, efc_JT_rowadr, nv, 1 ) \
|
|
||||||
X( int, efc_JT_rowsuper, nv, 1 ) \
|
|
||||||
X( int, efc_JT_colind, nv, MJ_M(njmax) ) \
|
|
||||||
X( mjtNum, efc_J, njmax, MJ_M(nv) ) \
|
|
||||||
X( mjtNum, efc_JT, nv, MJ_M(njmax) ) \
|
|
||||||
X( mjtNum, efc_pos, njmax, 1 ) \
|
|
||||||
X( mjtNum, efc_margin, njmax, 1 ) \
|
|
||||||
X( mjtNum, efc_frictionloss, njmax, 1 ) \
|
|
||||||
X( mjtNum, efc_diagApprox, njmax, 1 ) \
|
|
||||||
X( mjtNum, efc_KBIP, njmax, 4 ) \
|
|
||||||
X( mjtNum, efc_D, njmax, 1 ) \
|
|
||||||
X( mjtNum, efc_R, njmax, 1 ) \
|
|
||||||
X( int, efc_AR_rownnz, njmax, 1 ) \
|
|
||||||
X( int, efc_AR_rowadr, njmax, 1 ) \
|
|
||||||
X( int, efc_AR_colind, njmax, MJ_M(njmax) ) \
|
|
||||||
X( mjtNum, efc_AR, njmax, MJ_M(njmax) ) \
|
|
||||||
X( mjtNum, ten_velocity, ntendon, 1 ) \
|
X( mjtNum, ten_velocity, ntendon, 1 ) \
|
||||||
X( mjtNum, actuator_velocity, nu, 1 ) \
|
X( mjtNum, actuator_velocity, nu, 1 ) \
|
||||||
X( mjtNum, cvel, nbody, 6 ) \
|
X( mjtNum, cvel, nbody, 6 ) \
|
||||||
X( mjtNum, cdof_dot, nv, 6 ) \
|
X( mjtNum, cdof_dot, nv, 6 ) \
|
||||||
X( mjtNum, qfrc_bias, nv, 1 ) \
|
X( mjtNum, qfrc_bias, nv, 1 ) \
|
||||||
X( mjtNum, qfrc_passive, nv, 1 ) \
|
X( mjtNum, qfrc_passive, nv, 1 ) \
|
||||||
X( mjtNum, efc_vel, njmax, 1 ) \
|
|
||||||
X( mjtNum, efc_aref, njmax, 1 ) \
|
|
||||||
X( mjtNum, subtree_linvel, nbody, 3 ) \
|
X( mjtNum, subtree_linvel, nbody, 3 ) \
|
||||||
X( mjtNum, subtree_angmom, nbody, 3 ) \
|
X( mjtNum, subtree_angmom, nbody, 3 ) \
|
||||||
X( mjtNum, qH, nM, 1 ) \
|
X( mjtNum, qH, nM, 1 ) \
|
||||||
@@ -535,9 +508,6 @@
|
|||||||
X( mjtNum, qfrc_actuator, nv, 1 ) \
|
X( mjtNum, qfrc_actuator, nv, 1 ) \
|
||||||
X( mjtNum, qfrc_smooth, nv, 1 ) \
|
X( mjtNum, qfrc_smooth, nv, 1 ) \
|
||||||
X( mjtNum, qacc_smooth, nv, 1 ) \
|
X( mjtNum, qacc_smooth, nv, 1 ) \
|
||||||
X( mjtNum, efc_b, njmax, 1 ) \
|
|
||||||
X( mjtNum, efc_force, njmax, 1 ) \
|
|
||||||
X( int, efc_state, njmax, 1 ) \
|
|
||||||
X( mjtNum, qfrc_constraint, nv, 1 ) \
|
X( mjtNum, qfrc_constraint, nv, 1 ) \
|
||||||
X( mjtNum, qfrc_inverse, nv, 1 ) \
|
X( mjtNum, qfrc_inverse, nv, 1 ) \
|
||||||
X( mjtNum, cacc, nbody, 6 ) \
|
X( mjtNum, cacc, nbody, 6 ) \
|
||||||
@@ -545,6 +515,55 @@
|
|||||||
X( mjtNum, cfrc_ext, nbody, 6 )
|
X( mjtNum, cfrc_ext, nbody, 6 )
|
||||||
|
|
||||||
|
|
||||||
|
// macro for annotating that an array size in an X macro is a member of mjData
|
||||||
|
// by default this macro does nothing, but users can redefine it as necessary
|
||||||
|
#define MJ_D(n) n
|
||||||
|
|
||||||
|
// array of contacts
|
||||||
|
#define MJDATA_ARENA_POINTERS_CONTACT \
|
||||||
|
X( mjContact, contact, MJ_D(ncon), 1 )
|
||||||
|
|
||||||
|
// array fields of mjData that are used in the primal problem
|
||||||
|
#define MJDATA_ARENA_POINTERS_PRIMAL \
|
||||||
|
X( int, efc_type, MJ_D(nefc), 1 ) \
|
||||||
|
X( int, efc_id, MJ_D(nefc), 1 ) \
|
||||||
|
X( int, efc_J_rownnz, MJ_D(nefc), 1 ) \
|
||||||
|
X( int, efc_J_rowadr, MJ_D(nefc), 1 ) \
|
||||||
|
X( int, efc_J_rowsuper, MJ_D(nefc), 1 ) \
|
||||||
|
X( int, efc_J_colind, MJ_D(nefc), MJ_M(nv) ) \
|
||||||
|
X( int, efc_JT_rownnz, MJ_M(nv), 1 ) \
|
||||||
|
X( int, efc_JT_rowadr, MJ_M(nv), 1 ) \
|
||||||
|
X( int, efc_JT_rowsuper, MJ_M(nv), 1 ) \
|
||||||
|
X( int, efc_JT_colind, MJ_M(nv), MJ_D(nefc) ) \
|
||||||
|
X( mjtNum, efc_J, MJ_D(nefc), MJ_M(nv) ) \
|
||||||
|
X( mjtNum, efc_JT, MJ_M(nv), MJ_D(nefc) ) \
|
||||||
|
X( mjtNum, efc_pos, MJ_D(nefc), 1 ) \
|
||||||
|
X( mjtNum, efc_margin, MJ_D(nefc), 1 ) \
|
||||||
|
X( mjtNum, efc_frictionloss, MJ_D(nefc), 1 ) \
|
||||||
|
X( mjtNum, efc_diagApprox, MJ_D(nefc), 1 ) \
|
||||||
|
X( mjtNum, efc_KBIP, MJ_D(nefc), 4 ) \
|
||||||
|
X( mjtNum, efc_D, MJ_D(nefc), 1 ) \
|
||||||
|
X( mjtNum, efc_R, MJ_D(nefc), 1 ) \
|
||||||
|
X( mjtNum, efc_vel, MJ_D(nefc), 1 ) \
|
||||||
|
X( mjtNum, efc_aref, MJ_D(nefc), 1 ) \
|
||||||
|
X( mjtNum, efc_b, MJ_D(nefc), 1 ) \
|
||||||
|
X( mjtNum, efc_force, MJ_D(nefc), 1 ) \
|
||||||
|
X( int, efc_state, MJ_D(nefc), 1 ) \
|
||||||
|
|
||||||
|
// array fields of mjData that are used in the dual problem
|
||||||
|
#define MJDATA_ARENA_POINTERS_DUAL \
|
||||||
|
X( int, efc_AR_rownnz, MJ_D(nefc), 1 ) \
|
||||||
|
X( int, efc_AR_rowadr, MJ_D(nefc), 1 ) \
|
||||||
|
X( int, efc_AR_colind, MJ_D(nefc), MJ_D(nefc) ) \
|
||||||
|
X( mjtNum, efc_AR, MJ_D(nefc), MJ_D(nefc) )
|
||||||
|
|
||||||
|
// array fields of mjData that live in d->arena
|
||||||
|
#define MJDATA_ARENA_POINTERS \
|
||||||
|
MJDATA_ARENA_POINTERS_CONTACT \
|
||||||
|
MJDATA_ARENA_POINTERS_PRIMAL \
|
||||||
|
MJDATA_ARENA_POINTERS_DUAL
|
||||||
|
|
||||||
|
|
||||||
// scalar fields of mjData
|
// scalar fields of mjData
|
||||||
#define MJDATA_SCALAR \
|
#define MJDATA_SCALAR \
|
||||||
X( int, nstack ) \
|
X( int, nstack ) \
|
||||||
|
|||||||
@@ -457,18 +457,22 @@ class MuJoCoBindingsTest(parameterized.TestCase):
|
|||||||
|
|
||||||
# Grab a reference to the contacts upfront so that we know that they're
|
# Grab a reference to the contacts upfront so that we know that they're
|
||||||
# a view into mjData rather than a copy.
|
# a view into mjData rather than a copy.
|
||||||
contact = self.data.contact[:4]
|
contact = self.data.contact
|
||||||
|
|
||||||
self.model.opt.timestep = 2**-9 # 0.001953125; allows exact comparisons
|
self.model.opt.timestep = 2**-9 # 0.001953125; allows exact comparisons
|
||||||
self.assertEqual(self.data.time, 0)
|
self.assertEqual(self.data.time, 0)
|
||||||
while self.data.time < expected_contact_time:
|
while self.data.time < expected_contact_time:
|
||||||
self.assertEqual(self.data.ncon, 0)
|
self.assertEqual(self.data.ncon, 0)
|
||||||
|
self.assertEmpty(self.data.efc_type)
|
||||||
|
self.assertTrue(self.data.efc_type.flags['OWNDATA'])
|
||||||
prev_time = self.data.time
|
prev_time = self.data.time
|
||||||
mujoco.mj_step(self.model, self.data)
|
mujoco.mj_step(self.model, self.data)
|
||||||
self.assertEqual(self.data.time, prev_time + self.model.opt.timestep)
|
self.assertEqual(self.data.time, prev_time + self.model.opt.timestep)
|
||||||
|
|
||||||
mujoco.mj_forward(self.model, self.data)
|
mujoco.mj_forward(self.model, self.data)
|
||||||
self.assertEqual(self.data.ncon, 4)
|
self.assertEqual(self.data.ncon, 4)
|
||||||
|
self.assertLen(self.data.efc_type, 16)
|
||||||
|
self.assertFalse(self.data.efc_type.flags['OWNDATA'])
|
||||||
|
|
||||||
# Sort contacts in anticlockwise order
|
# Sort contacts in anticlockwise order
|
||||||
sorted_contact = sorted(
|
sorted_contact = sorted(
|
||||||
@@ -478,6 +482,11 @@ class MuJoCoBindingsTest(parameterized.TestCase):
|
|||||||
np.testing.assert_allclose(sorted_contact[2].pos[:2], [0.1, 0.1])
|
np.testing.assert_allclose(sorted_contact[2].pos[:2], [0.1, 0.1])
|
||||||
np.testing.assert_allclose(sorted_contact[3].pos[:2], [-0.1, 0.1])
|
np.testing.assert_allclose(sorted_contact[3].pos[:2], [-0.1, 0.1])
|
||||||
|
|
||||||
|
mujoco.mj_resetData(self.model, self.data)
|
||||||
|
self.assertEqual(self.data.ncon, 0)
|
||||||
|
self.assertEmpty(self.data.efc_type)
|
||||||
|
self.assertTrue(self.data.efc_type.flags['OWNDATA'])
|
||||||
|
|
||||||
def test_mj_step_multiple(self):
|
def test_mj_step_multiple(self):
|
||||||
self.model.opt.timestep = 2**-9 # 0.001953125; allows exact comparisons
|
self.model.opt.timestep = 2**-9 # 0.001953125; allows exact comparisons
|
||||||
self.assertEqual(self.data.time, 0)
|
self.assertEqual(self.data.time, 0)
|
||||||
@@ -488,24 +497,31 @@ class MuJoCoBindingsTest(parameterized.TestCase):
|
|||||||
self.assertIn('Optionally, repeat nstep times.', mujoco.mj_step.__doc__)
|
self.assertIn('Optionally, repeat nstep times.', mujoco.mj_step.__doc__)
|
||||||
|
|
||||||
def test_mj_contact_list(self):
|
def test_mj_contact_list(self):
|
||||||
self.assertLen(self.data.contact, self.model.nconmax)
|
self.assertEmpty(self.data.contact)
|
||||||
|
|
||||||
|
expected_ncon = 1234
|
||||||
|
self.data.ncon = expected_ncon
|
||||||
|
self.assertLen(self.data.contact, expected_ncon)
|
||||||
|
|
||||||
expected_pos = []
|
expected_pos = []
|
||||||
for contact in self.data.contact:
|
for contact in self.data.contact:
|
||||||
expected_pos.append(np.random.uniform(size=3))
|
expected_pos.append(np.random.uniform(size=3))
|
||||||
contact.pos = expected_pos[-1]
|
contact.pos = expected_pos[-1]
|
||||||
|
self.assertLen(expected_pos, expected_ncon)
|
||||||
np.testing.assert_array_equal(self.data.contact.pos, expected_pos)
|
np.testing.assert_array_equal(self.data.contact.pos, expected_pos)
|
||||||
|
|
||||||
expected_friction = []
|
expected_friction = []
|
||||||
for contact in self.data.contact:
|
for contact in self.data.contact:
|
||||||
expected_friction.append(np.random.uniform(size=5))
|
expected_friction.append(np.random.uniform(size=5))
|
||||||
contact.friction = expected_friction[-1]
|
contact.friction = expected_friction[-1]
|
||||||
|
self.assertLen(expected_friction, expected_ncon)
|
||||||
np.testing.assert_array_equal(self.data.contact.friction, expected_friction)
|
np.testing.assert_array_equal(self.data.contact.friction, expected_friction)
|
||||||
|
|
||||||
expected_H = [] # pylint: disable=invalid-name
|
expected_H = [] # pylint: disable=invalid-name
|
||||||
for contact in self.data.contact:
|
for contact in self.data.contact:
|
||||||
expected_H.append(np.random.uniform(size=36))
|
expected_H.append(np.random.uniform(size=36))
|
||||||
contact.H = expected_H[-1]
|
contact.H = expected_H[-1]
|
||||||
|
self.assertLen(expected_H, expected_ncon)
|
||||||
np.testing.assert_array_equal(self.data.contact.H, expected_H)
|
np.testing.assert_array_equal(self.data.contact.H, expected_H)
|
||||||
|
|
||||||
def test_mj_struct_list_equality(self):
|
def test_mj_struct_list_equality(self):
|
||||||
@@ -516,16 +532,16 @@ class MuJoCoBindingsTest(parameterized.TestCase):
|
|||||||
self.assertEqual(self.data.ncon, 4)
|
self.assertEqual(self.data.ncon, 4)
|
||||||
mujoco.mj_forward(model2, data2)
|
mujoco.mj_forward(model2, data2)
|
||||||
self.assertEqual(data2.ncon, 4)
|
self.assertEqual(data2.ncon, 4)
|
||||||
self.assertEqual(data2.contact[:4], self.data.contact[:4])
|
self.assertEqual(data2.contact, self.data.contact)
|
||||||
|
|
||||||
self.data.qpos[3:7] = [np.cos(np.pi/8), np.sin(np.pi/8), 0, 0]
|
self.data.qpos[3:7] = [np.cos(np.pi/8), np.sin(np.pi/8), 0, 0]
|
||||||
self.data.qpos[2] *= (np.sqrt(2) - 1) * 0.1 - 1e-6
|
self.data.qpos[2] *= (np.sqrt(2) - 1) * 0.1 - 1e-6
|
||||||
mujoco.mj_forward(self.model, self.data)
|
mujoco.mj_forward(self.model, self.data)
|
||||||
self.assertEqual(self.data.ncon, 2)
|
self.assertEqual(self.data.ncon, 2)
|
||||||
self.assertNotEqual(data2.contact[:2], self.data.contact[:2])
|
self.assertNotEqual(data2.contact, self.data.contact)
|
||||||
|
|
||||||
# Check that we can compare slices of different lengths
|
# Check that we can compare slices of different lengths
|
||||||
self.assertNotEqual(data2.contact[:2], self.data.contact[:4])
|
self.assertNotEqual(data2.contact, self.data.contact)
|
||||||
|
|
||||||
# Check that comparing things of different types do not raise an error
|
# Check that comparing things of different types do not raise an error
|
||||||
self.assertNotEqual(self.data.contact, self.data.warning)
|
self.assertNotEqual(self.data.contact, self.data.warning)
|
||||||
@@ -856,7 +872,7 @@ Euler integrator, semi-implicit in velocity.
|
|||||||
|
|
||||||
def test_can_raise_error(self):
|
def test_can_raise_error(self):
|
||||||
self.data.pstack = self.data.nstack
|
self.data.pstack = self.data.nstack
|
||||||
with self.assertRaisesWithLiteralMatch(mujoco.FatalError, 'Stack overflow'):
|
with self.assertRaisesRegex(mujoco.FatalError, r'\Astack overflow'):
|
||||||
mujoco.mj_forward(self.model, self.data)
|
mujoco.mj_forward(self.model, self.data)
|
||||||
|
|
||||||
def test_mjcb_time(self):
|
def test_mjcb_time(self):
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#ifndef MUJOCO_PYTHON_MJDATA_META_H_
|
#ifndef MUJOCO_PYTHON_MJDATA_META_H_
|
||||||
#define MUJOCO_PYTHON_MJDATA_META_H_
|
#define MUJOCO_PYTHON_MJDATA_META_H_
|
||||||
|
|
||||||
|
#include <mujoco/mujoco.h>
|
||||||
#include <mujoco/mjxmacro.h>
|
#include <mujoco/mjxmacro.h>
|
||||||
#include "raw.h"
|
#include "raw.h"
|
||||||
#include "util/crossplatform.h"
|
#include "util/crossplatform.h"
|
||||||
@@ -74,6 +75,8 @@ struct MjDataMetadata {
|
|||||||
MJDATA_METADATA
|
MJDATA_METADATA
|
||||||
#undef X
|
#undef X
|
||||||
|
|
||||||
|
bool is_dual;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MjDataMetadata() = default;
|
MjDataMetadata() = default;
|
||||||
MjDataMetadata(const MjDataMetadata& other) = default;
|
MjDataMetadata(const MjDataMetadata& other) = default;
|
||||||
@@ -94,10 +97,7 @@ struct MjDataMetadata {
|
|||||||
|
|
||||||
MJDATA_METADATA
|
MJDATA_METADATA
|
||||||
#undef X
|
#undef X
|
||||||
dummy_() {}
|
is_dual(mj_isDual(m)) {}
|
||||||
|
|
||||||
// Dummy variable to terminate X macro sequences.
|
|
||||||
MUJOCO_MAYBE_UNUSED bool dummy_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace mujoco::python
|
} // namespace mujoco::python
|
||||||
|
|||||||
@@ -41,25 +41,26 @@ inline char ReadChar(std::istream& input) {
|
|||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void WriteInt(std::ostream& output, int i) {
|
inline void WriteInt(std::ostream& output, std::size_t i) {
|
||||||
output.write(reinterpret_cast<char*>(&i), sizeof(int));
|
output.write(reinterpret_cast<char*>(&i), sizeof(std::size_t));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int ReadInt(std::istream& input) {
|
inline std::size_t ReadInt(std::istream& input) {
|
||||||
int i = 0;
|
std::size_t i = 0;
|
||||||
input.read(reinterpret_cast<char*>(&i), sizeof(int));
|
input.read(reinterpret_cast<char*>(&i), sizeof(std::size_t));
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void WriteBytes(std::ostream& output, const void* src, size_t nbytes) {
|
inline void WriteBytes(std::ostream& output, const void* src,
|
||||||
|
std::size_t nbytes) {
|
||||||
// Start by writing nbytes itself, so it can be validated at the time of
|
// Start by writing nbytes itself, so it can be validated at the time of
|
||||||
// reading.
|
// reading.
|
||||||
WriteInt(output, nbytes);
|
WriteInt(output, nbytes);
|
||||||
output.write(reinterpret_cast<const char*>(src), nbytes);
|
output.write(reinterpret_cast<const char*>(src), nbytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void ReadBytes(std::istream& input, void* dest, size_t nbytes) {
|
inline void ReadBytes(std::istream& input, void* dest, std::size_t nbytes) {
|
||||||
size_t actual_nbytes = ReadInt(input);
|
std::size_t actual_nbytes = ReadInt(input);
|
||||||
if (actual_nbytes != nbytes) {
|
if (actual_nbytes != nbytes) {
|
||||||
input.setstate(input.rdstate() | std::ios_base::failbit);
|
input.setstate(input.rdstate() | std::ios_base::failbit);
|
||||||
return;
|
return;
|
||||||
|
|||||||
+134
-52
@@ -52,6 +52,9 @@ namespace mujoco::python::_impl {
|
|||||||
namespace py = ::pybind11;
|
namespace py = ::pybind11;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
#define PTRDIFF(x, y) \
|
||||||
|
reinterpret_cast<const char*>(x) - reinterpret_cast<const char*>(y)
|
||||||
|
|
||||||
// Returns the shape of a NumPy array given the dimensions from an X Macro.
|
// Returns the shape of a NumPy array given the dimensions from an X Macro.
|
||||||
// If dim1 is a _literal_ constant 1, the resulting array is 1-dimensional of
|
// If dim1 is a _literal_ constant 1, the resulting array is 1-dimensional of
|
||||||
// length dim0, otherwise the resulting array is 2-dimensional of shape
|
// length dim0, otherwise the resulting array is 2-dimensional of shape
|
||||||
@@ -74,6 +77,9 @@ constexpr auto XArrayShapeImpl(const std::string_view dim1_str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline std::size_t NConMax(const mjData* d) {
|
||||||
|
return d->nstack * sizeof(mjtNum) / sizeof(mjContact);
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
// ==================== MJOPTION ===============================================
|
// ==================== MJOPTION ===============================================
|
||||||
@@ -498,51 +504,15 @@ MjContactWrapper::MjWrapper(const MjContactWrapper& other)
|
|||||||
*this->ptr_ = *other.ptr_;
|
*this->ptr_ = *other.ptr_;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define X(type, var) \
|
MjContactList::MjStructList(raw::MjContact* ptr, int nconmax,
|
||||||
var(std::vector<int>{num}, std::vector<int>{sizeof(raw::MjContact)}, \
|
int* ncon, py::handle owner)
|
||||||
&ptr->var, owner)
|
: StructListBase(ptr, nconmax, owner, /* lazy = */ true),
|
||||||
#define XN(type, var) \
|
ncon_(ncon) {}
|
||||||
var(std::vector<int>{num, sizeof(raw::MjContact::var) / sizeof(type)}, \
|
|
||||||
std::vector<int>{sizeof(raw::MjContact), sizeof(type)}, &ptr->var[0], \
|
|
||||||
owner)
|
|
||||||
MjContactList::MjStructList(raw::MjContact* ptr, int num, py::handle owner)
|
|
||||||
: StructListBase(ptr, num, owner),
|
|
||||||
X(mjtNum, dist),
|
|
||||||
XN(mjtNum, pos),
|
|
||||||
XN(mjtNum, frame),
|
|
||||||
X(mjtNum, includemargin),
|
|
||||||
XN(mjtNum, friction),
|
|
||||||
XN(mjtNum, solref),
|
|
||||||
XN(mjtNum, solimp),
|
|
||||||
X(mjtNum, mu),
|
|
||||||
XN(mjtNum, H),
|
|
||||||
X(int, dim),
|
|
||||||
X(int, geom1),
|
|
||||||
X(int, geom2),
|
|
||||||
X(int, exclude),
|
|
||||||
X(int, efc_address) {}
|
|
||||||
#undef X
|
|
||||||
#undef XN
|
|
||||||
|
|
||||||
// Slicing
|
// Slicing
|
||||||
#define X(type, var) var(other.var[slice])
|
|
||||||
MjContactList::MjStructList(MjContactList& other, py::slice slice)
|
MjContactList::MjStructList(MjContactList& other, py::slice slice)
|
||||||
: StructListBase(other, slice),
|
: StructListBase(other, slice),
|
||||||
X(mjtNum, dist),
|
ncon_(other.ncon_) {}
|
||||||
X(mjtNum, pos),
|
|
||||||
X(mjtNum, frame),
|
|
||||||
X(mjtNum, includemargin),
|
|
||||||
X(mjtNum, friction),
|
|
||||||
X(mjtNum, solref),
|
|
||||||
X(mjtNum, solimp),
|
|
||||||
X(mjtNum, mu),
|
|
||||||
X(mjtNum, H),
|
|
||||||
X(int, dim),
|
|
||||||
X(int, geom1),
|
|
||||||
X(int, geom2),
|
|
||||||
X(int, exclude),
|
|
||||||
X(int, efc_address) {}
|
|
||||||
#undef X
|
|
||||||
|
|
||||||
// ==================== MJDATA =================================================
|
// ==================== MJDATA =================================================
|
||||||
static void MjDataCapsuleDestructor(PyObject* pyobj) {
|
static void MjDataCapsuleDestructor(PyObject* pyobj) {
|
||||||
@@ -579,9 +549,11 @@ MjDataWrapper::MjWrapper(const MjModelWrapper& model)
|
|||||||
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
|
MJDATA_POINTERS
|
||||||
#undef MJ_M
|
#undef MJ_M
|
||||||
#define MJ_M(x) x
|
#define MJ_M(x) (x)
|
||||||
#undef X
|
#undef X
|
||||||
|
|
||||||
|
contact(MjContactList(ptr_->contact, NConMax(ptr_), &ptr_->ncon, owner_)),
|
||||||
|
|
||||||
#define X(dtype, var, dim0, dim1) var(InitPyArray(ptr_->var, owner_)),
|
#define X(dtype, var, dim0, dim1) var(InitPyArray(ptr_->var, owner_)),
|
||||||
MJDATA_VECTOR
|
MJDATA_VECTOR
|
||||||
#undef X
|
#undef X
|
||||||
@@ -607,9 +579,11 @@ MjDataWrapper::MjWrapper(const MjDataWrapper& other)
|
|||||||
owner_)),
|
owner_)),
|
||||||
MJDATA_POINTERS
|
MJDATA_POINTERS
|
||||||
#undef MJ_M
|
#undef MJ_M
|
||||||
#define MJ_M(x) x
|
#define MJ_M(x) (x)
|
||||||
#undef X
|
#undef X
|
||||||
|
|
||||||
|
contact(MjContactList(ptr_->contact, NConMax(ptr_), &ptr_->ncon, owner_)),
|
||||||
|
|
||||||
#define X(dtype, var, dim0, dim1) var(InitPyArray(ptr_->var, owner_)),
|
#define X(dtype, var, dim0, dim1) var(InitPyArray(ptr_->var, owner_)),
|
||||||
MJDATA_VECTOR
|
MJDATA_VECTOR
|
||||||
#undef X
|
#undef X
|
||||||
@@ -635,9 +609,11 @@ MjDataWrapper::MjWrapper(MjDataWrapper&& other)
|
|||||||
owner_)),
|
owner_)),
|
||||||
MJDATA_POINTERS
|
MJDATA_POINTERS
|
||||||
#undef MJ_M
|
#undef MJ_M
|
||||||
#define MJ_M(x) x
|
#define MJ_M(x) (x)
|
||||||
#undef X
|
#undef X
|
||||||
|
|
||||||
|
contact(MjContactList(ptr_->contact, NConMax(ptr_), &ptr_->ncon,owner_)),
|
||||||
|
|
||||||
#define X(dtype, var, dim0, dim1) var(InitPyArray(ptr_->var, owner_)),
|
#define X(dtype, var, dim0, dim1) var(InitPyArray(ptr_->var, owner_)),
|
||||||
MJDATA_VECTOR
|
MJDATA_VECTOR
|
||||||
#undef X
|
#undef X
|
||||||
@@ -664,9 +640,11 @@ MjDataWrapper::MjWrapper(MjDataMetadata&& metadata, raw::MjData* d)
|
|||||||
var(InitPyArray(X_ARRAY_SHAPE(metadata.dim0, dim1), ptr_->var, owner_)),
|
var(InitPyArray(X_ARRAY_SHAPE(metadata.dim0, dim1), ptr_->var, owner_)),
|
||||||
MJDATA_POINTERS
|
MJDATA_POINTERS
|
||||||
#undef MJ_M
|
#undef MJ_M
|
||||||
#define MJ_M(x) x
|
#define MJ_M(x) (x)
|
||||||
#undef X
|
#undef X
|
||||||
|
|
||||||
|
contact(MjContactList(ptr_->contact, NConMax(ptr_), &ptr_->ncon, owner_)),
|
||||||
|
|
||||||
#define X(dtype, var, dim0, dim1) var(InitPyArray(ptr_->var, owner_)),
|
#define X(dtype, var, dim0, dim1) var(InitPyArray(ptr_->var, owner_)),
|
||||||
MJDATA_VECTOR
|
MJDATA_VECTOR
|
||||||
#undef X
|
#undef X
|
||||||
@@ -707,6 +685,8 @@ void MjDataWrapper::Serialize(std::ostream& output) const {
|
|||||||
MJMODEL_INTS
|
MJMODEL_INTS
|
||||||
#undef X
|
#undef X
|
||||||
|
|
||||||
|
WriteInt(output, this->metadata_.is_dual);
|
||||||
|
|
||||||
#define X(dtype, var, n) \
|
#define X(dtype, var, n) \
|
||||||
WriteBytes(output, this->metadata_.var.get(), \
|
WriteBytes(output, this->metadata_.var.get(), \
|
||||||
this->metadata_.n * sizeof(dtype));
|
this->metadata_.n * sizeof(dtype));
|
||||||
@@ -716,9 +696,17 @@ void MjDataWrapper::Serialize(std::ostream& output) const {
|
|||||||
|
|
||||||
// Write struct and scalar fields
|
// Write struct and scalar fields
|
||||||
#define X(var) WriteBytes(output, &ptr_->var, sizeof(ptr_->var))
|
#define X(var) WriteBytes(output, &ptr_->var, sizeof(ptr_->var))
|
||||||
|
X(parena);
|
||||||
|
X(maxuse_stack);
|
||||||
|
X(maxuse_arena);
|
||||||
|
X(maxuse_con);
|
||||||
|
X(maxuse_efc);
|
||||||
X(solver);
|
X(solver);
|
||||||
X(timer);
|
X(timer);
|
||||||
X(warning);
|
X(warning);
|
||||||
|
X(ne);
|
||||||
|
X(nf);
|
||||||
|
X(nefc);
|
||||||
X(ncon);
|
X(ncon);
|
||||||
X(time);
|
X(time);
|
||||||
X(energy);
|
X(energy);
|
||||||
@@ -727,9 +715,32 @@ void MjDataWrapper::Serialize(std::ostream& output) const {
|
|||||||
// Write buffer contents
|
// Write buffer contents
|
||||||
{
|
{
|
||||||
MJDATA_POINTERS_PREAMBLE((&this->metadata_))
|
MJDATA_POINTERS_PREAMBLE((&this->metadata_))
|
||||||
|
|
||||||
#define X(type, name, nr, nc) \
|
#define X(type, name, nr, nc) \
|
||||||
WriteBytes(output, ptr_->name, sizeof(type)*(this->metadata_.nr)*(nc));
|
WriteBytes(output, ptr_->name, sizeof(type)*(this->metadata_.nr)*(nc));
|
||||||
MJDATA_POINTERS
|
MJDATA_POINTERS
|
||||||
|
#undef X
|
||||||
|
|
||||||
|
#undef MJ_M
|
||||||
|
#define MJ_M(x) this->metadata_.x
|
||||||
|
#undef MJ_D
|
||||||
|
#define MJ_D(x) this->ptr_->x
|
||||||
|
#define X(type, name, nr, nc) \
|
||||||
|
if ((nr) * (nc)) { \
|
||||||
|
WriteInt(output, PTRDIFF(ptr_->name, ptr_->arena)); \
|
||||||
|
WriteBytes(output, ptr_->name, sizeof(type) * (nr) * (nc)); \
|
||||||
|
}
|
||||||
|
|
||||||
|
MJDATA_ARENA_POINTERS_CONTACT
|
||||||
|
MJDATA_ARENA_POINTERS_PRIMAL
|
||||||
|
|
||||||
|
if (this->metadata_.is_dual) {
|
||||||
|
MJDATA_ARENA_POINTERS_DUAL
|
||||||
|
}
|
||||||
|
#undef MJ_M
|
||||||
|
#define MJ_M(x) x
|
||||||
|
#undef MJ_D
|
||||||
|
#define MJ_D(x) x
|
||||||
#undef X
|
#undef X
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -754,6 +765,8 @@ MjDataWrapper MjDataWrapper::Deserialize(std::istream& input) {
|
|||||||
MJMODEL_INTS
|
MJMODEL_INTS
|
||||||
#undef X
|
#undef X
|
||||||
|
|
||||||
|
metadata.is_dual = ReadInt(input);
|
||||||
|
|
||||||
#define X(dtype, var, n) \
|
#define X(dtype, var, n) \
|
||||||
metadata.var.reset(new dtype[metadata.n]); \
|
metadata.var.reset(new dtype[metadata.n]); \
|
||||||
ReadBytes(input, metadata.var.get(), metadata.n * sizeof(dtype)); \
|
ReadBytes(input, metadata.var.get(), metadata.n * sizeof(dtype)); \
|
||||||
@@ -773,9 +786,17 @@ MjDataWrapper MjDataWrapper::Deserialize(std::istream& input) {
|
|||||||
ReadBytes(input, (void*) &d->var, sizeof(d->var)); \
|
ReadBytes(input, (void*) &d->var, sizeof(d->var)); \
|
||||||
CheckInput(input, "mjData");
|
CheckInput(input, "mjData");
|
||||||
|
|
||||||
|
X(parena);
|
||||||
|
X(maxuse_stack);
|
||||||
|
X(maxuse_arena);
|
||||||
|
X(maxuse_con);
|
||||||
|
X(maxuse_efc);
|
||||||
X(solver);
|
X(solver);
|
||||||
X(timer);
|
X(timer);
|
||||||
X(warning);
|
X(warning);
|
||||||
|
X(ne);
|
||||||
|
X(nf);
|
||||||
|
X(nefc);
|
||||||
X(ncon);
|
X(ncon);
|
||||||
X(time);
|
X(time);
|
||||||
X(energy);
|
X(energy);
|
||||||
@@ -784,9 +805,33 @@ MjDataWrapper MjDataWrapper::Deserialize(std::istream& input) {
|
|||||||
// Read buffer contents
|
// Read buffer contents
|
||||||
{
|
{
|
||||||
MJDATA_POINTERS_PREAMBLE((&m))
|
MJDATA_POINTERS_PREAMBLE((&m))
|
||||||
|
|
||||||
#define X(type, name, nr, nc) \
|
#define X(type, name, nr, nc) \
|
||||||
ReadBytes(input, d->name, sizeof(type)*(m.nr)*(nc));
|
ReadBytes(input, d->name, sizeof(type)*(m.nr)*(nc));
|
||||||
MJDATA_POINTERS
|
MJDATA_POINTERS
|
||||||
|
#undef X
|
||||||
|
|
||||||
|
#undef MJ_M
|
||||||
|
#define MJ_M(x) m.x
|
||||||
|
#undef MJ_D
|
||||||
|
#define MJ_D(x) d->x
|
||||||
|
#define X(type, name, nr, nc) \
|
||||||
|
if ((nr) * (nc)) { \
|
||||||
|
d->name = reinterpret_cast<decltype(d->name)>( \
|
||||||
|
static_cast<char*>(d->arena) + ReadInt(input)); \
|
||||||
|
ReadBytes(input, d->name, sizeof(type) * (nr) * (nc)); \
|
||||||
|
}
|
||||||
|
|
||||||
|
MJDATA_ARENA_POINTERS_CONTACT
|
||||||
|
MJDATA_ARENA_POINTERS_PRIMAL
|
||||||
|
|
||||||
|
if (metadata.is_dual) {
|
||||||
|
MJDATA_ARENA_POINTERS_DUAL
|
||||||
|
}
|
||||||
|
#undef MJ_M
|
||||||
|
#define MJ_M(x) x
|
||||||
|
#undef MJ_D
|
||||||
|
#define MJ_D(x) x
|
||||||
#undef X
|
#undef X
|
||||||
}
|
}
|
||||||
CheckInput(input, "mjData");
|
CheckInput(input, "mjData");
|
||||||
@@ -1697,22 +1742,36 @@ This is useful for example when the MJB is not available as a file on disk.)"));
|
|||||||
mjContactList.def("__len__", &MjContactList::size);
|
mjContactList.def("__len__", &MjContactList::size);
|
||||||
DefineStructFunctions(mjContactList);
|
DefineStructFunctions(mjContactList);
|
||||||
|
|
||||||
#define X(type, var) mjContactList.def_readonly(#var, &MjContactList::var)
|
#define X(type, var) \
|
||||||
|
mjContactList.def_property_readonly(#var, [](const MjContactList& c) { \
|
||||||
|
return py::array_t<type>(std::vector<int>{c.size()}, \
|
||||||
|
std::vector<int>{sizeof(raw::MjContact)}, \
|
||||||
|
&c.get()->var, c.owner()); \
|
||||||
|
});
|
||||||
|
#define XN(type, var) \
|
||||||
|
mjContactList.def_property_readonly(#var, [](const MjContactList& c) { \
|
||||||
|
return py::array_t<type>( \
|
||||||
|
std::vector<int>{c.size(), \
|
||||||
|
sizeof(raw::MjContact::var) / sizeof(type)}, \
|
||||||
|
std::vector<int>{sizeof(raw::MjContact), sizeof(type)}, \
|
||||||
|
&c.get()->var[0], c.owner()); \
|
||||||
|
});
|
||||||
X(mjtNum, dist);
|
X(mjtNum, dist);
|
||||||
X(mjtNum, pos);
|
XN(mjtNum, pos);
|
||||||
X(mjtNum, frame);
|
XN(mjtNum, frame);
|
||||||
X(mjtNum, includemargin);
|
X(mjtNum, includemargin);
|
||||||
X(mjtNum, friction);
|
XN(mjtNum, friction);
|
||||||
X(mjtNum, solref);
|
XN(mjtNum, solref);
|
||||||
X(mjtNum, solimp);
|
XN(mjtNum, solimp);
|
||||||
X(mjtNum, mu);
|
X(mjtNum, mu);
|
||||||
X(mjtNum, H);
|
XN(mjtNum, H);
|
||||||
X(int, dim);
|
X(int, dim);
|
||||||
X(int, geom1);
|
X(int, geom1);
|
||||||
X(int, geom2);
|
X(int, geom2);
|
||||||
X(int, exclude);
|
X(int, exclude);
|
||||||
X(int, efc_address);
|
X(int, efc_address);
|
||||||
#undef X
|
#undef X
|
||||||
|
#undef XN
|
||||||
|
|
||||||
// ==================== MJDATA ===============================================
|
// ==================== MJDATA ===============================================
|
||||||
py::class_<MjDataWrapper> mjData(m, "MjData");
|
py::class_<MjDataWrapper> mjData(m, "MjData");
|
||||||
@@ -1749,6 +1808,29 @@ This is useful for example when the MJB is not available as a file on disk.)"));
|
|||||||
#define X(dtype, var, dim0, dim1) \
|
#define X(dtype, var, dim0, dim1) \
|
||||||
DefinePyArray(mjData, #var, &MjDataWrapper::var);
|
DefinePyArray(mjData, #var, &MjDataWrapper::var);
|
||||||
MJDATA_POINTERS
|
MJDATA_POINTERS
|
||||||
|
MJDATA_ARENA_POINTERS_CONTACT
|
||||||
|
#undef X
|
||||||
|
|
||||||
|
#undef MJ_M
|
||||||
|
#define MJ_M(x) d.metadata().x
|
||||||
|
#undef MJ_D
|
||||||
|
#define MJ_D(x) d.get()->x
|
||||||
|
#define X(dtype, var, dim0, dim1) \
|
||||||
|
mjData.def_property_readonly(#var, [](const MjDataWrapper& d) { \
|
||||||
|
return InitPyArray(X_ARRAY_SHAPE(dim0, dim1), d.get()->var, d.owner()); \
|
||||||
|
});
|
||||||
|
|
||||||
|
MJDATA_ARENA_POINTERS_PRIMAL
|
||||||
|
MJDATA_ARENA_POINTERS_DUAL
|
||||||
|
|
||||||
|
#undef MJ_M
|
||||||
|
#define MJ_M(x) (x)
|
||||||
|
#undef MJ_D
|
||||||
|
#define MJ_D(x) (x)
|
||||||
|
#undef X
|
||||||
|
|
||||||
|
#define X(dtype, var, dim0, dim1) \
|
||||||
|
DefinePyArray(mjData, #var, &MjDataWrapper::var);
|
||||||
MJDATA_VECTOR
|
MJDATA_VECTOR
|
||||||
#undef X
|
#undef X
|
||||||
|
|
||||||
|
|||||||
+53
-27
@@ -92,38 +92,59 @@ class MjWrapper {};
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
class StructListBase {
|
class StructListBase {
|
||||||
public:
|
public:
|
||||||
StructListBase(T* ptr, int num, pybind11::handle owner) : ptr_(ptr) {
|
StructListBase(T* ptr, int num, pybind11::handle owner, bool lazy = false)
|
||||||
for (int i = 0; i < num; ++i) {
|
: ptr_(ptr), num_(num), owner_(owner) {
|
||||||
wrappers_.push_back(std::make_shared<MjWrapper<T>>(&ptr[i], owner));
|
if (!lazy) {
|
||||||
|
PopulateUpTo(size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StructListBase(const StructListBase& other) = delete;
|
StructListBase(const StructListBase& other) = delete;
|
||||||
StructListBase(StructListBase&& other) = default;
|
StructListBase(StructListBase&& other) = default;
|
||||||
|
|
||||||
|
virtual ~StructListBase() = default;
|
||||||
|
|
||||||
MjWrapper<T>& operator[](int i) {
|
MjWrapper<T>& operator[](int i) {
|
||||||
if (i < 0 || i >= wrappers_.size()) {
|
if (i < 0 || i >= size()) {
|
||||||
throw pybind11::index_error();
|
throw pybind11::index_error();
|
||||||
}
|
}
|
||||||
|
PopulateUpTo(i);
|
||||||
return *wrappers_[i];
|
return *wrappers_[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
int size() const {
|
virtual int size() const {
|
||||||
return wrappers_.size();
|
return num_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
T* get() const { return ptr_; }
|
||||||
|
pybind11::handle owner() const { return owner_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void PopulateUpTo(int n) {
|
||||||
|
while (wrappers_.size() <= n) {
|
||||||
|
wrappers_.push_back(
|
||||||
|
std::make_shared<MjWrapper<T>>(&ptr_[wrappers_.size()], owner_));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Slicing
|
// Slicing
|
||||||
StructListBase(StructListBase& other, pybind11::slice slice) {
|
StructListBase(StructListBase& other, pybind11::slice slice)
|
||||||
|
: owner_(other.owner_) {
|
||||||
pybind11::size_t start, stop, step, slicelength;
|
pybind11::size_t start, stop, step, slicelength;
|
||||||
slice.compute(other.size(), &start, &stop, &step, &slicelength);
|
if (!slice.compute(other.size(), &start, &stop, &step, &slicelength)) {
|
||||||
|
throw pybind11::index_error();
|
||||||
|
}
|
||||||
|
other.PopulateUpTo(stop);
|
||||||
ptr_ = &other.ptr_[start];
|
ptr_ = &other.ptr_[start];
|
||||||
for (int i = start; i < stop; i += step) {
|
for (int i = start; i < stop; i += step) {
|
||||||
wrappers_.push_back(other.wrappers_[i]);
|
wrappers_.push_back(other.wrappers_[i]);
|
||||||
}
|
}
|
||||||
|
num_ = wrappers_.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
T* ptr_;
|
T* ptr_;
|
||||||
|
int num_;
|
||||||
|
pybind11::handle owner_;
|
||||||
|
|
||||||
// Using shared_ptr here so that we get identical Python objects when slicing.
|
// Using shared_ptr here so that we get identical Python objects when slicing.
|
||||||
std::vector<std::shared_ptr<MjWrapper<T>>> wrappers_;
|
std::vector<std::shared_ptr<MjWrapper<T>>> wrappers_;
|
||||||
@@ -277,6 +298,8 @@ class MjStructList<raw::MjWarningStat>
|
|||||||
: public StructListBase<raw::MjWarningStat> {
|
: public StructListBase<raw::MjWarningStat> {
|
||||||
public:
|
public:
|
||||||
MjStructList(raw::MjWarningStat* ptr, int num, pybind11::handle owner);
|
MjStructList(raw::MjWarningStat* ptr, int num, pybind11::handle owner);
|
||||||
|
MjStructList(MjStructList&&) = default;
|
||||||
|
~MjStructList() override = default;
|
||||||
|
|
||||||
using StructListBase::operator[];
|
using StructListBase::operator[];
|
||||||
using StructListBase::size;
|
using StructListBase::size;
|
||||||
@@ -325,6 +348,8 @@ template <>
|
|||||||
class MjStructList<raw::MjTimerStat> : public StructListBase<raw::MjTimerStat> {
|
class MjStructList<raw::MjTimerStat> : public StructListBase<raw::MjTimerStat> {
|
||||||
public:
|
public:
|
||||||
MjStructList(raw::MjTimerStat* ptr, int num, pybind11::handle owner);
|
MjStructList(raw::MjTimerStat* ptr, int num, pybind11::handle owner);
|
||||||
|
MjStructList(MjStructList&&) = default;
|
||||||
|
~MjStructList() override = default;
|
||||||
|
|
||||||
using StructListBase::operator[];
|
using StructListBase::operator[];
|
||||||
using StructListBase::size;
|
using StructListBase::size;
|
||||||
@@ -374,6 +399,8 @@ class MjStructList<raw::MjSolverStat>
|
|||||||
: public StructListBase<raw::MjSolverStat> {
|
: public StructListBase<raw::MjSolverStat> {
|
||||||
public:
|
public:
|
||||||
MjStructList(raw::MjSolverStat* ptr, int num, pybind11::handle owner);
|
MjStructList(raw::MjSolverStat* ptr, int num, pybind11::handle owner);
|
||||||
|
MjStructList(MjStructList&&) = default;
|
||||||
|
~MjStructList() override = default;
|
||||||
|
|
||||||
using StructListBase::operator[];
|
using StructListBase::operator[];
|
||||||
using StructListBase::size;
|
using StructListBase::size;
|
||||||
@@ -489,33 +516,28 @@ struct enable_if_mj_struct<raw::MjContact> { using type = void; };
|
|||||||
template <>
|
template <>
|
||||||
class MjStructList<raw::MjContact> : public StructListBase<raw::MjContact> {
|
class MjStructList<raw::MjContact> : public StructListBase<raw::MjContact> {
|
||||||
public:
|
public:
|
||||||
MjStructList(raw::MjContact* ptr, int num, pybind11::handle owner);
|
MjStructList(raw::MjContact* ptr, int nconmax,
|
||||||
|
int* ncon, pybind11::handle owner);
|
||||||
|
MjStructList(MjStructList&&) = default;
|
||||||
|
~MjStructList() override = default;
|
||||||
|
|
||||||
using StructListBase::operator[];
|
using StructListBase::operator[];
|
||||||
using StructListBase::size;
|
|
||||||
|
int size() const override {
|
||||||
|
if (ncon_) {
|
||||||
|
return *ncon_;
|
||||||
|
} else {
|
||||||
|
return StructListBase::size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MjStructList Slice(pybind11::slice slice) {
|
MjStructList Slice(pybind11::slice slice) {
|
||||||
return MjStructList(*this, slice);
|
return MjStructList(*this, slice);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define X(type, var) pybind11::array_t<type> var
|
|
||||||
X(mjtNum, dist);
|
|
||||||
X(mjtNum, pos);
|
|
||||||
X(mjtNum, frame);
|
|
||||||
X(mjtNum, includemargin);
|
|
||||||
X(mjtNum, friction);
|
|
||||||
X(mjtNum, solref);
|
|
||||||
X(mjtNum, solimp);
|
|
||||||
X(mjtNum, mu);
|
|
||||||
X(mjtNum, H);
|
|
||||||
X(int, dim);
|
|
||||||
X(int, geom1);
|
|
||||||
X(int, geom2);
|
|
||||||
X(int, exclude);
|
|
||||||
X(int, efc_address);
|
|
||||||
#undef X
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
MjStructList(MjStructList& other, pybind11::slice slice);
|
MjStructList(MjStructList& other, pybind11::slice slice);
|
||||||
|
int* ncon_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
using MjContactList = MjStructList<raw::MjContact>;
|
using MjContactList = MjStructList<raw::MjContact>;
|
||||||
@@ -539,6 +561,7 @@ class MjWrapper<raw::MjData>: public WrapperBase<raw::MjData> {
|
|||||||
MjWrapper(MjWrapper&&);
|
MjWrapper(MjWrapper&&);
|
||||||
~MjWrapper();
|
~MjWrapper();
|
||||||
|
|
||||||
|
const MjDataMetadata& metadata() const { return metadata_; }
|
||||||
MjDataIndexer& indexer() { return indexer_; }
|
MjDataIndexer& indexer() { return indexer_; }
|
||||||
|
|
||||||
void Serialize(std::ostream& output) const;
|
void Serialize(std::ostream& output) const;
|
||||||
@@ -548,10 +571,13 @@ class MjWrapper<raw::MjData>: public WrapperBase<raw::MjData> {
|
|||||||
"__MUJOCO_STRUCTS_MJDATAWRAPPER_LOOKUP";
|
"__MUJOCO_STRUCTS_MJDATAWRAPPER_LOOKUP";
|
||||||
static MjWrapper* FromRawPointer(raw::MjData* m) noexcept;
|
static MjWrapper* FromRawPointer(raw::MjData* m) noexcept;
|
||||||
|
|
||||||
|
|
||||||
#define X(dtype, var, dim0, dim1) py_array_or_tuple_t<dtype> var;
|
#define X(dtype, var, dim0, dim1) py_array_or_tuple_t<dtype> var;
|
||||||
MJDATA_POINTERS
|
MJDATA_POINTERS
|
||||||
#undef X
|
#undef X
|
||||||
|
|
||||||
|
py_array_or_tuple_t<mjContact> contact;
|
||||||
|
|
||||||
py_array_or_tuple_t<raw::MjWarningStat> warning;
|
py_array_or_tuple_t<raw::MjWarningStat> warning;
|
||||||
py_array_or_tuple_t<raw::MjTimerStat> timer;
|
py_array_or_tuple_t<raw::MjTimerStat> timer;
|
||||||
py_array_or_tuple_t<raw::MjSolverStat> solver;
|
py_array_or_tuple_t<raw::MjSolverStat> solver;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
#include "lodepng.h"
|
#include "lodepng.h"
|
||||||
#include <mujoco/mjmodel.h>
|
#include <mujoco/mjmodel.h>
|
||||||
|
#include <mujoco/mjtnum.h>
|
||||||
#include <mujoco/mjvisualize.h>
|
#include <mujoco/mjvisualize.h>
|
||||||
#include <mujoco/mjxmacro.h>
|
#include <mujoco/mjxmacro.h>
|
||||||
#include "glfw_dispatch.h"
|
#include "glfw_dispatch.h"
|
||||||
@@ -475,9 +476,9 @@ void infotext(mj::Simulate* sim,
|
|||||||
solerr = mju_log10(mju_max(mjMINVAL, solerr));
|
solerr = mju_log10(mju_max(mjMINVAL, solerr));
|
||||||
|
|
||||||
// prepare info text
|
// prepare info text
|
||||||
mju::strcpy_arr(title, "Time\nSize\nCPU\nSolver \nFPS\nstack\nconbuf\nefcbuf");
|
mju::strcpy_arr(title, "Time\nSize\nCPU\nSolver \nFPS\nMemory");
|
||||||
mju::sprintf_arr(content,
|
mju::sprintf_arr(content,
|
||||||
"%-9.3f\n%d (%d con)\n%.3f\n%.1f (%d it)\n%.0f\n%.3f\n%.3f\n%.3f",
|
"%-9.3f\n%d (%d con)\n%.3f\n%.1f (%d it)\n%.0f\n%.3f",
|
||||||
d->time,
|
d->time,
|
||||||
d->nefc, d->ncon,
|
d->nefc, d->ncon,
|
||||||
sim->run ?
|
sim->run ?
|
||||||
@@ -485,9 +486,7 @@ void infotext(mj::Simulate* sim,
|
|||||||
d->timer[mjTIMER_FORWARD].duration / mjMAX(1, d->timer[mjTIMER_FORWARD].number),
|
d->timer[mjTIMER_FORWARD].duration / mjMAX(1, d->timer[mjTIMER_FORWARD].number),
|
||||||
solerr, d->solver_iter,
|
solerr, d->solver_iter,
|
||||||
1/interval,
|
1/interval,
|
||||||
d->maxuse_stack/(double)d->nstack,
|
d->maxuse_arena/(double)(d->nstack * sizeof(mjtNum)));
|
||||||
d->maxuse_con/(double)m->nconmax,
|
|
||||||
d->maxuse_efc/(double)m->njmax);
|
|
||||||
|
|
||||||
// add Energy if enabled
|
// add Energy if enabled
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include <mujoco/mjdata.h>
|
#include <mujoco/mjdata.h>
|
||||||
#include <mujoco/mjmodel.h>
|
#include <mujoco/mjmodel.h>
|
||||||
|
#include <mujoco/mjxmacro.h>
|
||||||
#include "engine/engine_callback.h"
|
#include "engine/engine_callback.h"
|
||||||
#include "engine/engine_collision_convex.h"
|
#include "engine/engine_collision_convex.h"
|
||||||
#include "engine/engine_collision_primitive.h"
|
#include "engine/engine_collision_primitive.h"
|
||||||
@@ -55,7 +56,7 @@ void mj_collision(const mjModel* m, mjData* d) {
|
|||||||
int *broadphasepair = 0;
|
int *broadphasepair = 0;
|
||||||
mjMARKSTACK;
|
mjMARKSTACK;
|
||||||
|
|
||||||
// clear size
|
// reset the size of the contact array
|
||||||
d->ncon = 0;
|
d->ncon = 0;
|
||||||
|
|
||||||
// return if disabled
|
// return if disabled
|
||||||
|
|||||||
@@ -13,13 +13,15 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#include "engine/engine_core_constraint.h"
|
#include "engine/engine_core_constraint.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <mujoco/mjdata.h>
|
#include <mujoco/mjdata.h>
|
||||||
#include <mujoco/mjmodel.h>
|
#include <mujoco/mjmodel.h>
|
||||||
#include "engine/engine_collision_driver.h"
|
#include <mujoco/mjxmacro.h>
|
||||||
|
#include "engine/engine_array_safety.h"
|
||||||
#include "engine/engine_core_smooth.h"
|
#include "engine/engine_core_smooth.h"
|
||||||
#include "engine/engine_io.h"
|
#include "engine/engine_io.h"
|
||||||
#include "engine/engine_macro.h"
|
#include "engine/engine_macro.h"
|
||||||
@@ -101,14 +103,27 @@ mjtNum mj_assignMargin(const mjModel* m, mjtNum source) {
|
|||||||
|
|
||||||
// add contact to d->contact list; return 0 if success; 1 if buffer full
|
// add contact to d->contact list; return 0 if success; 1 if buffer full
|
||||||
int mj_addContact(const mjModel* m, mjData* d, const mjContact* con) {
|
int mj_addContact(const mjModel* m, mjData* d, const mjContact* con) {
|
||||||
// if out of space, warn and return error
|
// if nconmax is specified and ncon >= nconmax, warn and return error
|
||||||
if (d->ncon >= m->nconmax) {
|
if (m->nconmax != -1 && d->ncon >= m->nconmax) {
|
||||||
mj_warning(d, mjWARN_CONTACTFULL, m->nconmax);
|
mj_warning(d, mjWARN_CONTACTFULL, d->ncon);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// move arena pointer back to the end of the existing contact array and invalidate efc_ arrays
|
||||||
|
d->parena = d->ncon * sizeof(mjContact);
|
||||||
|
d->nefc = 0;
|
||||||
|
#define X(type, name, nr, nc) d->name = NULL;
|
||||||
|
MJDATA_ARENA_POINTERS
|
||||||
|
#undef X
|
||||||
|
d->contact = d->arena;
|
||||||
|
|
||||||
// copy contact
|
// copy contact
|
||||||
d->contact[d->ncon] = *con;
|
mjContact* dst = mj_arenaAlloc(d, sizeof(mjContact), _Alignof(mjContact));
|
||||||
|
if (!dst) {
|
||||||
|
mj_warning(d, mjWARN_CONTACTFULL, d->ncon);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
*dst = *con;
|
||||||
|
|
||||||
// increase counter, return success
|
// increase counter, return success
|
||||||
d->ncon++;
|
d->ncon++;
|
||||||
@@ -127,12 +142,6 @@ int mj_addConstraint(const mjModel* m, mjData* d,
|
|||||||
int *nnz = d->efc_J_rownnz, *adr = d->efc_J_rowadr, *ind = d->efc_J_colind;
|
int *nnz = d->efc_J_rownnz, *adr = d->efc_J_rowadr, *ind = d->efc_J_colind;
|
||||||
mjtNum *J = d->efc_J;
|
mjtNum *J = d->efc_J;
|
||||||
|
|
||||||
// if out of space, warn and return error
|
|
||||||
if (nefc+size > m->njmax) {
|
|
||||||
mj_warning(d, mjWARN_CNSTRFULL, m->njmax);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// init empty guard for constraints other than contact
|
// init empty guard for constraints other than contact
|
||||||
if (type==mjCNSTR_CONTACT_FRICTIONLESS ||
|
if (type==mjCNSTR_CONTACT_FRICTIONLESS ||
|
||||||
type==mjCNSTR_CONTACT_PYRAMIDAL ||
|
type==mjCNSTR_CONTACT_PYRAMIDAL ||
|
||||||
@@ -348,7 +357,7 @@ void mj_instantiateEquality(const mjModel* m, mjData* d) {
|
|||||||
mjtNum *jac[2], *jacdif, *data, *sparse_buf = NULL;
|
mjtNum *jac[2], *jacdif, *data, *sparse_buf = NULL;
|
||||||
mjMARKSTACK;
|
mjMARKSTACK;
|
||||||
|
|
||||||
// disabled or no equality contraints: return
|
// disabled or no equality constraints: return
|
||||||
if (mjDISABLED(mjDSBL_EQUALITY) || m->nemax==0) {
|
if (mjDISABLED(mjDSBL_EQUALITY) || m->nemax==0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -774,12 +783,6 @@ void mj_instantiateContact(const mjModel* m, mjData* d) {
|
|||||||
b1 = m->geom_bodyid[con->geom1];
|
b1 = m->geom_bodyid[con->geom1];
|
||||||
b2 = m->geom_bodyid[con->geom2];
|
b2 = m->geom_bodyid[con->geom2];
|
||||||
|
|
||||||
// check size here, because pyramid rows are added incrementally
|
|
||||||
if (d->nefc + (dim==1 ? 1 : (ispyramid ? 2*(dim-1) : dim)) > m->njmax) {
|
|
||||||
mj_warning(d, mjWARN_CNSTRFULL, m->njmax);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// save efc_address
|
// save efc_address
|
||||||
con->efc_address = d->nefc;
|
con->efc_address = d->nefc;
|
||||||
|
|
||||||
@@ -1238,6 +1241,164 @@ void mj_makeImpedance(const mjModel* m, mjData* d) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------- constraint counting ----------------------------------------
|
||||||
|
|
||||||
|
// count equality constraints
|
||||||
|
static inline int mj_ne(const mjModel* m, const mjData* d) {
|
||||||
|
// disabled or no equality constraints: return
|
||||||
|
if (mjDISABLED(mjDSBL_EQUALITY) || m->nemax==0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ne = 0;
|
||||||
|
|
||||||
|
for (int i=0; i<m->neq; i++) {
|
||||||
|
if (!m->eq_active[i]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// process according to type
|
||||||
|
switch (m->eq_type[i]) {
|
||||||
|
case mjEQ_CONNECT:
|
||||||
|
ne += 3;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case mjEQ_WELD:
|
||||||
|
ne += 6;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case mjEQ_JOINT:
|
||||||
|
case mjEQ_TENDON:
|
||||||
|
ne++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: // SHOULD NOT OCCUR
|
||||||
|
mju_error_i("Invalid equality constraint type %d", m->eq_type[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ne;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// count frictional constraints
|
||||||
|
static inline int mj_nf(const mjModel* m, const mjData* d) {
|
||||||
|
// disabled: return
|
||||||
|
if (mjDISABLED(mjDSBL_FRICTIONLOSS)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nf = 0;
|
||||||
|
const int nv = m->nv;
|
||||||
|
const int ntendon = m->ntendon;
|
||||||
|
|
||||||
|
// count frictional dofs
|
||||||
|
for (int i=0; i<nv; i++) {
|
||||||
|
nf += (m->dof_frictionloss[i] > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// count frictional tendons
|
||||||
|
for (int i=0; i<ntendon; i++) {
|
||||||
|
nf += (m->tendon_frictionloss[i] > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nf;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// count limit constraints
|
||||||
|
static inline int mj_nl(const mjModel* m, const mjData* d) {
|
||||||
|
// disabled: return
|
||||||
|
if (mjDISABLED(mjDSBL_LIMIT)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nl = 0;
|
||||||
|
const int njnt = m->njnt;
|
||||||
|
const int ntendon = m->ntendon;
|
||||||
|
|
||||||
|
// count limited joints
|
||||||
|
for (int i=0; i<njnt; i++) {
|
||||||
|
if (!m->jnt_limited[i]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// slides and hinges can have active limits on two sides, check both
|
||||||
|
if (m->jnt_type[i]==mjJNT_SLIDE || m->jnt_type[i]==mjJNT_HINGE) {
|
||||||
|
// get margin
|
||||||
|
mjtNum margin = m->jnt_margin[i];
|
||||||
|
|
||||||
|
// get joint value
|
||||||
|
mjtNum value = d->qpos[m->jnt_qposadr[i]];
|
||||||
|
|
||||||
|
// check lower and upper limits
|
||||||
|
for (int side=-1; side<=1; side+=2) {
|
||||||
|
// compute distance (negative: penetration)
|
||||||
|
mjtNum dist = side * (m->jnt_range[2*i+(side+1)/2] - value);
|
||||||
|
|
||||||
|
// detect joint limit
|
||||||
|
if (dist<margin) {
|
||||||
|
nl++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nl++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// count limited tendons
|
||||||
|
for (int i=0; i<ntendon; i++) {
|
||||||
|
nl += m->tendon_limited[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return nl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// count contact constraints
|
||||||
|
static inline int mj_nc(const mjModel* m, const mjData* d) {
|
||||||
|
// disabled or no contacts: return
|
||||||
|
int ncon = d->ncon;
|
||||||
|
if (mjDISABLED(mjDSBL_CONTACT) || ncon==0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nc = 0;
|
||||||
|
int ispyramid = mj_isPyramidal(m);
|
||||||
|
|
||||||
|
// find contacts to be counted
|
||||||
|
for (int i=0; i<ncon; i++) {
|
||||||
|
mjContact* con = d->contact + i;
|
||||||
|
if (con->exclude) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dim = con->dim;
|
||||||
|
|
||||||
|
// dim 1: single constraint
|
||||||
|
if (dim==1) {
|
||||||
|
nc++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dim > 1: depends on cone type
|
||||||
|
else {
|
||||||
|
nc += (ispyramid ? 2*(dim-1) : dim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// count all constraints
|
||||||
|
static inline int mj_nefc(const mjModel* m, const mjData* d) {
|
||||||
|
return mj_ne(m, d) + mj_nf(m, d) + mj_nl(m, d) + mj_nc(m, d);
|
||||||
|
}
|
||||||
|
|
||||||
//---------------------------- top-level API for constraint construction ---------------------------
|
//---------------------------- top-level API for constraint construction ---------------------------
|
||||||
|
|
||||||
// driver: call all functions above
|
// driver: call all functions above
|
||||||
@@ -1246,16 +1407,56 @@ void mj_makeConstraint(const mjModel* m, mjData* d) {
|
|||||||
d->ne = d->nf = d->nefc = 0;
|
d->ne = d->nf = d->nefc = 0;
|
||||||
|
|
||||||
// disabled or Jacobian not allocated: return
|
// disabled or Jacobian not allocated: return
|
||||||
if (mjDISABLED(mjDSBL_CONSTRAINT) || m->njmax==0) {
|
if (mjDISABLED(mjDSBL_CONSTRAINT)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int nefc_allocated = mj_nefc(m, d);
|
||||||
|
d->nefc = nefc_allocated;
|
||||||
|
|
||||||
|
#undef MJ_M
|
||||||
|
#define MJ_M(n) m->n
|
||||||
|
#undef MJ_D
|
||||||
|
#define MJ_D(n) d->n
|
||||||
|
|
||||||
|
// move arena pointer to end of contact array
|
||||||
|
d->parena = d->ncon * sizeof(mjContact);
|
||||||
|
|
||||||
|
#define X(type, name, nr, nc) \
|
||||||
|
d->name = mj_arenaAlloc(d, sizeof(type) * (nr) * (nc), _Alignof(type)); \
|
||||||
|
if (!d->name) { \
|
||||||
|
mj_warning(d, mjWARN_CNSTRFULL, d->nstack * sizeof(mjtNum)); \
|
||||||
|
d->nefc = 0; \
|
||||||
|
return; \
|
||||||
|
}
|
||||||
|
|
||||||
|
MJDATA_ARENA_POINTERS_PRIMAL
|
||||||
|
if (mj_isDual(m)) {
|
||||||
|
MJDATA_ARENA_POINTERS_DUAL
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef X
|
||||||
|
|
||||||
|
#undef MJ_M
|
||||||
|
#define MJ_M(n) n
|
||||||
|
#undef MJ_D
|
||||||
|
#define MJ_D(n) n
|
||||||
|
|
||||||
|
d->nefc = 0;
|
||||||
|
|
||||||
// instantiate all elements of Jacobian
|
// instantiate all elements of Jacobian
|
||||||
mj_instantiateEquality(m, d);
|
mj_instantiateEquality(m, d);
|
||||||
mj_instantiateFriction(m, d);
|
mj_instantiateFriction(m, d);
|
||||||
mj_instantiateLimit(m, d);
|
mj_instantiateLimit(m, d);
|
||||||
mj_instantiateContact(m, d);
|
mj_instantiateContact(m, d);
|
||||||
|
|
||||||
|
if (d->nefc > nefc_allocated) {
|
||||||
|
char msg[1024];
|
||||||
|
mjSNPRINTF(
|
||||||
|
msg, "nefc under-allocation: found nefc=%d but allocated only %d", d->nefc, nefc_allocated);
|
||||||
|
mju_error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
// collect memory use statistics
|
// collect memory use statistics
|
||||||
d->maxuse_con = mjMAX(d->maxuse_con, d->ncon);
|
d->maxuse_con = mjMAX(d->maxuse_con, d->ncon);
|
||||||
d->maxuse_efc = mjMAX(d->maxuse_efc, d->nefc);
|
d->maxuse_efc = mjMAX(d->maxuse_efc, d->nefc);
|
||||||
|
|||||||
+77
-45
@@ -24,37 +24,17 @@
|
|||||||
#include <mujoco/mjmodel.h>
|
#include <mujoco/mjmodel.h>
|
||||||
#include <mujoco/mjplugin.h>
|
#include <mujoco/mjplugin.h>
|
||||||
#include <mujoco/mjxmacro.h>
|
#include <mujoco/mjxmacro.h>
|
||||||
|
#include "engine/engine_array_safety.h"
|
||||||
#include "engine/engine_macro.h"
|
#include "engine/engine_macro.h"
|
||||||
#include "engine/engine_plugin.h"
|
#include "engine/engine_plugin.h"
|
||||||
#include "engine/engine_util_blas.h"
|
#include "engine/engine_util_blas.h"
|
||||||
#include "engine/engine_util_errmem.h"
|
#include "engine/engine_util_errmem.h"
|
||||||
#include "engine/engine_vfs.h"
|
#include "engine/engine_vfs.h"
|
||||||
|
|
||||||
#ifdef ADDRESS_SANITIZER
|
|
||||||
#include <sanitizer/asan_interface.h>
|
|
||||||
#elif defined(_MSC_VER)
|
|
||||||
#define ASAN_POISON_MEMORY_REGION(addr, size)
|
|
||||||
#define ASAN_UNPOISON_MEMORY_REGION(addr, size)
|
|
||||||
#else
|
|
||||||
#define ASAN_POISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size))
|
|
||||||
#define ASAN_UNPOISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef MEMORY_SANITIZER
|
|
||||||
#include <sanitizer/msan_interface.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
#pragma warning (disable: 4305) // disable MSVC warning: truncation from 'double' to 'float'
|
#pragma warning (disable: 4305) // disable MSVC warning: truncation from 'double' to 'float'
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef __has_builtin
|
|
||||||
#define __has_builtin(x) 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define PTRDIFF(x, y) ((void*)(x) - (void*)(y))
|
|
||||||
|
|
||||||
|
|
||||||
//------------------------------ mjLROpt -----------------------------------------------------------
|
//------------------------------ mjLROpt -----------------------------------------------------------
|
||||||
|
|
||||||
// set default options for length range computation
|
// set default options for length range computation
|
||||||
@@ -260,14 +240,6 @@ void mj_defaultStatistic(mjStatistic* stat) {
|
|||||||
static const int ID = 54321;
|
static const int ID = 54321;
|
||||||
|
|
||||||
|
|
||||||
// number of bytes to be skipped to achieve 64-byte alignment
|
|
||||||
static unsigned int SKIP(intptr_t offset) {
|
|
||||||
const unsigned int align = 64;
|
|
||||||
// compute skipped bytes
|
|
||||||
return (align - (offset % align)) % align;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// count ints in mjModel
|
// count ints in mjModel
|
||||||
static int getnint(void) {
|
static int getnint(void) {
|
||||||
@@ -840,6 +812,13 @@ static void mj_setPtrData(const mjModel* m, mjData* d) {
|
|||||||
if (d->nbuffer != sz) {
|
if (d->nbuffer != sz) {
|
||||||
mju_error("mjData buffer size mismatch");
|
mju_error("mjData buffer size mismatch");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// zero-initialize arena pointers
|
||||||
|
#define X(type, name, nr, nc) d->name = NULL;
|
||||||
|
MJDATA_ARENA_POINTERS
|
||||||
|
#undef X
|
||||||
|
|
||||||
|
d->contact = d->arena;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -859,7 +838,7 @@ static mjData* _makeData(const mjModel* m) {
|
|||||||
|
|
||||||
// compute buffer size
|
// compute buffer size
|
||||||
d->nbuffer = 0;
|
d->nbuffer = 0;
|
||||||
d->buffer = d->stack = NULL;
|
d->buffer = d->arena = NULL;
|
||||||
#define X(type, name, nr, nc) \
|
#define X(type, name, nr, nc) \
|
||||||
if (!safeAddToBufferSize(&offset, &d->nbuffer, sizeof(type), m->nr, nc)) { \
|
if (!safeAddToBufferSize(&offset, &d->nbuffer, sizeof(type), m->nr, nc)) { \
|
||||||
mju_free(d); \
|
mju_free(d); \
|
||||||
@@ -880,12 +859,12 @@ static mjData* _makeData(const mjModel* m) {
|
|||||||
mju_error("Could not allocate mjData buffer");
|
mju_error("Could not allocate mjData buffer");
|
||||||
}
|
}
|
||||||
|
|
||||||
// allocate stack
|
// allocate arena
|
||||||
d->stack = (mjtNum*) mju_malloc(d->nstack * sizeof(mjtNum));
|
d->arena = mju_malloc(d->nstack * sizeof(mjtNum));
|
||||||
if (!d->stack) {
|
if (!d->arena) {
|
||||||
mju_free(d->buffer);
|
mju_free(d->buffer);
|
||||||
mju_free(d);
|
mju_free(d);
|
||||||
mju_error("Could not allocate mjData stack");
|
mju_error("Could not allocate mjData arena");
|
||||||
}
|
}
|
||||||
|
|
||||||
// set pointers into buffer, reset data
|
// set pointers into buffer, reset data
|
||||||
@@ -917,7 +896,7 @@ mjData* mj_makeData(const mjModel* m) {
|
|||||||
// copy mjData, if dest==NULL create new data
|
// copy mjData, if dest==NULL create new data
|
||||||
mjData* mj_copyData(mjData* dest, const mjModel* m, const mjData* src) {
|
mjData* mj_copyData(mjData* dest, const mjModel* m, const mjData* src) {
|
||||||
void* save_buffer;
|
void* save_buffer;
|
||||||
mjtNum* save_stack;
|
void* save_arena;
|
||||||
|
|
||||||
// allocate new data if needed
|
// allocate new data if needed
|
||||||
if (!dest) {
|
if (!dest) {
|
||||||
@@ -939,10 +918,10 @@ mjData* mj_copyData(mjData* dest, const mjModel* m, const mjData* src) {
|
|||||||
|
|
||||||
// save pointers, copy everything, restore pointers
|
// save pointers, copy everything, restore pointers
|
||||||
save_buffer = dest->buffer;
|
save_buffer = dest->buffer;
|
||||||
save_stack = dest->stack;
|
save_arena = dest->arena;
|
||||||
*dest = *src;
|
*dest = *src;
|
||||||
dest->buffer = save_buffer;
|
dest->buffer = save_buffer;
|
||||||
dest->stack = save_stack;
|
dest->arena = save_arena;
|
||||||
mj_setPtrData(m, dest);
|
mj_setPtrData(m, dest);
|
||||||
|
|
||||||
// save plugin_data, since the X macro copying block below will override it
|
// save plugin_data, since the X macro copying block below will override it
|
||||||
@@ -965,6 +944,16 @@ mjData* mj_copyData(mjData* dest, const mjModel* m, const mjData* src) {
|
|||||||
#undef X
|
#undef X
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copy arena memory
|
||||||
|
memcpy(dest->arena, src->arena, src->nstack * sizeof(mjtNum));
|
||||||
|
#define X(type, name, nr, nc) \
|
||||||
|
dest->name = src->name ? (type*)((char*)dest->arena + PTRDIFF(src->name, src->arena)) : NULL;
|
||||||
|
MJDATA_ARENA_POINTERS
|
||||||
|
#undef X
|
||||||
|
|
||||||
|
// restore contact pointer
|
||||||
|
dest->contact = dest->arena;
|
||||||
|
|
||||||
// restore plugin_data
|
// restore plugin_data
|
||||||
if (plugin_data_size) {
|
if (plugin_data_size) {
|
||||||
memcpy(dest->plugin_data, save_plugin_data, plugin_data_size);
|
memcpy(dest->plugin_data, save_plugin_data, plugin_data_size);
|
||||||
@@ -986,25 +975,60 @@ mjData* mj_copyData(mjData* dest, const mjModel* m, const mjData* src) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// allocate memory from the mjData arena
|
||||||
|
void* mj_arenaAlloc(mjData* d, int bytes, int alignment) {
|
||||||
|
int misalignment = d->parena % alignment;
|
||||||
|
int padding = misalignment ? alignment - misalignment : 0;
|
||||||
|
|
||||||
|
// check size
|
||||||
|
size_t bytes_available = (d->nstack - d->pstack) * sizeof(mjtNum);
|
||||||
|
if (d->parena + padding + bytes > bytes_available) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocate, update max, return pointer to buffer
|
||||||
|
void* result = (char*)d->arena + d->parena + padding;
|
||||||
|
d->parena += padding + bytes;
|
||||||
|
d->maxuse_arena = mjMAX(d->maxuse_arena, d->pstack*sizeof(mjtNum) + d->parena);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// allocate size mjtNums on the mjData stack
|
// allocate size mjtNums on the mjData stack
|
||||||
mjtNum* mj_stackAlloc(mjData* d, int size) {
|
mjtNum* mj_stackAlloc(mjData* d, int size) {
|
||||||
mjtNum* result;
|
|
||||||
|
|
||||||
// return NULL if empty
|
// return NULL if empty
|
||||||
if (!size) {
|
if (!size) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check size
|
// check size
|
||||||
if (d->pstack + size > d->nstack) {
|
size_t stack_available_bytes = d->nstack * sizeof(mjtNum) - d->parena;
|
||||||
mju_error("Stack overflow");
|
size_t stack_required_bytes = (d->pstack + size) * sizeof(mjtNum);
|
||||||
|
if (stack_required_bytes > stack_available_bytes) {
|
||||||
|
char err[256];
|
||||||
|
mjSNPRINTF(err, "stack overflow: max = %zu, available = %zu, requested = %zu "
|
||||||
|
"(ne = %d, nf = %d, nefc = %d, ncon = %d)",
|
||||||
|
d->nstack * sizeof(mjtNum), stack_available_bytes, stack_required_bytes,
|
||||||
|
d->ne, d->nf, d->nefc, d->ncon);
|
||||||
|
mju_error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// allocate, update max, return pointer to buffer
|
// allocate at end of arena
|
||||||
result = (mjtNum*)d->stack + d->pstack;
|
char* end_ptr = (char*)d->arena + d->nstack * sizeof(mjtNum);
|
||||||
|
char* result = end_ptr - (d->pstack + size + 1) * sizeof(mjtNum);
|
||||||
|
|
||||||
|
#ifdef ADDRESS_SANITIZER
|
||||||
|
if ((uintptr_t)result % sizeof(mjtNum)) {
|
||||||
|
mju_error("mj_stackAlloc fails to align to sizeof(mjtNum)");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// update max, return pointer to buffer
|
||||||
d->pstack += size;
|
d->pstack += size;
|
||||||
d->maxuse_stack = mjMAX(d->maxuse_stack, d->pstack);
|
d->maxuse_stack = mjMAX(d->maxuse_stack, d->pstack);
|
||||||
return result;
|
d->maxuse_arena = mjMAX(d->maxuse_arena, d->pstack*sizeof(mjtNum) + d->parena);
|
||||||
|
return (mjtNum*)result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1022,8 +1046,16 @@ static void _resetData(const mjModel* m, mjData* d, unsigned char debug_value) {
|
|||||||
// clear stack pointer
|
// clear stack pointer
|
||||||
d->pstack = 0;
|
d->pstack = 0;
|
||||||
|
|
||||||
|
// clear arena pointers
|
||||||
|
d->parena = 0;
|
||||||
|
#define X(type, name, nr, nc) d->name = NULL;
|
||||||
|
MJDATA_ARENA_POINTERS
|
||||||
|
#undef X
|
||||||
|
d->contact = d->arena;
|
||||||
|
|
||||||
// clear memory utilization stats
|
// clear memory utilization stats
|
||||||
d->maxuse_stack = 0;
|
d->maxuse_stack = 0;
|
||||||
|
d->maxuse_arena = 0;
|
||||||
d->maxuse_con = 0;
|
d->maxuse_con = 0;
|
||||||
d->maxuse_efc = 0;
|
d->maxuse_efc = 0;
|
||||||
|
|
||||||
@@ -1162,7 +1194,7 @@ void mj_deleteData(mjData* d) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
mju_free(d->buffer);
|
mju_free(d->buffer);
|
||||||
mju_free(d->stack);
|
mju_free(d->arena);
|
||||||
mju_free(d);
|
mju_free(d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,9 @@ MJAPI void mj_resetDataDebug(const mjModel* m, mjData* d, unsigned char debug_va
|
|||||||
// reset data, set fields from specified keyframe
|
// reset data, set fields from specified keyframe
|
||||||
MJAPI void mj_resetDataKeyframe(const mjModel* m, mjData* d, int key);
|
MJAPI void mj_resetDataKeyframe(const mjModel* m, mjData* d, int key);
|
||||||
|
|
||||||
|
// mjData arena allocate
|
||||||
|
void* mj_arenaAlloc(mjData* d, int bytes, int alignment);
|
||||||
|
|
||||||
// mjData stack allocate
|
// mjData stack allocate
|
||||||
MJAPI mjtNum* mj_stackAlloc(mjData* d, int size);
|
MJAPI mjtNum* mj_stackAlloc(mjData* d, int size);
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
#ifndef MUJOCO_SRC_ENGINE_ENGINE_MACRO_H_
|
#ifndef MUJOCO_SRC_ENGINE_ENGINE_MACRO_H_
|
||||||
#define MUJOCO_SRC_ENGINE_ENGINE_MACRO_H_
|
#define MUJOCO_SRC_ENGINE_ENGINE_MACRO_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "engine/engine_callback.h" // IWYU pragma: export
|
#include "engine/engine_callback.h" // IWYU pragma: export
|
||||||
|
|
||||||
//-------------------------------- utility macros --------------------------------------------------
|
//-------------------------------- utility macros --------------------------------------------------
|
||||||
@@ -40,4 +42,35 @@
|
|||||||
#define TM_START1 mjtNum _tm1 = (mjcb_time ? mjcb_time() : 0);
|
#define TM_START1 mjtNum _tm1 = (mjcb_time ? mjcb_time() : 0);
|
||||||
#define TM_END1(i) {d->timer[i].duration += ((mjcb_time ? mjcb_time() : 0) - _tm1); d->timer[i].number++;}
|
#define TM_END1(i) {d->timer[i].duration += ((mjcb_time ? mjcb_time() : 0) - _tm1); d->timer[i].number++;}
|
||||||
|
|
||||||
|
//-------------------------- sanitizer macros ------------------------------------------------------
|
||||||
|
|
||||||
|
#ifdef ADDRESS_SANITIZER
|
||||||
|
#include <sanitizer/asan_interface.h>
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
#define ASAN_POISON_MEMORY_REGION(addr, size)
|
||||||
|
#define ASAN_UNPOISON_MEMORY_REGION(addr, size)
|
||||||
|
#else
|
||||||
|
#define ASAN_POISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size))
|
||||||
|
#define ASAN_UNPOISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef MEMORY_SANITIZER
|
||||||
|
#include <sanitizer/msan_interface.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef __has_builtin
|
||||||
|
#define __has_builtin(x) 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//-------------------------- pointer arithmetic ----------------------------------------------------
|
||||||
|
|
||||||
|
#define PTRDIFF(x, y) ((char*)(x) - (char*)(y))
|
||||||
|
|
||||||
|
// number of bytes to be skipped to achieve 64-byte alignment
|
||||||
|
static inline unsigned int SKIP(intptr_t offset) {
|
||||||
|
const unsigned int align = 64;
|
||||||
|
// compute skipped bytes
|
||||||
|
return (align - (offset % align)) % align;
|
||||||
|
}
|
||||||
|
|
||||||
#endif // MUJOCO_SRC_ENGINE_ENGINE_MACRO_H_
|
#endif // MUJOCO_SRC_ENGINE_ENGINE_MACRO_H_
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
#define FLOAT_FORMAT "% -9.2g"
|
#define FLOAT_FORMAT "% -9.2g"
|
||||||
#define FLOAT_FORMAT_MAX_LEN 20
|
#define FLOAT_FORMAT_MAX_LEN 20
|
||||||
#define INT_FORMAT " %d"
|
#define INT_FORMAT " %d"
|
||||||
|
#define SIZE_T_FORMAT " %zu"
|
||||||
#define NAME_FORMAT "%-21s"
|
#define NAME_FORMAT "%-21s"
|
||||||
|
|
||||||
|
|
||||||
@@ -46,6 +47,9 @@
|
|||||||
// print 2D array of mjtNum into file
|
// print 2D array of mjtNum into file
|
||||||
static void printArray(const char* str, int nr, int nc, const mjtNum* data, FILE* fp,
|
static void printArray(const char* str, int nr, int nc, const mjtNum* data, FILE* fp,
|
||||||
const char* float_format) {
|
const char* float_format) {
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (nr && nc) {
|
if (nr && nc) {
|
||||||
fprintf(fp, "%s\n ", str);
|
fprintf(fp, "%s\n ", str);
|
||||||
for (int r=0; r<nr; r++) {
|
for (int r=0; r<nr; r++) {
|
||||||
@@ -62,6 +66,9 @@ static void printArray(const char* str, int nr, int nc, const mjtNum* data, FILE
|
|||||||
|
|
||||||
// print 2D array of int into file
|
// print 2D array of int into file
|
||||||
static void printArrayInt(const char* str, int nr, int nc, const int* data, FILE* fp) {
|
static void printArrayInt(const char* str, int nr, int nc, const int* data, FILE* fp) {
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (nr && nc) {
|
if (nr && nc) {
|
||||||
fprintf(fp, "%s\n ", str);
|
fprintf(fp, "%s\n ", str);
|
||||||
for (int r=0; r<nr; r++) {
|
for (int r=0; r<nr; r++) {
|
||||||
@@ -80,6 +87,9 @@ static void printArrayInt(const char* str, int nr, int nc, const int* data, FILE
|
|||||||
static void printSparse(const char* str, const mjtNum* mat, int nr,
|
static void printSparse(const char* str, const mjtNum* mat, int nr,
|
||||||
const int* rownnz, const int* rowadr,
|
const int* rownnz, const int* rowadr,
|
||||||
const int* colind, FILE* fp, const char* float_format) {
|
const int* colind, FILE* fp, const char* float_format) {
|
||||||
|
if (!mat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
fprintf(fp, "%s\n ", str);
|
fprintf(fp, "%s\n ", str);
|
||||||
|
|
||||||
for (int r=0; r<nr; r++) {
|
for (int r=0; r<nr; r++) {
|
||||||
@@ -98,6 +108,9 @@ static void printSparse(const char* str, const mjtNum* mat, int nr,
|
|||||||
// print vector
|
// print vector
|
||||||
static void printVector(const char* str, const mjtNum* data, int n, FILE* fp,
|
static void printVector(const char* str, const mjtNum* data, int n, FILE* fp,
|
||||||
const char* float_format) {
|
const char* float_format) {
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// print str
|
// print str
|
||||||
fprintf(fp, "%s", str);
|
fprintf(fp, "%s", str);
|
||||||
|
|
||||||
@@ -754,7 +767,11 @@ void mj_printFormattedData(const mjModel* m, mjData* d, const char* filename,
|
|||||||
fprintf(fp, "SIZES\n");
|
fprintf(fp, "SIZES\n");
|
||||||
#define X(type, name) \
|
#define X(type, name) \
|
||||||
{ \
|
{ \
|
||||||
const char* format = _Generic(d->name, int : INT_FORMAT, default : NULL); \
|
const char* format = _Generic( \
|
||||||
|
d->name, \
|
||||||
|
int : INT_FORMAT, \
|
||||||
|
size_t : SIZE_T_FORMAT, \
|
||||||
|
default : NULL); \
|
||||||
if (format) { \
|
if (format) { \
|
||||||
fprintf(fp, " "); \
|
fprintf(fp, " "); \
|
||||||
fprintf(fp, NAME_FORMAT, #name); \
|
fprintf(fp, NAME_FORMAT, #name); \
|
||||||
|
|||||||
@@ -956,11 +956,16 @@ const char* mju_warningText(int warning, int info) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case mjWARN_CONTACTFULL:
|
case mjWARN_CONTACTFULL:
|
||||||
mjSNPRINTF(str, "Pre-allocated contact buffer is full. Increase nconmax above %d.", info);
|
mjSNPRINTF(str,
|
||||||
|
"Too many contacts. Either the arena memory is full, or nconmax is specified and is "
|
||||||
|
"exceeded. Increase arena memory allocation, or increase/remove nconmax. "
|
||||||
|
"(ncon = %d)", info);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case mjWARN_CNSTRFULL:
|
case mjWARN_CNSTRFULL:
|
||||||
mjSNPRINTF(str, "Pre-allocated constraint buffer is full. Increase njmax above %d.", info);
|
mjSNPRINTF(str,
|
||||||
|
"Insufficient arena memory for the number of constraints generated. "
|
||||||
|
"Increase arena memory allocation above %d bytes.", info);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case mjWARN_VGEOMFULL:
|
case mjWARN_VGEOMFULL:
|
||||||
|
|||||||
+29
-19
@@ -118,6 +118,7 @@ mjCModel::mjCModel() {
|
|||||||
modelname = "MuJoCo Model";
|
modelname = "MuJoCo Model";
|
||||||
mj_defaultOption(&option);
|
mj_defaultOption(&option);
|
||||||
mj_defaultVisual(&visual);
|
mj_defaultVisual(&visual);
|
||||||
|
memory = -1;
|
||||||
nemax = 0;
|
nemax = 0;
|
||||||
njmax = -1;
|
njmax = -1;
|
||||||
nconmax = -1;
|
nconmax = -1;
|
||||||
@@ -274,6 +275,8 @@ void mjCModel::Clear(void) {
|
|||||||
ntupledata = 0;
|
ntupledata = 0;
|
||||||
npluginattr = 0;
|
npluginattr = 0;
|
||||||
nnames = 0;
|
nnames = 0;
|
||||||
|
memory = -1;
|
||||||
|
nstack = -1;
|
||||||
nemax = 0;
|
nemax = 0;
|
||||||
nM = 0;
|
nM = 0;
|
||||||
nD = 0;
|
nD = 0;
|
||||||
@@ -1012,16 +1015,6 @@ void mjCModel::SetSizes(void) {
|
|||||||
} else {
|
} else {
|
||||||
nemax += 1;
|
nemax += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// nconmax
|
|
||||||
if (nconmax<0) {
|
|
||||||
nconmax = 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
// njmax
|
|
||||||
if (njmax<0) {
|
|
||||||
njmax = 500;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1604,7 +1597,6 @@ void mjCModel::CopyObjects(mjModel* m) {
|
|||||||
m->nemax = nemax;
|
m->nemax = nemax;
|
||||||
m->njmax = njmax;
|
m->njmax = njmax;
|
||||||
m->nconmax = nconmax;
|
m->nconmax = nconmax;
|
||||||
m->nstack = nstack;
|
|
||||||
m->nsensordata = nsensordata;
|
m->nsensordata = nsensordata;
|
||||||
m->nuserdata = nuserdata;
|
m->nuserdata = nuserdata;
|
||||||
|
|
||||||
@@ -2661,15 +2653,33 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
|
|||||||
mj_setTotalmass(m, settotalmass);
|
mj_setTotalmass(m, settotalmass);
|
||||||
}
|
}
|
||||||
|
|
||||||
// set stack size: user-specified or conservative heuristic
|
// set arena size into m->nstack
|
||||||
if (nstack>0) {
|
if (memory != -1) {
|
||||||
m->nstack = nstack;
|
// memory size is user-specified in bytes, round down to nearest sizeof(mjtNum)
|
||||||
|
m->nstack = memory / sizeof(mjtNum);
|
||||||
} else {
|
} else {
|
||||||
m->nstack = mjMAX(
|
const int nconmax = m->nconmax == -1 ? 100 : m->nconmax;
|
||||||
1000,
|
const int njmax = m->njmax == -1 ? 500 : m->njmax;
|
||||||
5*(m->njmax + m->neq + m->nv)*(m->njmax + m->neq + m->nv) +
|
if (nstack != -1) {
|
||||||
20*(m->nq + m->nv + m->nu + m->na + m->nbody + m->njnt +
|
// (legacy) stack size is user-specified, already as multiple of sizeof(mjtNum)
|
||||||
m->ngeom + m->nsite + m->neq + m->ntendon + m->nwrap));
|
m->nstack = nstack;
|
||||||
|
} else {
|
||||||
|
// use a conservative heuristic if neither memory nor nstack is specified in XML
|
||||||
|
m->nstack = mjMAX(
|
||||||
|
1000,
|
||||||
|
5*(njmax + m->neq + m->nv)*(njmax + m->neq + m->nv) +
|
||||||
|
20*(m->nq + m->nv + m->nu + m->na + m->nbody + m->njnt +
|
||||||
|
m->ngeom + m->nsite + m->neq + m->ntendon + m->nwrap));
|
||||||
|
}
|
||||||
|
|
||||||
|
// add an arena space equal to memory footprint prior to the introduction of the arena
|
||||||
|
const std::size_t arena_bytes = (
|
||||||
|
nconmax * sizeof(mjContact) +
|
||||||
|
njmax * (8 * sizeof(int) + 14 * sizeof(mjtNum)) +
|
||||||
|
m->nv * (3 * sizeof(int)) +
|
||||||
|
njmax * m->nv * (2 * sizeof(int) + 2 * sizeof(mjtNum)) +
|
||||||
|
njmax * njmax * (sizeof(int) + sizeof(mjtNum)));
|
||||||
|
m->nstack += (arena_bytes / sizeof(mjtNum)) + (arena_bytes % sizeof(mjtNum) ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// create data
|
// create data
|
||||||
|
|||||||
@@ -148,10 +148,11 @@ class mjCModel {
|
|||||||
std::string modelname; // model name
|
std::string modelname; // model name
|
||||||
mjOption option; // options
|
mjOption option; // options
|
||||||
mjVisual visual; // visual options
|
mjVisual visual; // visual options
|
||||||
|
std::size_t memory; // size of arena+stack memory in bytes
|
||||||
int nemax; // max number of equality constraints
|
int nemax; // max number of equality constraints
|
||||||
int njmax; // max number of constraints (Jacobian rows)
|
int njmax; // max number of constraints (Jacobian rows)
|
||||||
int nconmax; // max number of detected contacts (mjContact array size)
|
int nconmax; // max number of detected contacts (mjContact array size)
|
||||||
int nstack; // number of fields in mjData stack
|
int nstack; // (deprecated) number of fields in mjData stack
|
||||||
int nuserdata; // number extra fields in mjData
|
int nuserdata; // number extra fields in mjData
|
||||||
int nuser_body; // number of mjtNums in body_user
|
int nuser_body; // number of mjtNums in body_user
|
||||||
int nuser_jnt; // number of mjtNums in jnt_user
|
int nuser_jnt; // number of mjtNums in jnt_user
|
||||||
|
|||||||
@@ -15,11 +15,15 @@
|
|||||||
#include "xml/xml_native_reader.h"
|
#include "xml/xml_native_reader.h"
|
||||||
|
|
||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <limits>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
@@ -103,7 +107,7 @@ static const char* MJCF[nMJCF][mjXATTRNUM] = {
|
|||||||
"override", "energy", "fwdinv", "sensornoise", "multiccd"},
|
"override", "energy", "fwdinv", "sensornoise", "multiccd"},
|
||||||
{">"},
|
{">"},
|
||||||
|
|
||||||
{"size", "*", "13", "njmax", "nconmax", "nstack", "nuserdata", "nkey",
|
{"size", "*", "14", "memory", "njmax", "nconmax", "nstack", "nuserdata", "nkey",
|
||||||
"nuser_body", "nuser_jnt", "nuser_geom", "nuser_site", "nuser_cam",
|
"nuser_body", "nuser_jnt", "nuser_geom", "nuser_site", "nuser_cam",
|
||||||
"nuser_tendon", "nuser_actuator", "nuser_sensor"},
|
"nuser_tendon", "nuser_actuator", "nuser_sensor"},
|
||||||
|
|
||||||
@@ -978,13 +982,134 @@ void mjXReader::Option(XMLElement* section, mjOption* opt) {
|
|||||||
|
|
||||||
// size section parser
|
// size section parser
|
||||||
void mjXReader::Size(XMLElement* section, mjCModel* mod) {
|
void mjXReader::Size(XMLElement* section, mjCModel* mod) {
|
||||||
|
// read memory bytes
|
||||||
|
{
|
||||||
|
constexpr char err_msg[] =
|
||||||
|
"unsigned integer with an optional suffix {K,M,G,T,P,E} is expected in "
|
||||||
|
"attribute 'memory' (or the size specified is too big)";
|
||||||
|
|
||||||
|
auto memory = [&]() -> std::optional<std::size_t> {
|
||||||
|
const char* pstr = section->Attribute("memory");
|
||||||
|
if (!pstr) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// trim entire string
|
||||||
|
std::string trimmed;
|
||||||
|
{
|
||||||
|
std::istringstream strm((std::string(pstr)));
|
||||||
|
strm >> trimmed;
|
||||||
|
std::string trailing;
|
||||||
|
strm >> trailing;
|
||||||
|
if (!trailing.empty() || !strm.eof()) {
|
||||||
|
throw mjXError(section, err_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow explicit specification of the default "-1" value
|
||||||
|
if (trimmed == "-1") {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::istringstream strm(trimmed);
|
||||||
|
|
||||||
|
// check that the number is not negative
|
||||||
|
if (strm.peek() == '-') {
|
||||||
|
throw mjXError(section, err_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t base_size;
|
||||||
|
strm >> base_size;
|
||||||
|
if (strm.fail()) {
|
||||||
|
// either not an integer or the number without the suffix is already bigger than size_t
|
||||||
|
throw mjXError(section, err_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the multiplier suffix
|
||||||
|
int multiplier_bit = 0;
|
||||||
|
if (!strm.eof()) {
|
||||||
|
char suffix = strm.get();
|
||||||
|
if (suffix == 'K' || suffix == 'k') {
|
||||||
|
multiplier_bit = 10;
|
||||||
|
} else if (suffix == 'M' || suffix == 'm') {
|
||||||
|
multiplier_bit = 20;
|
||||||
|
} else if (suffix == 'G' || suffix == 'g') {
|
||||||
|
multiplier_bit = 30;
|
||||||
|
} else if (suffix == 'T' || suffix == 't') {
|
||||||
|
multiplier_bit = 40;
|
||||||
|
} else if (suffix == 'P' || suffix == 'p') {
|
||||||
|
multiplier_bit = 50;
|
||||||
|
} else if (suffix == 'E' || suffix == 'e') {
|
||||||
|
multiplier_bit = 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for invalid suffix, or suffix longer than one character
|
||||||
|
strm.get();
|
||||||
|
if (!multiplier_bit || !strm.eof()) {
|
||||||
|
throw mjXError(section, err_msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that the specified suffix isn't bigger than size_t
|
||||||
|
if (multiplier_bit + 1 > std::numeric_limits<std::size_t>::digits) {
|
||||||
|
throw mjXError(section, err_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that the suffix won't take the total size beyond size_t
|
||||||
|
const std::size_t max_base_size =
|
||||||
|
(std::numeric_limits<std::size_t>::max() << multiplier_bit) >> multiplier_bit;
|
||||||
|
if (base_size > max_base_size) {
|
||||||
|
throw mjXError(section, err_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t total_size = base_size << multiplier_bit;
|
||||||
|
return total_size;
|
||||||
|
}();
|
||||||
|
|
||||||
|
if (memory.has_value()) {
|
||||||
|
if (*memory / sizeof(mjtNum) > std::numeric_limits<int>::max()) {
|
||||||
|
throw mjXError(section, err_msg);
|
||||||
|
}
|
||||||
|
mod->memory = static_cast<int>(*memory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// read sizes
|
// read sizes
|
||||||
ReadAttrInt(section, "njmax", &mod->njmax);
|
|
||||||
ReadAttrInt(section, "nconmax", &mod->nconmax);
|
|
||||||
ReadAttrInt(section, "nstack", &mod->nstack);
|
|
||||||
ReadAttrInt(section, "nuserdata", &mod->nuserdata);
|
ReadAttrInt(section, "nuserdata", &mod->nuserdata);
|
||||||
ReadAttrInt(section, "nkey", &mod->nkey);
|
ReadAttrInt(section, "nkey", &mod->nkey);
|
||||||
|
|
||||||
|
ReadAttrInt(section, "nconmax", &mod->nconmax);
|
||||||
|
if (mod->nconmax < -1) throw mjXError(section, "nconmax must be >= -1");
|
||||||
|
|
||||||
|
{
|
||||||
|
int nstack = -1;
|
||||||
|
const bool has_nstack = ReadAttrInt(section, "nstack", &nstack);
|
||||||
|
if (has_nstack) {
|
||||||
|
if (mod->nstack < -1) {
|
||||||
|
throw mjXError(section, "nstack must be >= -1");
|
||||||
|
}
|
||||||
|
if (mod->memory != -1 && nstack != -1) {
|
||||||
|
throw mjXError(section,
|
||||||
|
"either 'memory' and 'nstack' attribute can be specified, not both");
|
||||||
|
}
|
||||||
|
mod->nstack = nstack;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
int njmax = -1;
|
||||||
|
const bool has_njmax = ReadAttrInt(section, "njmax", &njmax);
|
||||||
|
if (has_njmax) {
|
||||||
|
if (mod->njmax < -1) {
|
||||||
|
throw mjXError(section, "njmax must be >= -1");
|
||||||
|
}
|
||||||
|
if (mod->memory != -1 && njmax != -1) {
|
||||||
|
throw mjXError(section,
|
||||||
|
"either 'memory' and 'njmax' attribute can be specified, not both");
|
||||||
|
}
|
||||||
|
mod->njmax = njmax;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ReadAttrInt(section, "nuser_body", &mod->nuser_body);
|
ReadAttrInt(section, "nuser_body", &mod->nuser_body);
|
||||||
if (mod->nuser_body < -1) throw mjXError(section, "nuser_body must be >= -1");
|
if (mod->nuser_body < -1) throw mjXError(section, "nuser_body must be >= -1");
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <mujoco/mjmodel.h>
|
#include <mujoco/mjmodel.h>
|
||||||
#include <mujoco/mjplugin.h>
|
#include <mujoco/mjplugin.h>
|
||||||
@@ -818,6 +820,32 @@ void mjXWriter::Option(XMLElement* root) {
|
|||||||
void mjXWriter::Size(XMLElement* root) {
|
void mjXWriter::Size(XMLElement* root) {
|
||||||
XMLElement* section = InsertEnd(root, "size");
|
XMLElement* section = InsertEnd(root, "size");
|
||||||
|
|
||||||
|
// write memory
|
||||||
|
if (model->memory != -1) {
|
||||||
|
const std::size_t memory = static_cast<std::size_t>(model->memory);
|
||||||
|
const std::vector<std::pair<int, char>> kSuffix = {
|
||||||
|
{60, 'E'}, {50, 'P'}, {40, 'T'}, {30, 'G'}, {20, 'M'}, {10, 'K'},
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostringstream strm;
|
||||||
|
|
||||||
|
// check for divisibility by each suffixed size
|
||||||
|
for (const auto& [multiplier_bit, suffix_char] : kSuffix) {
|
||||||
|
const std::size_t multiplier = static_cast<std::size_t>(1) << multiplier_bit;
|
||||||
|
if (memory >= multiplier && !(memory & (multiplier - 1))) {
|
||||||
|
strm << (memory >> multiplier_bit) << suffix_char;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// doesn't match any suffix, just write the number out as-is
|
||||||
|
if (!strm.tellp()) {
|
||||||
|
strm << memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteAttrTxt(section, "memory", strm.str());
|
||||||
|
}
|
||||||
|
|
||||||
// write sizes
|
// write sizes
|
||||||
WriteAttrInt(section, "njmax", model->njmax, -1);
|
WriteAttrInt(section, "njmax", model->njmax, -1);
|
||||||
WriteAttrInt(section, "nconmax", model->nconmax, -1);
|
WriteAttrInt(section, "nconmax", model->nconmax, -1);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace mujoco {
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using ::testing::ElementsAre;
|
using ::testing::ElementsAre;
|
||||||
using UserDataTest = MujocoTest;
|
using XMLReaderTest = MujocoTest;
|
||||||
|
|
||||||
static std::vector<mjtNum> GetRow(const mjtNum* array, int ncolumn, int row) {
|
static std::vector<mjtNum> GetRow(const mjtNum* array, int ncolumn, int row) {
|
||||||
return std::vector<mjtNum>(array + ncolumn * row,
|
return std::vector<mjtNum>(array + ncolumn * row,
|
||||||
@@ -36,7 +36,7 @@ static std::vector<mjtNum> GetRow(const mjtNum* array, int ncolumn, int row) {
|
|||||||
|
|
||||||
// ------------- test automatic inference of nuser_xxx -------------------------
|
// ------------- test automatic inference of nuser_xxx -------------------------
|
||||||
|
|
||||||
TEST_F(UserDataTest, AutoNUserBody) {
|
TEST_F(XMLReaderTest, AutoNUserBody) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
@@ -52,7 +52,7 @@ TEST_F(UserDataTest, AutoNUserBody) {
|
|||||||
mj_deleteModel(m);
|
mj_deleteModel(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, AutoNUserJoint) {
|
TEST_F(XMLReaderTest, AutoNUserJoint) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
@@ -71,7 +71,7 @@ TEST_F(UserDataTest, AutoNUserJoint) {
|
|||||||
mj_deleteModel(m);
|
mj_deleteModel(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, AutoNUserGeom) {
|
TEST_F(XMLReaderTest, AutoNUserGeom) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
@@ -87,7 +87,7 @@ TEST_F(UserDataTest, AutoNUserGeom) {
|
|||||||
mj_deleteModel(m);
|
mj_deleteModel(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, AutoNUserSite) {
|
TEST_F(XMLReaderTest, AutoNUserSite) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
@@ -103,7 +103,7 @@ TEST_F(UserDataTest, AutoNUserSite) {
|
|||||||
mj_deleteModel(m);
|
mj_deleteModel(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, AutoNUserCamera) {
|
TEST_F(XMLReaderTest, AutoNUserCamera) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
@@ -119,7 +119,7 @@ TEST_F(UserDataTest, AutoNUserCamera) {
|
|||||||
mj_deleteModel(m);
|
mj_deleteModel(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, AutoNUserTendon) {
|
TEST_F(XMLReaderTest, AutoNUserTendon) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
@@ -145,7 +145,7 @@ TEST_F(UserDataTest, AutoNUserTendon) {
|
|||||||
mj_deleteModel(m);
|
mj_deleteModel(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, AutoNUserActuator) {
|
TEST_F(XMLReaderTest, AutoNUserActuator) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
@@ -169,7 +169,7 @@ TEST_F(UserDataTest, AutoNUserActuator) {
|
|||||||
mj_deleteModel(m);
|
mj_deleteModel(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, AutoNUserSensor) {
|
TEST_F(XMLReaderTest, AutoNUserSensor) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
|
|||||||
@@ -459,9 +459,9 @@ TEST_F(ActRangeTest, ActRangeDefaultsPropagate) {
|
|||||||
|
|
||||||
// ------------- test nuser_xxx fields -----------------------------------------
|
// ------------- test nuser_xxx fields -----------------------------------------
|
||||||
|
|
||||||
using UserDataTest = MujocoTest;
|
using XMLReaderTest = MujocoTest;
|
||||||
|
|
||||||
TEST_F(UserDataTest, NBodyTooSmall) {
|
TEST_F(XMLReaderTest, NBodyTooSmall) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<size nuser_body="2"/>
|
<size nuser_body="2"/>
|
||||||
@@ -476,7 +476,7 @@ TEST_F(UserDataTest, NBodyTooSmall) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("nuser_body"));
|
EXPECT_THAT(error.data(), HasSubstr("nuser_body"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, NJointTooSmall) {
|
TEST_F(XMLReaderTest, NJointTooSmall) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<size nuser_jnt="2"/>
|
<size nuser_jnt="2"/>
|
||||||
@@ -494,7 +494,7 @@ TEST_F(UserDataTest, NJointTooSmall) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("nuser_jnt"));
|
EXPECT_THAT(error.data(), HasSubstr("nuser_jnt"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, NGeomTooSmall) {
|
TEST_F(XMLReaderTest, NGeomTooSmall) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<size nuser_geom="2"/>
|
<size nuser_geom="2"/>
|
||||||
@@ -509,7 +509,7 @@ TEST_F(UserDataTest, NGeomTooSmall) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("nuser_geom"));
|
EXPECT_THAT(error.data(), HasSubstr("nuser_geom"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, NSiteTooSmall) {
|
TEST_F(XMLReaderTest, NSiteTooSmall) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<size nuser_site="2"/>
|
<size nuser_site="2"/>
|
||||||
@@ -524,7 +524,7 @@ TEST_F(UserDataTest, NSiteTooSmall) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("nuser_site"));
|
EXPECT_THAT(error.data(), HasSubstr("nuser_site"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, NCameraTooSmall) {
|
TEST_F(XMLReaderTest, NCameraTooSmall) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<size nuser_cam="2"/>
|
<size nuser_cam="2"/>
|
||||||
@@ -539,7 +539,7 @@ TEST_F(UserDataTest, NCameraTooSmall) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("nuser_cam"));
|
EXPECT_THAT(error.data(), HasSubstr("nuser_cam"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, NTendonTooSmall) {
|
TEST_F(XMLReaderTest, NTendonTooSmall) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<size nuser_tendon="2"/>
|
<size nuser_tendon="2"/>
|
||||||
@@ -561,7 +561,7 @@ TEST_F(UserDataTest, NTendonTooSmall) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("nuser_tendon"));
|
EXPECT_THAT(error.data(), HasSubstr("nuser_tendon"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, NActuatorTooSmall) {
|
TEST_F(XMLReaderTest, NActuatorTooSmall) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<size nuser_actuator="2"/>
|
<size nuser_actuator="2"/>
|
||||||
@@ -582,7 +582,7 @@ TEST_F(UserDataTest, NActuatorTooSmall) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("nuser_actuator"));
|
EXPECT_THAT(error.data(), HasSubstr("nuser_actuator"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, NSensorTooSmall) {
|
TEST_F(XMLReaderTest, NSensorTooSmall) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<size nuser_sensor="2"/>
|
<size nuser_sensor="2"/>
|
||||||
|
|||||||
@@ -37,9 +37,108 @@ using ::testing::IsNan;
|
|||||||
using ::testing::IsNull;
|
using ::testing::IsNull;
|
||||||
using ::testing::NotNull;
|
using ::testing::NotNull;
|
||||||
|
|
||||||
using UserDataTest = MujocoTest;
|
using XMLReaderTest = MujocoTest;
|
||||||
|
|
||||||
TEST_F(UserDataTest, InvalidNUserBody) {
|
TEST_F(XMLReaderTest, MemorySize) {
|
||||||
|
std::array<char, 1024> error;
|
||||||
|
{
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<size memory="128"/>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
|
EXPECT_EQ(model->nstack, 128 / sizeof(mjtNum));
|
||||||
|
mj_deleteModel(model);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<size memory="1K "/>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
|
EXPECT_EQ(model->nstack, 1024 / sizeof(mjtNum));
|
||||||
|
mj_deleteModel(model);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<size memory=" 10K"/>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
|
EXPECT_EQ(model->nstack, 10240 / sizeof(mjtNum));
|
||||||
|
mj_deleteModel(model);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<size memory=" 4M "/>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
|
EXPECT_EQ(model->nstack, 4*1024*1024 / sizeof(mjtNum));
|
||||||
|
mj_deleteModel(model);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<size memory="1G"/>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
|
EXPECT_EQ(model->nstack, 1024*1024*1024 / sizeof(mjtNum));
|
||||||
|
mj_deleteModel(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(XMLReaderTest, InvalidMemorySize) {
|
||||||
|
std::array<char, 1024> error;
|
||||||
|
{
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<size memory="-3"/>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||||
|
ASSERT_THAT(model, IsNull());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<size memory="1 M"/>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||||
|
ASSERT_THAT(model, IsNull());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<size memory="2X"/>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||||
|
ASSERT_THAT(model, IsNull());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<size memory="K"/>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||||
|
ASSERT_THAT(model, IsNull());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(XMLReaderTest, InvalidNUserBody) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<size nuser_body="-2"/>
|
<size nuser_body="-2"/>
|
||||||
@@ -51,7 +150,7 @@ TEST_F(UserDataTest, InvalidNUserBody) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("nuser_body"));
|
EXPECT_THAT(error.data(), HasSubstr("nuser_body"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, InvalidNUserJoint) {
|
TEST_F(XMLReaderTest, InvalidNUserJoint) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<size nuser_jnt="-2"/>
|
<size nuser_jnt="-2"/>
|
||||||
@@ -63,7 +162,7 @@ TEST_F(UserDataTest, InvalidNUserJoint) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("nuser_jnt"));
|
EXPECT_THAT(error.data(), HasSubstr("nuser_jnt"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, InvalidNUserGeom) {
|
TEST_F(XMLReaderTest, InvalidNUserGeom) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<size nuser_geom="-2"/>
|
<size nuser_geom="-2"/>
|
||||||
@@ -75,7 +174,7 @@ TEST_F(UserDataTest, InvalidNUserGeom) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("nuser_geom"));
|
EXPECT_THAT(error.data(), HasSubstr("nuser_geom"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, InvalidNUserSite) {
|
TEST_F(XMLReaderTest, InvalidNUserSite) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<size nuser_site="-2"/>
|
<size nuser_site="-2"/>
|
||||||
@@ -87,7 +186,7 @@ TEST_F(UserDataTest, InvalidNUserSite) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("nuser_site"));
|
EXPECT_THAT(error.data(), HasSubstr("nuser_site"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, InvalidNUserCamera) {
|
TEST_F(XMLReaderTest, InvalidNUserCamera) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<size nuser_cam="-2"/>
|
<size nuser_cam="-2"/>
|
||||||
@@ -99,7 +198,7 @@ TEST_F(UserDataTest, InvalidNUserCamera) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("nuser_cam"));
|
EXPECT_THAT(error.data(), HasSubstr("nuser_cam"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, InvalidNUserTendon) {
|
TEST_F(XMLReaderTest, InvalidNUserTendon) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<size nuser_tendon="-2"/>
|
<size nuser_tendon="-2"/>
|
||||||
@@ -111,7 +210,7 @@ TEST_F(UserDataTest, InvalidNUserTendon) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("nuser_tendon"));
|
EXPECT_THAT(error.data(), HasSubstr("nuser_tendon"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, InvalidNUserActuator) {
|
TEST_F(XMLReaderTest, InvalidNUserActuator) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<size nuser_actuator="-2"/>
|
<size nuser_actuator="-2"/>
|
||||||
@@ -123,7 +222,7 @@ TEST_F(UserDataTest, InvalidNUserActuator) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("nuser_actuator"));
|
EXPECT_THAT(error.data(), HasSubstr("nuser_actuator"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, InvalidNUserSensor) {
|
TEST_F(XMLReaderTest, InvalidNUserSensor) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<size nuser_sensor="-2"/>
|
<size nuser_sensor="-2"/>
|
||||||
@@ -135,7 +234,7 @@ TEST_F(UserDataTest, InvalidNUserSensor) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("nuser_sensor"));
|
EXPECT_THAT(error.data(), HasSubstr("nuser_sensor"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, CanParseInf) {
|
TEST_F(XMLReaderTest, CanParseInf) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
@@ -158,7 +257,7 @@ TEST_F(UserDataTest, CanParseInf) {
|
|||||||
mj_deleteModel(model);
|
mj_deleteModel(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, CanParseNanAndRaisesWarning) {
|
TEST_F(XMLReaderTest, CanParseNanAndRaisesWarning) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
@@ -187,7 +286,7 @@ TEST_F(UserDataTest, CanParseNanAndRaisesWarning) {
|
|||||||
mj_deleteModel(model);
|
mj_deleteModel(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, InvalidArrayElement) {
|
TEST_F(XMLReaderTest, InvalidArrayElement) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
@@ -203,7 +302,7 @@ TEST_F(UserDataTest, InvalidArrayElement) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("problem reading attribute 'axisangle'"));
|
EXPECT_THAT(error.data(), HasSubstr("problem reading attribute 'axisangle'"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, InvalidArrayLength) {
|
TEST_F(XMLReaderTest, InvalidArrayLength) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
@@ -219,7 +318,7 @@ TEST_F(UserDataTest, InvalidArrayLength) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("has too much data"));
|
EXPECT_THAT(error.data(), HasSubstr("has too much data"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, InvalidNumber) {
|
TEST_F(XMLReaderTest, InvalidNumber) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
@@ -235,7 +334,7 @@ TEST_F(UserDataTest, InvalidNumber) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("problem reading attribute"));
|
EXPECT_THAT(error.data(), HasSubstr("problem reading attribute"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, AllowsSpaces) {
|
TEST_F(XMLReaderTest, AllowsSpaces) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
@@ -251,7 +350,7 @@ TEST_F(UserDataTest, AllowsSpaces) {
|
|||||||
mj_deleteModel(model);
|
mj_deleteModel(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, InvalidDoubleOrientation) {
|
TEST_F(XMLReaderTest, InvalidDoubleOrientation) {
|
||||||
std::string prefix = "<mujoco><worldbody><";
|
std::string prefix = "<mujoco><worldbody><";
|
||||||
std::string suffix = "/></worldbody></mujoco>";
|
std::string suffix = "/></worldbody></mujoco>";
|
||||||
std::vector<std::string> orientations = {
|
std::vector<std::string> orientations = {
|
||||||
@@ -270,15 +369,18 @@ TEST_F(UserDataTest, InvalidDoubleOrientation) {
|
|||||||
if (orient1 == orient2) continue;
|
if (orient1 == orient2) continue;
|
||||||
std::string xml = prefix + field + orient1 + orient2 + suffix;
|
std::string xml = prefix + field + orient1 + orient2 + suffix;
|
||||||
std::array<char, 1024> error;
|
std::array<char, 1024> error;
|
||||||
mjModel* model = LoadModelFromString(xml.c_str(), error.data(), error.size());
|
mjModel* model =
|
||||||
|
LoadModelFromString(xml.c_str(), error.data(), error.size());
|
||||||
ASSERT_THAT(model, IsNull());
|
ASSERT_THAT(model, IsNull());
|
||||||
EXPECT_THAT(error.data(), HasSubstr("multiple orientation specifiers for the same field"));
|
EXPECT_THAT(
|
||||||
|
error.data(),
|
||||||
|
HasSubstr("multiple orientation specifiers for the same field"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, InvalidInertialOrientation) {
|
TEST_F(XMLReaderTest, InvalidInertialOrientation) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
@@ -291,10 +393,11 @@ TEST_F(UserDataTest, InvalidInertialOrientation) {
|
|||||||
std::array<char, 1024> error;
|
std::array<char, 1024> error;
|
||||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||||
ASSERT_THAT(model, IsNull());
|
ASSERT_THAT(model, IsNull());
|
||||||
EXPECT_THAT(error.data(), HasSubstr("multiple orientation specifiers for the same field"));
|
EXPECT_THAT(error.data(),
|
||||||
|
HasSubstr("multiple orientation specifiers for the same field"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, ReadShellParameter) {
|
TEST_F(XMLReaderTest, ReadShellParameter) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<asset>
|
<asset>
|
||||||
@@ -313,7 +416,7 @@ TEST_F(UserDataTest, ReadShellParameter) {
|
|||||||
mj_deleteModel(model);
|
mj_deleteModel(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, ReadsDamper) {
|
TEST_F(XMLReaderTest, ReadsDamper) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<compiler autolimits="true"/>
|
<compiler autolimits="true"/>
|
||||||
@@ -339,7 +442,7 @@ TEST_F(UserDataTest, ReadsDamper) {
|
|||||||
mj_deleteModel(model);
|
mj_deleteModel(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, RequiresPoisitiveDamping) {
|
TEST_F(XMLReaderTest, RequiresPoisitiveDamping) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
@@ -356,10 +459,11 @@ TEST_F(UserDataTest, RequiresPoisitiveDamping) {
|
|||||||
std::array<char, 1024> error;
|
std::array<char, 1024> error;
|
||||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||||
ASSERT_THAT(model, IsNull());
|
ASSERT_THAT(model, IsNull());
|
||||||
EXPECT_THAT(error.data(), HasSubstr("damping coefficient cannot be negative"));
|
EXPECT_THAT(error.data(),
|
||||||
|
HasSubstr("damping coefficient cannot be negative"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, RequiresControlRange) {
|
TEST_F(XMLReaderTest, RequiresControlRange) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
@@ -379,7 +483,7 @@ TEST_F(UserDataTest, RequiresControlRange) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("invalid control range"));
|
EXPECT_THAT(error.data(), HasSubstr("invalid control range"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, PositiveControlRange) {
|
TEST_F(XMLReaderTest, PositiveControlRange) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
@@ -399,7 +503,7 @@ TEST_F(UserDataTest, PositiveControlRange) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("control range cannot be negative"));
|
EXPECT_THAT(error.data(), HasSubstr("control range cannot be negative"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, ReadsSkinGroups) {
|
TEST_F(XMLReaderTest, ReadsSkinGroups) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
@@ -430,7 +534,7 @@ TEST_F(UserDataTest, ReadsSkinGroups) {
|
|||||||
mj_deleteModel(model);
|
mj_deleteModel(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, InvalidSkinGroup) {
|
TEST_F(XMLReaderTest, InvalidSkinGroup) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
@@ -446,7 +550,9 @@ TEST_F(UserDataTest, InvalidSkinGroup) {
|
|||||||
std::array<char, 1024> error;
|
std::array<char, 1024> error;
|
||||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||||
ASSERT_THAT(model, IsNull());
|
ASSERT_THAT(model, IsNull());
|
||||||
EXPECT_THAT(error.data(), HasSubstr("skin group must be between 0 and 5\nElement 'skin', line 7"));
|
EXPECT_THAT(
|
||||||
|
error.data(),
|
||||||
|
HasSubstr("skin group must be between 0 and 5\nElement 'skin', line 7"));
|
||||||
mj_deleteModel(model);
|
mj_deleteModel(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,7 +635,8 @@ TEST_F(ActuatorTest, IncompleteActlimited) {
|
|||||||
std::array<char, 1024> error;
|
std::array<char, 1024> error;
|
||||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||||
ASSERT_THAT(model, IsNull());
|
ASSERT_THAT(model, IsNull());
|
||||||
EXPECT_THAT(error.data(), HasSubstr("attribute 'actrange' does not have enough data"));
|
EXPECT_THAT(error.data(),
|
||||||
|
HasSubstr("attribute 'actrange' does not have enough data"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ActuatorTest, ReadsByte) {
|
TEST_F(ActuatorTest, ReadsByte) {
|
||||||
@@ -762,7 +869,7 @@ TEST_F(ActuatorParseTest, DampersDontRequireRange) {
|
|||||||
mj_deleteModel(model);
|
mj_deleteModel(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, ZnearZeroNotAllowed) {
|
TEST_F(XMLReaderTest, ZnearZeroNotAllowed) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<visual>
|
<visual>
|
||||||
@@ -776,7 +883,7 @@ TEST_F(UserDataTest, ZnearZeroNotAllowed) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("znear must be strictly positive"));
|
EXPECT_THAT(error.data(), HasSubstr("znear must be strictly positive"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, ZnearNegativeNotAllowed) {
|
TEST_F(XMLReaderTest, ZnearNegativeNotAllowed) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<visual>
|
<visual>
|
||||||
@@ -790,7 +897,7 @@ TEST_F(UserDataTest, ZnearNegativeNotAllowed) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("znear must be strictly positive"));
|
EXPECT_THAT(error.data(), HasSubstr("znear must be strictly positive"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, ExtentZeroNotAllowed) {
|
TEST_F(XMLReaderTest, ExtentZeroNotAllowed) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<statistic extent="0"/>
|
<statistic extent="0"/>
|
||||||
@@ -802,7 +909,7 @@ TEST_F(UserDataTest, ExtentZeroNotAllowed) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("extent must be strictly positive"));
|
EXPECT_THAT(error.data(), HasSubstr("extent must be strictly positive"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UserDataTest, ExtentNegativeNotAllowed) {
|
TEST_F(XMLReaderTest, ExtentNegativeNotAllowed) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<statistic extent="-1"/>
|
<statistic extent="-1"/>
|
||||||
|
|||||||
@@ -52,6 +52,69 @@ using ::testing::NotNull;
|
|||||||
|
|
||||||
using XMLWriterTest = MujocoTest;
|
using XMLWriterTest = MujocoTest;
|
||||||
|
|
||||||
|
TEST_F(XMLWriterTest, SavesMemory) {
|
||||||
|
{
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<size memory=" 1023 "/>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
|
EXPECT_THAT(saved_xml, HasSubstr("memory=\"1023\""));
|
||||||
|
mj_deleteModel(model);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<size memory="1024"/>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
|
EXPECT_THAT(saved_xml, HasSubstr("memory=\"1K\""));
|
||||||
|
mj_deleteModel(model);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<size memory="4096"/>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
|
EXPECT_THAT(saved_xml, HasSubstr("memory=\"4K\""));
|
||||||
|
mj_deleteModel(model);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<size memory="1048576"/>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
|
EXPECT_THAT(saved_xml, HasSubstr("memory=\"1M\""));
|
||||||
|
mj_deleteModel(model);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<size memory="1047552"/>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
|
EXPECT_THAT(saved_xml, HasSubstr("memory=\"1023K\""));
|
||||||
|
mj_deleteModel(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(XMLWriterTest, SavesDisableSensor) {
|
TEST_F(XMLWriterTest, SavesDisableSensor) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
@@ -118,6 +181,7 @@ TEST_F(XMLWriterTest, NotAddsInertial) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, Not(HasSubstr("inertial")));
|
EXPECT_THAT(saved_xml, Not(HasSubstr("inertial")));
|
||||||
mj_deleteModel(model);
|
mj_deleteModel(model);
|
||||||
@@ -136,6 +200,7 @@ TEST_F(XMLWriterTest, DropsInertialIfFromGeom) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, Not(HasSubstr("inertial")));
|
EXPECT_THAT(saved_xml, Not(HasSubstr("inertial")));
|
||||||
mj_deleteModel(model);
|
mj_deleteModel(model);
|
||||||
@@ -154,6 +219,7 @@ TEST_F(XMLWriterTest, DoesNotKeepInferredJointLimited) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("range=\"-1 1\""));
|
EXPECT_THAT(saved_xml, HasSubstr("range=\"-1 1\""));
|
||||||
EXPECT_THAT(saved_xml, Not(HasSubstr("limited=\"true\"")));
|
EXPECT_THAT(saved_xml, Not(HasSubstr("limited=\"true\"")));
|
||||||
@@ -173,6 +239,7 @@ TEST_F(XMLWriterTest, DoesNotKeepExplicitJointLimitedIfAutoLimits) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("autolimits=\"true\""));
|
EXPECT_THAT(saved_xml, HasSubstr("autolimits=\"true\""));
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("range=\"-1 1\""));
|
EXPECT_THAT(saved_xml, HasSubstr("range=\"-1 1\""));
|
||||||
@@ -193,6 +260,7 @@ TEST_F(XMLWriterTest, KeepsJointLimitedFalseIfAutoLimits) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("limited=\"false\" range=\"-1 1\""));
|
EXPECT_THAT(saved_xml, HasSubstr("limited=\"false\" range=\"-1 1\""));
|
||||||
mj_deleteModel(model);
|
mj_deleteModel(model);
|
||||||
@@ -219,6 +287,7 @@ TEST_F(XMLWriterTest, DoesNotKeepInferredTendonLimited) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("autolimits=\"true\""));
|
EXPECT_THAT(saved_xml, HasSubstr("autolimits=\"true\""));
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("range=\"-1 1\""));
|
EXPECT_THAT(saved_xml, HasSubstr("range=\"-1 1\""));
|
||||||
@@ -247,6 +316,7 @@ TEST_F(XMLWriterTest, DoesNotKeepExplicitTendonLimitedIfAutoLimits) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("autolimits=\"true\""));
|
EXPECT_THAT(saved_xml, HasSubstr("autolimits=\"true\""));
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("range=\"-1 1\""));
|
EXPECT_THAT(saved_xml, HasSubstr("range=\"-1 1\""));
|
||||||
@@ -275,6 +345,7 @@ TEST_F(XMLWriterTest, KeepsTendonLimitedFalseIfAutoLimits) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("limited=\"false\" range=\"-1 1\""));
|
EXPECT_THAT(saved_xml, HasSubstr("limited=\"false\" range=\"-1 1\""));
|
||||||
mj_deleteModel(model);
|
mj_deleteModel(model);
|
||||||
@@ -296,6 +367,7 @@ TEST_F(XMLWriterTest, DoesNotKeepInferredActlimited) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("autolimits=\"true\""));
|
EXPECT_THAT(saved_xml, HasSubstr("autolimits=\"true\""));
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("actrange=\"-1 1\""));
|
EXPECT_THAT(saved_xml, HasSubstr("actrange=\"-1 1\""));
|
||||||
@@ -319,6 +391,7 @@ TEST_F(XMLWriterTest, DoesNotKeepExplicitActlimitedIfAutoLimits) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("autolimits=\"true\""));
|
EXPECT_THAT(saved_xml, HasSubstr("autolimits=\"true\""));
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("actrange=\"-1 1\""));
|
EXPECT_THAT(saved_xml, HasSubstr("actrange=\"-1 1\""));
|
||||||
@@ -342,6 +415,7 @@ TEST_F(XMLWriterTest, KeepsActlimitedFalse) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("actlimited=\"false\" actrange=\"-1 1\""));
|
EXPECT_THAT(saved_xml, HasSubstr("actlimited=\"false\" actrange=\"-1 1\""));
|
||||||
mj_deleteModel(model);
|
mj_deleteModel(model);
|
||||||
@@ -363,6 +437,7 @@ TEST_F(XMLWriterTest, DoesNotKeepInferredCtrllimited) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("ctrlrange=\"-1 1\""));
|
EXPECT_THAT(saved_xml, HasSubstr("ctrlrange=\"-1 1\""));
|
||||||
EXPECT_THAT(saved_xml, Not(HasSubstr("ctrllimited=\"true\"")));
|
EXPECT_THAT(saved_xml, Not(HasSubstr("ctrllimited=\"true\"")));
|
||||||
@@ -385,6 +460,7 @@ TEST_F(XMLWriterTest, DoesNotKeepExplicitCtrllimitedIfAutoLimits) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("ctrlrange=\"-1 1\""));
|
EXPECT_THAT(saved_xml, HasSubstr("ctrlrange=\"-1 1\""));
|
||||||
EXPECT_THAT(saved_xml, Not(HasSubstr("ctrllimited=\"true\"")));
|
EXPECT_THAT(saved_xml, Not(HasSubstr("ctrllimited=\"true\"")));
|
||||||
@@ -406,6 +482,7 @@ TEST_F(XMLWriterTest, KeepsCtrllimitedFalse) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("ctrllimited=\"false\" ctrlrange=\"-1 1\""));
|
EXPECT_THAT(saved_xml, HasSubstr("ctrllimited=\"false\" ctrlrange=\"-1 1\""));
|
||||||
mj_deleteModel(model);
|
mj_deleteModel(model);
|
||||||
@@ -427,6 +504,7 @@ TEST_F(XMLWriterTest, DoesNotKeepInferredForcelimited) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("autolimits=\"true\""));
|
EXPECT_THAT(saved_xml, HasSubstr("autolimits=\"true\""));
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("forcerange=\"-1 1\""));
|
EXPECT_THAT(saved_xml, HasSubstr("forcerange=\"-1 1\""));
|
||||||
@@ -449,6 +527,7 @@ TEST_F(XMLWriterTest, DoesNotKeepExplicitForcelimited) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("autolimits=\"true\""));
|
EXPECT_THAT(saved_xml, HasSubstr("autolimits=\"true\""));
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("forcerange=\"-1 1\""));
|
EXPECT_THAT(saved_xml, HasSubstr("forcerange=\"-1 1\""));
|
||||||
@@ -471,8 +550,10 @@ TEST_F(XMLWriterTest, KeepsForcelimitedFalse) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("forcelimited=\"false\" forcerange=\"-1 1\""));
|
EXPECT_THAT(saved_xml,
|
||||||
|
HasSubstr("forcelimited=\"false\" forcerange=\"-1 1\""));
|
||||||
mj_deleteModel(model);
|
mj_deleteModel(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,6 +568,7 @@ TEST_F(XMLWriterTest, UndefinedMassDensity) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, Not(HasSubstr("density")));
|
EXPECT_THAT(saved_xml, Not(HasSubstr("density")));
|
||||||
EXPECT_THAT(saved_xml, Not(HasSubstr("mass")));
|
EXPECT_THAT(saved_xml, Not(HasSubstr("mass")));
|
||||||
@@ -507,6 +589,7 @@ TEST_F(XMLWriterTest, WritesDefaults) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, Not(HasSubstr("mass")));
|
EXPECT_THAT(saved_xml, Not(HasSubstr("mass")));
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("<geom density=\"100\"/>"));
|
EXPECT_THAT(saved_xml, HasSubstr("<geom density=\"100\"/>"));
|
||||||
@@ -524,6 +607,7 @@ TEST_F(XMLWriterTest, WritesDensity) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("density=\"100\""));
|
EXPECT_THAT(saved_xml, HasSubstr("density=\"100\""));
|
||||||
EXPECT_THAT(saved_xml, Not(HasSubstr("mass")));
|
EXPECT_THAT(saved_xml, Not(HasSubstr("mass")));
|
||||||
@@ -541,6 +625,7 @@ TEST_F(XMLWriterTest, WritesMass) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, Not(HasSubstr("density")));
|
EXPECT_THAT(saved_xml, Not(HasSubstr("density")));
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("mass=\"0.1\""));
|
EXPECT_THAT(saved_xml, HasSubstr("mass=\"0.1\""));
|
||||||
@@ -558,6 +643,7 @@ TEST_F(XMLWriterTest, ZeroMass) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, Not(HasSubstr("density")));
|
EXPECT_THAT(saved_xml, Not(HasSubstr("density")));
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("mass=\"0\""));
|
EXPECT_THAT(saved_xml, HasSubstr("mass=\"0\""));
|
||||||
@@ -575,6 +661,7 @@ TEST_F(XMLWriterTest, OverwritesDensity) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, Not(HasSubstr("density")));
|
EXPECT_THAT(saved_xml, Not(HasSubstr("density")));
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("mass=\"100\""));
|
EXPECT_THAT(saved_xml, HasSubstr("mass=\"100\""));
|
||||||
@@ -589,6 +676,7 @@ TEST_F(XMLWriterTest, UsesTwoSpaces) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, HasSubstr(" "));
|
EXPECT_THAT(saved_xml, HasSubstr(" "));
|
||||||
EXPECT_THAT(saved_xml, Not(HasSubstr(" ")));
|
EXPECT_THAT(saved_xml, Not(HasSubstr(" ")));
|
||||||
@@ -609,9 +697,13 @@ TEST_F(XMLWriterTest, WritesSkin) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
mjModel* mtemp = LoadModelFromString(SaveAndReadXml(model));
|
ASSERT_THAT(model, NotNull());
|
||||||
EXPECT_THAT(model->nskin, 1);
|
EXPECT_THAT(model->nskin, 1);
|
||||||
|
|
||||||
|
mjModel* mtemp = LoadModelFromString(SaveAndReadXml(model));
|
||||||
|
ASSERT_THAT(mtemp, NotNull());
|
||||||
EXPECT_THAT(mtemp->nskin, 1);
|
EXPECT_THAT(mtemp->nskin, 1);
|
||||||
|
|
||||||
mj_deleteModel(model);
|
mj_deleteModel(model);
|
||||||
mj_deleteModel(mtemp);
|
mj_deleteModel(mtemp);
|
||||||
}
|
}
|
||||||
@@ -626,12 +718,17 @@ TEST_F(XMLWriterTest, SetPrecision) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
|
|
||||||
// save to XML and re-load, expect to lose precision
|
// save to XML and re-load, expect to lose precision
|
||||||
mjModel* model_lo = LoadModelFromString(SaveAndReadXml(model));
|
mjModel* model_lo = LoadModelFromString(SaveAndReadXml(model));
|
||||||
|
ASSERT_THAT(model_lo, NotNull());
|
||||||
|
|
||||||
EXPECT_EQ(model->geom_size[1], model_lo->geom_size[1]);
|
EXPECT_EQ(model->geom_size[1], model_lo->geom_size[1]);
|
||||||
EXPECT_NE(model->geom_size[2], model_lo->geom_size[2]);
|
EXPECT_NE(model->geom_size[2], model_lo->geom_size[2]);
|
||||||
{
|
{
|
||||||
// save to XML and re-load with FullFloatPrecision, expect to maintain precision
|
// save to XML and re-load with FullFloatPrecision
|
||||||
|
// expect to maintain precision
|
||||||
FullFloatPrecision increase_precision;
|
FullFloatPrecision increase_precision;
|
||||||
mjModel* model_hi = LoadModelFromString(SaveAndReadXml(model));
|
mjModel* model_hi = LoadModelFromString(SaveAndReadXml(model));
|
||||||
EXPECT_EQ(model->geom_size[2], model_hi->geom_size[2]);
|
EXPECT_EQ(model->geom_size[2], model_hi->geom_size[2]);
|
||||||
@@ -664,6 +761,7 @@ TEST_F(XMLWriterLocaleTest, IgnoresLocale) {
|
|||||||
</mujoco>
|
</mujoco>
|
||||||
)";
|
)";
|
||||||
mjModel* model = LoadModelFromString(xml);
|
mjModel* model = LoadModelFromString(xml);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
std::string saved_xml = SaveAndReadXml(model);
|
std::string saved_xml = SaveAndReadXml(model);
|
||||||
EXPECT_THAT(saved_xml, HasSubstr("0.1 1.23 2.345"));
|
EXPECT_THAT(saved_xml, HasSubstr("0.1 1.23 2.345"));
|
||||||
mj_deleteModel(model);
|
mj_deleteModel(model);
|
||||||
@@ -675,7 +773,7 @@ TEST_F(XMLWriterLocaleTest, IgnoresLocale) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ------------------------ test loading and saving multiple files ---------------------------------
|
// ------------------- test loading and saving multiple files ------------------
|
||||||
namespace mju = ::mujoco::util;
|
namespace mju = ::mujoco::util;
|
||||||
static constexpr int kFieldSize = 500;
|
static constexpr int kFieldSize = 500;
|
||||||
|
|
||||||
@@ -695,10 +793,12 @@ template<typename T = mjtNum> T Compare(T val1, T val2) {
|
|||||||
return error < 2*10*std::numeric_limits<T>::epsilon() ? 0 : error;
|
return error < 2*10*std::numeric_limits<T>::epsilon() ? 0 : error;
|
||||||
}
|
}
|
||||||
|
|
||||||
mjtNum CompareModel(const mjModel* m1, const mjModel* m2, char (&field)[kFieldSize]) {
|
mjtNum CompareModel(const mjModel* m1, const mjModel* m2,
|
||||||
|
char (&field)[kFieldSize]) {
|
||||||
mjtNum dif, maxdif = 0.0;
|
mjtNum dif, maxdif = 0.0;
|
||||||
|
|
||||||
// define symbols corresponding to number of columns (needed in MJMODEL_POINTERS)
|
// define symbols corresponding to number of columns
|
||||||
|
// (needed in MJMODEL_POINTERS)
|
||||||
MJMODEL_POINTERS_PREAMBLE(m1);
|
MJMODEL_POINTERS_PREAMBLE(m1);
|
||||||
|
|
||||||
// compare ints
|
// compare ints
|
||||||
@@ -739,7 +839,8 @@ mjtNum CompareModel(const mjModel* m1, const mjModel* m2, char (&field)[kFieldSi
|
|||||||
TEST_F(XMLWriterTest, WriteReadCompare) {
|
TEST_F(XMLWriterTest, WriteReadCompare) {
|
||||||
FullFloatPrecision increase_precision;
|
FullFloatPrecision increase_precision;
|
||||||
// Loop over all xml files in data
|
// Loop over all xml files in data
|
||||||
std::vector<std::string> paths = {GetTestDataFilePath("."), GetModelPath(".")};
|
std::vector<std::string> paths = {GetTestDataFilePath("."),
|
||||||
|
GetModelPath(".")};
|
||||||
std::string ext(".xml");
|
std::string ext(".xml");
|
||||||
for (auto const& path : paths) {
|
for (auto const& path : paths) {
|
||||||
for (auto &p : std::filesystem::recursive_directory_iterator(path)) {
|
for (auto &p : std::filesystem::recursive_directory_iterator(path)) {
|
||||||
@@ -753,15 +854,18 @@ TEST_F(XMLWriterTest, WriteReadCompare) {
|
|||||||
|
|
||||||
// load model
|
// load model
|
||||||
std::array<char, 1000> error;
|
std::array<char, 1000> error;
|
||||||
mjModel* m = mj_loadXML(xml.c_str(), nullptr, error.data(), error.size());
|
mjModel* m = mj_loadXML(
|
||||||
ASSERT_THAT(m, NotNull()) << "Failed to load " << xml.c_str() << ": " << error.data();
|
xml.c_str(), nullptr, error.data(), error.size());
|
||||||
|
ASSERT_THAT(m, NotNull())
|
||||||
|
<< "Failed to load " << xml.c_str() << ": " << error.data();
|
||||||
|
|
||||||
// make data
|
// make data
|
||||||
mjData* d = mj_makeData(m);
|
mjData* d = mj_makeData(m);
|
||||||
ASSERT_THAT(d, testing::NotNull()) << "Failed to create data" << std::endl;
|
ASSERT_THAT(d, testing::NotNull()) << "Failed to create data\n";
|
||||||
|
|
||||||
// save and load back
|
// save and load back
|
||||||
mjModel* mtemp = LoadModelFromString(SaveAndReadXml(m), error.data(), error.size());
|
mjModel* mtemp =
|
||||||
|
LoadModelFromString(SaveAndReadXml(m), error.data(), error.size());
|
||||||
|
|
||||||
if (!mtemp) {
|
if (!mtemp) {
|
||||||
// if failing because assets are missing, accept the test
|
// if failing because assets are missing, accept the test
|
||||||
@@ -770,9 +874,9 @@ TEST_F(XMLWriterTest, WriteReadCompare) {
|
|||||||
// compare and delete
|
// compare and delete
|
||||||
char field[kFieldSize] = "";
|
char field[kFieldSize] = "";
|
||||||
mjtNum result = CompareModel(m, mtemp, field);
|
mjtNum result = CompareModel(m, mtemp, field);
|
||||||
EXPECT_LE(result, 0) << "Loaded and saved models are different!" << std::endl
|
EXPECT_LE(result, 0) << "Loaded and saved models are different!\n"
|
||||||
<< "Affected file " << p.path().string() << std::endl
|
<< "Affected file " << p.path().string() << '\n'
|
||||||
<< "Different field: " << field << std::endl;
|
<< "Different field: " << field << '\n';
|
||||||
mj_deleteModel(mtemp);
|
mj_deleteModel(mtemp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -549,8 +549,10 @@ public unsafe struct mjData_ {
|
|||||||
public int nstack;
|
public int nstack;
|
||||||
public int nbuffer;
|
public int nbuffer;
|
||||||
public int nplugin;
|
public int nplugin;
|
||||||
public int pstack;
|
public UIntPtr pstack;
|
||||||
|
public UIntPtr parena;
|
||||||
public int maxuse_stack;
|
public int maxuse_stack;
|
||||||
|
public UIntPtr maxuse_arena;
|
||||||
public int maxuse_con;
|
public int maxuse_con;
|
||||||
public int maxuse_efc;
|
public int maxuse_efc;
|
||||||
public mjWarningStat_ warning0;
|
public mjWarningStat_ warning0;
|
||||||
@@ -1584,7 +1586,7 @@ public unsafe struct mjData_ {
|
|||||||
public double time;
|
public double time;
|
||||||
public fixed double energy[2];
|
public fixed double energy[2];
|
||||||
public void* buffer;
|
public void* buffer;
|
||||||
public double* stack;
|
public void* arena;
|
||||||
public double* qpos;
|
public double* qpos;
|
||||||
public double* qvel;
|
public double* qvel;
|
||||||
public double* act;
|
public double* act;
|
||||||
@@ -1635,30 +1637,6 @@ public unsafe struct mjData_ {
|
|||||||
public double* qLD;
|
public double* qLD;
|
||||||
public double* qLDiagInv;
|
public double* qLDiagInv;
|
||||||
public double* qLDiagSqrtInv;
|
public double* qLDiagSqrtInv;
|
||||||
public mjContact_* contact;
|
|
||||||
public int* efc_type;
|
|
||||||
public int* efc_id;
|
|
||||||
public int* efc_J_rownnz;
|
|
||||||
public int* efc_J_rowadr;
|
|
||||||
public int* efc_J_rowsuper;
|
|
||||||
public int* efc_J_colind;
|
|
||||||
public int* efc_JT_rownnz;
|
|
||||||
public int* efc_JT_rowadr;
|
|
||||||
public int* efc_JT_rowsuper;
|
|
||||||
public int* efc_JT_colind;
|
|
||||||
public double* efc_J;
|
|
||||||
public double* efc_JT;
|
|
||||||
public double* efc_pos;
|
|
||||||
public double* efc_margin;
|
|
||||||
public double* efc_frictionloss;
|
|
||||||
public double* efc_diagApprox;
|
|
||||||
public double* efc_KBIP;
|
|
||||||
public double* efc_D;
|
|
||||||
public double* efc_R;
|
|
||||||
public int* efc_AR_rownnz;
|
|
||||||
public int* efc_AR_rowadr;
|
|
||||||
public int* efc_AR_colind;
|
|
||||||
public double* efc_AR;
|
|
||||||
public double* ten_velocity;
|
public double* ten_velocity;
|
||||||
public double* actuator_velocity;
|
public double* actuator_velocity;
|
||||||
public double* cvel;
|
public double* cvel;
|
||||||
@@ -1680,14 +1658,38 @@ public unsafe struct mjData_ {
|
|||||||
public double* qfrc_actuator;
|
public double* qfrc_actuator;
|
||||||
public double* qfrc_smooth;
|
public double* qfrc_smooth;
|
||||||
public double* qacc_smooth;
|
public double* qacc_smooth;
|
||||||
public double* efc_b;
|
|
||||||
public double* efc_force;
|
|
||||||
public int* efc_state;
|
|
||||||
public double* qfrc_constraint;
|
public double* qfrc_constraint;
|
||||||
public double* qfrc_inverse;
|
public double* qfrc_inverse;
|
||||||
public double* cacc;
|
public double* cacc;
|
||||||
public double* cfrc_int;
|
public double* cfrc_int;
|
||||||
public double* cfrc_ext;
|
public double* cfrc_ext;
|
||||||
|
public mjContact_* contact;
|
||||||
|
public int* efc_type;
|
||||||
|
public int* efc_id;
|
||||||
|
public int* efc_J_rownnz;
|
||||||
|
public int* efc_J_rowadr;
|
||||||
|
public int* efc_J_rowsuper;
|
||||||
|
public int* efc_J_colind;
|
||||||
|
public int* efc_JT_rownnz;
|
||||||
|
public int* efc_JT_rowadr;
|
||||||
|
public int* efc_JT_rowsuper;
|
||||||
|
public int* efc_JT_colind;
|
||||||
|
public double* efc_J;
|
||||||
|
public double* efc_JT;
|
||||||
|
public double* efc_pos;
|
||||||
|
public double* efc_margin;
|
||||||
|
public double* efc_frictionloss;
|
||||||
|
public double* efc_diagApprox;
|
||||||
|
public double* efc_KBIP;
|
||||||
|
public double* efc_D;
|
||||||
|
public double* efc_R;
|
||||||
|
public double* efc_b;
|
||||||
|
public double* efc_force;
|
||||||
|
public int* efc_state;
|
||||||
|
public int* efc_AR_rownnz;
|
||||||
|
public int* efc_AR_rowadr;
|
||||||
|
public int* efc_AR_colind;
|
||||||
|
public double* efc_AR;
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
@@ -3252,7 +3254,7 @@ public static unsafe extern void mju_warning_s([MarshalAs(UnmanagedType.LPStr)]s
|
|||||||
public static unsafe extern void mju_clearHandlers();
|
public static unsafe extern void mju_clearHandlers();
|
||||||
|
|
||||||
[DllImport("mujoco", CallingConvention = CallingConvention.Cdecl)]
|
[DllImport("mujoco", CallingConvention = CallingConvention.Cdecl)]
|
||||||
public static unsafe extern void* mju_malloc(uint size);
|
public static unsafe extern void* mju_malloc(UIntPtr size);
|
||||||
|
|
||||||
[DllImport("mujoco", CallingConvention = CallingConvention.Cdecl)]
|
[DllImport("mujoco", CallingConvention = CallingConvention.Cdecl)]
|
||||||
public static unsafe extern void mju_free(void* ptr);
|
public static unsafe extern void mju_free(void* ptr);
|
||||||
|
|||||||
Reference in New Issue
Block a user