Add policies for global attribute conflict resolution upon attach

For example when loading parent_merge.xml:

```
WARNING: Attach conflict when attaching 'child' to 'parent_merge', policy is 'merge'
timestep: parent has 0.005, child has 0.002, taking the minimum
iterations: parent has 50, child has 100, taking the maximum
flag 'Damper': added from child
```

When loading parent_error.xml:

```
XML Error: Attach conflict when attaching 'child' to 'parent_error', policy is 'error'
timestep: parent has 0.005, child has 0.002
iterations: parent has 50, child has 100
Element 'attach', line 10
```
PiperOrigin-RevId: 933620810
Change-Id: Ib477863b5ef763474d27fb4be5a4148be1d5d500
This commit is contained in:
Yuval Tassa
2026-06-17 03:34:10 -07:00
committed by Copybara-Service
parent 30357c38a3
commit 410c73168c
37 changed files with 2705 additions and 449 deletions
+9
View File
@@ -915,6 +915,15 @@ Type of built-in procedural mesh.
.. mujoco-include:: mjtMeshBuiltin
.. _mjtConflict:
mjtConflict
~~~~~~~~~~~
Conflict resolution mode for attach.
.. mujoco-include:: mjtConflict
.. _mjtCTimer:
+21
View File
@@ -900,6 +900,27 @@ has any effect. The settings here are global and apply to the entire model.
:at:`saveinertial`: :at-val:`[false, true], "false"`
If set to "true", the compiler will save explicit :ref:`inertial <body-inertial>` clauses for all bodies.
.. _compiler-conflict:
:at:`conflict`: :at-val:`[warning, merge, error], "warning"`
This attribute controls how conflicting global attributes (physics options, sizes, visual settings) are resolved when
a child spec is attached to a parent using :ref:`mjs_attach`. A conflict occurs when both the parent and child specify
authored values for the same field and those values differ. See :ref:`Attribute Merging <meAttributeMerging>` for
details and a per-field table.
:at-val:`warning`
Parent values take precedence. When a conflict is detected, a warning is emitted but the parent value is not
modified. This is the default and preserves the pre-existing attachment behavior.
:at-val:`merge`
Fields are merged using field-specific strategies (minimum, maximum, OR, or error), depending on the field's
semantics. When only the child specifies an authored value, it is adopted by the parent. See the
:ref:`merging table <meAttributeMergingTable>` for per-field details.
:at-val:`error`
Any conflict between authored values results in a compile error. This is the strictest mode and is useful for
detecting unintended attribute mismatches.
.. _compiler-lengthrange:
:el-prefix:`compiler/` |-| **lengthrange** |?|
+3
View File
@@ -254,6 +254,9 @@
.. grid-item::
:ref:`alignfree<compiler-alignfree>`
.. grid-item::
:ref:`conflict<compiler-conflict>`
.. dropdown:: :ref:`lengthrange<compiler-lengthrange>` :octicon:`dot`
+11
View File
@@ -22,6 +22,17 @@ General
- Added :ref:`mjs_numWarnings` and :ref:`mjs_getWarning` for retrieving all warnings accumulated during model
compilation and attachment. Deprecated :ref:`mjs_isWarning` in favor of ``mjs_numWarnings(s) > 0``.
- Added the :ref:`compiler/conflict<compiler-conflict>` attribute for controlling how conflicting global attributes
are resolved during :ref:`attachment<mjs_attach>`. Possible values are "warning" (default: parent values take
precedence, warnings emitted on conflicts), "merge" (per-field min/max/error strategy), and "error" (any
conflict raises an error). See :ref:`Attribute Merging <meAttributeMerging>` for details.
.. admonition:: Future breaking API changes
:class: warning
The current default conflict resolution policy "warn" (ignore the child model) is backward compatible.
However, the default policy will change to "merge" in a future release.
- Improved primal solver convergence under float32. Improvements initially proposed by :github:user:`n3b` in
:issue:`2313` and :github:user:`denzeler-nvidia` in :doc:`MJWarp <mjwarp/index>` pull request
`1374 <https://github.com/google-deepmind/mujoco_warp/pull/1374>`__.
+6
View File
@@ -1549,6 +1549,11 @@ typedef enum mjtOrientation_ { // type of orientation specifier
mjORIENTATION_ZAXIS, // z axis (minimal rotation)
mjORIENTATION_EULER, // Euler angles
} mjtOrientation;
typedef enum mjtConflict_ { // conflict resolution for attach
mjCONFLICT_WARNING = 0, // keep parent, warn on conflict
mjCONFLICT_MERGE, // merge: min/max/error per field
mjCONFLICT_ERROR, // error on any conflict
} mjtConflict;
typedef enum mjtCTimer_ { // compiler timing categories
// top-level timers (wall-clock)
mjCTIMER_TOTAL = 0, // total compile time
@@ -1585,6 +1590,7 @@ typedef struct mjsCompiler_ { // compiler options
int inertiagrouprange[2]; // range of geom groups used to compute inertia
mjtByte saveinertial; // save explicit inertial clause for all bodies to XML
int alignfree; // align free joints with inertial frame
int conflict; // conflict resolution for attach (mjtConflict)
mjLROpt LRopt; // options for lengthrange computation
mjString* meshdir; // mesh and hfield directory
mjString* texturedir; // texture directory
+61
View File
@@ -171,6 +171,67 @@ Note also that once a child is attached by reference to a parent, the child cann
finalized. If a second attachment is performed without compilation, the keyframes from the first attachment will be
lost.
.. _meAttributeMerging:
Attribute Merging
^^^^^^^^^^^^^^^^^
When attaching a child spec (or an element from a child spec) to a parent spec using :ref:`mjs_attach`, global
attributes from the child may conflict with those in the parent. A conflict occurs when both the parent and child
specify authored values for the same field and those values differ. Note that for XML-based models, explicitly writing a
value (even if it matches the default value) counts as authoring and can trigger conflicts. The
:ref:`compiler/conflict<compiler-conflict>` attribute controls how such conflicts are resolved. Fields where only one
side specifies an authored value never conflict.
:at-val:`warning` (default)
Parent values take precedence. Whenever a conflict is detected, a warning is emitted but the parent value is not
modified. This preserves the pre-existing attachment behavior.
:at-val:`merge`
Attribute values are merged using a per-field strategy as described in the table below. When only the child specifies
an authored value, it is adopted by the parent.
:at-val:`error`
Any conflict results in a compile error. No values are modified.
The table below describes the per-field merge strategy used in :at-val:`merge` mode.
.. list-table:: Attribute Merging Behavior (:at-val:`merge` mode)
:widths: 15 60 25
:header-rows: 1
:name: meAttributeMergingTable
* - Behavior
- Fields
- Justification
* - **Minimum**
- **option**: :ref:`timestep<option-timestep>`, :ref:`tolerance<option-tolerance>`, :ref:`ls_tolerance<option-ls_tolerance>`,
:ref:`noslip_tolerance<option-noslip_tolerance>`, :ref:`ccd_tolerance<option-ccd_tolerance>`,
:ref:`sleep_tolerance<option-sleep_tolerance>`,
|br| **visual**: :ref:`znear<visual-map-znear>`, :ref:`realtime<visual-global-realtime>`
- Preserves precision and stability.
* - **Maximum**
- **option**: :ref:`iterations<option-iterations>`, :ref:`ls_iterations<option-ls_iterations>`,
:ref:`noslip_iterations<option-noslip_iterations>`, :ref:`ccd_iterations<option-ccd_iterations>`,
:ref:`sdf_iterations<option-sdf_iterations>`, :ref:`sdf_initpoints<option-sdf_initpoints>`,
|br| **size**: :ref:`memory<size-memory>`, :ref:`nkey<size-nkey>`, :ref:`nuserdata<size-nuserdata>`,
:ref:`nuser_body<size-nuser_body>`, :ref:`nuser_jnt<size-nuser_jnt>`, :ref:`nuser_geom<size-nuser_geom>`, :ref:`nuser_site<size-nuser_site>`,
:ref:`nuser_cam<size-nuser_cam>`, :ref:`nuser_tendon<size-nuser_tendon>`, :ref:`nuser_actuator<size-nuser_actuator>`, :ref:`nuser_sensor<size-nuser_sensor>`
|br| **visual**: :ref:`zfar<visual-map-zfar>`
- Ensures sufficient resources and limits.
* - **OR (union)**
- **option**: :ref:`disableflags<option-flag>`, :ref:`enableflags<option-flag>`,
:ref:`disableactuator<option-actuatorgroupdisable>`
- Flags from both models are combined.
* - **Error**
- **option**: :ref:`gravity<option-gravity>`, :ref:`wind<option-wind>`, :ref:`magnetic<option-magnetic>`,
:ref:`density<option-density>`, :ref:`viscosity<option-viscosity>`, :ref:`integrator<option-integrator>`,
:ref:`cone<option-cone>`, :ref:`jacobian<option-jacobian>`, :ref:`solver<option-solver>`,
:ref:`impratio<option-impratio>`,
:ref:`o_margin<option-o_margin>`, :ref:`o_solref<option-o_solref>`, :ref:`o_solimp<option-o_solimp>`,
:ref:`o_friction<option-o_friction>`
- Raised if non-default values conflict.
.. _meDefault:
Default classes