Skip to content

Reachability only validates tasks-relevant objects (Part 2.5/3)#947

Open
xyao-nv wants to merge 11 commits into
mainfrom
xyao/feature/task_constraint_reachability_targets
Open

Reachability only validates tasks-relevant objects (Part 2.5/3)#947
xyao-nv wants to merge 11 commits into
mainfrom
xyao/feature/task_constraint_reachability_targets

Conversation

@xyao-nv

@xyao-nv xyao-nv commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Reachability used to validate over all non-anchor objects in the candidate layout (#914). In this MR, it checks reachability against task-related objects. For PnP tasks, it is auto-configured as pickup objects and place destination objects.

Detailed description

  • Introduced task_constraints in the env graph spec to capture the reachability constraint.
  • Reachability of an Object is an RelationBase tied to the object, which affects the geometry of layout (only at the validation step, not solving step). Defined as RequiresReachability.
  • PickAndPlaceTask specified what objects need to be reached thru a init data member reachability_target_objects. This data is also added to the TaskBase.
  • Tested on one Robolab env

TODO

  1. More testing over other Robolab envs
  2. Enable joint collision checking
Screenshot from 2026-07-23 21-45-10 (banana and bowl are checked against reachability.)

assets_by_node_id = instantiate_assets_from_spec(graph_spec, AssetRegistry(), enable_cameras=enable_cameras)
_ensure_scene_lighting(graph_spec, assets_by_node_id)
_attach_spatial_relations_to_assets(graph_spec.relations, assets_by_node_id)
_attach_reachability_relations_to_assets(graph_spec.get_reachability_target_object_ids(), assets_by_node_id)

@xyao-nv xyao-nv Jul 24, 2026

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.

Open for discussions if any alternative to tie the reachability to object. It does not make sense to me to add it as a property to the object class. So I treat it as a Relation instead.

Comment thread isaaclab_arena/environment_spec/arena_env_graph_spec.py Outdated
@xyao-nv
xyao-nv marked this pull request as ready for review July 24, 2026 05:49
Comment thread isaaclab_arena_environments/robolab/tasks/banana_on_plate.yaml Outdated
Comment thread isaaclab_arena/tasks/task_base.py
Comment thread isaaclab_arena/tasks/pick_and_place_task.py Outdated
@arena-review-bot

Copy link
Copy Markdown
Contributor

🤖 Isaac Lab-Arena Review Bot

Summary

This PR scopes the build-time cuRobo reachability check from every movable object to only the objects a task actually needs the robot to reach (pickup + place destination for PnP). It adds a RequiresReachability marker relation, a task_constraints surface in the env graph spec (with a reachable constraint type), and per-task reachability_target_objects metadata that auto-derives targets from task params. The mechanism is clean, well-tested, and the once-per-validator zero-target warning is a nice touch. Only minor points below.

Findings

🔵 banana_on_plate.yaml:8 — the explicit task_constraints for banana/plate look redundant with auto-derivation (the PnP task already declares pick_up_object/destination_location as targets). Worth confirming whether they are needed or just illustrative.
🔵 task_base.py:22reachability_target_objects holds task-param keys, not assets; the "Assets…" docstring is slightly misleading. Consider reachability_target_params.
🔵 pick_and_place_task.py:56 — small docstring typo ("the it places onto").

Test Coverage

Good. New spec-level tests cover parsing, subject validation (unknown / non-object / object-reference rejected), auto-population, explicit+auto union/dedup, and marker attachment. The cuRobo validator gains tests that only stamped objects are IK-checked and that the zero-target path passes trivially and warns exactly once. Sim-dependent conversion test uses the inner/outer run_simulation_app_function pattern with a pytest.importorskip guard.

Verdict

Ship it (address the minor nits at your discretion).

@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR scopes reachability validation to only the objects the robot actually needs to reach (pickup target and place destination), replacing the previous behavior of IK-checking every non-anchor movable object. The mechanism uses a new RequiresReachability marker relation stamped onto target assets at env-construction time, driven by either explicit task_constraints in the YAML spec or automatically derived from a task class's reachability_target_objects declaration.

  • Introduces TaskConstraintSpec / TaskConstraintType Pydantic types, a task_constraints field on ArenaEnvGraphSpec, and validation that constraint subjects are placed movable objects.
  • Adds RequiresReachability unary relation, requires_reachability property on ObjectBase and DummyObject, and _attach_reachability_relations_to_assets in conversion utils to wire the spec to asset objects at build time.
  • PickAndPlaceTask auto-declares (\"pick_up_object\", \"destination_location\") as reachability targets; ReachabilityValidator._select_reachability_targets filters to stamped non-anchor objects only, with a one-shot warning when no targets are resolved.

Confidence Score: 4/5

Safe to merge; all findings are non-blocking style or minor performance observations with no correctness impact.

The change is well-tested and logically sound. Explicit constraints are validated against placed object ids at spec-parse time, deduplication prevents double-stamping, and the test suite is extended with targeted cases for all new paths. The only substantive tweak worth considering is moving the reachability-target check before the GPU world-update call so layouts with no targets skip the cuboid rebuild entirely.

isaaclab_arena_curobo/ik_reachability_validator.py has the minor world-update ordering issue; banana_on_plate.yaml has redundant explicit constraints worth reviewing.

Important Files Changed

Filename Overview
isaaclab_arena_curobo/ik_reachability_validator.py Core change: filters IK-checked objects to only those stamped with RequiresReachability; adds one-time warning when no targets are found; world-collision update happens before the early-exit check for no targets (minor wasted GPU work)
isaaclab_arena/environment_spec/arena_env_graph_spec.py Adds task_constraints field and get_reachability_target_object_ids() that unions explicit constraints with task-class-declared targets; validation guards that subjects are placed object ids
isaaclab_arena/environment_spec/arena_env_graph_conversion_utils.py Adds _attach_reachability_relations_to_assets() which stamps RequiresReachability onto target assets during env construction; clean and safe since IDs are pre-validated
isaaclab_arena/tasks/pick_and_place_task.py Adds reachability_target_objects class attribute declaring pick_up_object and destination_location as IK-check targets; minor docstring typo
isaaclab_arena/environment_spec/arena_env_graph_types.py Adds TaskConstraintType enum and TaskConstraintSpec Pydantic model; clean, well-typed
isaaclab_arena/relations/relations.py Adds RequiresReachability marker relation following the same pattern as IsAnchor; correctly registered and is_unary returns True
isaaclab_arena_environments/robolab/tasks/banana_on_plate.yaml Adds explicit task_constraints for banana and plate; likely redundant since PickAndPlaceTask auto-derives the same targets, but deduplication prevents double-stamping
isaaclab_arena/tests/test_arena_env_graph_spec.py Well-written tests covering explicit constraints, auto-derivation, deduplication, and invalid subject validation
isaaclab_arena_curobo/tests/test_ik_reachability_validator.py Adds targeted tests for stamped-only IK checking and the one-time no-target warning; good coverage of the new behavior

Comments Outside Diff (1)

  1. isaaclab_arena_curobo/ik_reachability_validator.py, line 119-139 (link)

    P2 The world collision cuboid rebuild and update_world GPU call happen unconditionally before checking whether any reachability targets exist. When no RequiresReachability-stamped object exists, this GPU work is wasted on every _validate invocation before hitting the early return. Moving _select_reachability_targets before update_world lets us short-circuit immediately.

Reviews (1): Last reviewed commit: "remove static objects" | Re-trigger Greptile

Comment thread isaaclab_arena/tasks/pick_and_place_task.py
Comment thread isaaclab_arena_curobo/ik_reachability_validator.py Outdated
Comment on lines +8 to +12
task_constraints:
- type: reachable
subject: banana
- type: reachable
subject: plate

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 Likely redundant explicit task_constraints

PickAndPlaceTask.reachability_target_objects = ("pick_up_object", "destination_location") already auto-derives the reachability targets from the task params. If the task params map pick_up_object: banana and destination_location: plate, these explicit task_constraints entries produce exactly the same result as leaving them out - get_reachability_target_object_ids() deduplicates via dict.fromkeys so no double-stamping occurs, but the YAML becomes misleading for future authors who may not realize the config is redundant. Consider removing these entries if the intent is only to validate the PickAndPlaceTask auto-derivation path.

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

Choose a reason for hiding this comment

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

I agree, the explicit task_constraints in yaml/env seems unnecessary
the task interface already defines which objects are reachability_target_objects, there's no need to expose this again through env/yaml. I'd suggest remove this and the task_constraints TaskConstraintSpec ect entirely.

a user can design to switch on/off reachability for an env through validator params?

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.

That's a valid design critique. Looking at the full implementation:

  • task_constraints in the YAML only adds to what the task class already derives via reachability_target_objects — there's no use case where it's the sole source of truth for a PickAndPlaceTask
  • On/off control already exists via placement_validators.enabled_checks / required_checks in build_checks_for_placer_params — no new mechanism needed

The one trade-off worth considering before removing TaskConstraintSpec entirely: it's currently the only hook for env authors using custom task types that don't declare reachability_target_objects on their class. If you remove it, those authors must touch the task class. But if the team standard is that all task classes own their reachability targets (which is the cleaner design), that's fine.

If you remove task_constraints, the net effect is:

  1. TaskConstraintSpec, TaskConstraintType deleted from arena_env_graph_types.py
  2. task_constraints field and _assert_task_constraint_subject_is_valid removed from arena_env_graph_spec.py
  3. get_reachability_target_object_ids simplifies to just _select_task_declared_reachable_subjects
  4. The banana_on_plate.yaml entries disappear (as originally flagged)
  5. Tests for task_constraints parsing/validation are removed; the auto-derivation tests remain

Would you like me to create a rule so I don't flag missing task_constraints in future PRs?

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.

Yes it was just for showcase. But I agree earlier to remove the task_constraints entirely.

reachability_target_objects = ("pick_up_object", "destination_location")
"""The robot must reach the object it picks up and the it places onto.

An object reference or the background scene (a static location, not a placed object) is skipped

@xyao-nv xyao-nv Jul 24, 2026

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.

I'm debating the tradeoff to support object reference for reachability validation. Here is my thinking process. Open for discussion.
Object reference is never a placed entity thru the solver (e.g. shelf inside the fridge). But its parent object (the fridge) could be inside the background scene (e.g. kitchen) or a foreground object (e.g. the microwave on table). It does not make sense to reject layout if it's a background for unreachable as none of them will be returned.
Can be in another MR if enable the object reference if it's a foreground placeable object is necessary.

@xyao-nv xyao-nv changed the title Reachability only validates over pickup objects and place destinations (Part 2.5/3) Reachability only validates tasks-relevant objects (Part 2.5/3) Jul 24, 2026

@alexmillane alexmillane left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Look pretty good. I left some points for discussion.

Comment on lines +51 to +54
task_constraints: list[TaskConstraintSpec] | None = Field(
default=None,
description="Optional per-object task constraints.",
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Point for discussion. Given that the reachability is described as a relation (which is probably correct IMO) is there a good reason to introduce another field here to hold it? Could we not have the reachability relation with the other relations.

With the splitting of the graph yamls into scenes and tasks, relations can be added in a task-specific manner. They live inside the task portion of the yaml file(s) and are added to the scene if that task is used.

Wdyt?

@qianl-nv for viz

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would actually go one step further and don't expose task_constraints at the env/yaml level.
I feel adding reachable relation to objects should be handled internally in the Task class, and user should not need to add the task-based constraints explicitly.

Comment on lines +150 to +163
class TaskConstraintType(str, Enum):
"""Kind of task constraint declared on a scene object."""

REACHABLE = "reachable"


class TaskConstraintSpec(BaseModel):
"""One task constraint: a requirement of ``type`` on the object named by ``subject``."""

type: TaskConstraintType = Field(description="Constraint kind; must match TaskConstraintType exactly.")
subject: str = Field(
min_length=1,
description="Graph node id of the object the constraint applies to. Must match an object id exactly.",
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

See comment above. Any reason not to just treat the reachable constraint as another relation?

Probably later, we'll actually want it to be a binary relation, rather than a unary one. The reachable constrain relates the object and the robot.

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.

Removed task constraint.

Comment on lines +278 to +294
@register_object_relation
class RequiresReachability(RelationBase):
"""Marker indicating the robot shall be able to reach this object.

Stamped onto an object from a 'reachable' task constraint. It does not affect placement geometry
during optimization, but rejects unreachable placements.

Usage:
banana.add_relation(RequiresReachability()) # IK-check reachability of the banana
"""

name = "requires_reachability"

@staticmethod
def is_unary() -> bool:
"""Return whether the relation constrains a single object."""
return True

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We should possibly make this a binary constraint between the robot and the object. Right now the robot is sort of implied.

I wonder if this constraint could also have an affect on the optimization by adding a reach radius constraint to the optimization between the robot (which will soon be placeable) and the object(s).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Perhaps this should be the subject of a future MR though.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would lean towards this being a "unary" constraint, since one of the end of the relation is always the robot (it doesn't make sense to be any other asset), and there's only one embodiment in an arena env.
so it's either define as unary, or as binary + asset that the reference is always THE embodiment. In that case going for unary seems simpler.

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.

It makes sense to me, once radius constraint is added and tested, this can be changed to subclassing BinaryRelation class. Added a TODO.

Once robot-object is in, it could be binary relation.

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.

On a side note, there are other better candidates for a surrogate loss than the radius constraint for reachability. Lio's MR is just 2D, and does not reflect robot's reachability map.

The factory receives ``arm_mode`` from the env builder and returns a constructed cfg.
"""

reachability_target_objects = ("pick_up_object", "destination_location")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm wondering why these need to be added here?

Aren't we relying on the user to specify the reachability relation manually? I'm wondering why they're here? (maybe there's a good reason, I am just unsure).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ha I was suggesting the exact opposite in my comment above lol... to not let user specify the reachability relation and also add them in the PickAndPlaceTask automatically.

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.

I think configuring them thru Task interface is a better design. It's a requirement from the task, and task interface can easily derive the target to be checked.

Comment on lines +98 to +103
return tuple(
dict.fromkeys((
*(c.subject for c in (self.task_constraints or []) if c.type is TaskConstraintType.REACHABLE),
*self._select_task_declared_reachable_subjects(object_ids),
))
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: large compound comprehensions like this (i find) are basically impossible for a human to read. Suggestion to break things like this up over multiple lines and statements.

assets_by_node_id = instantiate_assets_from_spec(graph_spec, AssetRegistry(), enable_cameras=enable_cameras)
_ensure_scene_lighting(graph_spec, assets_by_node_id)
_attach_spatial_relations_to_assets(graph_spec.relations, assets_by_node_id)
_attach_reachability_relations_to_assets(graph_spec.get_reachability_target_object_ids(), assets_by_node_id)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm thinking that attaching reachability constraints to the environment in the graph layer is the wrong place to do it. For starters, it means that the python way of specifying environments does get these.

A good comparison might be the non-collision constraints which are added in the env-builder / object placer.

I'm thinking that we could add to PickAndPlaceTask a function that adds reachability constraints to it's input objects? That's not perfect either but I feel it's better than having it in the graph layer.

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.

Yes, it was added.
And now it's only configured in the task.

@qianl-nv qianl-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the change, it's a good place to start refining.

Do we have any use cases where we want to add reachable relation to non-task-related object? If not it feels better to have the Task class automatically add the relation to its pick/target objects instead of always having the user specific the relation outside of the task.

The only main concern for that approach is backward compatibility: some of our existing envs (esp those hand crafted ones) might not even satisfy these relations in the first place. For those we might just explicitly switch the reachability checks off through PlacementParams instead.

wdyt? maybe I'm missing other scenarios where the user might want to have manual control over the reachability relations.

Comment on lines +8 to +12
task_constraints:
- type: reachable
subject: banana
- type: reachable
subject: plate

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I agree, the explicit task_constraints in yaml/env seems unnecessary
the task interface already defines which objects are reachability_target_objects, there's no need to expose this again through env/yaml. I'd suggest remove this and the task_constraints TaskConstraintSpec ect entirely.

a user can design to switch on/off reachability for an env through validator params?



@register_object_relation
class RequiresReachability(RelationBase):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

parent class should be UnaryRelation

@xyao-nv xyao-nv Jul 24, 2026

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.

I don't agree, UnaryRelation has been using for solver-optimized position constraints. If inheriting that, it will crash the solver as no loss strategy is implemented (yet) for this reachability.

We either rename the following func to be explicit on those are used in solver optimization in another MR, or change this to UnaryRelation once reachability loss is added.

If you insist suggesting UnaryRelation to use here, then a zero-loss strategy needed to be added as a no-op loss for non-geometric relations. For simplicity, I prefer subclassing RelationBase.

In object_base.py,

    def get_spatial_relations(self) -> list[RelationBase]:
        """Get only spatial relations (On, NextTo, AtPosition, etc.), excluding markers like IsAnchor."""
        return [r for r in self.relations if isinstance(r, (Relation, UnaryRelation))]

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.

Subclassing RelationBase is similiar to how is_anchor is used.

Comment on lines +51 to +54
task_constraints: list[TaskConstraintSpec] | None = Field(
default=None,
description="Optional per-object task constraints.",
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would actually go one step further and don't expose task_constraints at the env/yaml level.
I feel adding reachable relation to objects should be handled internally in the Task class, and user should not need to add the task-based constraints explicitly.

Comment on lines +278 to +294
@register_object_relation
class RequiresReachability(RelationBase):
"""Marker indicating the robot shall be able to reach this object.

Stamped onto an object from a 'reachable' task constraint. It does not affect placement geometry
during optimization, but rejects unreachable placements.

Usage:
banana.add_relation(RequiresReachability()) # IK-check reachability of the banana
"""

name = "requires_reachability"

@staticmethod
def is_unary() -> bool:
"""Return whether the relation constrains a single object."""
return True

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would lean towards this being a "unary" constraint, since one of the end of the relation is always the robot (it doesn't make sense to be any other asset), and there's only one embodiment in an arena env.
so it's either define as unary, or as binary + asset that the reference is always THE embodiment. In that case going for unary seems simpler.

The factory receives ``arm_mode`` from the env builder and returns a constructed cfg.
"""

reachability_target_objects = ("pick_up_object", "destination_location")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ha I was suggesting the exact opposite in my comment above lol... to not let user specify the reachability relation and also add them in the PickAndPlaceTask automatically.

Comment thread isaaclab_arena/assets/object_base.py Outdated
"""True if this object has an IsAnchor relation."""
return any(isinstance(r, IsAnchor) for r in self.relations)

@property

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It might be a bad idea to add relation-type-specific API to the object_base class. Imaging we would have other task constraints and each time adding a new API to object_base sounds a bad idea.

either we make this a generic def has_relation(self, relation_type: Relation) in object_base
or make this a helper/util in the relations module instead.

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.

valid, updated it to has_relation.

@xyao-nv
xyao-nv force-pushed the xyao/feature/task_constraint_reachability_targets branch from 82408e9 to 7351cb4 Compare July 24, 2026 22:59
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.

3 participants