Skip to content

Fix Newton preset validation#5939

Open
AntoineRichard wants to merge 2 commits into
isaac-sim:developfrom
AntoineRichard:antoiner/fix-newton-preset-validation
Open

Fix Newton preset validation#5939
AntoineRichard wants to merge 2 commits into
isaac-sim:developfrom
AntoineRichard:antoiner/fix-newton-preset-validation

Conversation

@AntoineRichard
Copy link
Copy Markdown
Collaborator

Description

This fixes Newton solver preset validation during Hydra config resolution.
When a user selects presets=newton_mjwarp or presets=newton_kamino, the selected solver now has to match an active PhysicsCfg preset. This prevents tasks that only expose non-physics presets with the same name, such as scalar actuator or sensor variants, from silently creating mixed PhysX/Newton configurations.

Fixes #

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Screenshots

Not applicable.

Checklist

  • I have read and understood the contribution guidelines
  • I have run the pre-commit checks with ./isaaclab.sh --format
    • Full ./isaaclab.sh -f was attempted twice, but the local environment is missing git-lfs, so check Git LFS pointers fails before it can run. SKIP=check-git-lfs-pointers ./isaaclab.sh -f passes.
  • I have made corresponding changes to the documentation
    • Not applicable beyond the changelog fragment.
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
    • No tests added by request; verified with direct config-resolution probes.
  • I have added a changelog fragment under source/<pkg>/changelog.d/ for every touched package (do not edit CHANGELOG.rst or bump extension.toml — CI handles that)
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

Verification

  • ./isaaclab.sh -p -m pytest source/isaaclab_tasks/test/core/test_hydra.py -q -> 78 passed
  • Navigation config probe: Isaac-Navigation-Flat-Anymal-C-v0 with presets=newton_mjwarp raises Selected Newton physics preset(s) newton_mjwarp did not match any active physics preset.
  • Locomotion config probe: Isaac-Velocity-Flat-Anymal-C-v0, Isaac-Velocity-Rough-Anymal-C-v0, Isaac-Velocity-Flat-Anymal-D-v0, and Isaac-Velocity-Rough-Unitree-Go2-v0 still resolve presets=newton_mjwarp to NewtonCfg.
  • ./isaaclab.sh -f -> failed only because git-lfs is not installed in this environment.
  • SKIP=check-git-lfs-pointers ./isaaclab.sh -f -> passed.

Require selected Newton solver presets to match an active physics preset during Hydra resolution. This prevents tasks that only expose non-physics presets with the same name from silently creating mixed PhysX/Newton configs.
@AntoineRichard AntoineRichard requested a review from Mayankm96 as a code owner June 3, 2026 13:22
@github-actions github-actions Bot added bug Something isn't working isaac-lab Related to Isaac Lab team labels Jun 3, 2026
@AntoineRichard AntoineRichard requested a review from hujc7 June 3, 2026 13:24
Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Choose a reason for hiding this comment

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

Review Summary

This PR adds post-resolution validation to ensure that when a user selects a Newton physics preset (e.g., presets=newton_mjwarp), it actually matched an active PhysicsCfg field in the resolved task. Without this fix, tasks that expose non-physics presets with the same name (e.g., scalar actuator or sensor variants) could silently create mixed PhysX/Newton configurations — a subtle and dangerous silent failure.

Overall: Clean, well-scoped fix that addresses an important correctness issue. The approach of tracking consumed targets alongside consumed names is sound.


✅ Strengths

  1. Minimal invasive change. The fix threads consumed_selected_targets through the existing resolution machinery without restructuring the call graph. This is the right approach for a targeted bug fix.
  2. Clear error messaging. The ValueError raised by _validate_selected_newton_physics_presets gives actionable guidance — it tells users what failed, why, and what to do about it.
  3. Defensive design. Uses setdefault and set operations, so multiple preset resolution passes accumulate targets correctly without duplication issues.

💡 Suggestions

  1. _preset_targets iterates all PresetTarget members (line ~79) — this is fine for 3 members, but if the enum grows, consider caching per-type results or short-circuiting after the first match if targets are expected to be mutually exclusive. Low priority given current enum size.

  2. Validation only covers PresetTarget.PHYSICS — if RENDERER gains similar silent-failure potential in the future, the same pattern could apply there. Worth a # TODO comment in _validate_selected_newton_physics_presets to signal this is extensible, but not blocking.

  3. No regression test added. The PR notes "No tests added by request; verified with direct config-resolution probes" and the checklist mentions existing test_hydra.py passes (78 tests). However, given this bug's silent nature, a targeted test that asserts the ValueError is raised when a Newton preset is selected for a task without physics support would be valuable regression protection. Even a single parametrized case (e.g., mock task with only domain presets + presets=newton_mjwarpValueError) would prevent future regressions.

  4. Minor: redundant val assignment movement (lines 319/325). The refactor moves val = fields[name] up and removes the later fields[name] reference — this is correct and slightly more efficient, but the diff could be confusing to reviewers since it looks like a behavior change. The net effect is identical (just avoids a second dict lookup). No action needed, just noting for context.


⚠️ Potential Edge Case

The validation in _selected_newton_physics_presets uses PresetTarget.PHYSICS.legacy_aliases to determine the canonical Newton preset names:

newton_names = set(aliases.values())
return {aliases.get(name, name) for name in selected if aliases.get(name, name) in newton_names}

This means only presets whose canonical name appears in the alias values are considered Newton physics presets. If a new Newton solver is added in the future (e.g., newton_foo) without adding it to legacy_aliases, it would bypass this validation. Consider whether the validation should instead check membership against a dedicated registry or naming convention (e.g., name.startswith("newton_")), though this depends on how solver presets are expected to evolve.


CI Status

All checks are still pending at time of review. No pre-commit or changelog issues detected from the passed checks.

Verdict: Approve-worthy once CI passes. The missing regression test is the only notable gap — would recommend adding one before merge if feasible.


🔄 Update (re-review of 6971203)

All previous concerns addressed:

  • Dedicated registry introduced_NEWTON_PHYSICS_PRESETS module-level constant cleanly separates preset identity from legacy alias resolution. New Newton solvers just need to be added to this set.
  • Validation order fixedconsumed_selected gate ensures Newton validation only fires for presets that were actually consumed (not truly unknown ones), eliminating the misleading error ordering issue.
  • Cleaner comprehension_selected_newton_physics_presets refactored for clarity.

No new issues found. LGTM. 👍

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 3, 2026

Greptile Summary

This PR adds validation to prevent silent mixed PhysX/Newton configurations when a user selects presets=newton_mjwarp or presets=newton_kamino on a task that only exposes those names as non-physics scalar presets (e.g., actuator armature values) rather than on a PhysicsCfg.

  • Introduces consumed_selected_targets (a dict[str, set[PresetTarget]]) threaded through _pick_alternative and _resolve_active_presets to record, per preset name, which typed target categories (PHYSICS, RENDERER, etc.) were actually matched during active-tree resolution.
  • Adds _selected_newton_physics_presets and _validate_selected_newton_physics_presets called at the end of register_task to raise a descriptive ValueError when a Newton solver preset was consumed but never matched a PhysicsCfg instance.

Confidence Score: 4/5

Safe to merge; the core fix correctly prevents mixed PhysX/Newton configs and the 78-test suite passes, but two design rough edges in the validation helper are worth addressing.

The targeted fix works correctly for the described scenarios: tasks with only scalar Newton presets now raise, while locomotion tasks with a real PhysicsCfg Newton entry continue to resolve. Two non-blocking concerns remain: the Newton validator runs before the unknown-preset check, so a completely-unrecognised Newton name produces a misleading error message; and the set of Newton physics preset names is derived from legacy_aliases.values() rather than a dedicated registry, meaning future Newton variants added without a legacy alias entry would silently bypass the check.

source/isaaclab_tasks/isaaclab_tasks/utils/hydra.py — specifically _selected_newton_physics_presets and the ordering of _validate_selected_newton_physics_presets relative to the unknown_presets block.

Important Files Changed

Filename Overview
source/isaaclab_tasks/isaaclab_tasks/utils/hydra.py Adds consumed_selected_targets tracking through _pick_alternative/_resolve_active_presets and a post-resolution Newton physics preset validation gate in register_task; core fix logic is correct but two design edge cases around error-ordering and preset enumeration need attention.
source/isaaclab_tasks/changelog.d/antoiner-fix-newton-preset-validation.rst Changelog fragment accurately describes the fix; no issues.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["register_task()\nglobal_presets, consumed_preset_targets = {}"] --> B["_resolve_active_presets(env_cfg)\nthreads consumed_selected_targets"]
    B --> C["_pick_alternative(preset_obj, selected)"]
    C --> D{{"preset name\nmatches field?"}}
    D -- yes --> E["_preset_targets(val)\nisinstance check vs PhysicsCfg / RendererCfg"]
    E --> F{{"targets\nnon-empty?"}}
    F -- yes --> G["consumed_selected_targets[name].update(targets)"]
    F -- no --> H["consumed_selected only\n(scalar preset, no typed target)"]
    G --> I["resolve continues"]
    H --> I
    D -- no --> I
    I --> J["_resolve_active_presets(agent_cfg)"]
    J --> K["_validate_selected_newton_physics_presets"]
    K --> L{{"newton preset\nhas PHYSICS target?"}}
    L -- yes --> M["✓ valid"]
    L -- no --> N["✗ ValueError"]
    K --> O["unknown_presets check"]
Loading

Reviews (1): Last reviewed commit: "Fix Newton preset validation" | Re-trigger Greptile

Comment on lines +562 to +572
missing = sorted(
name
for name in _selected_newton_physics_presets(selected)
if PresetTarget.PHYSICS not in consumed_selected_targets.get(name, set())
)
if missing:
raise ValueError(
f"Selected Newton physics preset(s) {', '.join(missing)} did not match any active physics preset. "
"This task does not declare Newton physics support for the selected solver. "
"Remove the Newton preset or choose a task that declares it on a PhysicsCfg."
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Newton validation fires before unknown-preset check, producing misleading error

_validate_selected_newton_physics_presets runs before the unknown_presets block (line 634). When a task has no newton_mjwarp entry whatsoever — not even a scalar actuator preset — consumed_preset_targets contains no entry for that name, so the Newton validator raises "did not match any active physics preset." The unknown_presets check, which would emit the clearer "Unknown preset(s): newton_mjwarp" message, never runs.

Adding a guard if name in consumed_presets inside the missing comprehension restricts the Newton error to the intended case (preset was applied somewhere but never to a PhysicsCfg), leaving truly-unknown names for the existing unknown-preset error path.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 6971203. The validator now only raises for Newton physics preset names that were consumed somewhere but did not resolve through a PHYSICS target, so truly unknown names continue to use the existing Unknown preset(s) error path.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice — that distinction (consumed-but-not-resolved via PHYSICS vs truly-unknown) cleanly addresses the concern. 👍

Comment on lines +551 to +555
def _selected_newton_physics_presets(selected: list[str]) -> set[str]:
"""Return canonical Newton physics preset names requested by the user."""
aliases = PresetTarget.PHYSICS.legacy_aliases
newton_names = set(aliases.values())
return {aliases.get(name, name) for name in selected if aliases.get(name, name) in newton_names}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Newton preset enumeration relies on legacy_aliases values, not on a dedicated registry

newton_names = set(aliases.values()) — i.e. {"newton_mjwarp", "newton_kamino"} — is derived from the deprecation-rename table in PresetTarget.PHYSICS.legacy_aliases, whose purpose is tracking old-to-new renames, not listing every valid Newton preset. If a future Newton variant (e.g. newton_tgs) is added directly to task configs without a corresponding legacy alias entry, _selected_newton_physics_presets will silently omit it from the check and the mixed-config bug will resurface without warning. A brief inline comment explaining why aliases.values() is the right source of truth here would protect future contributors from this non-obvious assumption.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 6971203. Replaced legacy_aliases.values() as the source of truth with an explicit _NEWTON_PHYSICS_PRESETS registry; aliases are now only used to canonicalize legacy names. This keeps names such as newton_renderer out of physics validation and makes future solver additions explicit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Excellent — an explicit registry is exactly what this needed. Much cleaner separation between "canonicalize legacy names" and "what counts as a physics preset". 👍

@AntoineRichard AntoineRichard requested a review from ooctipus June 3, 2026 13:31
Keep unknown Newton-like preset names on the existing unknown-preset error path and use an explicit Newton physics preset registry instead of deriving solver names from legacy aliases.
@kellyguo11
Copy link
Copy Markdown
Contributor

@ooctipus @hujc7 could you pls review?

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

Labels

bug Something isn't working isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants