Skip to content

mjcf: restore schema declarations lost in the schema.xml regeneration - #553

Open
shoemoney wants to merge 2 commits into
google-deepmind:mainfrom
shoemoney:fix/schema-regeneration-regressions
Open

mjcf: restore schema declarations lost in the schema.xml regeneration#553
shoemoney wants to merge 2 commits into
google-deepmind:mainfrom
shoemoney:fix/schema-regeneration-regressions

Conversation

@shoemoney

Copy link
Copy Markdown

Fixes #552.

The schema regeneration in 985d094 dropped three declarations that the pre-regeneration schema.xml carried and that MuJoCo 3.12 still accepts. Each one breaks a previously working PyMJCF model at HEAD:

  1. sensor lost its contact child element. Parsing a model with <sensor><contact .../></sensor> raises KeyError while raw MuJoCo loads the same XML.
  2. jointinparent on the nine actuator elements degraded from type="reference" reference_namespace="joint" to type="string". attach() therefore emits the joint name unprefixed and the composed model fails to compile with unknown transmission target.
  3. custom/numeric data degraded from type="array" array_type="float" to type="string", so root.custom.add('numeric', name='x', data=[1, 2, 3]) raises ValueError.

This PR restores the three declarations exactly as they were in the pre-regeneration schema and adds a SchemaRegressionTest to schema_test.py covering all three (parse and compile of a contact sensor, attach round-trip that must scope jointinparent, and array data on a custom numeric). All three tests fail at HEAD and pass with the fix; the full dm_control/mjcf test suite passes (157 tests).

If the preferred fix is in the schema generator rather than the checked-in file, the regression tests here should transfer directly.

The schema regeneration in 985d094 dropped three declarations that
MuJoCo 3.12 still accepts, breaking previously working PyMJCF models:

- sensor lost its contact child element, so parsing a model with
  <sensor><contact .../></sensor> raises KeyError while raw MuJoCo
  loads it fine.
- jointinparent on the nine actuator elements degraded from
  type="reference" reference_namespace="joint" to type="string", so
  attach() no longer prefixes the joint name and the composed model
  fails to compile with "unknown transmission target".
- custom/numeric data degraded from a float array to a string, so
  add('numeric', name='x', data=[1, 2, 3]) raises ValueError.

Restore the three declarations as they were before the regeneration
and add regression tests covering all three.

@sylvesterkaczmarek sylvesterkaczmarek left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One attach-time namespace case is still missing: the restored contact sensor declares site as a plain reference. MuJoCo’s generated dm_control schema uses type="reference" reference_namespace="site". Without that namespace, attaching a child model with a contact sensor configured by site leaves the child site name unprefixed and can fail compilation, the same class of regression this PR fixes for jointinparent. Please restore reference_namespace="site" and add an attach regression for the site-based contact sensor path.

The restored contact sensor declared site as a bare type="reference" while every
sibling reference in the same element carried a namespace. It was the only bare
reference in the whole file, which makes it the odd one out against MuJoCo's own
generated schema and against the nine jointinparent declarations this PR exists
to restore.

The behavioral claim it was reported under does not hold, and the reason is
schema.py:152-153:

    other_kwargs['reference_namespace'] = (
        attribute_xml.get('reference_namespace') or name)

An absent namespace falls back to the attribute's own name, and the attribute is
named site, so the resolved namespace is "site" either way. Measured: attaching a
child whose contact sensor is configured by site produces byte-identical XML with
and without the declaration, the reference resolves to an _AttachableElement in
both cases, and it follows a rename of the target in both cases. So it does not
leave the child site unprefixed and cannot fail compilation.

That also means an attach regression for this cannot fail, so instead of shipping
one that only looks like verification, the added test asserts the invariant that
can actually break: no attribute of type="reference" may omit its namespace.
Without this commit it fails with ['site'] has length of 1. That is the same
class of loss as the jointinparent regression, caught on the file rather than on
one code path, so the next regeneration that drops a namespace is caught even
where the fallback happens to mask it.

schema_test.py: 8 passed.
@shoemoney

Copy link
Copy Markdown
Author

Made the change in de77a71, and it should be made, but the reason given for it does not hold and the regression you asked for cannot fail. Both worth saying plainly.

The change is right. site was declared as a bare type="reference" while body1, body2, subtree1, subtree2, geom1 and geom2 in the same element all carry a namespace. Stronger than that: grep -c 'type="reference"/>' over the whole schema returned 1, and it was that line. It was the only bare reference in the file, which is exactly the shape of loss this PR exists to undo.

The stated consequence does not reproduce. dm_control/mjcf/schema.py:152-153:

other_kwargs['reference_namespace'] = (
    attribute_xml.get('reference_namespace') or name)

An absent reference_namespace falls back to the attribute's own name, and the attribute is named site, so the resolved namespace is "site" either way. Measured on the attach case you described, with and without the declaration:

  • the emitted XML is byte-identical, <contact name="child/cs" num="1" data="found" site="child/s"/> in both, so the child site is not left unprefixed
  • root.find('sensor', 'cs').site resolves to an _AttachableElement in both
  • renaming the target site updates the reference in both

So it does not fail compilation, and an attach regression for it passes with the bug present. I wrote the test you asked for first and it passed against the unfixed schema, which is the only reason I went looking for why.

What I added instead asserts the invariant that can actually break: no attribute of type="reference" may omit its namespace. Without the fix it fails with ['site'] has length of 1. That catches the same class of regeneration loss as jointinparent, checked on the file rather than on one code path, so it still fires in the cases where the name-based fallback happens to mask the symptom. schema_test.py: 8 passed.

The fallback is also why this was easy to miss when the element was restored, and why the file-level assertion seemed worth more than a per-attribute one.

@sylvesterkaczmarek sylvesterkaczmarek left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rechecked de77a71f. You are right that the attach-time symptom I described is masked when the attribute name and fallback namespace are both site. Restoring the explicit namespace is still correct, and the file-level invariant that every reference declares its namespace is a stronger regression for this regeneration class. No remaining blocker from me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Schema regeneration in 985d0940 dropped three declarations MuJoCo 3.12 accepts (contact sensor, jointinparent references, custom numeric arrays)

2 participants